You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit ingestion transactions outside the ledger-range cache lock - #1036
rpcv1 requests no longer stall while a ledger is being ingested. The ingestion commit used to hold the ledger-range cache lock across the SQLite commit, so every request waited out the whole ledger write (tens to hundreds of milliseconds, once per ledger).
Commit reads the post-trim oldest ledger inside its own write tx, commits, then publishes both cache bounds under one short lock. The cache only ever holds committed bounds, a failed commit leaves it untouched, and there is no trim predicate to keep in step with trimLedgers.
getEvents validates startLedger and scans inside one read snapshot (store.EventReader.NewTx returns an EventReaderTx, a LedgerReaderTx that also scans events), as getLedgers and getTransactions already did.
Snapshot readers take both bounds from their own snapshot (one statement, two index probes) and use the cached pair only for close times, falling back to the row reads when a commit landed after NewTx or the cache was reset. The cache is purely a fast path; no reader depends on it for correctness.
The cache is primed from the stored bounds when the DB is opened and republished by every commit, so the lazy fills, the "0 means unknown" convention and the post-backfill ResetCache are gone; a zero latest simply means an empty DB.
NewEventReader drops its never-read passphrase parameter.
Cost on a trivial in-memory DB: the snapshot range read is ~18µs against ~21µs for the old oldest-row read, so getLedgers/getTransactions get slightly cheaper and getEvents pays ~10µs over its old pooled path (BEGIN/ROLLBACK plus the bounds statement). Prepared statements would remove most of that across all three endpoints and are a follow-up.
See comments of #1003 for the speedup from this round of optimizations, and #1018 for what the original speeds are pre-optimization + pre-views-adoption.
Why
This created a universal speedup across each endpoint in RPCv1, whereas previously, we'd seen on-LCM-ingestion-periodic latency spikes: the commit stall set p99 on every endpoint.
Known limitations
For the microseconds between tx.Commit() returning and the publish, the pooled readers (getTransaction, getHealth, getFeeStats, sendTransaction) echo bounds one ledger behind in their responses. Snapshot readers are unaffected.
This new initialization failure path returns after the SQLite session has already been opened, but the caller receives no handle to close it. Close d before returning (as openSQLiteDB does on migration failure) so callers that handle the error do not leak the connection and database file descriptor.
If cache priming fails, both open functions return this error without closing the newly created session, leaving its connection pool and file descriptors owned by nobody. The migration-error path above explicitly closes the session; do the same here before returning.
If cache priming fails, both open functions return this error while leaving the newly opened SQLite session (and its connection pool) alive. This is especially visible to callers that retry opening the database. Close the session on this post-open initialization failure, as openSQLiteDB already does when migrations fail.
The new range/scan snapshot guarantee is not covered by a regression test. The added ledger test only checks cache-bound reconciliation; it would not catch eventScanner accidentally using the pool instead of tx.tx, which would reintroduce the trim race this change is intended to fix. Add a test that pins an EventReaderTx snapshot, commits a trim, and verifies the transaction can still scan the event whose startLedger was validated in that snapshot.
The reason will be displayed to describe this comment to others. Learn more.
Approving at e101bc0. The cache is primed at open and republished once per commit, after the commit; snapshot readers take both bounds from their own transaction; getEvents validates and scans in one snapshot; getTransaction clamps its latest bound to a just-found transaction.
One non-blocking suggestion: Copilot's regression test for the snapshot guarantee, open an event snapshot, commit a trim underneath it, and check the snapshot still scans the validated event, needs no pause seam since SQLite's snapshot isolation makes it deterministic, and it is the one test that would catch the event scanner reading the pool again.
One non-blocking suggestion: Copilot's regression test for the snapshot guarantee, open an event snapshot, commit a trim underneath it, and check the snapshot still scans the validated event, needs no pause seam since SQLite's snapshot isolation makes it deterministic, and it is the one test that would catch the event scanner reading the pool again.
Added as TestEventReaderTx_SurvivesTrim. Two things had to differ from the sketch for it to test what it claims:
NewTx does not establish the snapshot. SQLite's deferred BEGIN takes it at the first statement, so the test calls GetLedgerRange on the snapshot before trimming; without that, the trim lands first and the test passes for the wrong reason.
The trim is two raw DELETEs with the same predicates trimLedgers/trimEvents use, not a Commit. Commit's TRUNCATE checkpoint blocks on any reader holding an older snapshot for the busy timeout (5s by default), which would make the test take five seconds for no extra coverage; snapshot isolation is the property under test and the deletes commit the same way.
It pins the snapshot's event count against the pool's zero after the trim. Mutation-checked with go test -overlay: wiring eventReaderTx's scanner to the pool instead of its tx fails it (expected 4, actual 0).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
rpcv1 requests no longer stall while a ledger is being ingested. The ingestion commit used to hold the ledger-range cache lock across the SQLite commit, so every request waited out the whole ledger write (tens to hundreds of milliseconds, once per ledger).
Commitreads the post-trim oldest ledger inside its own write tx, commits, then publishes both cache bounds under one short lock. The cache only ever holds committed bounds, a failed commit leaves it untouched, and there is no trim predicate to keep in step withtrimLedgers.startLedgerand scans inside one read snapshot (store.EventReader.NewTxreturns anEventReaderTx, aLedgerReaderTxthat also scans events), as getLedgers and getTransactions already did.NewTxor the cache was reset. The cache is purely a fast path; no reader depends on it for correctness.ResetCacheare gone; a zero latest simply means an empty DB.NewEventReaderdrops its never-read passphrase parameter.Cost on a trivial in-memory DB: the snapshot range read is ~18µs against ~21µs for the old oldest-row read, so getLedgers/getTransactions get slightly cheaper and getEvents pays ~10µs over its old pooled path (BEGIN/ROLLBACK plus the bounds statement). Prepared statements would remove most of that across all three endpoints and are a follow-up.
See comments of #1003 for the speedup from this round of optimizations, and #1018 for what the original speeds are pre-optimization + pre-views-adoption.
Why
This created a universal speedup across each endpoint in RPCv1, whereas previously, we'd seen on-LCM-ingestion-periodic latency spikes: the commit stall set p99 on every endpoint.
Known limitations
For the microseconds between
tx.Commit()returning and the publish, the pooled readers (getTransaction, getHealth, getFeeStats, sendTransaction) echo bounds one ledger behind in their responses. Snapshot readers are unaffected.Stack created with GitHub Stacks CLI • Give Feedback 💬