Repository navigation
fix(explorer): stop the API truncating the indexer's tables; upsert the cursor - #10
Merged
Merged
Conversation
…he cursor Stage showed an `indexer_cursor` table with zero rows while the indexer logged a cursor walking 295 to 303, and 7 blocks on a chain at 303. Two defects compounding: - `run_api` called `cleanup_database`, which TRUNCATEs `indexer_cursor`, `blocks` and everything else. The API is a READER of tables the indexer writes, and the two run as separate containers against one database, so that truncate races a live indexer. Wiping now belongs to `run_indexer`, which owns the data, and happens after the migrations rather than before them -- the API ran it first, where a truncate of tables that do not exist yet fails on a fresh database, and swallowed the error. - `set_cursor` was an `UPDATE`. An `UPDATE` whose row is missing affects zero rows and returns Ok, so once the row was gone the indexer went on counting in memory and persisted nothing, with no error anywhere. It is an upsert now: whatever removes the row, the next poll puts it back. The wipe was invisible from the tip, too. `reconcile_head` only compares the head, so blocks written after a truncate matched and nothing looked wrong while 1..295 stayed missing for good. Also drops the developer-mode shutdown wipe from the API for the same reason; the indexer keeps its own. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Symptom
On stage:
indexer_cursorhad zero rows while the indexer logged a cursor walking 295 → 303, andblocksheld 7 rows on a chain at height 303.Two defects
The API truncates tables it does not own.
run_apicalledcleanup_database, which isTRUNCATE ... indexer_cursor, blocks, ... CASCADE. The API is a reader; the indexer is a separate container against the same database. A truncate from the API races a live indexer.Moved to
run_indexer, and now after the migrations — the API ran it before them, where truncating tables that do not exist yet fails on a fresh database, and the error was logged and swallowed.set_cursorwas anUPDATE. AnUPDATEwhose row is missing affects zero rows and returnsOk. So once the row was gone the indexer kept its position only in memory and persisted nothing, silently, forever. Now an upsert — whatever removes the row, the next poll puts it back.Why nothing noticed
reconcile_headcompares only the head. Blocks written after the truncate matched the tip, so reconciliation was content while 1..295 stayed missing permanently. The hole is invisible from the one height that gets checked.Stage remediation
None needed. The cursor row is currently absent, so the next indexer start takes
ensure_cursor'sstart_heightof 0 and walks 1..303;index_heightupserts blocksON CONFLICT (height), so the existing rows are rewritten rather than colliding.Not tested
CI runs
cargo test --all-targetswith no Postgres service, and both changes are SQL against a live database. The upsert is one statement; the move is a relocated call site.🤖 Generated with Claude Code