Run a database's async commits on up to four concurrent commit threads - #902
Merged
Merged
Conversation
Contributor
There was a problem hiding this comment.
Code Review
This pull request introduces concurrent commit threads per database (commitThreads) to run async transaction commits, defaulting to min(4, cores). This replaces the single-threaded commit worker bottleneck, allowing RocksDB to validate and apply concurrent commits across multiple cores and group them into single writes. To support out-of-order commit resolution safely, the transaction log's flush correlation logic was updated to sample the latest sequence number under the dataSetsMutex lock, preventing replay gaps. The changes also include new benchmarks, stats, and comprehensive native and integration tests. I have no feedback to provide as there are no review comments.
This was referenced Oct 5, 2026
Open
kriszyp
marked this pull request as ready for review
October 5, 2026 22:14
This was referenced Oct 5, 2026
kriszyp
added a commit
to HarperFast/harper
that referenced
this pull request
Oct 6, 2026
…y at registration A RocksDB transaction's timestamp is assigned when it is created, and commits to one database complete in any order: a transaction whose body awaits can commit after a newer one today, and rocksdb-js concurrent commit threads (HarperFast/rocksdb-js#902) make that routine. Subscription delivery assumed timestamp order in three places and silently dropped such commits: - An older patch merged into a record after the newer write's event had gone out was filtered as superseded (#3024). A superseded live mutation that the record lists among its folded writes (additionalAuditRefs, VERSION_REUSED) now delivers the record's current state, once per merged write. - A collection current-state subscription advanced its start time to the newest record time its scan saw and dropped everything at or below it (#2933). Default subscriptions no longer use that time as a boundary (their events carry only current versions); buffered record events the record has moved past are dropped at drain. includeSuperseded keeps the time gate. - DurableSubscriptionsSession.saveSubscriptions wrote the JS clock onto the session's live subscriptions' startTime, re-arming that gate on QoS 0 subscriptions (#3027). The resume position now has its own field. Without the time gate, registration becomes the delivery boundary: a new subscriber at an idle RocksDB database restarts the broadcast iterator at the log end, and otherwise everything already committed is dispatched to the existing subscribers before it is added, so it never receives a message or a write committed before it registered. Fixes #3024 Fixes #2933 Fixes #3027 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
github-actions Bot
pushed a commit
to HarperFast/harper
that referenced
this pull request
Oct 6, 2026
…y at registration A RocksDB transaction's timestamp is assigned when it is created, and commits to one database complete in any order: a transaction whose body awaits can commit after a newer one today, and rocksdb-js concurrent commit threads (HarperFast/rocksdb-js#902) make that routine. Subscription delivery assumed timestamp order in three places and silently dropped such commits: - An older patch merged into a record after the newer write's event had gone out was filtered as superseded (#3024). A superseded live mutation that the record lists among its folded writes (additionalAuditRefs, VERSION_REUSED) now delivers the record's current state, once per merged write. - A collection current-state subscription advanced its start time to the newest record time its scan saw and dropped everything at or below it (#2933). Default subscriptions no longer use that time as a boundary (their events carry only current versions); buffered record events the record has moved past are dropped at drain. includeSuperseded keeps the time gate. - DurableSubscriptionsSession.saveSubscriptions wrote the JS clock onto the session's live subscriptions' startTime, re-arming that gate on QoS 0 subscriptions (#3027). The resume position now has its own field. Without the time gate, registration becomes the delivery boundary: a new subscriber at an idle RocksDB database restarts the broadcast iterator at the log end, and otherwise everything already committed is dispatched to the existing subscribers before it is added, so it never receives a message or a write committed before it registered. Fixes #3024 Fixes #2933 Fixes #3027 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
kriszyp
force-pushed
the
fix/shared-occ-lock-buckets
branch
from
October 6, 2026 04:59
37b6c55 to
4c49e7f
Compare
The default single commit lane (#694) ran every async RocksDB commit for a database on one thread. Under load that thread saturated a core: about 38% in optimistic validation and 33% in memtable inserts, both of which RocksDB runs in parallel across concurrent writers. With four workers committing 64-key transactions (transaction-log entries on, WAL off) it committed 4.6k/s against 9.3k/s for the legacy libuv path. CommitWorker now runs up to `commitThreads` threads per database (RocksDatabase.config, default min(4, cores), read at open). A thread starts only when queued commits outnumber idle threads, so a database that is never committed to concurrently keeps one, and a burst queued before an idle thread wakes still grows the pool. Each thread runs a commit's transaction-log write and RocksDB commit back to back; `commitThreads: 1` is the previous lane. Commits stay off the libuv threadpool, so the #694 starvation fix holds. Routing log writes through the ordered txnlog lane first was measured no faster under concurrency and 13-25% slower for a caller with one commit in flight; it stays selectable with ROCKSDB_JS_COMMIT_THREAD=2. Measured on an i7-12700H, one database, txn log on, 3-5 runs: 4 workers x 64 keys 9.9k/s (lane 4.6k, legacy 9.3k); 8 workers x 1 key 202k/s (lane 108k, legacy 194k); single caller with one commit in flight unchanged. Concurrent commits finish out of order, which exposed a pre-existing race (legacy mode and commitSync() racing the lane had it too): commitFinished() paired the committed log prefix with a RocksDB sequence read before taking dataSetsMutex, so an earlier log position could commit at a later sequence in between and a flush could record a replay start past unflushed data. The sequence is now read under that mutex. Also adds the commitPipeline.commitThreads gauge, makes the transaction stress worker await every commit, extends the OCC benchmark with --log, --stat-probe and --commit-threads, and updates the occValidation / occLockBuckets guidance: serial validation gives back the concurrency gain, and large transactions need more buckets under concurrent commits. Closes #898 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- commitSync reads the RocksDB sequence through its pinned descriptor instead of re-reading the handle's descriptor after the dataSetsMutex wait. - The descriptor's worker comment and the two-lane comment no longer promise dispatch order with more than one commit thread. - README: log position is not same-key apply order after a retried commit; consumers replaying one key must order by transaction timestamp. - The OCC benchmark retries a retryable commit on the same transaction, as db.transaction() does, so --log no longer abandons it on abort(); it also validates --commit-threads. - The lazy-start fixture allows the one-thread window a sequential awaiter can hit between a completion and the thread going idle. - Trim comment narration. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
It asserted that a stalled legacy (libuv) commit delays fs.stat, which is a property of the runtime rather than of rocksdb-js: Deno runs node:fs outside the pool N-API async work uses, so the delay never happens there and the test failed on every Deno leg. The demonstration stays in the benchmark (--stat-probe), where legacy mode measured fs.stat p50 of 4-121 ms under load. The regression test that commits leave the threadpool free is kept, skipped on Deno, where it cannot fail. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
cb1kenobi
approved these changes
Oct 6, 2026
Member
|
Looks good! |
…ding, bounded test wait - README: a retried commit's transaction timestamp is frozen at the same point as its log position, so replaying by timestamp does not recover true apply order either; point producers needing that at their own last-writer-wins marker instead. - DESIGN.md: every successful commit emits 'committed' on completion, not only the one that advances the watermark — out-of-order completion means an emission does not imply the emitter's own entry is visible to a committed log.query() yet. - Drop two comments that only restated the code below them. - Bound the previously-unbounded wait on the flush-correlation sampler in TransactionLogFlushedState.OutOfOrderCommitsKeepFlushCorrelationBehindUnflushedPositions so a regression that stops calling the sampler fails in 5s instead of hanging the native suite; the commit thread is joined unconditionally before the assertion so a timeout can't leave a joinable std::thread behind. Dispatch-Task: pr-maint-26ecc6f7c44020182fa8e263a375173a Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…doc accuracy - README: don't claim the transaction timestamp freezes "at the same point" as the log position — it's assigned earlier, at construction/first addLogEntry, while the log position is assigned at commit-time writeBatch. Keep only the verified claim: neither value advances across a retry, so neither recovers true apply order. - docs/stats.md: commitPipeline.commitQueueDepth covers the whole commit (log write + RocksDB commit) in the default single-lane mode regardless of commitThreads; only the two-lane pipeline (ROCKSDB_JS_COMMIT_THREAD=2) splits the RocksDB commit into this queue separately from logQueueDepth. The old wording keyed the distinction off commitThreads:1, which was never the dimension that mattered. Dispatch-Task: pr-maint-26ecc6f7c44020182fa8e263a375173a Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
kriszyp
force-pushed
the
kris/commit-pool-898
branch
from
October 6, 2026 06:31
8648bf2 to
679533c
Compare
kriszyp
added a commit
that referenced
this pull request
Oct 6, 2026
#902) * Run a database's async commits on up to four concurrent commit threads The default single commit lane (#694) ran every async RocksDB commit for a database on one thread. Under load that thread saturated a core: about 38% in optimistic validation and 33% in memtable inserts, both of which RocksDB runs in parallel across concurrent writers. With four workers committing 64-key transactions (transaction-log entries on, WAL off) it committed 4.6k/s against 9.3k/s for the legacy libuv path. CommitWorker now runs up to `commitThreads` threads per database (RocksDatabase.config, default min(4, cores), read at open). A thread starts only when queued commits outnumber idle threads, so a database that is never committed to concurrently keeps one, and a burst queued before an idle thread wakes still grows the pool. Each thread runs a commit's transaction-log write and RocksDB commit back to back; `commitThreads: 1` is the previous lane. Commits stay off the libuv threadpool, so the #694 starvation fix holds. Routing log writes through the ordered txnlog lane first was measured no faster under concurrency and 13-25% slower for a caller with one commit in flight; it stays selectable with ROCKSDB_JS_COMMIT_THREAD=2. Measured on an i7-12700H, one database, txn log on, 3-5 runs: 4 workers x 64 keys 9.9k/s (lane 4.6k, legacy 9.3k); 8 workers x 1 key 202k/s (lane 108k, legacy 194k); single caller with one commit in flight unchanged. Concurrent commits finish out of order, which exposed a pre-existing race (legacy mode and commitSync() racing the lane had it too): commitFinished() paired the committed log prefix with a RocksDB sequence read before taking dataSetsMutex, so an earlier log position could commit at a later sequence in between and a flush could record a replay start past unflushed data. The sequence is now read under that mutex. Also adds the commitPipeline.commitThreads gauge, makes the transaction stress worker await every commit, extends the OCC benchmark with --log, --stat-probe and --commit-threads, and updates the occValidation / occLockBuckets guidance: serial validation gives back the concurrency gain, and large transactions need more buckets under concurrent commits. Closes #898 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Address pre-push review: ordering docs, benchmark retries, sync pin - commitSync reads the RocksDB sequence through its pinned descriptor instead of re-reading the handle's descriptor after the dataSetsMutex wait. - The descriptor's worker comment and the two-lane comment no longer promise dispatch order with more than one commit thread. - README: log position is not same-key apply order after a retried commit; consumers replaying one key must order by transaction timestamp. - The OCC benchmark retries a retryable commit on the same transaction, as db.transaction() does, so --log no longer abandons it on abort(); it also validates --commit-threads. - The lazy-start fixture allows the one-thread window a sequential awaiter can hit between a completion and the thread going idle. - Trim comment narration. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Move the legacy fs-starvation demonstration out of the unit suite It asserted that a stalled legacy (libuv) commit delays fs.stat, which is a property of the runtime rather than of rocksdb-js: Deno runs node:fs outside the pool N-API async work uses, so the delay never happens there and the test failed on every Deno leg. The demonstration stays in the benchmark (--stat-probe), where legacy mode measured fs.stat p50 of 4-121 ms under load. The regression test that commits leave the threadpool free is kept, skipped on Deno, where it cannot fail. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Address pre-push rebase review: timestamp-ordering doc, watermark wording, bounded test wait - README: a retried commit's transaction timestamp is frozen at the same point as its log position, so replaying by timestamp does not recover true apply order either; point producers needing that at their own last-writer-wins marker instead. - DESIGN.md: every successful commit emits 'committed' on completion, not only the one that advances the watermark — out-of-order completion means an emission does not imply the emitter's own entry is visible to a committed log.query() yet. - Drop two comments that only restated the code below them. - Bound the previously-unbounded wait on the flush-correlation sampler in TransactionLogFlushedState.OutOfOrderCommitsKeepFlushCorrelationBehindUnflushedPositions so a regression that stops calling the sampler fails in 5s instead of hanging the native suite; the commit thread is joined unconditionally before the assertion so a timeout can't leave a joinable std::thread behind. Dispatch-Task: pr-maint-26ecc6f7c44020182fa8e263a375173a Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * Address delta review: timestamp-assignment wording, commitQueueDepth doc accuracy - README: don't claim the transaction timestamp freezes "at the same point" as the log position — it's assigned earlier, at construction/first addLogEntry, while the log position is assigned at commit-time writeBatch. Keep only the verified claim: neither value advances across a retry, so neither recovers true apply order. - docs/stats.md: commitPipeline.commitQueueDepth covers the whole commit (log write + RocksDB commit) in the default single-lane mode regardless of commitThreads; only the two-lane pipeline (ROCKSDB_JS_COMMIT_THREAD=2) splits the RocksDB commit into this queue separately from logQueueDepth. The old wording keyed the distinction off commitThreads:1, which was never the dimension that mattered. Dispatch-Task: pr-maint-26ecc6f7c44020182fa8e263a375173a Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 6, 2026
kriszyp
added a commit
to HarperFast/harper
that referenced
this pull request
Oct 6, 2026
…y at registration A RocksDB transaction's timestamp is assigned when it is created, and commits to one database complete in any order: a transaction whose body awaits can commit after a newer one today, and rocksdb-js concurrent commit threads (HarperFast/rocksdb-js#902) make that routine. Subscription delivery assumed timestamp order in three places and silently dropped such commits: - An older patch merged into a record after the newer write's event had gone out was filtered as superseded (#3024). A superseded live mutation that the record lists among its folded writes (additionalAuditRefs, VERSION_REUSED) now delivers the record's current state, once per merged write. - A collection current-state subscription advanced its start time to the newest record time its scan saw and dropped everything at or below it (#2933). Default subscriptions no longer use that time as a boundary (their events carry only current versions); buffered record events the record has moved past are dropped at drain. includeSuperseded keeps the time gate. - DurableSubscriptionsSession.saveSubscriptions wrote the JS clock onto the session's live subscriptions' startTime, re-arming that gate on QoS 0 subscriptions (#3027). The resume position now has its own field. Without the time gate, registration becomes the delivery boundary: a new subscriber at an idle RocksDB database restarts the broadcast iterator at the log end, and otherwise everything already committed is dispatched to the existing subscribers before it is added, so it never receives a message or a write committed before it registered. Fixes #3024 Fixes #2933 Fixes #3027 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
github-actions Bot
pushed a commit
to HarperFast/harper
that referenced
this pull request
Oct 6, 2026
…y at registration A RocksDB transaction's timestamp is assigned when it is created, and commits to one database complete in any order: a transaction whose body awaits can commit after a newer one today, and rocksdb-js concurrent commit threads (HarperFast/rocksdb-js#902) make that routine. Subscription delivery assumed timestamp order in three places and silently dropped such commits: - An older patch merged into a record after the newer write's event had gone out was filtered as superseded (#3024). A superseded live mutation that the record lists among its folded writes (additionalAuditRefs, VERSION_REUSED) now delivers the record's current state, once per merged write. - A collection current-state subscription advanced its start time to the newest record time its scan saw and dropped everything at or below it (#2933). Default subscriptions no longer use that time as a boundary (their events carry only current versions); buffered record events the record has moved past are dropped at drain. includeSuperseded keeps the time gate. - DurableSubscriptionsSession.saveSubscriptions wrote the JS clock onto the session's live subscriptions' startTime, re-arming that gate on QoS 0 subscriptions (#3027). The resume position now has its own field. Without the time gate, registration becomes the delivery boundary: a new subscriber at an idle RocksDB database restarts the broadcast iterator at the log end, and otherwise everything already committed is dispatched to the existing subscribers before it is added, so it never receives a message or a write committed before it registered. Fixes #3024 Fixes #2933 Fixes #3027 Co-Authored-By: Claude Opus 5.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.
I don't why previous benchmarks showed no gains from writer concurrency, but it clearly seems to help now.
Closes #898.
⊙ Problem
The default commit lane from #694 runs every async RocksDB commit for a database on one thread, and under concurrent load that thread is the bottleneck. With four workers committing 64-key transactions to one database (transaction-log entries on, WAL off), it committed 4.7k transactions/s against 9.5k for the legacy libuv path. A
perfprofile showed therocksdb-committhread at 96-100% of a core: 38% in optimistic validation (CheckKeysForConflicts), 33% in memtable inserts, 10% in the transaction-log write and 0.4% in completion dispatch. RocksDB runs validation and memtable inserts in parallel when it sees concurrent writers, but a single lane never gives it any.💡 Solution
CommitWorkernow runs up tocommitThreadsdedicated threads per database (RocksDatabase.config({ commitThreads }), defaultmin(4, cores), read when a database is first opened). A thread starts only when queued commits outnumber idle threads, so a database that is never committed to concurrently keeps one. Each thread runs a commit's transaction-log write and its RocksDB commit back to back. Log writes serialize on the store's write mutex, as legacy commits did. Commits never use the libuv threadpool, so #694's fix forfs/dns/cryptostarvation holds.commitThreads: 1restores the previous lane exactly.Concurrent completion exposed a pre-existing durability race, which this PR fixes.
commitFinished()pairs the committed log prefix with a RocksDB sequence for flush correlation, and the sequence was read before takingdataSetsMutex. Between the read and the lock, an earlier log position could commit at a later sequence and finish. A flush at the smaller sequence would then record a replay start past the earlier transaction's unflushed data, and crash replay (startFromLastFlushed, WAL off) would skip it. Legacy mode andcommitSync()racing the lane already had this race. The sequence is now read through a callback under that mutex.Results on an i7-12700H, with one database, transaction log on, pinned to P-cores, and 3 runs (median and range):
The main thread timed
fs.statduring a 4 worker × 64 key burst withUV_THREADPOOL_SIZE=4. With the lane, p50/p99 were 14/23 µs. With concurrent commits they were 18/25 µs. With legacy they were 19/560 µs, and in earlier runs legacy reached a p50 of 4-14 ms at 8 workers × 64 keys and 78-121 ms at 1,000 keys.At 1,000 keys the gain is capped by lock-bucket collisions, not by threads. See the #897 follow-up below.
⚖️ Alternatives
rocksdb-txnloglane, then to the commit threads. This was the issue's plan, and it was built and measured. It was no faster under concurrency (8 × 1 key: 200k vs 195k/s; 8 × 64 keys: 10.2k vs 9.9k/s). For a caller with one commit in flight it was 13% slower at 1 key and 25% slower at 64 keys, from an extra thread handoff per commit. That caller is Harper's replication receiver, which awaits each commit before applying the next. It remains selectable asROCKSDB_JS_COMMIT_THREAD=2.commitThreadsthreads per database, and only for databases that are committed to concurrently.Write()on the lane. RocksDB's optimistic API has no multi-transaction commit, so this would re-implement OCC validation, including conflicts within the merged batch.ROCKSDB_JS_COMMIT_THREAD=0) the default. It has the same throughput, but it puts commits back on the libuv pool. The--stat-proberuns above measuredfs.statp50 at 4-121 ms under legacy load.🔧 Changes
src/binding/database/commit_worker.h:CommitWorkerholds a vector of threads and amaxThreadslimit.enqueuestarts a thread when the queue outnumbers idle threads. A woken thread counts as idle until it retakes the mutex, so testing for an idle thread alone let one thread drain a burst. If no thread can be started, the task runs inline. With one thread the worker is still a lane that drains its queue in order; with more, each thread pops one task. A thread counts itself idle only while it waits.threadCount()backs the new gauge.src/binding/database/db_settings.{h,cpp}: thecommitThreadssetting (defaultmin(4, hardware_concurrency), validated as an integer from 1 through 64 before any setting is stored; getter).src/binding/database/db_descriptor.{h,cpp}: the descriptor builds its commit worker with the setting current at open, and the member comment no longer promises dispatch order.src/binding/transaction/transaction.cpp: the mode comments describe concurrent threads. BothcommitFinishedcall sites pass a sequence getter (async, sync);commitSyncuses its pinned descriptor.src/binding/transaction_log/transaction_log_store.{h,cpp}:commitFinishedtakes the getter and calls it underdataSetsMutex.commitPipeline.commitThreadsgauge:db_handle.cpp,src/stats.ts, anddocs/stats.md, which also updates the queue-depth descriptions.src/load-binding.tsandREADME.mddocumentcommitThreads(typings, README, example). The README notes that log position is not same-key apply order after a retried commit, so consumers must order by transaction timestamp. Both revise theoccValidation/occLockBucketsguidance with the measurements above (serial, buckets, typings, validation typings) and the flush-stall notes.AGENTS.mdupdates the commit-execution map, the env var, and the three invariants that assumed one commit thread (14, 16, 24).benchmark/occ-commit-contention.mts:--logwrites one transaction-log entry per key, as Harper writes one audit entry per record.--stat-probetimesfs.staton the main thread (report), and--commit-threadssets the limit. Retryable commits are now retried on the same transaction, so--logno longer abandons them.stress-test/workers/stress-transaction-put-worker.mts: awaits every commit in a batch rather than the last one, which can settle first.binding.gyp: adds the new native test.✅ Verification
The end-to-end route is fork-fixture integration tests through the public API, plus native tests for the scheduler and the store.
test/commit-threads.test.tswithtest/fixtures/fork-commit-threads.mts, run under modes1and2. Both modes cover these cases:commitThreads: 1, eight commits stalled byROCKSDB_JS_COMMIT_EXECUTE_DELAY_MS=100take 800 ms or more. With4they take under 600 ms and start four threads after a warm-up, so the pool grows past an idle thread.UV_THREADPOOL_SIZE=2,fs.statreturns in under 150 ms. It is skipped on Deno, which runsnode:fsoutside the pool N-API async work uses, so it cannot fail there. That a stalled legacy commit does delayfs.statis a runtime property, not a rocksdb-js one, so it is demonstrated by the benchmark's--stat-probe(legacy p50 4-121 ms above) rather than asserted in the unit suite.test/native/commit_worker_test.cc: a burst against a warm idle thread must run four tasks at once. With the old growth rule it failed in every round. It also covers in-order execution with one thread, and shutdown draining queued tasks and then running later tasks inline.test/native/transaction_log_flushed_state_test.cc: forces the race schedule with a condition variable. The later position samples sequence 10, the earlier one commits at 11 and publishes, and a flush at 10 must not record a replay start past the earlier position. With the sample moved back before the lock, the test fails (flushed position past the earlier one); with the fix it passes. The existing purge test moves to the callback signature.pnpm check,pnpm test(77 files, 1,120 passed),pnpm test:native(303 passed) andSTRESS_MODE=essential pnpm test:stress(8 passed) on macOS arm64.🤖 Generated with Claude Code (Claude Opus 5.5); posted via @kriszyp.
Related PRs: #742 independent (shares DBSettings/load-binding.ts surface only), #767 independent (shares the DBDescriptor initializer list and binding.gyp native-test list; merge-check on combination), #842 independent (check lease admission/drain still holds under concurrent commit threads), #897 overlaps (base branch: this PR is stacked on it; its commits are inherited into this branch), #900 independent, #901 independent (check VT intent release and retry interaction under multiple commit threads), #890 overlaps (merged; both touch the transaction-log store — keep the commitFinished callback signature on merge)
Complexity: complicated
Review-Coverage: authored=claude; ran=codex,gemini; adjudicated=domain; declined=cursor-grok,cursor-composer,cursor-kimi,cursor-muse; rounds=3; full=1 @ 679533c
Review-Attention: study ~20m (critical: transaction.cpp, transaction_log_store.cpp +2; decisions: default-commit-threads, per-database-thread-sets, thread-start-failure-fallback, apply-order-contract, stacked-on-897, do-less-alternative) @ 679533c