perf(dedup): backend-aware chunk read-ahead for CDC reassembly
read_blob_stream / read_blob_range_stream reassembled a CDC file by fetching its chunks with `buffered(1)` — strictly sequential, so the next chunk's backend fetch (a file `open` locally; a full request round-trip on S3/Azure) only started after the current chunk was fully drained. A benchmark of the exact pipeline (stream::iter(chunks).map(get).buffered(K) .try_flatten()) showed a blind `buffered(4)` is the WRONG fix: on a local disk it is neutral on a warm page cache and ~37% SLOWER cold, because concurrent opens turn one sequential read into several competing random-I/O streams over content-addressed (scattered) chunk files. The win is entirely on remote backends, where per-chunk request latency dominates and overlapping fetches hide it (≈ linear in K). So the read-ahead depth is now a backend hint, not a constant: - BlobStorageBackend::read_prefetch() default 1 (sequential; safe for local). - S3 / Azure override to 8 (overlap GETs to hide TTFB). - cached / encrypted / retry / migration delegate to the backend that serves the bytes. - Both CDC read paths use `self.backend.read_prefetch().max(1)`. Net: local backend unchanged (no regression); remote reassembly ~4-8x faster. Ordered `buffered` (not buffer_unordered) keeps chunks in sequence. Bench (per-chunk fetch-latency model): buffered(1)->(4)/(8) = x3.9 / x7.8 @1ms, x4.0 / x8.1 @5ms, x4.0 / x8.0 @20ms. Local warm: 230ms@1 vs 227ms@4 (noise); local cold: 425ms@1 vs 585ms@4 (why local stays at 1). https://claude.ai/code/session_01DCszkkU11LYxMEUWr4setK
This commit is contained in:
@@ -126,4 +126,18 @@ pub trait BlobStorageBackend: Send + Sync + 'static {
|
||||
/// Only meaningful for local-filesystem backends. Remote backends
|
||||
/// return `None`; callers that need a local file must stream + spool.
|
||||
fn local_blob_path(&self, hash: &str) -> Option<PathBuf>;
|
||||
|
||||
/// How many chunk fetches the CDC reader may run concurrently when
|
||||
/// reassembling a file (`read_blob_stream`'s `buffered(N)` read-ahead).
|
||||
///
|
||||
/// The default is **1** — sequential, because for a local disk concurrent
|
||||
/// opens turn one sequential read into several competing random-I/O streams
|
||||
/// over content-addressed (scattered) chunk files, which is neutral on a
|
||||
/// warm page cache and *slower* cold. Remote backends (S3/Azure) override
|
||||
/// this with a higher value: there the dominant cost is per-chunk request
|
||||
/// latency, and overlapping fetches hides it (≈ N× faster reassembly).
|
||||
/// Wrapping backends delegate to the backend that actually serves the bytes.
|
||||
fn read_prefetch(&self) -> usize {
|
||||
1
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user