Skip to content

fix: 2026-08 deep-review wave — durability, long-uptime arithmetic, cluster election, silent-failure hardening - #453

Merged
TinDang97 merged 12 commits into
mainfrom
fix/deep-review-2026-08
Aug 8, 2026
Merged

fix: 2026-08 deep-review wave — durability, long-uptime arithmetic, cluster election, silent-failure hardening#453
TinDang97 merged 12 commits into
mainfrom
fix/deep-review-2026-08

Conversation

@TinDang97

@TinDang97 TinDang97 commented Aug 8, 2026

Copy link
Copy Markdown
Collaborator

Summary

Self-directed deep review across six dimensions chosen for long-uptime, large-scale deployments — durability/crash-recovery ordering, long-uptime resource growth, concurrency/unsafe, replication+cluster correctness, long-horizon arithmetic, and silent degradation. 32 findings total; this PR fixes the 19 bounded, unambiguous defects. The remaining structural items are filed as #451 (cluster control-plane gaps, for the v0.9 C-2/C-3 track) and #452 (durability follow-ups).

Fixes by commit

  • fix(storage) — full-width last_access + 24-bit version (A1/A3/A4/A2): CompactEntry's unused _pad: u32 becomes a full last_access_secs: u32 (the old 16-bit field wrapped every ~18h, corrupting LRU ordering and OBJECT IDLETIME on any server up longer than that). Version is now 24-bit (wraps skipping 0, starts at 1) so get_version() == 0 reliably means "absent". lru_is_older uses a wrapping signed compare valid for ±68 years. OBJECT IDLETIME drops its & 0xFFFF truncation.
  • fix(persistence) — fail-loud everysec fsync (F2/F3): all four writer-task deadline-fsync sites (tokio TopLevel/PerShard + monoio) now log on error and only advance last_fsync on success (fsyncgate: a failed fsync must not be treated as durable). New INFO fields: aof_last_fsync_status, aof_fsync_failures.
  • fix(storage) — spill-failure re-insert (F1): a failed background spill write silently dropped the evicted key. SpillCompletion now carries the failed request back; the shard rehydrates and re-inserts it (version-checked so a newer write wins), with a counter exposed in INFO.
  • fix(tracking) — enforce max_keys (G1): the documented 1M tracking-table cap was dead code; a long-lived tracking client reading distinct keys grew the table without bound. track_key now evicts at the cap and pushes a fake invalidation to the evicted key's trackers (Redis semantics).
  • fix(cluster) — inline fast path gated off in cluster mode (R6): the inline GET/SET dispatch bypassed MOVED/slot ownership checks entirely, silently serving keys the node doesn't own.
  • fix(persistence) — rewrite prune safety (D1/D4): AOF rewrite now flushes all off-loop manifest-sync agents (new ack-only flush barrier) and fsyncs the manifest directory entry before pruning the old generation; on failure the old files are kept. Closes the crash window where the new manifest wasn't durable but the old generation was already deleted.
  • fix(cluster) — election correctness + lock rules (C1-C4/R8/A5): failover auth acks are now read back on the request stream with an epoch echo; stale-epoch acks are discarded; voters are deduplicated (a double-ack could previously fake a quorum); promotion happens at the voted epoch instead of double-bumping. CLUSTER FAILOVER without FORCE/TAKEOVER returns an error instead of wedging in WaitingDelay. Remaining std::sync::RwLock users migrated to parking_lot per repo rules.
  • fix(storage) — fail-loud + bounds (F4/G3/G4): vector compaction install errors are logged instead of swallowed; CDC fanout reaps disconnected subscribers even when idle; temporal registry bounded at 262,144 bindings.

Testing

  • cargo fmt --check
  • cargo clippy -- -D warnings ✅ (default features)
  • cargo clippy --no-default-features --features runtime-tokio,jemalloc -- -D warnings
  • cargo test --lib: 4518 passed (monoio default) ✅
  • cargo test --lib --no-default-features --features runtime-tokio,jemalloc: 3682 passed
  • New red/green tests for every behavioral fix (metadata roundtrip/wrap, LRU beyond 9h, fsync-failure latch, spill payload round-trip + failed-write carry-back, tracking-cap eviction, inline-loop cluster gate, flush-barrier durable commit, promote-at-voted-epoch, failover no-wedge, CDC idle reap, temporal cap).

Follow-ups (filed, out of scope here)

author: Tin Dang

Summary by CodeRabbit

  • New Features
    • Persistence information now reports AOF fsync status, failures, and spill-recovery failures.
    • Failed spill writes can restore evicted data when recovery is possible.
    • Client tracking now enforces key limits and sends invalidations for evicted keys.
  • Bug Fixes
    • Improved cluster routing, failover election validation, and failover command handling.
    • Strengthened AOF rewrite durability and prevented premature cleanup after failures.
    • Corrected long-idle calculations, version tracking, LFU decay, and LRU ordering.
    • Disconnected CDC subscribers are cleaned up during idle periods.
  • Reliability
    • Temporal registries remain bounded, and background compaction failures provide clearer diagnostics.

…iew A1/A3/A4/A2)

The packed 32-bit entry metadata stored last_access in 16 bits with three
mutually inconsistent wraparound treatments, and the WATCH/EXEC version in
8 bits:

- lfu_decay subtracted the u16-truncated last_access from the full u32
  epoch clock and truncated the quotient with `as u8`, so the decay value
  cycled 0->255 every ~4.5h of wall clock — LFU victim selection was
  effectively pseudo-random from day one.
- lru_is_older's i16 circular compare inverted verdicts once two keys'
  access times differed by more than ~9.1h, so allkeys-lru evicted hot
  keys and retained cold ones under diurnal idle patterns.
- OBJECT IDLETIME reported idle time modulo 65536s (18.2h wrap).
- The 8-bit version wrapped after 256 writes between WATCH and EXEC and
  reported 0 for both "absent" and "freshly created", so EXEC committed
  over concurrent modifications (ABA) and never detected key creation.

Fix, at zero size cost (CompactEntry stays 32 bytes): repurpose the unused
_pad u32 as a full-width last_access_secs field, and use the freed 16
metadata bits to widen version to 24 bits ([version:24|counter:8]).
Versions start at INITIAL_VERSION=1 and the wrap skips 0, so version 0 is
now a reliable "key absent" sentinel for WATCH creation detection, and the
ABA window moves from 256 to 16.7M intervening writes.

lfu_decay and IDLETIME use saturating_sub so a lagging cached clock yields
zero elapsed rather than a near-2^32 value; lru_is_older uses i32 circular
distance (valid to +/-68 years, correct across the year-2106 wrap).

Red/green: new regression tests for >9.1h LRU ordering, 2h/11h LFU decay,
>18.2h idle survival, 24-bit wrap-skip-0, and version-1-on-create; stale
8-bit/16-bit assertions updated to the new contract.
author: Tin Dang
…tch (deep-review F2/F3)

Both tokio everysec deadline-fsync sites dropped flush/sync_data errors
with `let _ =` and then unconditionally recorded the fsync as successful
(last_fsync reset, pending cleared) — zero log lines, and the PerShard
variant additionally lacked the `!write_error` torn-stream gate its three
siblings have. Because the kernel marks pages clean after a failed fsync
(fsyncgate), the next attempt succeeds trivially: the failed window's
records are permanently absent from the AOF while the server keeps
claiming the <=1s everysec loss bound.

- Tokio TopLevel + PerShard now mirror the monoio sites: log error!,
  record the fsync metric only on success, keep last_fsync unadvanced on
  failure, and gate on !write_error.
- New AOF_LAST_FSYNC_OK / AOF_FSYNC_FAILURES statics
  (record_everysec_fsync_result), wired into all four everysec sites
  across both runtimes and surfaced in INFO # Persistence as
  aof_last_fsync_status:ok|err + aof_fsync_failures — Moon's analogue of
  Redis's aof_last_write_status, closing the "no operator surface"
  inconsistency with the WalSyncAgent poison latch and PR #211 policy.

Red/green: everysec_fsync_failure_latches_err_status covers the latch
contract (err latches + counts; success restores status, never the count).
author: Tin Dang
… (deep-review F1)

evict_one_async_spill removes the hot entry as soon as the spill request is
queued, relying on the completion to land the key in the cold index. When
the background pwrite failed (ENOSPC/EIO), the failure completion carried
no payload and the handler warned and continued — the key existed in
NEITHER plane, so GET returned nil for an acked write until an AOF-replay
restart (and permanently on the no-AOF sync paths' async sibling).

- SpillCompletion now carries the original SpillRequest back on failure
  (Box'd; Bytes fields are refcounted, so the clone is cheap).
- apply_completion_vec rehydrates the payload (rehydrate_spill_payload,
  the exact inverse of build_spill_payload) and re-inserts into the hot
  table via with_shard_db — fail-closed, mirroring the enqueue-failure
  path which retains the hot value. A newer write that recreated the key
  while the spill was in flight wins (version!=0 check); the stale payload
  is dropped.
- Loud operator surface: error! log + new spill_failed_reinserted counter
  in INFO # Persistence (nonzero means the spill volume is failing
  writes).

Red/green: spill_payload_round_trips_through_rehydrate (hash + string,
TTL fidelity) and failed_spill_write_carries_request_back (unwritable
shard_dir → failure completion carries key/payload/ttl back).
author: Tin Dang
…-review G1)

TrackingTable documented "bounded: max_keys (default 1_000_000), evict
oldest with fake invalidation" but the field was #[allow(dead_code)] — a
long-lived CLIENT TRACKING client reading many distinct never-written keys
grew key_clients without limit (writes and disconnects clean up, but a
read-mostly keyspace never triggers either).

track_key now enforces the cap: tracking a NEW key at capacity evicts an
arbitrary existing entry and returns its (key, senders) so track_read_keys
pushes the RESP3 invalidation for the evicted key — clients drop their
cached copy instead of holding it stale forever (Redis's tracking-table
"fake invalidation" semantics). Re-tracking an existing key never evicts.

Known follow-up (documented, out of scope here): expiry/eviction of the
underlying KEY still doesn't invalidate trackers — only writes do.

Red/green: test_track_key_enforces_max_keys_bound (cap holds, evicted
key's tracker notified, existing-key re-track exempt).
author: Tin Dang
…eep-review R6)

The monoio inline dispatch loop executes plain GET/SET against the local
DashTable before the generic dispatch loop runs, and had no cluster gate:
can_inline_writes checked ACL/multi/tracking/replica/spill/fanout but not
cluster_enabled(), and GET inlining consulted no gate at all. In cluster
mode the two hottest commands therefore bypassed try_handle_cluster_routing
entirely — writes for slots owned elsewhere were silently misplaced
locally, reads returned stale/nil instead of MOVED, and non-ASKING writes
to migrating slots broke the ASK handoff (the known "three dispatch paths"
bug class).

try_inline_dispatch_loop now takes cluster_enabled (passed from
crate::cluster::cluster_enabled() at the call site; a param for
unit-testability) and inlines nothing when set — every command falls
through to the generic loop where MOVED/ASK routing lives. Non-cluster
deployments are unaffected (one relaxed atomic load per batch).

Red/green: test_inline_loop_disabled_in_cluster_mode.
author: Tin Dang
…urable dirent (deep-review D1/D4)

Two crash windows around the rewrite's old-generation prune:

D1 — The task-#59 deferred ShardManifest commit is justified by "AOF
replay + orphan sweep reconstruct anything a lost manifest commit would
have recorded". The #433 auto-rewrite deletes exactly those AOF records
(the fold snapshots only db.data(), so cold-spilled keys exist ONLY in the
old incr), and the rewrite's own multi-GB write+fsync is what stalls the
manifest-sync thread, widening the deferred window across the rewrite. A
kill-9 after the prune but before the deferred root persists loses every
spilled key in the un-persisted batch — and the boot orphan sweep then
deletes the .mpf holding the only surviving copy.

D4 — write_manifest's rename fsyncs its parent dir best-effort (warn
only). Pruning after a FAILED dir fsync can crash into "old manifest
resolves, old files deleted": shard replay treats that as a fresh init and
cleanup_orphans deletes the NEW generation too — silent total loss
instead of a fail-stop.

Fix: both prune sites (TopLevel AofManifest::advance step 4; PerShard
RewriteCoordinator::shard_done) now delete the old generation only after
(a) manifest_sync::flush_all_agents() — a new ack-only barrier that blocks
until every live agent's pending deferred snapshot is durable (bounded
30s; registry of Weak agent handles, registered at spawn, deregistered at
shutdown) — and (b) an explicit propagating fsync_directory on the AOF
manifest's parent. On either failure the old generation is retained: costs
disk space, never data.

Red/green: flush_all_agents_forces_pending_deferred_commit_durable
(injected sync delay; barrier alone must make the snapshot durable with no
shutdown flush) + flush_all_agents_is_noop_when_idle.
author: Tin Dang
…ck rules (deep-review C1-C4/R8/A5)

Five defects in the v0.9 W0/C-1 control plane, each small and independently
verifiable (the structural C-2/C-3 gaps — slot-map propagation, demotion,
plane wiring, node persistence — are tracked separately, not half-fixed
here):

- C1: election votes were structurally unreceivable. The replica sent
  FailoverAuthRequest on a fire-and-forget connection and dropped it before
  the master's reply, which was written back on that same dying stream; no
  master ever dials the replica's bus. Any election with votes_needed >= 2
  timed out unconditionally (masked in small test clusters where a FAILed
  master deflates quorum to 1 and the self-vote wins alone). The requester
  now holds each stream open and reads the length-prefixed ack back
  (4s bound, frame-length guard); the bus inbound ack arm stays as a
  secondary path.
- C2: acks carried no election binding and voters no identity check — a
  stale ack from a previous timed-out election, or repeated acks from one
  voter, counted toward quorum. Acks now ECHO the request epoch; the vote
  loop discards epoch mismatches and dedups voters via HashSet (self-id
  pre-seeded so a reflected self-ack can never double-count).
- C4: on win, check_and_initiate_failover bumped state.epoch a SECOND time,
  so the promoted master claimed an epoch nobody voted for — defeating
  last_vote_epoch's single-vote-per-epoch guarantee and enabling epoch ties
  gossip's strict-> merge cannot resolve. Election wins now promote via
  promote_self_to_master at the voted epoch; FORCE/TAKEOVER keep their
  explicit bumps.
- R8: graceful CLUSTER FAILOVER (no args) parked failover_state at
  WaitingDelay{0} forever — nothing consumed it and the ticker's election
  gate requires FailoverState::None, so one admin command permanently
  disabled automatic failover on the node. It now returns an explicit
  "not yet supported" error (state untouched) until the coordinated
  offset-sync protocol lands.
- C3: ClusterState was std::sync::RwLock with ~25 .read()/.write().unwrap()
  sites including the per-command route_slot path — violating the repo lock
  rules and turning any panic under a write guard into poisoning that
  panics every shard thread. Migrated to parking_lot::RwLock (no poisoning,
  no unwraps) across src/cluster, main.rs, shard wiring, and conn handlers.
- A5: a peer announcing a client port > 55535 got a silently wrapped bus
  port (our own port is already refused at startup); the wrap now warns
  loudly that gossip to that peer will fail.

Red/green: test_promote_self_keeps_voted_epoch,
test_failover_normal_errors_without_wedging_state; existing 55 cluster
tests green.
author: Tin Dang
…temporal registry (deep-review F4/G3/G4)

F4 — vector background compaction failed silently at every layer: a dead
worker (panic/pool teardown) cleared inflight with no log, a compaction
Err (e.g. B2 disk persist on a full volume) was dropped with no log, and
the resubmit swallow made it an invisible infinite retry loop while the
mutable segment grew past COMPACT_THRESHOLD and FT.SEARCH degraded toward
brute force. The merge path learned this lesson in PR #353; the compact
path now matches: error! on worker death, warn! on compaction failure,
debug trail on deferred submits.

G3 — CDC subscribers whose consumer disconnected were only reaped via a
failed try_send driven by a NEW WAL record; write-idle shards accumulated
dead subscribers (each holding a tail reader) indefinitely. fanout_tick
now checks tx.is_disconnected() every tick.

G4 — TemporalRegistry grew one binding per TEMPORAL.SNAPSHOT_AT for the
life of the process. Bounded at 262,144 bindings/shard (~4 MB); overflow
evicts the oldest, bounding AS_OF history depth instead of memory.

Red/green: test_cdc_fanout_reaps_disconnected_subscriber_when_idle,
test_registry_bounded_evicts_oldest.
author: Tin Dang
Consolidated [Unreleased] entry for the eight fix groups landed from the
six-dimension architecture review; structural follow-ups tracked in
issues #451 (cluster C-track) and #452 (durability).
author: Tin Dang
Formatting-only pass over files touched by the 2026-08 deep-review
fixes (cluster, tracking, AOF, conn tests). No behavior change.

author: Tin Dang
@coderabbitai

coderabbitai Bot commented Aug 8, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@TinDang97, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 24 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 67372971-e466-4dc0-a3cc-b8fa661416c2

📥 Commits

Reviewing files that changed from the base of the PR and between a528b92 and 9a4a5eb.

📒 Files selected for processing (12)
  • src/command/connection.rs
  • src/persistence/aof/mod.rs
  • src/persistence/aof/writer_task.rs
  • src/persistence/manifest.rs
  • src/persistence/manifest_sync.rs
  • src/server/conn/handler_monoio/dispatch.rs
  • src/server/conn/handler_sharded/mod.rs
  • src/shard/persistence_tick.rs
  • src/storage/db/mod.rs
  • src/storage/eviction.rs
  • src/storage/tiered/spill_thread.rs
  • tests/integration.rs
📝 Walkthrough

Walkthrough

This PR applies correctness and resilience fixes across storage metadata, spill recovery, AOF durability, cluster failover and routing, tracking limits, CDC cleanup, temporal bounds, compaction logging, and persistence reporting.

Changes

Storage metadata and spill recovery

Layer / File(s) Summary
Storage metadata and spill recovery
src/storage/{entry.rs,db/*,eviction.rs,tiered/spill_thread.rs}, src/shard/persistence_tick.rs
Entry versions and timestamps use wider representations. LFU and LRU calculations use saturating or full-width time handling. Failed spills return the original request and can restore values with TTL metadata.

AOF telemetry and rewrite durability

Layer / File(s) Summary
AOF telemetry and rewrite durability
src/persistence/aof/*, src/persistence/aof_manifest/mod.rs, src/persistence/manifest_sync.rs, src/command/connection.rs
Everysec fsync results are tracked and exposed through INFO Persistence. Rewrite cleanup waits for manifest-agent flushing and directory durability before deleting old generations.

Cluster state and failover correctness

Layer / File(s) Summary
Cluster state and failover correctness
src/cluster/*, src/main.rs
Cluster state uses parking_lot::RwLock. Failover acknowledgements carry epochs, votes are deduplicated and epoch-checked, and successful promotion preserves the voted epoch. Normal failover without a mode returns an error.

Cluster routing and connection wiring

Layer / File(s) Summary
Cluster routing and connection wiring
src/server/conn/*, src/shard/{conn_accept.rs,event_loop.rs}
Connection and shard interfaces use the new cluster-state lock type. Inline command dispatch is disabled in cluster mode so requests reach slot routing.

Bounded tracking and background cleanup

Layer / File(s) Summary
Bounded tracking and background cleanup
src/tracking/*, src/temporal/mod.rs, src/cdc/fanout.rs, src/vector/store.rs, src/command/key.rs
Tracking and temporal registries enforce bounds. Evictions and disconnected CDC subscribers are cleaned up. Compaction failures are logged, and OBJECT IDLETIME uses full-range elapsed-time subtraction.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related issues

Possibly related PRs

Suggested reviewers: pilotspacex-byte

Sequence Diagram(s)

sequenceDiagram
  participant Replica
  participant ClusterPeer
  participant Election
  participant ClusterState

  Replica->>ClusterPeer: Send failover request
  ClusterPeer-->>Replica: Return voter ID and acknowledgement epoch
  Replica->>Election: Submit vote and epoch
  Election->>ClusterState: Validate epoch and deduplicate voter
  Election->>ClusterState: Promote replica at voted epoch
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the PR's primary fixes across durability, long-uptime arithmetic, cluster election, and silent-failure handling.
Description check ✅ Passed The description covers scope, individual fixes, testing results, and follow-ups, with only minor deviations from the template headings.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/deep-review-2026-08

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Deep-review fixes: long-uptime storage metadata, AOF fsync visibility, cluster election/routing

🐞 Bug fix 🧪 Tests 📝 Documentation 🕐 40+ Minutes

Grey Divider

AI Description

• Fix long-uptime correctness bugs in eviction/LFU/LRU, WATCH versions, and OBJECT IDLETIME.
• Harden durability and crash-recovery: fail-loud everysec fsync and safe AOF rewrite pruning.
• Fix cluster routing/election correctness and bound long-lived resource growth
 (tracking/CDC/temporal).
Diagram

graph TD
  C{{"Client"}} --> SVC["Conn dispatch"] --> ROUTE["Cluster routing"] --> STG[("Storage engine")]
  STG --> SPILL["Tiered spill"] --> STG
  STG --> AOF[("AOF writers")]
  AOF --> MS["Manifest sync barrier"] --> AOF
  ROUTE --> CCP["Cluster control-plane"] --> ROUTE

  subgraph Legend
    direction LR
    _c{{"Client"}} ~~~ _svc["Service"] ~~~ _db[("Persistent state")]
  end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Keep inline dispatch but add cluster routing checks
  • ➕ Retains the performance benefits of inlining in cluster mode
  • ➕ Avoids a full fallback to the generic dispatch loop for GET/SET
  • ➖ Higher risk: duplicates routing logic across multiple dispatch paths
  • ➖ Harder to maintain correctness for all commands/edge cases (ASKING, multi-key, scripts)
2. Per-writer (or per-shard) fsync status instead of global latch
  • ➕ More precise diagnostics when only a subset of writers fail fsync
  • ➕ Allows targeted alerting (e.g., a single shard’s AOF device issues)
  • ➖ More metrics/state to plumb into INFO and tests
  • ➖ Potentially confusing operationally unless summarized well
3. Tracking-table eviction with true LRU instead of arbitrary victim
  • ➕ Better cache hit rate under tracking pressure
  • ➕ More predictable behavior for large tracking workloads
  • ➖ Extra bookkeeping on every track_key() call
  • ➖ More complex correctness requirements (redirects, noloop semantics, invalidation fanout)

Recommendation: Current approach is appropriate for correctness-first hardening: (1) disabling inline dispatch in cluster mode removes an entire class of routing bypass bugs with minimal logic duplication; (2) the global fsync failure latch is a simple, high-signal operator indicator that avoids the ‘silent hole’ failure mode; (3) arbitrary tracking eviction is acceptable because correctness is preserved via fake invalidation, and more complex eviction policies can be deferred if needed.

Files changed (32) +1187 / -246

Enhancement (2) +41 / -2
connection.rsExpose AOF fsync and spill reinsertion counters in INFO +10/-0

Expose AOF fsync and spill reinsertion counters in INFO

• Adds INFO fields for everysec fsync status and failure count, and for spill failed reinsertion counts. Wires these to new global atomics/counters for operator visibility.

src/command/connection.rs

store.rsLog vector background-compaction submit/worker/failure states +31/-2

Log vector background-compaction submit/worker/failure states

• Adds debug/warn/error logs around background compaction submission, worker disconnect, and compaction failure paths. Prevents silent long-term degradation when the compaction pipeline stalls.

src/vector/store.rs

Bug fix (21) +1049 / -223
fanout.rsReap disconnected CDC subscribers on idle shards +35/-2

Reap disconnected CDC subscribers on idle shards

• Detects disconnected subscriber channels even when no new WAL records arrive, preventing unbounded retention of dead subscribers and tail readers. Adds a regression test covering idle-shard disconnect cleanup.

src/cdc/fanout.rs

bus.rsFix failover ack semantics and remove RwLock poisoning risk +25/-12

Fix failover ack semantics and remove RwLock poisoning risk

• Migrates ClusterState locking to parking_lot::RwLock and removes unwrap-based poisoning handling in the hot path. Sends FailoverAuthAck on the same stream and includes epoch in the vote channel payload for stale-ack rejection and voter deduplication.

src/cluster/bus.rs

command.rsFail-loud for unimplemented graceful CLUSTER FAILOVER; parking_lot locks +64/-57

Fail-loud for unimplemented graceful CLUSTER FAILOVER; parking_lot locks

• Switches ClusterState access to parking_lot::RwLock across cluster subcommands and tests. Changes CLUSTER FAILOVER (no args) to return a clear error instead of wedging automatic failover by leaving an unconsumed WaitingDelay state.

src/cluster/command.rs

failover.rsElection correctness: read acks, bind to epoch, dedup voters, avoid epoch double-bump +133/-26

Election correctness: read acks, bind to epoch, dedup voters, avoid epoch double-bump

• Reads FailoverAuthAck responses back on the request stream, time-bounded, fixing elections that previously could never gather votes. Deduplicates voters and rejects stale-epoch acks, and introduces promote_self_to_master() so the winner promotes at the voted epoch instead of claiming an unvoted epoch; adds tests for the epoch correctness.

src/cluster/failover.rs

gossip.rsWire election task with direct vote sender; parking_lot ClusterState +7/-5

Wire election task with direct vote sender; parking_lot ClusterState

• Uses parking_lot::RwLock for ClusterState and passes both vote receiver and sender into the election task so direct-ack reads can feed the same vote pipeline. Keeps the shared vote_tx lifecycle consistent across election start/end.

src/cluster/gossip.rs

mod.rsWarn on cluster bus-port wraparound from peer announcements +14/-2

Warn on cluster bus-port wraparound from peer announcements

• Adds an explicit warning when a peer announces a client port whose derived bus port wraps u16, indicating gossip connectivity will likely fail. Makes the failure mode visible instead of silently using a wrapped bus port.

src/cluster/mod.rs

key.rsFix OBJECT IDLETIME long-uptime truncation +6/-4

Fix OBJECT IDLETIME long-uptime truncation

• Removes 16-bit idle-time truncation and computes idle time using full u32 epoch seconds with saturating subtraction. Prevents IDLETIME wraparound and avoids negative/overflow behavior on clock skew.

src/command/key.rs

mod.rsLatch everysec fsync outcome + safe rewrite pruning gate +91/-4

Latch everysec fsync outcome + safe rewrite pruning gate

• Adds global counters/latched status for everysec fsync outcomes and exposes a helper to record results. Hardens rewrite pruning by adding a manifest-sync flush barrier and requiring a durable manifest directory entry before deleting old generations; adds tests for fsync status behavior.

src/persistence/aof/mod.rs

writer_task.rsFail-loud everysec fsync and only advance last_fsync on success +52/-9

Fail-loud everysec fsync and only advance last_fsync on success

• Stops swallowing tokio everysec fsync errors and records success/failure via the new latch/counter. Ensures last_fsync is not advanced on failure so the deadline remains armed and operators can observe the error state.

src/persistence/aof/writer_task.rs

mod.rsMake AOF manifest advance prune crash-safe +45/-5

Make AOF manifest advance prune crash-safe

• Adds a manifest-sync flush barrier and propagating directory fsync before pruning old base/incr files. On failure, keeps both generations to avoid crash windows that could lead to replay against missing files and data loss.

src/persistence/aof_manifest/mod.rs

manifest_sync.rsAdd global manifest-sync agent registry and flush barrier +107/-5

Add global manifest-sync agent registry and flush barrier

• Introduces a process-global registry of manifest-sync agents and a flush_all_agents() barrier that waits (bounded) for all pending deferred commits to become durable. Updates spawn/shutdown to register/deregister safely and adds tests proving the barrier persists deferred commits and is a no-op when idle.

src/persistence/manifest_sync.rs

blocking.rsDisable inline dispatch loop in cluster mode (monoio) +12/-0

Disable inline dispatch loop in cluster mode (monoio)

• Adds a cluster_enabled parameter and short-circuits the inline dispatch loop when cluster mode is active. Prevents bypassing MOVED/ASK routing checks via the inline fast path.

src/server/conn/blocking.rs

mod.rsPlumb cluster_enabled into inline dispatch loop (monoio) +3/-0

Plumb cluster_enabled into inline dispatch loop (monoio)

• Passes cluster_enabled into the inline dispatch loop call site to enforce the new ‘no inlining in cluster mode’ rule. Documents the rationale inline for future maintainers.

src/server/conn/handler_monoio/mod.rs

persistence_tick.rsReinsert hot key on failed spill completion (fail-closed) +48/-4

Reinsert hot key on failed spill completion (fail-closed)

• On spill pwrite failure, rehydrates the spilled payload and reinserts it into the hot table if no newer version exists, preventing ‘key in neither plane’ silent nil reads. Adds a counter and logs loudly for operator awareness.

src/shard/persistence_tick.rs

kv_ops.rsUse 24-bit version bump and reserve 0 as ‘absent’ +3/-2

Use 24-bit version bump and reserve 0 as ‘absent’

• Switches updates to use Entry::bump_version and clarifies that new entries start at INITIAL_VERSION=1. Fixes WATCH/EXEC creation detection semantics by ensuring live entries never report version 0.

src/storage/db/kv_ops.rs

entry.rsFull-width last_access + 24-bit version; fix LFU decay arithmetic +124/-70

Full-width last_access + 24-bit version; fix LFU decay arithmetic

• Repurposes CompactEntry padding to store last_access_secs as u32 and widens version to 24 bits with wrap skipping 0. Fixes LFU decay and LRU/IDLETIME long-uptime wrap issues, introduces INITIAL_VERSION=1, and adds targeted regression tests for the corrected time/version behavior.

src/storage/entry.rs

eviction.rsFix LRU wrap compare window and add spill payload rehydration +87/-9

Fix LRU wrap compare window and add spill payload rehydration

• Updates lru_is_older to a u32 wrap-safe signed-distance compare valid over multi-decade gaps, preventing LRU inversion after ~9 hours. Adds rehydrate_spill_payload helper and tests to support reinsertion on failed spills.

src/storage/eviction.rs

spill_thread.rsCarry spill request back on failure and count failed reinserts +64/-0

Carry spill request back on failure and count failed reinserts

• Makes SpillRequest clonable and extends SpillCompletion to include failed_request so the event loop can reinsert already-evicted keys on write failure. Adds a new counter (spill_failed_reinserted) and tests ensuring failed writes carry the payload.

src/storage/tiered/spill_thread.rs

mod.rsBound TemporalRegistry growth with oldest-eviction +31/-0

Bound TemporalRegistry growth with oldest-eviction

• Adds a fixed cap on temporal wall-clock→LSN bindings and evicts the oldest entries on overflow. Includes a regression test ensuring the registry remains bounded and recent entries still resolve.

src/temporal/mod.rs

invalidation.rsEmit fake invalidations when tracking table evicts keys +9/-1

Emit fake invalidations when tracking table evicts keys

• Handles track_key() evictions by pushing invalidations to the evicted key’s trackers, preventing permanently stale client caches. Aligns behavior with Redis client tracking semantics under table pressure.

src/tracking/invalidation.rs

mod.rsEnforce max_keys bound for CLIENT TRACKING +89/-6

Enforce max_keys bound for CLIENT TRACKING

• Implements max_keys enforcement by evicting an existing tracked key when the cap is reached and returning the evicted key plus senders for caller-driven invalidation. Adds constructor for explicit caps and a regression test proving the bound holds.

src/tracking/mod.rs

Refactor (6) +13 / -16
main.rsUse parking_lot::RwLock for ClusterState wiring +4/-7

Use parking_lot::RwLock for ClusterState wiring

• Updates cluster mode initialization and control-plane runner signatures to use parking_lot::RwLock. Removes unwrap-based read usage when logging node id at startup.

src/main.rs

core.rsSwitch ConnectionContext ClusterState to parking_lot::RwLock +2/-2

Switch ConnectionContext ClusterState to parking_lot::RwLock

• Updates the connection context’s cluster_state type to use parking_lot::RwLock, aligning with cluster module changes and removing poisoning semantics from per-command access.

src/server/conn/core.rs

dispatch.rsRemove std::RwLock unwrap in cluster routing checks (monoio) +1/-1

Remove std::RwLock unwrap in cluster routing checks (monoio)

• Updates try_handle_cluster_routing to use parking_lot RwLock read guards directly. Maintains existing routing behavior while avoiding poison unwraps.

src/server/conn/handler_monoio/dispatch.rs

mod.rsRemove std::RwLock unwrap in cluster routing checks (tokio sharded) +1/-1

Remove std::RwLock unwrap in cluster routing checks (tokio sharded)

• Updates cluster routing checks in the tokio sharded handler to use parking_lot RwLock reads. Keeps behavior intact while removing poison-related unwraps.

src/server/conn/handler_sharded/mod.rs

conn_accept.rsPlumb parking_lot ClusterState through connection spawners +4/-4

Plumb parking_lot ClusterState through connection spawners

• Updates both tokio and monoio connection spawn functions to accept cluster_state as parking_lot::RwLock. Aligns wiring across migrated and non-migrated connection entrypoints.

src/shard/conn_accept.rs

event_loop.rsShard event loop uses parking_lot ClusterState type +1/-1

Shard event loop uses parking_lot ClusterState type

• Updates shard event loop constructor signature to accept the new ClusterState lock type. Keeps shard initialization consistent with the rest of the cluster refactor.

src/shard/event_loop.rs

Tests (2) +50 / -5
tests.rsAdd coverage for cluster-mode inline dispatch disablement +44/-0

Add coverage for cluster-mode inline dispatch disablement

• Updates inline dispatch tests for the new function signature and adds a regression test asserting that cluster mode prevents any inlining. Ensures commands fall through to generic dispatch for MOVED/ASK enforcement.

src/server/conn/tests.rs

mod.rsUpdate version-tracking tests for INITIAL_VERSION semantics +6/-5

Update version-tracking tests for INITIAL_VERSION semantics

• Adjusts tests to reflect that get_version() returns 0 only for missing keys and newly created keys start at version 1. Extends increment_version coverage accordingly.

src/storage/db/mod.rs

Documentation (1) +34 / -0
CHANGELOG.mdDocument 2026-08 deep-review fix set +34/-0

Document 2026-08 deep-review fix set

• Adds a detailed changelog entry describing the deep-review wave and its fix groups (metadata/eviction, AOF fsync visibility, spill reinsertion, rewrite prune safety, tracking bounds, and cluster correctness). Serves as user-facing release notes for the correctness and durability hardening.

CHANGELOG.md

…ng_lot

The C3 lock migration moved `Shard::run`'s `cluster_state` parameter to
`parking_lot::RwLock`, but the integration-test cluster harness still
wrapped ClusterState in `std::sync::RwLock`, so `cargo test` (all
targets) failed to compile on CI while the local `--lib`-only gate
stayed green. Lesson applied: verify with `cargo check --all-targets`
on both feature sets, both now clean.

author: Tin Dang
@qodo-code-review

qodo-code-review Bot commented Aug 8, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (4) 📜 Skill insights (0)

Grey Divider


Action required

1. Stale spill reinsert ✓ Resolved 🐞 Bug ≡ Correctness
Description
On spill write failure, apply_completion_vec re-inserts the evicted payload when
db.get_version(key)==0, but get_version only checks the hot table. If the key was rewritten and then
became cold-only before the failure completion is applied, this logic can overwrite the newer cold
value by resurrecting the stale payload into hot RAM (hot shadows cold on reads).
Code

src/shard/persistence_tick.rs[R642-646]

+            if let Some(req) = c.failed_request {
+                crate::shard::slice::with_shard_db(req.db_index, |db| {
+                    if db.get_version(&req.key) != 0 {
+                        // A newer write recreated the key while the spill was
+                        // in flight; the failed payload is stale — drop it.
Relevance

●● Moderate

Potentially subtle storage-versioning semantics change; no close historical precedent found for this
exact hot/cold staleness case.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The new failure handler re-inserts when db.get_version(&req.key) == 0, but get_version only
checks self.data (hot). Since Database::get() promotes from cold on hot-miss, cold-only keys are
a normal steady-state; resurrecting a stale value into hot will shadow the correct cold value and
serve stale reads.

src/shard/persistence_tick.rs[631-673]
src/storage/db/kv_ops.rs[815-818]
src/storage/db/kv_ops.rs[22-50]
src/storage/db/kv_ops.rs[536-552]
src/storage/tiered/spill_thread.rs[193-214]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The failed-spill recovery path re-inserts a spilled value back into the hot table when `db.get_version(key) == 0`. However, `get_version()` only consults the hot table, so it cannot detect the case where a newer value for the same key exists only in the cold tier. In that scenario, the failure completion can resurrect an older payload into hot RAM, shadowing the correct cold value and returning stale reads.

## Issue Context
- `Database::get_version()` returns `0` when the key is absent from **hot**, even if it exists in **cold_index**.
- Reads on hot-miss consult `cold_index` and promote from cold, but if a stale value is inserted into hot, hot wins and the cold value is effectively masked.
- `SpillRequest` currently does not carry any version/generation information, so the completion handler cannot perform a robust “newer write wins” check.

## Fix Focus Areas
- src/shard/persistence_tick.rs[631-673]
- src/storage/db/kv_ops.rs[536-552]
- src/storage/db/kv_ops.rs[815-818]
- src/storage/tiered/spill_thread.rs[193-214]

## Suggested fix approach
1. In the failure reinsert path, ensure the key is absent from *both* tiers before re-inserting:
  - Use a cold-aware check (e.g., `db.exists(key)` or an explicit `cold_contains_alive`) in addition to `get_version()`.
  - Only reinsert if the key does not exist in hot *and* does not exist in cold.
2. For full correctness (“newer write wins” even if deleted/recreated), extend `SpillRequest` to carry an `expected_version` captured at eviction time, and compare against the current version if the key exists hot; consider a tier-wide generation/tombstone mechanism if deletes must also suppress reinsertion.
3. Add a regression test that:
  - Evicts K with payload V1 (spill in-flight),
  - Writes V2 and makes it cold-only,
  - Forces the original spill completion to fail,
  - Asserts the failure handler does **not** resurrect V1 over V2.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. victim unwrap comment missing 📘 Rule violation ✧ Quality
Description
The added #[allow(clippy::unwrap_used)] for self.key_clients.keys().next().unwrap() does not
have the required justification comment on the line directly above it. This violates the unwrap
annotation rule and makes the safety invariant easy to miss during review.
Code

src/tracking/mod.rs[R152-153]

+            #[allow(clippy::unwrap_used)] // len >= 1 guaranteed by the branch
+            let victim = self.key_clients.keys().next().unwrap().clone();
Relevance

●●● Strong

Matches previously accepted request to place unwrap_used justification comment correctly with the
allow attribute.

PR-#71

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The checklist requires a justification comment directly above the #[allow(clippy::unwrap_used)]
attribute. In src/tracking/mod.rs, the attribute exists but the justification is on the same line
as the attribute (not directly above it), so it does not meet the required format.

Rule 302083: Annotate safe unwrap calls with allow and justification
src/tracking/mod.rs[149-154]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A production `.unwrap()` was added with `#[allow(clippy::unwrap_used)]`, but the required justification comment is not on the line directly above the attribute.

## Issue Context
Compliance requires a single-line comment immediately preceding the `#[allow(clippy::unwrap_used)]` attribute explaining why the unwrap cannot panic (the invariant established by the prior branch).

## Fix Focus Areas
- src/tracking/mod.rs[149-154]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Knob-lock unwrap lacks comment 📘 Rule violation ✧ Quality
Description
An added #[allow(clippy::unwrap_used)] does not have the required justification comment on the
line directly above it. This violates the unwrap annotation rule and reduces auditability of why the
unwrap is safe.
Code

src/persistence/manifest_sync.rs[R338-339]

+        #[allow(clippy::unwrap_used)] // test-only; poisoning would already be a failed test
+        let _knob = super::super::manifest::TEST_SYNC_KNOB_LOCK.lock().unwrap();
Relevance

●●● Strong

They previously accepted fixing allow-attribute justification formatting (comment placement) for
unwrap_used.

PR-#71

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The rule requires a justification comment directly above the #[allow(clippy::unwrap_used)]
attribute. In manifest_sync.rs, the attribute is present but the justification is not on the line
immediately preceding it (it is absent; the current comment is trailing on the attribute line), so
the required pattern is not met.

Rule 302083: Annotate safe unwrap calls with allow and justification
src/persistence/manifest_sync.rs[336-340]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The unwrap allow attribute is missing the required preceding justification comment line.

## Issue Context
Compliance requires a single-line justification comment immediately above `#[allow(clippy::unwrap_used)]` (no blank line), explaining the invariant that makes the unwrap safe.

## Fix Focus Areas
- src/persistence/manifest_sync.rs[336-341]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


4. spill_thread.rs test uses unwrap() 📘 Rule violation ✧ Quality
Description
A newly added test uses .unwrap() without the required #[allow(clippy::unwrap_used)] plus
justification comment, and it is placed in a split-module subfile instead of mod.rs. This violates
the unwrap-annotation and split-module test placement requirements.
Code

src/storage/tiered/spill_thread.rs[R748-751]

+        let tmp = tempfile::tempdir().unwrap();
+        // Make shard_dir an existing FILE so the spill write cannot succeed.
+        let bogus_dir = tmp.path().join("not-a-dir");
+        std::fs::write(&bogus_dir, b"occupied").unwrap();
Relevance

●● Moderate

Unwrap-allow policy is inconsistently enforced, and moving split-module tests to mod.rs was rejected
before.

PR-#211
PR-#450

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The checklist requires unwraps to be covered by an allow attribute with a preceding justification
comment, and requires tests for split modules to be located in mod.rs. The new test in
src/storage/tiered/spill_thread.rs includes .unwrap() calls and the presence of
src/storage/tiered/mod.rs indicates this is a split module tree.

Rule 302083: Annotate safe unwrap calls with allow and justification
Rule 302093: Keep test code for split Rust modules in mod.rs
src/storage/tiered/spill_thread.rs[744-752]
src/storage/tiered/mod.rs[1-10]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A new unit test was added in a split-module subfile (`src/storage/tiered/spill_thread.rs`) and it introduces `.unwrap()` calls without the required allow+justification.

## Issue Context
Compliance requires unit tests for split modules to live in the corresponding `mod.rs`, and any `.unwrap()` remaining in diffs must be annotated with `#[allow(clippy::unwrap_used)]` plus a justification comment directly above the attribute.

## Fix Focus Areas
- src/storage/tiered/spill_thread.rs[744-772]
- src/storage/tiered/mod.rs[1-10]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View more (1)
5. fanout.rs test uses unwrap() 📘 Rule violation ✧ Quality
Description
A newly added test uses .unwrap() without the required #[allow(clippy::unwrap_used)] plus
justification comment, and it is placed in a split-module subfile instead of mod.rs. This violates
the unwrap-annotation and split-module test placement requirements.
Code

src/cdc/fanout.rs[R261-263]

+    #[test]
+    fn test_cdc_fanout_reaps_disconnected_subscriber_when_idle() {
+        let tmp = tempfile::tempdir().unwrap();
Relevance

●● Moderate

Team enforces unwrap-allow sometimes, but split-module test-in-mod.rs move was previously rejected.

PR-#211
PR-#450

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The checklist requires unwraps to be annotated with an allow attribute and a preceding justification
comment, and requires tests for split modules to be located in mod.rs. The added test in
src/cdc/fanout.rs calls tempfile::tempdir().unwrap() and the module clearly uses a mod.rs
split (src/cdc/mod.rs), so this test should live there.

Rule 302083: Annotate safe unwrap calls with allow and justification
Rule 302093: Keep test code for split Rust modules in mod.rs
src/cdc/fanout.rs[256-265]
src/cdc/mod.rs[26-34]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A new unit test was added in a split-module subfile (`src/cdc/fanout.rs`) and it uses `.unwrap()` without the required allow+justification pattern.

## Issue Context
Compliance requires (1) unit tests for split modules live in the module `mod.rs`, and (2) any remaining `.unwrap()` usage in diffs must be covered by `#[allow(clippy::unwrap_used)]` with a justification comment directly above the attribute.

## Fix Focus Areas
- src/cdc/fanout.rs[256-283]
- src/cdc/mod.rs[26-34]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Context used
✅ Compliance rules (platform): 55 rules

To customize comments, go to the Qodo configuration screen, or learn more in the docs.

Qodo Logo

Comment thread src/cdc/fanout.rs
Comment on lines +261 to +263
#[test]
fn test_cdc_fanout_reaps_disconnected_subscriber_when_idle() {
let tmp = tempfile::tempdir().unwrap();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Remediation recommended

1. fanout.rs test uses unwrap() 📘 Rule violation ✧ Quality

A newly added test uses .unwrap() without the required #[allow(clippy::unwrap_used)] plus
justification comment, and it is placed in a split-module subfile instead of mod.rs. This violates
the unwrap-annotation and split-module test placement requirements.
Agent Prompt
## Issue description
A new unit test was added in a split-module subfile (`src/cdc/fanout.rs`) and it uses `.unwrap()` without the required allow+justification pattern.

## Issue Context
Compliance requires (1) unit tests for split modules live in the module `mod.rs`, and (2) any remaining `.unwrap()` usage in diffs must be covered by `#[allow(clippy::unwrap_used)]` with a justification comment directly above the attribute.

## Fix Focus Areas
- src/cdc/fanout.rs[256-283]
- src/cdc/mod.rs[26-34]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +748 to +751
let tmp = tempfile::tempdir().unwrap();
// Make shard_dir an existing FILE so the spill write cannot succeed.
let bogus_dir = tmp.path().join("not-a-dir");
std::fs::write(&bogus_dir, b"occupied").unwrap();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Remediation recommended

2. spill_thread.rs test uses unwrap() 📘 Rule violation ✧ Quality

A newly added test uses .unwrap() without the required #[allow(clippy::unwrap_used)] plus
justification comment, and it is placed in a split-module subfile instead of mod.rs. This violates
the unwrap-annotation and split-module test placement requirements.
Agent Prompt
## Issue description
A new unit test was added in a split-module subfile (`src/storage/tiered/spill_thread.rs`) and it introduces `.unwrap()` calls without the required allow+justification.

## Issue Context
Compliance requires unit tests for split modules to live in the corresponding `mod.rs`, and any `.unwrap()` remaining in diffs must be annotated with `#[allow(clippy::unwrap_used)]` plus a justification comment directly above the attribute.

## Fix Focus Areas
- src/storage/tiered/spill_thread.rs[744-772]
- src/storage/tiered/mod.rs[1-10]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +338 to +339
#[allow(clippy::unwrap_used)] // test-only; poisoning would already be a failed test
let _knob = super::super::manifest::TEST_SYNC_KNOB_LOCK.lock().unwrap();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Remediation recommended

3. Knob-lock unwrap lacks comment 📘 Rule violation ✧ Quality

An added #[allow(clippy::unwrap_used)] does not have the required justification comment on the
line directly above it. This violates the unwrap annotation rule and reduces auditability of why the
unwrap is safe.
Agent Prompt
## Issue description
The unwrap allow attribute is missing the required preceding justification comment line.

## Issue Context
Compliance requires a single-line justification comment immediately above `#[allow(clippy::unwrap_used)]` (no blank line), explaining the invariant that makes the unwrap safe.

## Fix Focus Areas
- src/persistence/manifest_sync.rs[336-341]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment thread src/tracking/mod.rs
Comment on lines +152 to +153
#[allow(clippy::unwrap_used)] // len >= 1 guaranteed by the branch
let victim = self.key_clients.keys().next().unwrap().clone();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Remediation recommended

4. victim unwrap comment missing 📘 Rule violation ✧ Quality

The added #[allow(clippy::unwrap_used)] for self.key_clients.keys().next().unwrap() does not
have the required justification comment on the line directly above it. This violates the unwrap
annotation rule and makes the safety invariant easy to miss during review.
Agent Prompt
## Issue description
A production `.unwrap()` was added with `#[allow(clippy::unwrap_used)]`, but the required justification comment is not on the line directly above the attribute.

## Issue Context
Compliance requires a single-line comment immediately preceding the `#[allow(clippy::unwrap_used)]` attribute explaining why the unwrap cannot panic (the invariant established by the prior branch).

## Fix Focus Areas
- src/tracking/mod.rs[149-154]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment thread src/shard/persistence_tick.rs

@coderabbitai coderabbitai 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.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/main.rs (1)

1011-1031: 📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift

Split src/main.rs before adding more bootstrap logic.

src/main.rs reaches at least line 2151. It exceeds the 1500-line file limit. Extract cluster bootstrap and control-plane setup into dedicated modules, and keep only top-level orchestration in main.

As per coding guidelines, “No single Rust file should exceed 1500 lines.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main.rs` around lines 1011 - 1031, Extract the cluster bootstrap and
control-plane setup currently embedded in main into dedicated Rust modules,
including the logic around ClusterState initialization and cluster-enabled
validation. Keep main limited to top-level orchestration and call the new module
APIs from it, ensuring no Rust source file exceeds 1500 lines.

Source: Coding guidelines

src/cluster/bus.rs (1)

197-207: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Reject untrusted failover ACKs before forwarding them.

Line 206 forwards an ACK from any TCP peer with its claimed voter ID. run_election_task only checks the epoch and de-duplicates that supplied ID. A peer that knows the active epoch can send ACKs with distinct forged IDs and satisfy quorum without master votes.

Before tx.send, verify that sender_id identifies a current master and that peer_addr.ip() matches that node's registered address. Recheck master membership in the election loop as defense in depth.

Proposed authorization check
                 debug!(
                     "Received failover ACK from {} (epoch {})",
                     sender_id, msg.config_epoch
                 );
+                let authorized_voter = {
+                    let cs = cluster_state.read();
+                    cs.nodes.get(&sender_id).is_some_and(|node| {
+                        matches!(node.flags, crate::cluster::NodeFlags::Master)
+                            && node.addr.ip() == peer_addr.ip()
+                    })
+                };
+                if !authorized_voter {
+                    warn!("Ignoring failover ACK from unauthorized peer {}", peer_addr);
+                    continue;
+                }
                 if let Some(tx) = vote_tx.lock().as_ref() {
                     let _ = tx.send((sender_id, msg.config_epoch));
                 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/cluster/bus.rs` around lines 197 - 207, Authorize failover ACKs in the
GossipMsgType::FailoverAuthAck handler before tx.send: resolve sender_id against
the current master membership and require peer_addr.ip() to match that master’s
registered address, dropping unauthorized ACKs. Also revalidate master
membership in run_election_task before counting received votes, while preserving
epoch and deduplication checks.
🧹 Nitpick comments (5)
src/storage/eviction.rs (1)

1168-1181: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider pinning the wraparound claim.

The doc comment on lru_is_older states that verdicts stay correct across the year-2106 clock wrap. No assertion covers that case. Add one so the documented guarantee is verified.

💚 Proposed assertion
         // Still correct at multi-day idle.
         let idle_3d = now - 259_200;
         assert!(lru_is_older(idle_3d, idle_10h));
+        // Across the u32 (year-2106) wrap: `before` precedes `after` in time.
+        let before = u32::MAX - 100;
+        let after = 100u32;
+        assert!(lru_is_older(before, after));
+        assert!(!lru_is_older(after, before));
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/storage/eviction.rs` around lines 1168 - 1181, Add a regression assertion
in lru_is_older_valid_beyond_nine_hours that exercises timestamps across the u32
year-2106 clock wrap, verifying lru_is_older preserves the documented ordering
in both argument directions. Keep the existing long-idle assertions unchanged.
src/persistence/aof/mod.rs (1)

429-462: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

Consider releasing the manifest lock before the durability barrier.

flush_all_agents() waits up to 30 seconds per registered agent (see src/persistence/manifest_sync.rs lines 116-121). fsync_directory is also a blocking syscall. Both run while the AofManifest mutex guard m is held; drop(m) is at line 462.

At this call site the terminal writer is the only manifest user, so contention is low today. The pattern still couples an unbounded-latency wait to a shared lock. The barrier needs no manifest state, so it can run before the guard is taken for the prune.

No deadlock exists: the manifest-sync agents persist ShardManifest state and never take this AofManifest mutex.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/persistence/aof/mod.rs` around lines 429 - 462, Move the manifest-sync
durability barrier and directory fsync out of the scope holding the AofManifest
guard in the rewrite flow. Run flush_all_agents() and fsync_directory using the
manifest path before acquiring the guard needed for prune_shard_files, then
retain the existing safe_to_prune behavior and only lock the manifest for
pruning.
src/storage/entry.rs (1)

485-618: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider extracting the shared metadata initializer.

Thirteen constructors now repeat the same two lines. A future constructor can be added without the version stamp, which would reintroduce a version-0 entry and break WATCH creation detection.

Extract a private helper and call it from each constructor.

♻️ Proposed helper
+/// Fresh metadata for a newly constructed entry: version [`INITIAL_VERSION`],
+/// LFU counter at [`LFU_INIT_VAL`], last access at the current clock.
+#[inline]
+fn fresh_metadata() -> (u32, u32) {
+    (
+        pack_metadata_u32(INITIAL_VERSION, LFU_INIT_VAL),
+        current_secs(),
+    )
+}

Each constructor then becomes:

let (metadata, last_access_secs) = fresh_metadata();
CompactEntry {
    value: CompactValue::from_redis_value(RedisValue::String(value)),
    ttl_ms: 0,
    metadata,
    last_access_secs,
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/storage/entry.rs` around lines 485 - 618, Extract a private
fresh_metadata helper near the CompactEntry constructors that returns the
initialized metadata and current access timestamp. Update all thirteen
constructors, including new_string_with_expiry, to obtain these fields from the
helper instead of calling pack_metadata_u32 and current_secs directly, while
preserving each constructor’s value and TTL behavior.
src/persistence/aof/writer_task.rs (1)

1359-1385: 🩺 Stability & Availability | 🔵 Trivial

Torn-write state is not visible in aof_last_fsync_status.

The new !write_error guard at line 1360 makes this site consistent with the other three everysec sites. That is the right change.

It also means a writer that has latched write_error stops calling record_everysec_fsync_result entirely. AOF_LAST_FSYNC_OK then keeps its last value, which is true if the tear followed a clean fsync. Under everysec there are no AppendSync waiters, so no client receives AOF_FSYNC_ERR either. An operator reading INFO sees aof_last_fsync_status:ok for a writer that has permanently stopped appending.

This gap is shared by all four everysec sites and is not introduced here. Consider exposing the latch as its own INFO field, for example aof_write_error:0|1, alongside the new aof_fsync_failures counter.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/persistence/aof/writer_task.rs` around lines 1359 - 1385, Expose the
latched write-error state in INFO as a dedicated field such as
aof_write_error:0|1, alongside aof_last_fsync_status and aof_fsync_failures.
Reuse the existing write_error latch from the everysec writer paths, including
the flow around the PerShard sync block, and ensure the field reports 1 after a
torn write and 0 otherwise without changing existing fsync status behavior.
src/persistence/aof_manifest/mod.rs (1)

1049-1068: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract the shared prune-durability barrier. Both sites implement the same rule before deleting an old AOF generation: make pending deferred ShardManifest commits durable via flush_all_agents(), then fsync the manifest directory entry, and skip the prune if either step fails. The rule is duplicated, and the copies have already diverged on log severity.

  • src/persistence/aof_manifest/mod.rs#L1049-L1068: extract the barrier into a helper on AofManifest, for example fn prune_is_safe(&self, old_seq: u64) -> bool, and call it here in place of the inline flush_all_agents() and fsync_directory block.
  • src/persistence/aof/mod.rs#L429-L462: replace the inline safe_to_prune match with a call to the same helper, so the error! severity used here and the warn! severity used in advance resolve to one level.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/persistence/aof_manifest/mod.rs` around lines 1049 - 1068, Extract the
shared prune-durability check into an AofManifest helper such as
prune_is_safe(&self, old_seq: u64) -> bool, preserving the flush_all_agents and
fsync_directory ordering and skip-on-failure behavior. In
src/persistence/aof_manifest/mod.rs lines 1049-1068, replace the inline barrier
with the helper call; in src/persistence/aof/mod.rs lines 429-462, replace the
safe_to_prune match with the same helper so both paths share the helper’s
warning severity and behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/cluster/failover.rs`:
- Around line 265-290: Update the tokio::spawn failover task around
TcpStream::connect, the length/data write_all calls, and read_ack so a single
four-second timeout covers the entire connection, request, and ACK exchange.
Move the timeout boundary to encompass all three phases, while preserving the
existing early return behavior for connection, write, timeout, or invalid ACK
failures.

In `@src/persistence/aof/mod.rs`:
- Around line 99-107: Update the doc comment for AOF_LAST_FSYNC_OK to describe
it as the status of the most recent everysec fsync attempt, noting that a later
successful fsync restores it to “ok” without requiring a successful write batch.
Clarify that AOF_FSYNC_FAILURES preserves failure history, and remove the claim
that the boolean latches “err”.

In `@src/server/conn/handler_monoio/dispatch.rs`:
- Around line 263-265: Remove the obsolete #[allow(clippy::unwrap_used)]
attribute and the accompanying std RwLock poison comment above the route lookup
in the dispatch handler, leaving the parking_lot::RwLock read flow through
cs.read().route_slot(slot, was_asking) unchanged.

In `@src/tracking/mod.rs`:
- Around line 149-164: Remove the suppressed unwrap in the eviction branch of
track_key. Replace the keys().next().unwrap() and subsequent remove sequence
with an Option-based remove_entry flow, handling the absent-entry case without
unwrap or expect while preserving victim notification behavior.

---

Outside diff comments:
In `@src/cluster/bus.rs`:
- Around line 197-207: Authorize failover ACKs in the
GossipMsgType::FailoverAuthAck handler before tx.send: resolve sender_id against
the current master membership and require peer_addr.ip() to match that master’s
registered address, dropping unauthorized ACKs. Also revalidate master
membership in run_election_task before counting received votes, while preserving
epoch and deduplication checks.

In `@src/main.rs`:
- Around line 1011-1031: Extract the cluster bootstrap and control-plane setup
currently embedded in main into dedicated Rust modules, including the logic
around ClusterState initialization and cluster-enabled validation. Keep main
limited to top-level orchestration and call the new module APIs from it,
ensuring no Rust source file exceeds 1500 lines.

---

Nitpick comments:
In `@src/persistence/aof_manifest/mod.rs`:
- Around line 1049-1068: Extract the shared prune-durability check into an
AofManifest helper such as prune_is_safe(&self, old_seq: u64) -> bool,
preserving the flush_all_agents and fsync_directory ordering and skip-on-failure
behavior. In src/persistence/aof_manifest/mod.rs lines 1049-1068, replace the
inline barrier with the helper call; in src/persistence/aof/mod.rs lines
429-462, replace the safe_to_prune match with the same helper so both paths
share the helper’s warning severity and behavior.

In `@src/persistence/aof/mod.rs`:
- Around line 429-462: Move the manifest-sync durability barrier and directory
fsync out of the scope holding the AofManifest guard in the rewrite flow. Run
flush_all_agents() and fsync_directory using the manifest path before acquiring
the guard needed for prune_shard_files, then retain the existing safe_to_prune
behavior and only lock the manifest for pruning.

In `@src/persistence/aof/writer_task.rs`:
- Around line 1359-1385: Expose the latched write-error state in INFO as a
dedicated field such as aof_write_error:0|1, alongside aof_last_fsync_status and
aof_fsync_failures. Reuse the existing write_error latch from the everysec
writer paths, including the flow around the PerShard sync block, and ensure the
field reports 1 after a torn write and 0 otherwise without changing existing
fsync status behavior.

In `@src/storage/entry.rs`:
- Around line 485-618: Extract a private fresh_metadata helper near the
CompactEntry constructors that returns the initialized metadata and current
access timestamp. Update all thirteen constructors, including
new_string_with_expiry, to obtain these fields from the helper instead of
calling pack_metadata_u32 and current_secs directly, while preserving each
constructor’s value and TTL behavior.

In `@src/storage/eviction.rs`:
- Around line 1168-1181: Add a regression assertion in
lru_is_older_valid_beyond_nine_hours that exercises timestamps across the u32
year-2106 clock wrap, verifying lru_is_older preserves the documented ordering
in both argument directions. Keep the existing long-idle assertions unchanged.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: a39a811f-e487-4853-9b83-74ffa1286832

📥 Commits

Reviewing files that changed from the base of the PR and between 34bcfe7 and a528b92.

📒 Files selected for processing (32)
  • CHANGELOG.md
  • src/cdc/fanout.rs
  • src/cluster/bus.rs
  • src/cluster/command.rs
  • src/cluster/failover.rs
  • src/cluster/gossip.rs
  • src/cluster/mod.rs
  • src/command/connection.rs
  • src/command/key.rs
  • src/main.rs
  • src/persistence/aof/mod.rs
  • src/persistence/aof/writer_task.rs
  • src/persistence/aof_manifest/mod.rs
  • src/persistence/manifest_sync.rs
  • src/server/conn/blocking.rs
  • src/server/conn/core.rs
  • src/server/conn/handler_monoio/dispatch.rs
  • src/server/conn/handler_monoio/mod.rs
  • src/server/conn/handler_sharded/mod.rs
  • src/server/conn/tests.rs
  • src/shard/conn_accept.rs
  • src/shard/event_loop.rs
  • src/shard/persistence_tick.rs
  • src/storage/db/kv_ops.rs
  • src/storage/db/mod.rs
  • src/storage/entry.rs
  • src/storage/eviction.rs
  • src/storage/tiered/spill_thread.rs
  • src/temporal/mod.rs
  • src/tracking/invalidation.rs
  • src/tracking/mod.rs
  • src/vector/store.rs

Comment thread src/cluster/failover.rs
Comment on lines 265 to +290
tokio::spawn(async move {
if let Ok(mut stream) = TcpStream::connect(addr).await {
let len = (data.len() as u32).to_be_bytes();
let _ = stream.write_all(&len).await;
let _ = stream.write_all(&data).await;
let Ok(mut stream) = TcpStream::connect(addr).await else {
return;
};
let len = (data.len() as u32).to_be_bytes();
if stream.write_all(&len).await.is_err() || stream.write_all(&data).await.is_err() {
return;
}
let read_ack = async {
let mut len_buf = [0u8; 4];
stream.read_exact(&mut len_buf).await.ok()?;
let body_len = u32::from_be_bytes(len_buf) as usize;
if body_len == 0 || body_len > crate::cluster::bus::MAX_GOSSIP_FRAME_LEN {
return None;
}
let mut buf = vec![0u8; body_len];
stream.read_exact(&mut buf).await.ok()?;
deserialize_gossip(&buf).ok()
};
// Bounded: a master that voted NO sends nothing and we must not
// hold the task past the election deadline.
let Ok(Some(msg)) =
tokio::time::timeout(std::time::Duration::from_secs(4), read_ack).await
else {
return;
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Bound connection setup and request writes.

The four-second timeout starts only after TcpStream::connect and both write_all calls finish. A blackholed peer can keep those operations pending beyond the election. Each later election then spawns more pending tasks.

Wrap connection setup, framing writes, and ACK reads in one deadline.

Proposed deadline scope
-        tokio::spawn(async move {
-            let Ok(mut stream) = TcpStream::connect(addr).await else {
-                return;
-            };
+        tokio::spawn(async move {
+            let request = async {
+                let mut stream = TcpStream::connect(addr).await.ok()?;
                 let len = (data.len() as u32).to_be_bytes();
-            if stream.write_all(&len).await.is_err() || stream.write_all(&data).await.is_err() {
-                return;
-            }
+                stream.write_all(&len).await.ok()?;
+                stream.write_all(&data).await.ok()?;
                 let read_ack = async {
                     // existing ACK read logic
                 };
-            let Ok(Some(msg)) =
-                tokio::time::timeout(std::time::Duration::from_secs(4), read_ack).await
-            else {
+                let msg = read_ack.await?;
+                Some(msg)
+            };
+            let Ok(Some(msg)) =
+                tokio::time::timeout(std::time::Duration::from_secs(4), request).await
+            else {
                 return;
             };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
tokio::spawn(async move {
if let Ok(mut stream) = TcpStream::connect(addr).await {
let len = (data.len() as u32).to_be_bytes();
let _ = stream.write_all(&len).await;
let _ = stream.write_all(&data).await;
let Ok(mut stream) = TcpStream::connect(addr).await else {
return;
};
let len = (data.len() as u32).to_be_bytes();
if stream.write_all(&len).await.is_err() || stream.write_all(&data).await.is_err() {
return;
}
let read_ack = async {
let mut len_buf = [0u8; 4];
stream.read_exact(&mut len_buf).await.ok()?;
let body_len = u32::from_be_bytes(len_buf) as usize;
if body_len == 0 || body_len > crate::cluster::bus::MAX_GOSSIP_FRAME_LEN {
return None;
}
let mut buf = vec![0u8; body_len];
stream.read_exact(&mut buf).await.ok()?;
deserialize_gossip(&buf).ok()
};
// Bounded: a master that voted NO sends nothing and we must not
// hold the task past the election deadline.
let Ok(Some(msg)) =
tokio::time::timeout(std::time::Duration::from_secs(4), read_ack).await
else {
return;
};
tokio::spawn(async move {
let request = async {
let mut stream = TcpStream::connect(addr).await.ok()?;
let len = (data.len() as u32).to_be_bytes();
stream.write_all(&len).await.ok()?;
stream.write_all(&data).await.ok()?;
let read_ack = async {
let mut len_buf = [0u8; 4];
stream.read_exact(&mut len_buf).await.ok()?;
let body_len = u32::from_be_bytes(len_buf) as usize;
if body_len == 0 || body_len > crate::cluster::bus::MAX_GOSSIP_FRAME_LEN {
return None;
}
let mut buf = vec![0u8; body_len];
stream.read_exact(&mut buf).await.ok()?;
deserialize_gossip(&buf).ok()
};
let msg = read_ack.await?;
Some(msg)
};
let Ok(Some(msg)) =
tokio::time::timeout(std::time::Duration::from_secs(4), request).await
else {
return;
};
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/cluster/failover.rs` around lines 265 - 290, Update the tokio::spawn
failover task around TcpStream::connect, the length/data write_all calls, and
read_ack so a single four-second timeout covers the entire connection, request,
and ACK exchange. Move the timeout boundary to encompass all three phases, while
preserving the existing early return behavior for connection, write, timeout, or
invalid ACK failures.

Comment thread src/persistence/aof/mod.rs Outdated
Comment thread src/server/conn/handler_monoio/dispatch.rs Outdated
Comment thread src/tracking/mod.rs
Comment on lines +149 to +164
let evicted = if self.key_clients.len() >= self.max_keys.max(1) {
// Evict an arbitrary entry (HashMap has no age order; correctness
// needs only that the evicted key's trackers are told to drop it).
#[allow(clippy::unwrap_used)] // len >= 1 guaranteed by the branch
let victim = self.key_clients.keys().next().unwrap().clone();
let clients = self.key_clients.remove(&victim).unwrap_or_default();
let mut senders = Vec::new();
for (cid, _noloop) in clients {
// No noloop skip: cap eviction is not a self-write — every
// tracker of the victim key must drop its cached copy.
let target_id = self.redirects.get(&cid).copied().unwrap_or(cid);
if let Some(tx) = self.client_channels.get(&target_id) {
senders.push(tx.clone());
}
}
Some((victim, senders))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Remove the prohibited unwrap.

track_key is library code. Line 152 suppresses Clippy for the unwrap on Line 153. Replace the lookup and removal sequence with an Option-based remove_entry flow.

As per coding guidelines, library code must avoid unwrap and expect.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/tracking/mod.rs` around lines 149 - 164, Remove the suppressed unwrap in
the eviction branch of track_key. Replace the keys().next().unwrap() and
subsequent remove sequence with an Option-based remove_entry flow, handling the
absent-entry case without unwrap or expect while preserving victim notification
behavior.

Source: Coding guidelines

…iew wave

Pre-merge adversarial review of this branch confirmed four defects in the
new code; all four are fixed here with red/green unit coverage.

1. P1 — flush_all_agents false-Ok after a failed persist: a failed
   deferred manifest commit consumed the root, latched nothing, and the
   next empty-slot barrier round acked Ok — letting the rewrite prune the
   old incr while the on-disk ShardManifest was missing spill placements
   (permanent cold-plane loss on a later crash). SyncShared now latches
   last_persist_failed; empty-slot rounds ack Err until a newer snapshot
   persists successfully.

2. P2 — AGENT_REGISTRY zombie: an agent handle dropped without shutdown()
   (panic unwind, early teardown) left the registry's wake-sender clone
   keeping the sync thread parked forever holding the manifest fd, and
   strong_count pruning could never collect it (the thread holds its own
   strong Arc). ManifestSyncAgent now deregisters in Drop, so the thread
   sees the disconnect, flushes, and exits.

3. P2 — AOF_LAST_FSYNC_OK cross-writer masking: one global bool meant a
   healthy shard's everysec success cleared a failing shard's err status
   within ~1s. Replaced with a per-writer bitmask (AOF_FSYNC_ERR_WRITERS);
   INFO reports err while ANY writer's latest fsync failed.

4. P2 — failed-spill re-insert stale shadow: a failed pwrite for key K
   could re-insert its stale payload into the hot table after K was
   re-created and re-evicted (newer spill in flight), shadowing the newer
   cold value and re-spilling the stale copy as authoritative. Databases
   now track the newest in-flight spill request id per key (requests
   already carry a unique monotonic file_id); a superseded request's
   failure arm skips the re-insert, and completions retire their own
   record.

Also removes two stale std-RwLock clippy annotations left over from the
parking_lot migration (route_slot call sites).

Refs the deep-review wave PR; found by pre-merge adversarial review.
author: Tin Dang
@TinDang97
TinDang97 merged commit 85307dd into main Aug 8, 2026
9 checks passed
@TinDang97
TinDang97 deleted the fix/deep-review-2026-08 branch August 8, 2026 08:15
TinDang97 added a commit that referenced this pull request Aug 8, 2026
… writes (#456)

Patch release rolling up the 2026-08 deep-review wave (#453) and
durability wave 1 (#452/#54, PR #454), both merged after two-round
adversarial review.

Headline: AOF rewrites no longer drop acked writes under sustained
pipelined load. A per-writer RewriteOverflow spill buffer (256 MiB cap,
strict ordering, all six fold arms on both runtimes) buffers appends
while the writer is mid-fold, with an exactly-once snapshot cut so a
committed fold discards pre-snapshot spills (effects live in the new
base) and an aborted fold writes everything. Merge-base A/B: main lost
~5.6k acked writes per hit; fixed is exact across SIGKILL + recovery
(251k appends through the overflow in the release-gate e2e). The
re-verify round closed an abort-treated-as-commit P0, a same-key
replay-inversion window, an ungated ordered-append leg, and taken-batch
loss accounting; residual architectural findings tracked in #455.

Also: WAL v3 mid-chain tears abort boot (exit 70; MOON_WAL_SALVAGE=1
override) instead of silently replaying past a hole; sticky
aof_last_append_status + per-writer aof_last_fsync_status latches;
reason-DEL escalated backpressure with one shared bound per eviction
sweep; manifest-sync failure latch; failed-spill victim re-insert;
eviction metadata widened (LFU decay, LRU inversion, OBJECT IDLETIME
wrap, WATCH ABA); cluster election acks received + inline fast path
disabled in cluster mode; CLIENT TRACKING max_keys enforced.

Validation: fmt + clippy x2 feature sets; macOS monoio 4541 + tokio
3705 and VM Linux 4563 lib tests; rewrite-under-pipelined-load e2e;
PR CI green on both PRs pre-merge; crash-matrix nightly + ITERS=20
soak dispatched on the RC (b346910), green before tag.

Rolls CHANGELOG [Unreleased] into [0.8.5], bumps Cargo.toml/lock,
adds the RELEASES.md row, updates the README milestone table.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant