fix(storage): a spilled value over ~4 MB reads back instead of OverflowBroken (moon#1201) - #1203
Merged
Merged
Conversation
…owBroken The cold-tier overflow-chain reader (`read_overflow_chain`) bounded its walk with a fixed MAX_OVERFLOW_PAGES = 1000 cycle guard (~4.03 MB of payload at 4032 B/page). The spill writer (`build_overflow_chain`) has no size cap, so every value above that size was written intact and indexed, then refused on every read as `OverflowBroken`: IOERR to XADD, XREADGROUP, GET, and the index entry kept for a retry that could never succeed. The cycle guard is now the file's own page count. An acyclic chain inside the file visits each page at most once, so it can never be longer than the file; a cycle always exceeds it. No on-disk format change. Evidence: - Soak (RC3) failing files, chains walked on disk: heap-4889612 1007 pages, heap-4897151 1004, heap-5026381 1003. All intact, all just over 1000. Every failing file is 4.1-4.15 MB. The streams are consumer-group streams read without XACK, so the PEL grows past MAXLEN trimming. - e2e repro (1 shard, 48mb maxmemory, a 20k-entry stream with a 20k PEL forced to spill): main = IOERR on XLEN/XADD/XREADGROUP, 4 OverflowBroken lines. Fixed = XLEN 20000, XADD/XREADGROUP OK, 0 unreadable. v0.8.9 = XLEN 0, XADD makes a new stream, XREADGROUP NOGROUP. v0.8.9 answered the same failed read as a silent miss (#1004 later made it IOERR), so the cap is pre-existing (ff51135, PR #43) and not a regression in this window. - Red/green/mutation: the new kv_page and cold_read tests fail on main with OverflowBroken, pass with the fix, and fail again when only the bound goes back to 1000. A cycle-rejection test pins the guard. Fixes #1201 author: Tin Dang
|
Navigate logical layers of code changes, visualize relationships, and explore their blast radius. Note Currently processing new changes in this PR. This may take a few minutes, please wait... ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Advanced Run ID: 📒 Files selected for processing (3)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
TinDang97
marked this pull request as ready for review
September 24, 2026 07:32
|
ⓘ Qodo reviews are paused because the subscription is no longer active. Ask your workspace admin to reactivate the subscription to resume reviews. Manage billing |
TinDang97
added a commit
that referenced
this pull request
Sep 24, 2026
Merges main at 32a7a02 (RC4) into the release branch. That brings two cold-tier fixes the release soak exposed: - #1201 (PR #1203): a spilled value over ~4 MB read back as OverflowBroken / IOERR. - #1202 (PR #1204): a MOON.SPILLED cut record could be dropped under AOF backpressure. Release notes: - Count 147 -> 149 PRs; date 2026-09-24. - New COLD TIER evidence paragraph. - #1215 disclosed as a known, pre-existing limitation deferred to v0.8.11 under the slip rule (format-level fix): a deleted cold-tier key resurrects after an AOF rewrite + restart (v0.8.9 has it too). Workaround: --disk-offload disable. The tree outside CHANGELOG.md, README.md, RELEASES.md, docs/roadmap/ROADMAP.md and the version bump is identical to RC4. Refs: #1154 author: Tin Dang
TinDang97
pushed a commit
that referenced
this pull request
Sep 24, 2026
…w fix wave Conflicts: writer_task.rs keeps main's moon#1158 post-drain release of AOF_REWRITE_IN_PROGRESS with WS6's aof_buf_writer (8 MiB, moon#1187); CHANGELOG.md keeps both sides' Fixed entries. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012JyGvRHGpDdG2GVKwwvsBj
5 of 6 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #1201.
Bug. A value too big for one 4 KB page is stored as a chain of overflow pages, each holding about 4 KB.
build_overflow_chainwrites chains of any length.read_overflow_chainrefused any chain longer than a fixed 1000 pages (about 4.03 MB), so a larger spilled value was written intact but could never be read back. The key stayed indexed and every read failed withOverflowBroken, which clients see as IOERR.In the v0.8.10 release soak this happened to consumer-group streams: the pending list keeps growing without XACK, so the streams passed 4 MB.
Not a regression. The 1000-page limit dates from PR #43. v0.8.9 has the same bug, but there the failed read was a silent miss (XLEN 0, GET nil). #1004 turned that miss into an IOERR, which is why it only now shows up as an error.
Fix. The walk is bounded by the file's own page count. A chain that doesn't loop visits each page at most once, so it can't be longer than the file; a looping chain always exceeds that count. No on-disk format change: files written by any earlier version read back.
Tests (red on unfixed code, green with the fix, red again with only the bound reverted):
kv_page::test_overflow_chain_longer_than_1000_pages_reads_back_1201cold_read::test_cold_read_overflow_chain_longer_than_1000_pages_1201: spills 4.5 MB and reads it back.kv_page::test_overflow_chain_cycle_is_rejected_1201: the cycle guard still works.Repro (1 shard,
--maxmemory 48mb, AOF on, a 20k-entry stream with a 20k-entry pending list, plus a 4.5 MB string):Known, separate: reading a multi-MB cold value back into memory can push the server over maxmemory, which is the #1156 class.