fix(verification): stop background verification scanning contiguous_data_ids - #957
Conversation
…ata_ids Marking a root verified ran `WHERE id = @id OR root_transaction_id = @id` with no index on root_transaction_id. The query used to get item IDs from bundles.bundle_data_items, which is indexed. When the column moved onto data.db (2025.07.23 add-bundle-metadata) no index came with it, so every verification scanned the whole table while holding the single data.db writer. On a 99M-row data.db each took ~10 min: half the writer's time, with chunk placement confirmations queueing ~3 min behind it. A partial index on root_transaction_id turns it into two index lookups. Selecting the next batch wrapped verification_priority in COALESCE, which stopped SQLite seeking, so it walked every unverified row whenever fewer than 1000 qualified (229 s -> 0.02 s). A positive minimum now uses the bare range. A minimum of 0 or below keeps the COALESCE statement, so NULL still counts as priority 0 and everything is verified. Adds query-plan tests for both statements and replaces the skipped saveVerificationStatus test, which failed in the full suite because the main-thread write dedupe skipped an ID an earlier test had written. Documents MIN_DATA_VERIFICATION_PRIORITY and MAX_VERIFICATION_RETRIES. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AF5ZbmQGHvRjPbYPcxo3iQ
|
Navigate logical layers of code changes, visualize relationships, and explore their blast radius. No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Advanced Run ID: 📒 Files selected for processing (8)
Included review availability: This review used your included allowance. Your plan provides up to 2 included reviews per hour; 1 remain after this review. 📝 WalkthroughWalkthroughThe background verification worker now accepts priority and retry limits, and selects data using a query suited to the configured minimum priority. A partial index supports root-transaction lookups. Tests cover selection behavior, status updates, and index use. ChangesBackground data verification
Priority: ⬆️ High Estimated code review effort: 3 (Moderate) | ~20 minutes Change: Bug fix Merge Risk: ⚪ Minimal · up to This change speeds up background verification queries and adds an index. The migration may take several minutes on very large databases, as the changelog notes, but no merge-blocking risk was identified. Security Architecture ReviewSecurity architecture risk: 🔵 Low · up to The verification changes preserve the existing access path and status-update scope, with tests covering selection and index use. A failed migration could leave the gateway running without the intended performance fix, but that startup behavior predates this PR. No introduced security finding was established. Retained concerns Security review detailsSecurity Blast Radius
Trust Boundaries and Controls
Resilience and Maintainability Implications
Hardening Proposals
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## develop #957 +/- ##
===========================================
+ Coverage 81.57% 81.58% +0.01%
===========================================
Files 227 227
Lines 87981 87998 +17
Branches 7700 7699 -1
===========================================
+ Hits 71768 71793 +25
+ Misses 16088 16080 -8
Partials 125 125 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Problem
Background data verification did two full scans of
data.db'scontiguous_data_ids, one for each batch. Measured on a production gateway with core up 13.2 h and a 72 GBdata.dbholding 99M rows:updateDataItemVerificationStatus(write)WHERE id = @id OR root_transaction_id = @idwith no index onroot_transaction_id→SCAN contiguous_data_idsselectVerifiableContiguousDataIds(read)COALESCE(verification_priority, 0) >= @minblocks the range seek, so the in-order walk covers every unverified row whenever fewer than 1000 qualifydata.dbhas a single writer, so the update scans occupied it for 6.6 of the 13.2 hours. Otherdata.dbwrites queued behind them:confirmChunkPlacementswaited ~166 s on average andsaveDataContentAttributes~3 s. Their duration was almost entirely queue wait.Root cause
bundles.bundle_data_items, whoseroot_transaction_idis indexed. b08111a (PE-8335) moved the lookup onto a newcontiguous_data_ids.root_transaction_idcolumn so thatdata.dbcould be distributed withoutbundles.db. The migration added the column but no index, so the lookup quietly became a full scan.COALESCE(…, 80), toCOALESCE(…, 0)so that NULL means "lowest priority", not "excluded". That is correct, but wrapping the column prevents a range seek. Once only a few rows qualify (53 on this gateway),LIMIT 1000never fills and the walk covers the whole partial index.Fix
contiguous_data_ids_root_transaction_id_idx ON (root_transaction_id) WHERE root_transaction_id IS NOT NULL. SQLite plans theORas aMULTI-INDEX OR: a primary-key probe plus an index probe. The index keepsdata.dbindependent ofbundles.db, which was the point of PE-8335.getVerifiableDataIdsuses a bareverification_priority >= @min, which seeks. When the minimum is 0 or below, it keeps the existingCOALESCEstatement. That preserves the contract that such a minimum verifies all unverified data, the behaviour from before priorities existed, and thereLIMITfills immediately so the walk is cheap.getVerifiableDataIdstakes the minimum and the retry cap as parameters that default to config, which resolves the method's own TODO.Measured on a copy of that production
data.dbThe copy was made with the SQLite backup API and run with the same SQLite (3.45.3) as the core image:
CREATE INDEXUpgrade cost: migrations run in
docker-entrypoint.shbefore the gateway starts, so a gateway this size starts about 11.5 minutes later on the first boot after upgrading. The 2025-05-28 migration built an index on this same table at startup too. The build costs about the same as one of the scans verification was running every 20 minutes. Rows with a root transaction are 97% of the table, so making the index partial saves little space, but it keeps L1 rows out.Tests
selectStableTransactionOffsetByIdtest: the select mustSEARCH … (verification_priority>?), and the update must probecontiguous_data_ids_root_transaction_id_idxand neverSCAN contiguous_data_ids. Both fail on develop.saveVerificationStatustest is replaced. It only passed when run on its own ("works when running the test individually") because the main-threadsaveDataContentAttributesdedupe (7-minute TTL) skipped an ID an earlier test had already written, afterafterEachemptied the table. It now uses fresh IDs and checks the currentroot_transaction_idsemantics.test/data-schema.sqlwas regenerated with./test/dump-test-schemas. Only the new index is committed; the dump also picked up unrelated drift in the core and bundles schemas, which I left out.yarn test: 3407 pass, 1 fail. The failure isparquet-exporter.test.ts, whose duckdb binding can't load on the local glibc; it is not related to this change.yarn typecheckis at the develop baseline (430 errors).Docs
MIN_DATA_VERIFICATION_PRIORITYandMAX_VERIFICATION_RETRIESwere indocker-compose.yamlbut missing fromdocs/envs.md; both are now documented.This came out of evaluating #448 (partitioned verification). Most of the cost that #448 would have saved was these scans.
🤖 Generated with Claude Code
https://claude.ai/code/session_01AF5ZbmQGHvRjPbYPcxo3iQ