fix(indexer): wait for the core service before starting the ETL - #1043
Closed
rickyrombo wants to merge 1 commit into
Closed
rickyrombo wants to merge 1 commit into
rickyrombo wants to merge 1 commit into
Conversation
The ETL's own chain-ID retry budget is 30 attempts at a flat 2s delay — a fixed ~58s, hardcoded as unexported constants in go-openaudio/pkg/etl, so it can't be raised from here. That is shorter than a cold audiusd start: today's node upgrade restarted both at once, audiusd took ~67s to become ready, and the indexer gave up six seconds early. Gate etlIndexer.Run() behind a readiness poll on the same core endpoint the ETL will use, with a 15 minute budget. By the time Run() executes its own retries, the service is already up and the first attempt succeeds. This is the graceful path, not the safety net. The preceding errgroup fix means a failure here now crashes the process and k8s restarts it, but waiting beats crash-looping through escalating backoff and re-running startup on each pass. If core is still down after 15 minutes, crashing is the right answer and that is what happens. Cancellation is distinguished from timeout so a SIGTERM during startup still returns context.Canceled and main.go shuts down quietly instead of panicking. Timeout and poll interval are parameters rather than constants read inside the function, mirroring the upstream initializeChainID signature, so the tests don't sleep for real. 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.
Follow-up to #1042, which fixed the part of today's stall that turned a 60-second blip into an 8-hour outage. This fixes the blip itself.
The race
The ETL resolves its chain ID from the core service before
indexBlocks()starts. Its retry budget is 30 attempts at a flat 2s delay — a fixed ~58 seconds, hardcoded as unexported constants (chainIDInitMaxAttempts,chainIDInitRetryDelay) ingo-openaudio/pkg/etl, withInitializeChainID(ctx)exposing no knobs. It can't be raised from this repo without an upstream release.That budget is shorter than a cold audiusd start. Today's GKE node upgrade recreated every pod at once:
creator-1/audiusd-0createdcore-indexerstarts pollingrpc.audius.cofor chain IDaudiusdcontainer actually starts (~67s)error initializing chain ID after 30 attemptsIt missed by about six seconds. Any simultaneous restart is a coin flip.
The fix
Gate
etlIndexer.Run()behind a readiness poll on the same core endpoint the ETL will use, with a 15 minute budget. By the timeRun()reaches its own retries, the service is up and attempt 1 succeeds.This is the graceful path, not the safety net. #1042 means a failure here now crashes the process and k8s restarts it — but waiting beats crash-looping through escalating
CrashLoopBackOffdelays and re-running startup on every pass. If core is still down after 15 minutes, crashing is the right answer, and that is what happens.Two details worth flagging for review:
context.Canceled, whichmain.go:58ignores, so shutdown stays quiet. Without that branch the wrappedlastErrwould panic on every graceful shutdown during startup — a regression this PR would otherwise have introduced.initializeChainID(ctx, maxAttempts, retryDelay)signature. That's what lets the tests run in milliseconds instead of sleeping for real.Testing
TestAwaitCoreReady{RetriesUntilCoreIsUp,TimesOut,ReturnsCanceledOnShutdown}against a fakeCoreServiceClientthat embeds the interface and overrides onlyGetNodeInfo. The three cases cover the recovery path, the give-up path, and the graceful-shutdown path. All pass in 0.5s;go build ./...andgo vet ./indexer/are clean. The rest of./indexer/needs postgres on :21300, which wasn't up locally, so those didn't run.Still outstanding
core-indexerhas no liveness, readiness, or startup probe, andmain.go'sindexercase doesn't start a health server the way thesolana-indexerandeth-indexercases do./health_check?max_core_indexer_block_diff=Nalready returns 500 on excess lag — nothing calls it. That's the remaining reason a wedged indexer goes unnoticed; happy to take it next.🤖 Generated with Claude Code