feat(backfill): move OwnerBackfill drain to a standalone worker (COW-1118)#116
Draft
yvesfracari wants to merge 1 commit into
Draft
Conversation
…1118) The non-deterministic order drain ran inside a Ponder block handler (OwnerBackfill:block). Ponder executes indexing functions serially in checkpoint order, so a 30s /account call parked event indexing, and a single un-drainable owner wedged /readyz forever (its per-page-less, non-resumable commit re-fetched the whole history every firing). Move the HTTP drain into a separate long-running worker that coordinates with the indexer only through the durable cow_cache schema, so time-to-ready ~= max(backfill, drain) instead of backfill + drain, with no owner able to wedge readiness. - New table cow_cache.owner_drain_state (durable work queue); created by createCowCacheTables, called from both setup.ts and the worker. - Two ponder-free modules so the worker can share decode/hash/cache logic without importing ponder:schema: orderbookHttp.ts (HTTP client + fetchAccountOrderPage) and composableCache.ts (cow_cache tables, filterAndProcessForCache, cache upsert/read, drain-state helpers). - Worker (src/worker/drain.ts, `pnpm drain`): claim owners with FOR UPDATE SKIP LOCKED + stale-lease reclaim, drain N concurrently, offset-walk newest->oldest committing each page and bumping next_offset, mark complete at a short page. Touches only cow_cache. - ConditionalOrderCreated enqueues non-deterministic owners (ON CONFLICT DO NOTHING). OwnerBackfill:block is now a DB-only projection of complete owners; OwnerBackfillLive removed. reconcile step dropped (OrderStatusTracker already polls open orders). - Deployment: drain sidecar in docker-compose; pg dependency; docs updated (deployment, architecture, agent_docs, .env.example).
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.
Closes COW-1118.
Problem
The non-deterministic order drain (COW-1117) ran inside a Ponder block handler (
OwnerBackfill:block/OwnerBackfillLive:block). Ponder executes indexing functions serially in checkpoint order, so:/accountcall parked event indexing for 30s; the "overlaps historical sync" claim was false.fetchComposableOrdersfetched all pages then committed once. A timeout mid-pagination abandoned the promise before any write, so the next firing restarted from the same cursor → timed out again →historyBackfillednever flipped →/readyzstayed 503 forever. Any owner too big to drain in 30s wedged readiness permanently (this is why prod was stuck).Fix
Move the HTTP drain into a separate long-running worker that coordinates with the indexer only through the durable
cow_cacheschema. Goal: time-to-ready ≈ max(backfill, drain) instead ofbackfill + drain, with no owner able to wedge readiness.Changes
cow_cache.owner_drain_state(chain_id, owner, status, next_offset, claimed_at, updated_at)— durable work queue.CREATE TABLE IF NOT EXISTSviacreateCowCacheTables, called by bothsetup.tsand the worker (not startup-ordering-dependent).ponder:schema:orderbookHttp.ts— HTTP client (retry/backoff, pagination, newfetchAccountOrderPagefor the offset-walk).composableCache.ts— cow_cache tables,filterAndProcessForCache(decode → reconstructgenerator_hash, no DB), cache upsert/read, claim/complete/release helpers.src/worker/drain.ts,pnpm drain) — claims owners withFOR UPDATE SKIP LOCKED+ stale-lease reclaim, drains ~10 concurrently, offset-walks newest→oldest committing each page and bumpingnext_offset, markscompleteat a short page. Touches onlycow_cache, so it's deployment-independent and safe to run N-wide.ConditionalOrderCreatedenqueues non-deterministic owners (ON CONFLICT DO NOTHING);OwnerBackfill:blockis now a DB-only projection ofcompleteowners (hash → current eventId, orphan-drop, fliphistoryBackfilled).OwnerBackfillLiveremoved;OwnerBackfillis one registration with noendBlock. The redundantreconcilestep was dropped (OrderStatusTracker already polls open orders).drainsidecar indocker-compose.yml;pgdependency; docs updated (deployment.md,architecture.md,agent_docs/,.env.example).Verification
pnpm codegen/typecheck/lintclean; 135/135 tests pass (obsoletefetchComposableOrderstests replaced withfetchAccountOrderPage+filterAndProcessForCachecoverage).pendingand reclaims a staledraininglease (preservingnext_offset), marks ownerscomplete, shuts down cleanly on SIGTERM.tsxin theNODE_ENV=productionimage so the sidecar runs.Reviewer note — deliberate trade-off
The projection runs at
interval: 250(a single registration can't have different backfill vs. realtime cadences), so a post-tip straggler adds up to one interval (~20 min gnosis / ~50 min mainnet) to time-to-ready. Bounded and can never wedge — matches the issue's "remove OwnerBackfillLive" instruction. If snappier post-tip readiness is preferred, this can be split into two projection-only registrations (coarse backfill + fine live).