Cross-key coalescing for lazy wide-column blob reads - #15178
Conversation
Summary: Follow-up (Phase 3) to the experimental lazy wide-column read API (DB::GetEntityLazy / DB::MultiGetEntityLazy). A MultiGetEntityLazy batch now resolves blob references across keys with coalesced I/O instead of key-by-key. - MultiGetEntityLazy acquires one SuperVersion and one consistent (implicit) sequence number for the whole batch via MultiCFSnapshot (as multi-CF iterators do) and transfers a single shared per-column-family pin into the LazyWideColumnsBatch, replacing the former per-key SuperVersion + explicit-snapshot loop. Single column family for now; a cross-CF ColumnFamily** overload remains a follow-up (the batch already models a per-CF pin map). - LazyWideColumnsBatch::MultiResolve groups the batch's reads by (Version, blob file) for separate-file references and by SST for embedded references, and issues one coalesced read per group -- for whole-column and byte-range reads alike -- caching whole values in each entity's resolver and pinning partial reads directly. - Adds the coalesced blob-read primitives this needs (none existed before): range-aware BlobFileReader::MultiGetBlobRange / BlobSource::MultiGetBlobRange / Version::MultiGetBlobLazy for separate-file references, and an embedded (SimpleGen2) batch read path (blob_gen2_format batch readers + BlobSource::MultiGetSimpleGen2Blob[Range] + BlockBasedTable::MultiGetSameFileBlob). Whole-value separate-file coalescing reuses the existing MultiGetBlob. The batch readers sort their requests by file offset before issuing the underlying MultiRead (which requires ascending offsets), and gen2 checksum verification is factored into one helper shared by the scalar and batch readers. - The hot non-lazy Get / MultiGet / iterator / compaction paths are untouched: the new multis are lazy-only, and the GetImpl change is gated on a new opt-in GetImplOptions field (an injected shared SuperVersion + sequence number) used only by the batched lazy read. - Blob read failures on these paths (short reads, invalid offsets, out-of-range requests, compression mismatches) now carry file/offset/size and expected-vs-actual context, mirroring the block-checksum diagnostics. - db_bench gains a multireadrandomentitylazy benchmark for measuring the coalesced batch path. No public API change, so this is a performance follow-up to the still-recent lazy API rather than a new feature. Test Plan: New unit tests in db/wide/db_lazy_entity_test.cc assert cross-key coalescing via a blob-file / SST MultiRead counter added to the test FileSystem wrapper: - N whole-column separate-file reads across keys in one blob file collapse to a single coalesced MultiRead (and the fetched values are cached, so a repeat read does no further blob I/O); - N byte-range separate-file reads collapse to one MultiRead, save the un-read bytes (rocksdb.blobdb.lazy.partial.bytes.saved), and never fill the blob cache; - embedded (same-file) reads across keys in one SST collapse to one MultiRead over that SST; - an out-of-order embedded batch (reads issued in descending record offset) still resolves correctly in one coalesced read, covering the batch readers' internal offset sort; - a mixed batch (inline + whole + byte-range across keys) returns the correct bytes for each read; - a batched MultiGetEntityLazy + one MultiResolve matches resolving each key via a separate GetEntityLazy. The existing db_stress lazy-vs-eager differential (MaybeTestMultiGetEntityLazy) exercises the new batched read and cross-key MultiResolve unchanged (same public API).
|
@pdillinger has imported this pull request. If you are a Meta employee, you can view this in D118660682. |
|
| Check | Count |
|---|---|
bugprone-argument-comment |
1 |
cppcoreguidelines-pro-type-member-init |
1 |
modernize-use-emplace |
5 |
performance-inefficient-vector-operation |
2 |
| Total | 9 |
Details
db/blob/blob_file_reader.cc (1 warning(s))
db/blob/blob_file_reader.cc:832:33: warning: argument name 'allocator' in comment does not match parameter name 'alloc' [bugprone-argument-comment]
db/db_impl/db_impl.cc (1 warning(s))
db/db_impl/db_impl.cc:2963:3: warning: uninitialized record type: 'cf_sv_pairs' [cppcoreguidelines-pro-type-member-init]
db/wide/db_lazy_entity_test.cc (7 warning(s))
db/wide/db_lazy_entity_test.cc:1059:12: warning: use emplace_back instead of push_back [modernize-use-emplace]
db/wide/db_lazy_entity_test.cc:1141:12: warning: use emplace_back instead of push_back [modernize-use-emplace]
db/wide/db_lazy_entity_test.cc:1212:12: warning: use emplace_back instead of push_back [modernize-use-emplace]
db/wide/db_lazy_entity_test.cc:1215:5: warning: 'emplace_back' is called inside a loop; consider pre-allocating the container capacity before the loop [performance-inefficient-vector-operation]
db/wide/db_lazy_entity_test.cc:1273:12: warning: use emplace_back instead of push_back [modernize-use-emplace]
db/wide/db_lazy_entity_test.cc:1276:5: warning: 'emplace_back' is called inside a loop; consider pre-allocating the container capacity before the loop [performance-inefficient-vector-operation]
db/wide/db_lazy_entity_test.cc:1380:12: warning: use emplace_back instead of push_back [modernize-use-emplace]
✅ Claude Code ReviewAuto-triggered after CI passed — reviewing commit 3479e8d SummaryWell-designed PR that replaces the per-key SuperVersion + explicit-snapshot loop in MultiGetEntityLazy with a genuinely batched path using MultiCFSnapshot, and adds cross-key I/O coalescing for lazy blob resolution. The architecture is sound: shared SV pin lifetime management is correct, new batch read primitives follow existing patterns, and the hot non-lazy paths are untouched. High-severity findings (0): No high-severity findings. Full review (click to expand)Findings🔴 HIGHNone. 🟡 MEDIUMM1. ReadOnly/Secondary DB GetImpl not updated for
|
| Context | Safe? | Reason |
|---|---|---|
| WritePreparedTxnDB | Yes | Public DB API, not used through txn layer |
| ReadOnly DB | N/A | MultiGetEntityLazy not available |
| Secondary Instance | N/A | Same |
| User-defined timestamps | Yes | Timestamp handling is after SV acquisition |
| Concurrent writers | Yes | MultiCFSnapshot handles flush races |
Assumption stress-test:
- "MultiCFSnapshot provides equivalent guarantee to explicit snapshot": Verified. Both capture
GetLastPublishedSequence(). MultiCFSnapshot retries on flush race. Semantically equivalent for reads. - "Shared SV is safe across multiple GetImpl calls": Verified. GetImpl is purely read-only w.r.t. SuperVersion.
- "Entities destroyed before cf_pins in batch Rep": Verified by explicit
entities.clear()beforecf_pins.clear()in destructor + field declaration order.
Positive Observations
- Clean architecture: The batch coalescing classifies reads first, groups by storage location, then dispatches -- a well-structured pipeline that will compose cleanly with future async execution.
- Shared checksum helper (
VerifySimpleGen2BlobChecksum): Eliminates divergence risk between scalar and batch readers. Good refactoring. - Error diagnostics: Adding file/offset/size/expected-vs-actual context to every error path is a significant debuggability win.
- Test design: The
BlobReadIOActivityFSwrapper with per-file-type read/multiread counters effectively verifies I/O coalescing at the system level. - MultiCFSnapshot reuse: Architecturally consistent with multi-CF iterators; avoids DB mutex overhead of explicit snapshots.
- Defensive destruction: Thread operation save/restore in batch Rep destructor prevents I/O misattribution.
ℹ️ About this response
Generated by Claude Code.
Review methodology: claude_md/ci_review_prompt.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
Summary:
Follow-up (Phase 3) to the experimental lazy wide-column read API (DB::GetEntityLazy / DB::MultiGetEntityLazy). A MultiGetEntityLazy batch now resolves blob references across keys with coalesced I/O instead of key-by-key.
No public API change, so this is a performance follow-up to the still-recent lazy API rather than a new feature.
Test Plan:
New unit tests in db/wide/db_lazy_entity_test.cc assert cross-key coalescing via a blob-file / SST MultiRead counter added to the test FileSystem wrapper: