Fix CompactedDBImpl blob values unreadable via read-only Get/MultiGet - #15082
Fix CompactedDBImpl blob values unreadable via read-only Get/MultiGet#15082dfa1 wants to merge 1 commit into
Conversation
✅ clang-tidy: No findings on changed linesCompleted in 105.3s. |
CompactedDBImpl::Get and ::MultiGet (the fast path DB::OpenForReadOnly takes when max_open_files == -1 and the DB has at most one file per level) passed a null is_blob_index pointer into GetContext. Any key resolving to a blob index therefore hit GetContext::kUnexpectedBlobIndex immediately and was reported as Status::NotFound, even though the key and its blob file were both present -- data loss on read, silently. Fix: pass a real is_blob_index flag through, and when it comes back true, resolve the raw blob reference via the VersionBlobFetcher both methods already construct (mirroring Version::Get, which does the same after its own TableReader lookup). Fixes facebook#12503. That issue's investigation suspected Options::fs or filesystem-implementation differences, based on CurrentOptions() (RocksDB's test-framework helper) not reproducing the bug while a bare default-constructed Options() did. The actual variable was max_open_files: DBTestBase::GetDefaultOptions() hardcodes 5000, while a real default Options() has RocksDB's actual default of -1 -- the exact value CompactedDBImpl::Open requires to engage at all. Verified by temporarily forcing GetDefaultOptions() to -1: the entire existing db_blob_basic_test suite (46 tests) still passes with this fix, and without the fix the new regression test fails with the reported NotFound. Added GetBlobReadOnlyFullyCompacted to db_blob_basic_test.cc, covering both Get and MultiGet through CompactedDBImpl.
a324eea to
877fbec
Compare
✅ Claude Code ReviewAuto-triggered after CI passed — reviewing commit 877fbec SummaryThis is a well-motivated, correctly-scoped bug fix for a real data-loss-on-read issue in High-severity findings (0): No high-severity findings. Full review (click to expand)Findings🔴 HIGHNone. 🟡 MEDIUMM1. MultiGet uses
|
| Context | Does code execute? | Assumptions hold? | Notes |
|---|---|---|---|
| WritePreparedTxnDB | NO | N/A | CompactedDBImpl is read-only |
| ReadOnly DB | YES (this is the fix target) | YES | Core fix is correct |
| User-defined timestamps | YES | GET: YES, MULTIGET: NO (M1) | MultiGet uses wrong key |
| Merge operator | NO | N/A | CompactedDBImpl::Open rejects merge_operator |
| WideColumnEntity | Not via CompactedDBImpl | N/A | CompactedDBImpl passes columns=nullptr |
Positive Observations
- The fix correctly mirrors the
Version::Getblob resolution pattern - The
Getpath correctly usesget_context.ukey_to_get_blob_value()for timestamp-aware blob lookup - Error propagation from
FetchBlobis handled correctly - The
VersionBlobFetcherwas already constructed but unreachable; the fix simply makes it reachable - The test correctly forces
max_open_files = -1to ensure CompactedDBImpl is used - Excellent PR description with thorough root cause analysis
- Release note is appropriately concise
ℹ️ About this response
Generated by Claude Code.
Review methodology: claude_md/code_review.md
Limitations:
- Claude may miss context from files not in the diff
- Large PRs may be truncated
- Always apply human judgment to AI suggestions
Commands:
/claude-review [context]— Request a code review/claude-query <question>— Ask about the PR or codebase
|
@pdillinger @joshkang97 hello guys, can you please review this code? So far the bug was not caught by tests because |
Summary
Fixes #12503.
CompactedDBImpl::GetandCompactedDBImpl::MultiGet(the fast pathDB::OpenForReadOnlytakes whenmax_open_files == -1and the DB has at most one file per level) pass a nullis_blob_indexpointer intoGetContext. Any key resolving to a blob index therefore hitsGetContext::kUnexpectedBlobIndeximmediately, even though aVersionBlobFetcheris already constructed and passed in -- the fetcher is unreachable becauseGetContext::SaveValuebails out forkTypeBlobIndexbefore ever reaching the branch that would use it, unlessis_blob_index_is non-null. The key is reported asStatus::NotFound(), even though it and its blob file are both present.Fix
Pass a real
is_blob_indexflag through in bothGetandMultiGet, and when it comes back true, resolve the raw blob reference via theVersionBlobFetcherboth methods already construct -- mirroring whatVersion::Getdoes after its ownTableReaderlookup succeeds.Why the existing test suite never caught this
The original issue's investigation (thank you @rhubner for the deep dive) suspected
Options::fs/ filesystem-implementation differences, based onCurrentOptions()(theDBTestBasetest-framework helper) not reproducing the bug while a bare default-constructedOptions()did.The actual variable was
max_open_files:DBTestBase::GetDefaultOptions()hardcodes5000(db/db_test_util.cc), while a real default-constructedOptions()has RocksDB's actual default of-1-- exactly the valueCompactedDBImpl::Openrequires to engage at all. Withmax_open_files != -1,DB::OpenForReadOnlyalways falls through to the ordinaryDBImplReadOnlypath, where this bug does not exist. Since none of the blob tests use RocksDB's parameterizedkInfiniteMaxOpenFilesconfig sweep (the one existing config that does set-1), theCompactedDBImplfast path was never exercised against blob data by any existing test.I verified this directly: temporarily forcing
GetDefaultOptions()to-1and running the fulldb_blob_basic_testsuite (46 tests) all still pass with this fix applied, confirming this was an isolated gap rather than a symptom of something broader inCompactedDBImpl. Without the fix, the new regression test below reproducibly fails with the reportedNotFound.Test plan
DBBlobBasicTest.GetBlob_ReadOnlyFullyCompactedtodb/blob/db_blob_basic_test.cc, covering bothGetandMultiGetthroughCompactedDBImpl, withmax_open_files = -1explicitly set (theDBTestBasedefault of5000would skip the fast path this test targets).Status::NotFound()when the fix is reverted, and passes with it applied.db_blob_basic_testsuite (46 tests) passes with the fix.make check-sourcesandmake format-autoclean.