Skip to content

FlatKV: background read/fold for account data - #4155

Open
cody-littley wants to merge 2 commits into
mainfrom
cjl/background-account-reads-2
Open

FlatKV: background read/fold for account data#4155
cody-littley wants to merge 2 commits into
mainfrom
cjl/background-account-reads-2

Conversation

@cody-littley

Copy link
Copy Markdown
Contributor

Describe your changes and provide context

Move account read/fold workflow off of the execution thread.

@cody-littley cody-littley self-assigned this Sep 11, 2026
@cody-littley cody-littley changed the title Cjl/background account reads 2 FlatKV: background read/fold for account data Sep 11, 2026
@cursor

cursor Bot commented Sep 11, 2026

Copy link
Copy Markdown

PR Summary

High Risk
Changes MVCC write/read synchronization and the FlatKV account apply path; incorrect staging or latch behavior could corrupt visible state or version diffs.

Overview
Adds BatchUpdate to the view engine: callers stage keys synchronously via a BatchUpdater, while prior-value reads and folds run on background threads. Staged keys block readers until resolved; fold failures propagate to reads, diffs, and Commit, and brick the manager. Shards track pendingValue handles, per-version latches, and wait in GetDiffsForVersions / iterators so hashing and flush see complete diffs.

FlatKV stops batch-reading accounts during ApplyChangeSets; partial field changes are staged with accountUpdater and merged via accountStore.BatchUpdate. Metrics rename BatchReadOldValuesLatencyAccountUpdateLatency. Tests cover staging semantics, differential opUpdate, and updated corruption timing (fold errors surface on read, not apply).

Reviewed by Cursor Bugbot for commit d38fe4d. Bugbot is set up for automated code reviews on this repo. Configure here.

@github-actions

Copy link
Copy Markdown

The latest Buf updates on your PR. Results from workflow Buf / buf (pull_request).

BuildFormatLintBreakingUpdated (UTC)
✅ passed✅ passed✅ passed✅ passedSep 11, 2026, 8:29 PM

@codecov

codecov Bot commented Sep 11, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 80.46647% with 67 lines in your changes missing coverage. Please review.
✅ Project coverage is 65.80%. Comparing base (ac460ac) to head (d38fe4d).

Files with missing lines Patch % Lines
sei-db/db_engine/view/shard.go 77.65% 59 Missing ⚠️
sei-db/db_engine/view/view_manager_impl.go 81.81% 6 Missing ⚠️
sei-db/db_engine/view/pending_value.go 88.88% 1 Missing ⚠️
sei-db/state_db/sc/flatkv/store_apply.go 97.22% 1 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #4155      +/-   ##
==========================================
- Coverage   66.60%   65.80%   -0.80%     
==========================================
  Files        2196     2103      -93     
  Lines      169188   160603    -8585     
==========================================
- Hits       112692   105690    -7002     
+ Misses      56355    54772    -1583     
  Partials      141      141              
Flag Coverage Δ
sei-chain-pr 88.69% <78.50%> (?)
sei-db 74.50% <ø> (ø)
sei-db-state-db ?
sei-db-state-db-pr 83.42% <97.22%> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
sei-db/db_engine/view/read_cache.go 95.83% <ø> (ø)
sei-db/db_engine/view/shard_manager.go 92.85% <100.00%> (+0.54%) ⬆️
sei-db/state_db/sc/flatkv/metrics.go 97.22% <ø> (ø)
sei-db/db_engine/view/pending_value.go 88.88% <88.88%> (ø)
sei-db/state_db/sc/flatkv/store_apply.go 92.96% <97.22%> (+0.10%) ⬆️
sei-db/db_engine/view/view_manager_impl.go 90.36% <81.81%> (-0.65%) ⬇️
sei-db/db_engine/view/shard.go 83.40% <77.65%> (-5.35%) ⬇️

... and 97 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Want higher recall? High effort reviews run extra passes and find more bugs. A team admin can switch effort levels in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit d38fe4d. Configure here.

shard.FoldStagedValues(folds, updater, version)
})
}
return nil

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Close races in-flight account folds

Medium Severity

