Skip to content

Repair report and version indexes and clean up stale Litestream files#922

Draft
epompeii wants to merge 1 commit into
develfrom
reindex-report-version
Draft

Repair report and version indexes and clean up stale Litestream files#922
epompeii wants to merge 1 commit into
develfrom
reindex-report-version

Conversation

@epompeii

@epompeii epompeii commented Jul 6, 2026

Copy link
Copy Markdown
Member

Migration: reindex_report_version

Long-lived databases can carry index corruption (wrong # of entries in index) that silently drops rows from index-driven queries, while the underlying table data remains intact. This migration rebuilds all indexes on the report and version tables. REINDEX is a no-op when the indexes are already consistent, so this is safe for every instance.

The migration also removes report_benchmark rows orphaned by deletes performed before ON DELETE CASCADE was enforced (see 2023-01-15-185835_perf_cascade), along with their dependent metric, boundary, and alert rows. Migrations run with foreign_keys = OFF, so cascades do not fire and the delete chain is explicit, bottom-up. Since metric.report_benchmark_id and boundary.metric_id are not indexed, the orphaned ids are collected into temp tables so each table is scanned only once.

Validated end-to-end on a multi-gigabyte database exhibiting this corruption: the migration completes in ~15 seconds, after which PRAGMA quick_check returns ok and PRAGMA foreign_key_check returns no rows, with all report rows intact.

Startup cleanup: stale Litestream files

Litestream 0.3 kept its state in a generations/ directory and a generation pointer file; Litestream 0.5+ only reads ltx/. Instances that upgraded through 0.3 carry that dead state on the data volume forever. An interrupted litestream restore can also leave <db>.tmp* output files behind.

The API server now removes these stale files before spawning Litestream, when nothing has them open. Failures are logged and ignored; cleanup never blocks startup. Covered by unit tests, including that the live ltx/ state and the database itself are untouched.

Add a migration that rebuilds all indexes on the report and version
tables. Long-lived databases can carry index corruption (wrong number
of entries in an index) that silently drops rows from index-driven
queries. REINDEX is a no-op when the indexes are already consistent.

The migration also removes report_benchmark rows (and their dependent
metric, boundary, and alert rows) orphaned by deletes performed before
ON DELETE CASCADE was enforced (see 2023-01-15-185835_perf_cascade).
Migrations run with foreign_keys = OFF, so the delete chain is explicit
and bottom-up, using temp tables to scan each unindexed table only once.

At API server startup, remove stale Litestream files: the generations
state from Litestream 0.3 that 0.5+ no longer reads, and leftover
restore output files from an interrupted litestream restore.
@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

🐰 Bencher Report

Branchreindex-report-version
Testbedintel-v1
Click to view all benchmark results
BenchmarkLatencyBenchmark Result
microseconds (µs)
(Result Δ%)
Upper Boundary
microseconds (µs)
(Limit %)
Adapter::Json📈 view plot
🚷 view threshold
4.60 µs
(-0.50%)Baseline: 4.62 µs
4.90 µs
(93.82%)
Adapter::Magic (JSON)📈 view plot
🚷 view threshold
4.45 µs
(-1.02%)Baseline: 4.50 µs
4.72 µs
(94.29%)
Adapter::Magic (Rust)📈 view plot
🚷 view threshold
25.92 µs
(+1.48%)Baseline: 25.54 µs
26.64 µs
(97.29%)
Adapter::Rust📈 view plot
🚷 view threshold
3.53 µs
(+1.19%)Baseline: 3.49 µs
3.60 µs
(97.97%)
Adapter::RustBench📈 view plot
🚷 view threshold
3.53 µs
(+1.24%)Baseline: 3.48 µs
3.59 µs
(98.15%)
🐰 View full continuous benchmarking report in Bencher

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant