Uh oh!
There was an error while loading. Please reload this page.
Add bencher_replica: in-process SQLite replication to replace Litestream - #929
Add bencher_replica: in-process SQLite replication to replace Litestream#929epompeii wants to merge 1 commit into
Conversation
🤖 Claude Code ReviewPR: #929 All four subsystem agents plus my own integration pass are complete. Here's the consolidated review. PR Review: |
|
| Project | Bencher |
| Branch | u/ep/bencher-replica |
| Testbed | intel-v1 |
🚨 2 Alerts
| Benchmark | Measure Units | View | Benchmark Result (Result Δ%) | Upper Boundary (Limit %) |
|---|---|---|---|---|
| Adapter::Rust | Latency microseconds (µs) | 📈 plot 🚷 threshold 🚨 alert (🔔) | 3.89 µs(+10.89%)Baseline: 3.51 µs | 3.71 µs (104.87%) |
| Adapter::RustBench | Latency microseconds (µs) | 📈 plot 🚷 threshold 🚨 alert (🔔) | 3.89 µs(+10.93%)Baseline: 3.50 µs | 3.71 µs (104.86%) |
Click to view all benchmark results
| Benchmark | Latency | Benchmark Result microseconds (µs) (Result Δ%) | Upper Boundary microseconds (µs) (Limit %) |
|---|---|---|---|
| Adapter::Json | 📈 view plot 🚷 view threshold | 4.87 µs(+4.48%)Baseline: 4.66 µs | 4.92 µs (98.98%) |
| Adapter::Magic (JSON) | 📈 view plot 🚷 view threshold | 4.72 µs(+4.13%)Baseline: 4.53 µs | 4.73 µs (99.74%) |
| Adapter::Magic (Rust) | 📈 view plot 🚷 view threshold | 26.39 µs(+2.73%)Baseline: 25.69 µs | 26.72 µs (98.76%) |
| Adapter::Rust | 📈 view plot 🚷 view threshold 🚨 view alert (🔔) | 3.89 µs(+10.89%)Baseline: 3.51 µs | 3.71 µs (104.87%) |
| Adapter::RustBench | 📈 view plot 🚷 view threshold 🚨 view alert (🔔) | 3.89 µs(+10.93%)Baseline: 3.50 µs | 3.71 µs (104.86%) |
4c2c1c5 to
58ac5e2Compare58ac5e2 to
473737eCompare473737e to
dcd02f4Comparedcd02f4 to
203d96bCompare203d96b to
24a3762Compare24a3762 to
6d14320Compare6d14320 to
176b289Compare176b289 to
ac3ae6aCompareLitestream 0.5.13 blocked all API writes for ~5.5 minutes in production (2026-07-10): when it decides a full re-snapshot is needed, it copies the entire database into a local LTX file while holding the SQLite write lock, and no configuration reaches that code path. Its LTX compaction also churns whole-database S3 transfers several times a day. bencher_replica is an in-process replacement built around six invariants (documented in src/lib.rs), the prime one being that the SQLite write lock is only ever held for O(WAL-tail) work, never O(database): - WAL parser with full salt and cumulative checksum-chain verification - Local filesystem XOR S3-compatible storage behind one contract - Step-driven sync engine; checkpoints are PASSIVE while the replicator itself holds BEGIN IMMEDIATE, closing the ship-vs-checkpoint race without ever needing RESTART or TRUNCATE checkpoints - Generation-based snapshots via a single-step SQLite online backup into a scratch file (transactionally consistent under concurrent checkpoints), throttled zstd multipart upload, snapshot.json as the atomic commit marker - Latest-only restore in the same startup handshake slot Litestream used, with chain pre-validation and checkpoint-consumption verification - Restore-and-compare verification (default daily) and shadow mode: with both plus.litestream and plus.replica configured, Litestream keeps checkpoint ownership and restore precedence during the burn-in Also included: - Fix: standalone sweep connections (stats, credit grants) now disable wal_autocheckpoint when replication is configured; previously the credit sweep could checkpoint and restart the WAL behind Litestream's back - plus.replica config (JsonReplication), otel Replica* counters, main.rs lifecycle wiring (restore precedence, fatal race arm, final ship inside the Fly kill budget), TestServer::new_with_replica, Dockerfile stubs - JsonLitestream.metrics_port and [[metrics]] in the Fly configs so litestream_* Prometheus metrics are scraped during the shadow period Testing: 275 crate tests (WAL fixtures cross-validated against SQLite itself, a three-backend storage contract suite, 8 fault-injection scenarios, 6 crash kill points, 8 seeded 200-op equivalence workloads, ignored soak and live-S3 tiers) plus 4 server-level integration tests. An adversarial multi-agent review confirmed 18 findings, all fixed with regression tests, including a silent data-loss gap in resume (salt-match resume now proves content against the replica tip, and the meta-verified path requires salt1 continuity).
ac3ae6a to
d5070f6Compare
Why
Litestream 0.5.13 blocked all Bencher Cloud API writes for ~5.5 minutes on 2026-07-10: when its verify step decides a full re-snapshot is needed, it copies the entire 4.35 GB database into a local LTX file while holding the SQLite write lock (via its
_litestream_locktable), and no configuration knob reaches that code path. The same stall recurs on process restarts, and LTX compaction churns whole-database S3 transfers several times a day. Upstream 0.5.14 does not fix it.bencher_replicais an in-process replacement. Being in-process is the structural fix: the app funnels writes through a single writer mutex, so the replicator coordinates checkpoints with the app's own write scheduling instead of fighting a separate process for locks. The prime invariant (I5): the SQLite write lock is only ever held for O(WAL-tail) work, never O(database). All six governing invariants are documented inplus/bencher_replica/src/lib.rs.What
BEGIN IMMEDIATE, closing the ship-vs-checkpoint race without ever needing blocking RESTART/TRUNCATE checkpointssnapshot.jsonas the atomic commit marker with a mandatory-replay boundary offset0disables); the backstop for externally-caused divergenceplus.litestreamandplus.replicaconfigured, Litestream keeps checkpoint ownership and restore precedence during the burn-inwal_autocheckpointwhen replication is configured; previously the credit sweep could checkpoint and restart the WAL behind Litestream's backReplica*otel counters and a critical-section histogram;JsonLitestream.metrics_portplus[[metrics]]in the Fly configs solitestream_*Prometheus metrics are scraped during the shadow periodTesting
cargo nextest run --all-features(2047 passed),cargo test --doc,cargo clippy --no-deps --all-targets --all-features -- -Dwarnings,cargo check --no-default-features,cargo gen-typesDeployment plan
plus.replica(S3 target, its own bucket/prefix) alongside the existingplus.litestreamconfig; watchreplica.verify.passandreplica.divergencemetricscargo nextest run -p bencher_replica --features plus,testing --run-ignored ignored-onlywithBENCHER_REPLICA_TEST_S3_*env varsplus.litestreamsection (forces one clean generation); the Litestream removal checklist (binary, Dockerfile stage, config types) follows after the burn-inNotes for review
#[expect(clippy::too_many_lines)]onApiCounter::description()inbencher_otel(matches existing precedent for exhaustive metadata matches)cargo deny checkfails on two PRE-EXISTING advisories viabencher_plot's tree (ttf-parser unmaintained, crossbeam-epoch RUSTSEC-2026-0204), unrelated to this change