Repository navigation
Commit 9ecbef9
bottomless: fix snapshot upload pipeline silently dropping generation snapshots
The continuous-backup pipeline could run for days while every generation it
created was missing its base snapshot in object storage, degrading or breaking
restore, and in one failure mode blocking checkpoints entirely so the WAL grew
without bound. Five interacting defects:
- A failed snapshot upload was never retried and blocked checkpoints until a
process restart: the snapshot task attempted its PutObject exactly once and
nothing ever re-ran it for the current generation, so the checkpoint gate
refused every subsequent checkpoint with SQLITE_BUSY. The upload is now
retried with backoff inside the snapshot task (rebuilding the body stream
per attempt, so a retry never sends a stale Content-Length), and the
checkpoint gate re-triggers the snapshot of the current generation when it
failed or never ran. Re-uploading at any later point is correct because the
main database file only changes on a TRUNCATE checkpoint, and checkpoints
stay refused until the upload succeeds.
- A checkpoint attempt that found an empty WAL unconditionally marked the
current generation as snapshotted, laundering failed or still-in-flight
uploads into success markers and letting the next checkpoint rotate the
generation while leaving no snapshot of it behind. The empty-WAL branch now
re-triggers a missing snapshot instead of overwriting the failure state.
- Concurrent snapshot tasks compressed the database into the same fixed
db.gz/db.zstd path with truncate(true), shrinking the file under the other
task's in-flight upload stream (surfacing as hyper BodyWriteAborted/NotEof
dispatch failures) or silently uploading a torn object. Compression now
targets a per-generation artifact path, an in-flight flag guarantees at most
one snapshot task at a time, and stale artifacts (including legacy
fixed-name ones) are swept only while no upload is running. S3 object keys
are unchanged.
- The AWS SDK's stalled-stream protection aborted slow snapshot uploads: a
whole-database PutObject can run for minutes and legitimately stalls beyond
the ~5s grace period whenever the process is busy, and the watchdog abort
surfaces as the same BodyWriteAborted signature. The watchdog is now
disabled on the bottomless client; bottomless owns its retry policy.
- Restarting on top of a non-empty database file created a generation with no
.dep parent link, severing the chain that restore walks when a generation
has no snapshot: compare_with_local returned early before storing the
generation it compared against. The store now happens before the early
return.
Snapshot upload log lines now include the database name and generation.
Tests: new snapshot_pipeline regression tests in libsql-server drive the
bottomless WAL wrapper against a mock S3 server with failure injection,
covering upload retry, checkpoint recovery without restart (including the
empty-WAL laundering case), the snapshot overlap guard, and the .dep link on
restart. Existing bottomless tests pass, including the ignored backup_restore
test.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013Q5tV6oA1zXfrWhchz2Cqk1 parent 6f451a1 commit 9ecbef9
4 files changed
Lines changed: 899 additions & 78 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
5 | 5 | | |
6 | 6 | | |
7 | 7 | | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
8 | 12 | | |
9 | 13 | | |
10 | 14 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
32 | 32 | | |
33 | 33 | | |
34 | 34 | | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
35 | 57 | | |
36 | 58 | | |
37 | 59 | | |
| |||
136 | 158 | | |
137 | 159 | | |
138 | 160 | | |
139 | | - | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
140 | 169 | | |
141 | 170 | | |
142 | 171 | | |
| |||
163 | 192 | | |
164 | 193 | | |
165 | 194 | | |
166 | | - | |
167 | | - | |
| 195 | + | |
168 | 196 | | |
169 | 197 | | |
170 | 198 | | |
| |||
0 commit comments