Coalescing ManifestStore.get_many#1033
Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #1033 +/- ##
==========================================
- Coverage 90.07% 89.93% -0.14%
==========================================
Files 36 36
Lines 2206 2275 +69
==========================================
+ Hits 1987 2046 +59
- Misses 219 229 +10
🚀 New features to boost your workflow:
|
834b1d0 to
e86e1f1
Compare
e86e1f1 to
3f770c4
Compare
Implement Store.get_many on ManifestStore: resolve the requested chunk keys through the manifests to (source file, byte range), group the requests by source file, coalesce each group into runs, and serve each run with a single ranged read that is sliced back into per-chunk buffers. Keys that are not plain manifest-backed chunks (metadata, inlined, or missing chunks) are served individually via `get`. Coalescing uses two knobs (the object_store / async-tiff model): a run merges references whose gap is <= `coalesce_max_gap_bytes` as long as the resulting read stays <= `coalesce_max_bytes`. The gap defaults to 0 - merge only adjacent references, a pure win with no wasted bytes - because benchmarking a 2D map-tile query against a remote Met Office file showed that bridging larger gaps pulls in the chunks that sit between rows (a 2D box is contiguous along one axis but strided along the other), reading ~3x the needed bytes and running slower than no coalescing at all. Merging only adjacent references was ~2.8x faster than the per-chunk baseline with zero over-read. `max_bytes` (default 8 MiB) bounds the size of any single read. Depends on the cross-key `Store.get_many` API in zarr-python (zarr-developers/zarr-python#4112, #4113); until that is released the method is a dormant override. See zarr-developers#947.
3f770c4 to
7761438
Compare
|
This is really cool work @TomNicholas, I like that this will be handled at the chunk manifest level since you have a map of the byte ranges. I'm curious on why this should be a Zarr responsibility. |
As opposed to what? For context there is a rationale here: #947 (comment) |
Probably as opposed to a lower level, e.g., https://developmentseed.org/obstore/latest/api/get/#obstore.get_ranges has a coalesce argument or a wrapper store for coalescing could be added to obspec_utils - https://obspec-utils.readthedocs.io/en/latest/. It does seem this is a more general problem than ManifestStore, which mostly exists to handle indirection. |
|
See #947 (comment) for why I don't think that can work - I want to coalesce requests over multiple independent keys, which means I need to be able to request multiple zarr keys at once.
|
Implements the chunk-coalescing idea from #947: teach
ManifestStoreto satisfy a batch of chunk requests with fewer, larger reads by deriving an "effective shard index" from the manifests at read time.ManifestStore.get_many(overriding the new cross-keyStore.get_many) resolves the requested chunk keys to(source file, byte range), groups requests by source file, coalesces each group into runs, and serves each run with one ranged read that is sliced back into per-chunk buffers. No file-format assumptions, no spec changes. Keys that aren't plain manifest-backed chunks (metadata, inlined, missing) fall back to per-keyget.Coalescing uses the object_store / async-tiff two-knob model: references merge into one read when their gap is
<= coalesce_max_gap_bytesand the merged read stays<= coalesce_max_bytes.Why
coalesce_max_gap_bytesdefaults to 0Benchmarking a 2D map-tile query (5×5 chunks of
air_temperature) against a remote Met Office file on Azure, versus the status quo of onegetper chunk:A 2D spatial box is contiguous along one axis but strided along the other (row-major chunk layout), so a large gap bridges the row stride and pulls in the chunks between rows — reading ~3× the needed bytes and running slower than no coalescing. Merging only adjacent references is a pure win: zero over-read, ~2.8× faster (collapsing 25 requests into 5). Hence the default gap of 0;
coalesce_max_bytes(8 MiB) just bounds the size of any single read. A non-zero gap is available for callers who know their access is contiguous.Depends on the
Store.get_manyAPI and its use inBatchedCodecPipelinein zarr-python (zarr-developers/zarr-python#4112, zarr-developers/zarr-python#4113); until those are released this is a dormant override, tested in isolation.xref #947
Draft — feedback welcome on the approach and the config surface.