Repository navigation
Keep active RocksDB scans alive during eviction - #2556
Conversation
Track native range activity without extending idle write holders. Report expired snapshots before native access, and keep the restart purge regression focused on allocated bytes reclaimed. Co-Authored-By: GPT-5 Codex <noreply@openai.com>
Forget read ownership when a live handle enters the commit retry loop, while existing range wrappers retain their expired-reader guard. Preserve iterable iterator and return-value contracts; cover a real conflict retry and key-only iteration. Co-Authored-By: GPT-5 Codex <noreply@openai.com>
There was a problem hiding this comment.
Code Review
This pull request introduces tracking for RocksDB range read activity and snapshot expiration. It wraps range iterators in PrimaryRocksDatabase and RocksIndexStore using a new trackReadRange helper in DatabaseTransaction, ensuring active range scans renew read timeouts and throwing a ReadSnapshotExpiredError if a scan resumes after its snapshot is released. The review feedback suggests robust improvements to the trackReadRange iterator wrapper, specifically addressing potential error masking during cleanup, this context binding issues, and ensuring delegation to the underlying iterator's throw method.
Release cherry-pick
|
… died The catch in `next()` is only reached once `checkActive()` has already thrown, which means the snapshot the underlying iterator was opened against is gone. Closing it there is the likeliest moment for the native layer to object, and because `DbiIterator.return()` forwards to the native handle with no guard of its own, that error would propagate in place of the ReadSnapshotExpiredError — replacing the named 503 with exactly the raw iterator error this wrapper exists to stop surfacing. Cleanup failures are now swallowed; the error being propagated is the actionable one. Cleanup also goes through the closure rather than `this`, so a destructured `next` still cleans up instead of throwing a TypeError on the way out. `throw()` still does not delegate to `iterator.throw`: DbiIterator.throw closes and rethrows the same error, so delegation buys nothing, and doing it after the close above would run it against an iterator already closed. `return`'s value parameter is now optional, which it always was at every call site — the type only went unchecked while these calls went through `this`. Addresses review thread r3975211616 (points 1 and 2 taken, 3 declined). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Reviewed; no blockers found. |
…failures Keep active RocksDB scans alive during eviction
All four conflicts are collateral: the cherry-pick pulled in main-only code that #2556 merely edited a line inside of. Each keeps v5.2's side and takes only #2556's own change. - monitorTransaction on v5.2 takes one argument; main's four extra parameters and its reportIfLongLived() call are the long-lived-transaction reporting work (#2471/#2473), which is not on this branch at all. Dropped, keeping the rangeReadActive renewal #2556 adds. - PrimaryRocksDatabase's import of Transaction from rocksdb-js is main-only. Dropped, keeping the trackReadRange import. - RocksIndexStore's bounds translation is inlined here rather than extracted into translateIndexBounds(). Kept inline, wrapped in trackReadRange. - DESIGN.md's conflict-retry-deadline paragraph (#2450) is main-only. Dropped, keeping the new range-activity section. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
harper#2529 is not on v5.2, so neither the test nor its fixture directory exists here. #2556 edits that test — Failure B, the over-specified purge-pass count — and because the file was absent, the cherry-pick applied the edit as a 254-line file add rather than the 7-line change it is on main. The fixture directory was never part of #2556's diff, so it did not come with it, and the test ENOENTs on stat of integrationTests/database/txnlog-restart-reclaim before running (Integration Tests 3/6, Node 24 and uWS). The assertion #2556 corrects there is about a purge pass rocksdb-js 2.9.0 performs during recovery. That coverage belongs with harper#2529, and follows to v5.2 with it if it is ever backported. The eviction-secondary-index changes are unaffected: v5.2 already has both that test and its fixture, so those applied as real modifications. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Keep actively progressing RocksDB scans alive during TTL eviction and report a defined read-snapshot expiration when an abandoned scan resumes. Keep the restart transaction-log regression focused on filesystem reclamation under rocksdb-js 2.9.0.
For the human reviewer
Planning review adopted Gemini's monitor-consumed activity flag, keeping owner lookup and renewal-policy chain walks out of the per-row path. Final planning verdict:
Framing-Verdict: chosen-approach-sound.Look hardest at read ownership versus native commit ownership: a handle handed to the commit retry loop is still live, and retry handlers must be able to open fresh ranges on it. Existing range wrappers retain their original owner so they cannot silently switch snapshots. A named 503 asks callers to retry only the read; progressing reads still cannot extend pending write holders. These preserve the requester-approved snapshot and rollback policy.
Review rulings: retained the measured activity wrapper (+0.43% median in one local workload; V8 inlines its guard) and intentional lifecycle comments. Declined defensive cleanup suppression because the 2.9.0 native return path explicitly tolerates invalidated/already-closed iterators, also covered by expiry tests. A separate paced secondary-index HTTP case remains optional coverage: both adapters have real monitor-tick unit coverage, while the HTTP regression targets the failing base dump. The claimed
WeakMap.get(undefined)crash is factually false (getreturns undefined).The PR bot’s wrapper suggestions are left for human adjudication: cleanup repeats the ruling above; detached
next()calls were already unsupported by the native driver’s receiver-dependent iterator; and nativereturnandthrowinvoke the same idempotent close macro, so return-then-rethrow preserves cleanup. This helper wraps those native iterators, not arbitrary generators with custom recovery. No post-review code change.Release follow-up: the milestone was externally changed to v5.2 after this draft opened. The automated cherry-pick report records conflicts and says release integration did not run, although the automation job reports success. This task is scoped to
main; release conflict resolution remains separate.Changes
trackReadRangerecords activity without extending idle write holders, preserves commit retry handoffs, and raisesReadSnapshotExpiredError(503) before invalid native access. Poisoned write holders retain the existing 422 rollback behavior.TransactionLogStore::load; Harper's startup purge remains valid, and the counter includes no-op scans. The restart oracle permits at least one purge while requiring 100 MB of removed allocation and 75% reconciled filesystem reclaim. Its seed volume increases to keep more than 100 MB purgeable after rotation.Verification
Next failed: Iterator not initialized; with this fix both target integration files pass all 5 tests.txnlog-restart-reclaim.test.tsremoved 106,516,480 allocated bytes and reconciled 100% through statfs.GIT_CONFIG_GLOBAL/GIT_PAGERfrom the test environment. RocksDB resource gate: 2,352 passing, 31 skipped. LMDB resource gate: 1,833 passing, 370 skipped.GitHub CI at 03:50 UTC: 41 passed, 6 skipped, 2 failed. All builds, unit gates (Node 22/24/26 and Windows), and all 24 integration shards are green. CI logs confirm the new paced HTTP scan and original Q1 execute successfully on Node 24, Bun, and uWS; reclaim executes successfully on Node 24 and uWS (intentionally guarded out on Bun/Windows). The two Next.js adapter jobs fail before tests at
npm ci: the existing lockfile omits@harperfast/rocksdb-js-linux-x64-musl@2.9.0. Dependency repair is explicitly outside this task; this PR changes neither dependency manifest.Refs #2506, #2337, #2549.
Reviewer substitution: ran=codex,gemini; declined/blocked=claude-quota. Codex authored and adjudicated; Gemini supplied the independent planning/code review. The Gemini code adapter needed a local supplied-diff-only prompt prefix after its first attempt requested a denied command; permissions were not expanded.
Co-Authored-By: GPT-5 Codex noreply@openai.com
Complexity: complicated
Review-Coverage: authored=codex; ran=gemini; adjudicated=domain; declined=claude,cursor-grok,cursor-composer; rounds=2 @ 881855c
Human-Review-Need: 3 (decisions: iterator-wrapper-boundary, activity-sampling-model, write-holder-liveness, expired-snapshot-contract, startup-purge-oracle) @ 881855c