BatchUpdate returns after staging and submits folds on miscPool with no wait, and Close was not updated to drain versionLatches for the current unsealed version. A legal BatchUpdate then Close sequence can let a fold read the backing DB after Close has shut it down, which breaks the guarantee that no manager work touches that DB once Close returns.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit d38fe4d. Configure here.

@seidroid seidroid Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moving the account read/fold off the execution thread is well structured — staging under the shard lock, pending-value handles checked on every read path, and per-version latches that gate the diff consumers — and the new tests (including the differential opUpdate op) cover the staging/chaining/delete/failure semantics well. The blocking gap is lifecycle: the folds are fire-and-forget work that nothing drains, so Close can tear down the read pool and the database underneath an in-flight fold.

Findings: 1 blocking | 2 non-blocking | 2 posted inline

Blockers

  • None at the file/PR level.
  • 1 blocking issue(s) flagged inline on specific lines.

Non-blocking

  • [suggestion] ApplyChangeSets no longer detects a corrupt stored account row — newAccountUpdater only parses the changeset, and deserialization of the row being folded onto now happens on a fold worker. The block is therefore accepted and the failure resurfaces later as a bricked manager (and a panic out of CommitStore.Get, as TestCrashRecoveryCorruptedAccountValueInDB now records) rather than as an error attributable to the apply. The node still halts before publishing a hash, so this is a change in failure surface rather than a correctness hole, but it is worth calling out explicitly in the CommitStore.ApplyChangeSets contract so operators reading a fold-failure brick know which block produced it.
  • 1 suggestion(s)/nit(s) flagged inline on specific lines.

continue
}
shard := c.shards[shardIndex]
c.miscPool.Submit(func() {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[blocker] These fold tasks are the first manager-owned background work that touches the database and that nothing waits for, and shutdown has no drain for them.

closeInternal waits only for the lifecycle runner, then cancels, takes the shards out of service and calls c.db.Close(). The lifecycle runner does no final flush on its exit path, so folds staged at the current (unsealed) version are never awaited by anything — GetDiffsForVersions's latch wait only covers versions that actually get flushed or hashed. CommitStore.Close then closes readPool before miscPool (store_lifecycle.go), and its own comment states the invariant this breaks: "Store Close does not return until no store-owned goroutine will touch the database again."

Concrete sequence, with a caller that has fully quiesced (last ApplyChangeSets returned, no Commit yet) and then calls Close:

  1. A fold worker gets past schedulePriorValueReadsUnlocked (out-of-service check passes) with a non-empty pendingRead list.
  2. closeInternal runs to completion: cancel, TakeOutOfService, c.db.Close().
  3. s.readPool.Close() runs (fixed pool, closed = true).
  4. The worker reaches readCache.ResolveBatchUnlocked, which calls c.readPool.Submit(...) unconditionally — fixedPool.Submit panics with "threading: submit on closed pool".

A slightly wider variant of the same window: the worker is already parked in InterruptiblePull while its scheduled read task calls readFromDB against the handle step 2 just closed.

The fix belongs at the choke point rather than at each caller: track outstanding folds on the manager (a WaitGroup incremented at stage time, decremented in recordFoldsUnlocked/FailStagedFolds) and drain it in closeInternal before c.db.Close(), so ViewManager.Close again returns only once no manager-owned goroutine will touch the database or either pool. A test that calls BatchUpdate and then Close without an intervening Commit/flush would pin it.

//
// Must be called without the shard lock held: it acquires versionLock, and the established order is
// versionLock before any shard lock.
// reportFoldFailure handles a fold that could not produce its value by bricking the manager. Separate

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[suggestion] reportFoldFailure was inserted between reportReadFailure's doc comment and its declaration. The result is that reportReadFailure now has no godoc at all, and its three paragraphs — including the load-bearing "Must be called without the shard lock held: it acquires versionLock, and the established order is versionLock before any shard lock" — read as documentation for reportFoldFailure.

That invariant does apply to both functions (it is why shard.FailStagedFolds releases the lock before calling reportFoldFailure), so it should be stated on each rather than silently transplanted. Move the new function below reportReadFailure, leaving the existing comment attached to its own declaration.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant