Skip to content

fix(persistence): MqDrop tombstone — DEL/UNLINK/FLUSHALL/FLUSHDB no longer resurrect durable MQ streams on kill-9 (task #46)#301

Merged
pilotspacex-byte merged 1 commit into
mainfrom
fix/mq-drop-tombstone
Jul 13, 2026
Merged

fix(persistence): MqDrop tombstone — DEL/UNLINK/FLUSHALL/FLUSHDB no longer resurrect durable MQ streams on kill-9 (task #46)#301
pilotspacex-byte merged 1 commit into
mainfrom
fix/mq-drop-tombstone

Conversation

@pilotspacex-byte

@pilotspacex-byte pilotspacex-byte commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Summary

Kernel M3 stage 3, task #46 (filed during PR #291's adversarial review): a durable MQ stream deleted via generic DEL/UNLINK/FLUSHALL/FLUSHDB was resurrected on kill-9 restart — wal-v3 replay unconditionally reapplied prior MqCreate/MqPush records with no way to represent the deletion. Same bug class as the KV/vector cold-plane resurrection fixed in PR #257, now closed for MQ.

Fix

New MqDrop WAL record (discriminant 0x75, versioned [version][db_index][key_len][key], fail-closed decode), modeled on the WorkspaceDrop/0x61 precedent:

  • Emitted from every delete path: monoio + sharded DEL/UNLINK/FLUSHALL/FLUSHDB handlers, sharded MULTI/EXEC, and the replica apply path (a replica must tombstone its OWN wal-v3 plane or it resurrects the stream on its own restart, even though its live copy was correctly deleted via command replication).
  • Strict-order replay: a drop kills only records that PRECEDE it for that key — create→drop→create round-trips kill-9 with the second incarnation intact (unit test + new crash cell cross_plane_mq_create_drop_create_survives).
  • Added to segment_holds_plane_history's MQ discriminant set so WAL recycle never prunes an unfloored tombstone.
  • Fuzz coverage for decode_mq_drop added to the MQ WAL fuzz target.

Crash cell cross_plane_seeded_red_mq_generic_del_resurrection flips from RED (red_guard-gated) to a default-GREEN tripwire.

Scope decision (documented in code): DurableQueueRegistry is not db-indexed (pre-existing), so FLUSHDB drops all durable queues like FLUSHALL, and a key match tombstones regardless of db. Two dbs sharing an MQ queue name is not a supported configuration.

Test-harness note: the DEL/FLUSH crash scenarios now sync a throwaway durable queue's MQ.PUSH (wal-v3-family) after the delete before crashing — an AOF-family marker alone no longer proves the tombstone reached disk.

Gates

Refs: task #46, kernel M3 stage 3, PR #291 (MQ effect records), PR #257 (cold-plane tombstone precedent).

Summary by CodeRabbit

  • Bug Fixes

    • Prevented durable message-queue streams from reappearing after DEL, UNLINK, FLUSHDB, or FLUSHALL, including after crashes and restarts.
    • Ensured stream deletions remain consistent across replicas and WAL recovery.
    • Preserved streams created after a deletion while removing only earlier stream data.
  • Tests

    • Added regression, crash-recovery, and fuzz coverage for durable stream deletion and resurrection scenarios.

…LUSHDB no longer resurrect on kill-9 (task #46)

Root cause: MQ durable streams live as ordinary keys in the shard
keyspace, but replay_mq_wal had no way to represent "this queue was
deleted after these pushes". A generic DEL/UNLINK/FLUSHDB/FLUSHALL
removed the stream from the live db and the DurableQueueRegistry, but
replay unconditionally reapplies every MqCreate/MqPush/... record on
boot regardless of that delete — a kill-9 after the delete resurrected
the full pre-delete content on the next restart. Same bug class as the
already-fixed KV/vector cold-plane resurrection (PR #257), now closed
for MQ. Reproduced via the crash-matrix RED cell
cross_plane_seeded_red_mq_generic_del_resurrection (kernel M3 brief
§1.4), now un-gated (no more harness::red_guard) and green 3/3
consecutive runs, plus a full 44/44 default-GREEN crash-matrix pass.

Fix: a new MqDrop WAL v3 record (discriminant 0x75) is emitted whenever
a durable MQ stream is removed via generic DEL/UNLINK/FLUSHDB/FLUSHALL.
Layout matches the other versioned MQ records: [version:u8][db_index:u32]
[key_len:u32][key:N], fails closed on any malformed/future-version
payload. New hooks mq_exec::auto_drop_mq_streams /
auto_drop_mq_streams_on_flush are wired into every connection-layer
write path that already runs the equivalent vector/text index-parity
hooks (handler_monoio/mod.rs, handler_sharded/mod.rs, the sharded
MULTI/EXEC helper in server/conn/shared.rs) plus the replica-side
apply_index_parity_hooks in replication/apply.rs — a replica must
tombstone its OWN WAL too, or it resurrects the stream on its own
restart even though its live copy stayed correctly deleted via normal
command replication.

Boot-time replay applies apply_mq_drop (shared_databases.rs) strictly
in WAL order alongside every other MQ record, so a Drop only kills
records that PRECEDE it for that key — a later MqCreate/MqPush for the
same key survives intact (create -> drop -> create round-trips a
kill-9 with the second incarnation whole; proven by both a unit test
and the new crash-matrix cell cross_plane_mq_create_drop_create_survives).
segment_plane_scan's plane-history block set gained MqDrop alongside
the other MQ discriminants so autovacuum/recycle never deletes a
sealed segment still holding an unfloored tombstone. The MQ WAL fuzz
target now also fuzzes decode_mq_drop.

Replication decision: MQ effect records replicate live only at
num_shards == 1 (the pre-existing gate shared with MqCreate/etc).
MqDrop follows the same posture — MQ._REPL.DROP is emitted and applied
by replication::apply::apply_mq exactly like the other MQ replay
commands. Separately, a replicated generic DEL/UNLINK/FLUSHDB/FLUSHALL
already removes the stream from the replica's live keyspace via normal
command replication regardless of that gate; what it did not do before
this fix is tombstone the replica's own wal-v3 MQ plane, which
apply_index_parity_hooks now closes.

Scope decision (documented in code): DurableQueueRegistry entries are
NOT db-indexed (pre-existing limitation, same as MqCreate's registry)
— a key match tombstones regardless of which db the deleting command
ran in, and FLUSHDB drops every registered durable queue exactly like
FLUSHALL since the registry cannot scope to one db. Two different dbs
sharing an MQ queue NAME is not a supported configuration.

Test-gotcha fixed along the way: the crash-matrix DEL/FLUSHALL
scenarios' original sync-marker strategy waited on an AOF-family write
to prove the delete was durable — correct pre-fix (DEL only touched
the AOF), but MqDrop lands on the wal-v3 MQ plane via a separate
fire-and-forget channel drained on its own 1ms tick, so an AOF-only
marker no longer proves the tombstone itself reached disk. Both tests
now also sync a throwaway durable queue's MQ.PUSH (wal-v3-family)
after the delete/flush before crashing.

Files touched:
- src/mq/wal.rs — encode_mq_drop/decode_mq_drop + MQ_REPL_DROP +
  is_mq_replay_command update + module docs + unit tests
- src/persistence/wal_v3/record.rs — WalRecordType::MqDrop = 0x75
- src/persistence/wal_v3/replay.rs — MqDrop routed through on_command
- src/persistence/wal_v3/segment.rs — MqDrop added to plane-history
  block set
- src/shard/shared_databases.rs — apply_mq_drop + replay dispatch
  wiring + MqReplayStats.drop + 4 new unit tests
- src/shard/mq_exec.rs — auto_drop_mq_streams /
  auto_drop_mq_streams_on_flush / emit_mq_drops
- src/server/conn/handler_monoio/mod.rs,
  src/server/conn/handler_sharded/mod.rs, src/server/conn/shared.rs —
  hook call sites (DEL/UNLINK + FLUSHDB/FLUSHALL)
- src/replication/apply.rs — MQ_REPL_DROP apply arm +
  apply_index_parity_hooks replica-side tombstone
- fuzz/fuzz_targets/mq_wal_record.rs — fuzz decode_mq_drop
- tests/crash_matrix_cross_plane/tests_seeded_red.rs — un-gated the
  RED cell, fixed its wal-v3-family sync race, added a FLUSHALL
  sibling and a create->drop->create ordering test
- CHANGELOG.md — new [Unreleased] entry + removed stale #46 caveats

author: Tin Dang
@qodo-code-review

Copy link
Copy Markdown

Qodo reviews are paused for this user.

Troubleshooting steps vary by plan Learn more →

On a Teams plan?
Reviews resume once this user has a paid seat and their Git account is linked in Qodo.
Link Git account →

Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?
These require an Enterprise plan - Contact us
Contact us →

@coderabbitai

coderabbitai Bot commented Jul 13, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds the MqDrop WAL v3 tombstone record and wires generic MQ deletions through local execution, transactions, replication, WAL replay, segment retention, fuzzing, and crash-matrix tests.

Changes

MQ durable-stream tombstones

Layer / File(s) Summary
MqDrop WAL contract
src/persistence/wal_v3/record.rs, src/mq/wal.rs
Defines the MqDrop record, payload codec, replication command, replay routing, and validation tests.
Deletion and replication hooks
src/shard/mq_exec.rs, src/server/conn/..., src/replication/apply.rs
Generic deletes, flushes, transactions, and replica apply paths remove durable MQ streams and emit or apply tombstones.
Replay and segment retention
src/shard/shared_databases.rs, src/persistence/wal_v3/{replay.rs,segment.rs}
Replay applies drops in WAL order, updates statistics, and retains segments containing MQ tombstones.
Crash, fuzz, and documentation coverage
tests/crash_matrix_cross_plane/*, fuzz/fuzz_targets/*, CHANGELOG.md
Adds decoder, replay, restart, flush, recreation, and durability coverage, and updates changelog status.
Estimated code review effort: 3 (Moderate) ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant ConnectionHandler
  participant mq_exec
  participant WAL
  participant Replica
  participant Replay
  Client->>ConnectionHandler: DEL, UNLINK, FLUSHDB, or FLUSHALL
  ConnectionHandler->>mq_exec: drop durable MQ streams
  mq_exec->>WAL: append MqDrop
  mq_exec->>Replica: replicate MQ_REPL_DROP
  WAL->>Replay: replay MqDrop in order
  Replay->>Replica: remove prior durable MQ state
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the MQ tombstone fix and affected delete paths.
Description check ✅ Passed The description covers the fix, scope, tests, and results, but it doesn't follow the template's Checklist, Performance Impact, and Notes sections.
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
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/mq-drop-tombstone

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.

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

🧹 Nitpick comments (1)
src/shard/shared_databases.rs (1)

432-432: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoff

Consider splitting shared_databases.rs — it exceeds the 1500-line limit.

The file is at ~2290 lines and this PR adds ~180 lines of tests. The coding guidelines state "No single Rust file should exceed 1500 lines." The test module (lines 1353–2290) is a natural extraction candidate — moving it to a tests submodule or sibling file would bring the production code well under the limit without behavior changes.

🤖 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/shard/shared_databases.rs` at line 432, Split the tests currently housed
in shared_databases.rs into a separate tests submodule or sibling test file,
keeping the production definitions such as MqReplayStats in shared_databases.rs.
Preserve all test behavior, imports, visibility, and module wiring while
reducing the source file below the 1500-line limit.

Source: Coding guidelines

🤖 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.

Nitpick comments:
In `@src/shard/shared_databases.rs`:
- Line 432: Split the tests currently housed in shared_databases.rs into a
separate tests submodule or sibling test file, keeping the production
definitions such as MqReplayStats in shared_databases.rs. Preserve all test
behavior, imports, visibility, and module wiring while reducing the source file
below the 1500-line limit.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8c46ee11-ede1-4eed-94b2-fb0bbdb1ce51

📥 Commits

Reviewing files that changed from the base of the PR and between 48a0ca2 and 88ef62e.

📒 Files selected for processing (13)
  • CHANGELOG.md
  • fuzz/fuzz_targets/mq_wal_record.rs
  • src/mq/wal.rs
  • src/persistence/wal_v3/record.rs
  • src/persistence/wal_v3/replay.rs
  • src/persistence/wal_v3/segment.rs
  • src/replication/apply.rs
  • src/server/conn/handler_monoio/mod.rs
  • src/server/conn/handler_sharded/mod.rs
  • src/server/conn/shared.rs
  • src/shard/mq_exec.rs
  • src/shard/shared_databases.rs
  • tests/crash_matrix_cross_plane/tests_seeded_red.rs

@pilotspacex-byte pilotspacex-byte merged commit b5b17ba into main Jul 13, 2026
11 checks passed
@TinDang97 TinDang97 deleted the fix/mq-drop-tombstone branch July 13, 2026 07:07
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.

2 participants