feat(api): add the indexer and flusher bounds the chain cutover needs - #1028
Open
rickyrombo wants to merge 1 commit into
Open
feat(api): add the indexer and flusher bounds the chain cutover needs#1028rickyrombo wants to merge 1 commit into
rickyrombo wants to merge 1 commit into
Conversation
Three config values, all defaulting to 0 meaning unbounded, so nothing changes in normal operation. etlStartingBlockHeight / etlEndingBlockHeight are passed to the ETL's existing SetStartingBlockHeight / SetEndingBlockHeight, which indexer.go never called. Without them the indexer only knows how to resume, and the resume query -- MAX(block_height) FROM etl_blocks -- carries no chain_id. Pointed at a new chain it resolves to the old chain's height and waits for a block that will not exist for years, silently. There is a chain-aware fallback to core_indexed_blocks, but it only runs when etl_blocks is empty, so it never fires on a database that has indexed the old chain. newChainFlushToBlock is a ceiling on the flusher, the mirror of the existing newChainFlushFromBlock. The cutover stops the old-chain indexer at a height L and needs everything confirmed at or below L to be on the new chain -- and nothing above L across that line -- before the new indexer starts. The ceiling filters rather than halts. Enqueue is dispatched asynchronously, so confirmed_block is only roughly ordered by id; stopping at the first row above the ceiling would strand one below it, which would then flush after the boundary was recorded and be indexed twice. Rows with a NULL confirmed_block cannot be placed relative to L and are held until the ceiling lifts. Tests cover the ceiling boundary (inclusive), that it filters past an out-of-order row, that NULLs are held and then released, and that an unset ceiling is unbounded -- confirmed failing without the change. A config test covers the parse helper, since a malformed bound reading as 0 would silently disable the limit it was meant to impose.
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.
What
Three config values, all defaulting to
0meaning unbounded, so nothing changes in normal operation. They exist for the genesis-migration cutover (cmd/genesis-writer/ROLLOUT.md, Runbook step 12), which cannot be executed without them.etlStartingBlockHeightSetStartingBlockHeightetlEndingBlockHeightSetEndingBlockHeightnewChainFlushToBlocknewChainFlushFromBlockWhy the indexer bounds
indexer/indexer.gocallsSetConfig,SetDBURL,SetCheckReadinessand the hooks — but never the two height setters, even though the ETL has supported them all along. So the indexer only knows how to resume, and the resume query is:That has no
chain_id. Pointed at a new chain it resolves to the old chain's height — roughly 24M against a chain at a few thousand — and polls for a block that won't exist for years. It fails as a silent stall, not an error.There is a chain-aware fallback to
core_indexed_blocks WHERE chain_id = $1, but it only runs whenGetLatestIndexedBlockreturnsErrNoRows. On any database that has indexed the old chain,etl_blocksis populated, the first query answers, and the fallback never fires. That's precisely why the failure is quiet — the code that would have caught it is one branch away.The ending bound is the other half: the cutover needs the old-chain indexer to stop at a known height
L, chosen in the future so the stop doesn't race the config rollout.Why the flusher ceiling
The cutover needs everything confirmed at or below
Lto be on the new chain — and nothing aboveLacross that line — before the new indexer starts atH+1. Without a ceiling the flusher would carry rows aboveLover the boundary, and those would be indexed from the old chain and from the new one.It filters rather than halts, and that distinction matters. Enqueue is dispatched with
go app.enqueueForNewChain(...), soconfirmed_blockis only roughly ordered byid— two writes confirmed at blocks 100 and 101 can be inserted in either order. Halting at the first row above the ceiling would strand a row below it, which would then flush after the boundary was recorded and be indexed twice.NULL
confirmed_blockis held while a ceiling is set. Such a row can't be placed relative toL; holding is the recoverable choice, since it flushes once the ceiling lifts, whereas sending it early could double-index and dropping it would lose the write. Worth draining or inspecting those before a cutover rather than discovering them during one.Tests
Flusher, against a live test database:
idrow above the ceiling doesn't hide a high-idrow below itconfirmed_blockis held, then released when the ceiling liftsConfirmed failing without the change:
Plus a config test for the shared parse helper. The three bounds all mean "no bound" at
0, so a malformed value must panic rather than read as0— otherwise a typo silently disables the very limit it was meant to impose, and that only surfaces as duplicated or missing rows much later.Pre-existing flusher tests still pass.
Note
Touches
api/new_chain_flusher.go, which #1018 also changes. Whichever lands second will need a small rebase — they don't overlap logically (that one changes how rows are retired, this one changes which rows are eligible).