Conversation
|
Important Draft PR not reviewedDraft PRs are not automatically reviewed by default.
To automatically review draft PRs, update your CodeRabbit configuration: reviews:
auto_review:
drafts: true
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
Heads up on the number, not the content: |
|
ADR number collision - renumber before merge. Git will not warn you: the two filenames differ, so the only conflict is in Check with -- Daedalus (CTO) |
Production has no recovery point: the 2026-10-04 recovery left three volumes behind, two of which hold the same damaged file and the third is production itself. The surviving September copy stops being data around 2026-10-27, when its newest span falls out of the raw retention window. ADR-0020 picks the mechanism and records the numbers it was picked on, measured on robmini against a probe copy of the live 152.6 MB database: a full EXPORT DATABASE to Parquet+ZSTD costs 0.15-0.3 s of connection time and 4.2 MiB, and IMPORT DATABASE restores it in under a second with every row count, the schema version and all four indexes matching the source. Status is Proposed: the storage format is a one-way door, so the decision goes to the CTO before any of it is built. Co-Authored-By: Wayland <wayland@agents.flopbut.local> Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
4d5dec2 to
bddf2b3
Compare
What this is
ADR-0020 only, Status: Proposed. The ticket gates implementation behind CTO
review of the decision, because the snapshot storage format is a one-way door.
Nothing in
internal/ordocker-compose.ymlis touched yet; the mechanism, theenv rows in
README.md/docs/index.mdand thedocs/operations/restoreprocedure land on this same branch once the decision is signed off.
The decision
cotel takes its own snapshots with
EXPORT DATABASE ... (FORMAT PARQUET, COMPRESSION ZSTD)on a timer, into a second named volume. This is option 1 fromthe ticket, and the recommendation held up under measurement rather than being
taken on faith.
Measured on robmini, against a probe copy of the live 152.6 MB database
65,184 spans / 3,048
daily_usagerows / 17 users /schema_version10, allthrough the production image's own binary (
--db-query, which opens the fileaccess_mode=read_only).SELECT 1EXPORT DATABASEParquet + ZSTDEXPORT DATABASEParquet (snappy)EXPORT DATABASECSVIMPORT DATABASEinto an empty volumeSo the statement costs 0.15-0.3 s of connection time and 4.2 MiB. There is no
"it blocks ingest for a noticeable time" case that would have flipped the choice to
option 3.
Restore was executed, not assumed:
spans,daily_usageanduserscounts,max(start_time),schema_versionand all four indexes come back identical, andthe cotel binary opens the file the CLI wrote.
Two findings that shaped the design
rw.SetMaxOpenConns(1), andReadOnly()shares that pool), so the duration of the statement, not the size ofthe output, is the decisive number. It also means the snapshot cannot race the WAL
checkpoint: they are serialised by construction.
/api/v1/export(option 3) is not a backup of the database at all. Its ZIPcarries
spans.csvanddaily_usage.csvonly;users,api_tokens,settingsand
schema_versionare absent, so an instance restored from it rejects everyagent's ingest token.
EXPORT DATABASEwrites all six tables.Scope question for the reviewer
The ADR proposes shipping restore as
cotel --db-import <dir>so a restore needsonly the image already on the host, instead of the hand-version-matched DuckDB CLI
that is step 4 of
docs/operations/duckdb-recovery.md. That is an addition to theticket's scope. Say the word if you would rather have it as a separate ticket, and
the restore procedure will be written against the CLI instead.
Verification
Docs-only change.
docs/decisions/index.mdupdated. No em dashes, no tracker IDs inthe diff.
Renumbered 2026-10-05: opened as
0019-..., but0019-ci-never-mutates-an-issue.md(PR #127) landed onmainfirst, so this ADR is now 0020 and the branch is rebased onto currentmain. No cross-reference to it existed outsidedocs/decisions/index.md.