Remote arrays, hierarchical store discovery, and unified caching - #708
Merged
Conversation
wait_until() polled a fixed frame count, which is the same race the module docstring warns about when the runner is loaded: 100 pilot.pause() calls can elapse before a page settles on an oversubscribed CI box, failing test_b2view_app_cache_dir_reused with 'data table never finished loading'. Poll for up to 15 seconds instead, and route the per-module _wait_table helpers through it (they were 50-200 frames of the same shape).
faulthandler wrote to the worker's stderr, which xdist does not forward, so the last crash gave only the nodeid. Append to deadman-stacks.log at the repo root (gitignored) and cat it in an always() step; drop the timeout to 300 s, still 40x the slowest test.
A remote browser is closed on a thread in on_unmount, so reopening the same cache_dir right after run_test() races the disk-cache lock the closer still holds -- test_b2view_app_cache_dir_reused failed its second pass with 'data table never finished loading' whenever the closer lost the race. Keep the thread on the app and join it before reopening.
The per-test watchdog cannot see a stall between tests, in collection or in interpreter exit. Between tests it now re-arms the remaining session budget (PYTEST_DEADMAN_SESSION_SECONDS, CI: 1500) instead of cancelling, so any process that outlives the suite is killed with its stacks, and the always() step prints them.
kerchunk builds a zarr hierarchy through zarr's sync API, which submits each call to a private event loop and waits with async.timeout unset -- i.e. forever. The CI deadman caught a wedged wait there twice, on macOS and Windows, inside scan_hdf5_refs (test_remote_store h5 hierarchies). Give the bridge 120 s, and if it fires, drop the loop reference the failed attempt may have wedged so the one retry gets a fresh one.
When the zarr sync bridge times out mid-translation, some of the Buffer objects kerchunk's MemoryStore holds survive in the returned references and the manifest fails to serialize with "can not serialize 'Buffer' object" (seen on the Windows runner right after the new timeout fired). A reference stores bytes anyway, so convert them on the way out.
kerchunk's per-node isolation catches every Exception, so the bridge's TimeoutError was recorded as an unsupported leaf and the next require_* call wedged all over again -- the translation never reached the retry. Re-raise it: scan_hdf5_refs then resets the loop and translates again, and a second timeout fails the call instead of stalling the worker.
Contributor
There was a problem hiding this comment.
🟡 Changes recommended
Unresolved critical and moderate findings remain in b2view lifecycle/concurrency, cache identity/resource coordination, and B2ND-to-Zarr conversion.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
Adds lazy remote array/store access, shared caching, format adapters, b2view integration, and B2ND-to-Zarr conversion.
Changes:
- Adds RemoteArray/RemoteStore support for B2Z, Zarr, HDF5, fsspec, and Caterva2.
- Adds memory/disk caching, snapshots, metadata handling, and storage options.
- Adds CLI/TUI tooling, documentation, examples, benchmarks, and tests.
File summaries
| File | Reviewed change |
|---|---|
tests/test_zarr_source.py |
Zarr adapter coverage |
tests/test_vlmeta.py |
Metadata behavior |
tests/test_python_blosc.py |
Core regression coverage |
tests/test_objectarray.py |
Object-array compatibility |
tests/test_locking.py |
Locking stress coverage |
tests/test_fsspec_s3.py |
fsspec and S3 access |
tests/test_ctable_cframe.py |
CTable frame coverage |
tests/test_b2view_model.py |
Remote browser model |
tests/test_b2objects.py |
Caterva2 object coverage |
tests/test_b2nd_to_zarr.py |
Conversion coverage |
tests/test_attrs.py |
Attribute interface |
tests/ndarray/test_proxy.py |
Proxy array behavior |
tests/ndarray/test_indexing.py |
Array indexing |
tests/ndarray/test_c2array_writes.py |
C2Array writes |
tests/ndarray/test_c2array_blocks.py |
C2Array blocks |
tests/ndarray/test_c2array_async.py |
Async C2Array behavior |
tests/ctable/test_utf8.py |
UTF-8 columns |
tests/ctable/test_object_spec.py |
Object column specifications |
tests/ctable/test_dictionary_column.py |
Dictionary columns |
tests/ctable/test_ctable_indexing.py |
CTable indexing |
tests/conftest.py |
Test fixtures |
tests/b2view/tui_wait.py |
TUI test helper |
tests/b2view/tree_store_gen.py |
Tree-store test generation |
tests/b2view/test_sort.py |
Browser sorting |
tests/b2view/test_render.py |
Browser rendering |
tests/b2view/test_group.py |
Browser grouping |
tests/b2view/test_cli.py |
b2view CLI coverage |
tests/b2view/test_basics.py |
b2view basics |
src/blosc2/zarr_source.py |
Zarr remote adapter |
src/blosc2/tree_store.py |
Tree-store persistence |
src/blosc2/remote_store_cache.py |
Persistent cache generations |
src/blosc2/ref.py |
Reference serialization |
src/blosc2/proxy_source.py |
Remote range-source infrastructure |
src/blosc2/objectarray.py |
Object-array implementation |
src/blosc2/ndarray.py |
NDArray behavior |
src/blosc2/msgpack_utils.py |
MessagePack utilities |
src/blosc2/list_array.py |
List-array behavior |
src/blosc2/lazyexpr.py |
Lazy expressions |
src/blosc2/dict_store.py |
Dictionary store |
src/blosc2/ctable.py |
CTable implementation |
src/blosc2/core.py |
Core URL and storage handling |
src/blosc2/cli/__init__.py |
CLI registration |
src/blosc2/c2array.py |
C2Array implementation |
src/blosc2/batch_array.py |
Batch-array behavior |
src/blosc2/b2z_source.py |
B2Z range adapter |
src/blosc2/b2view/render.py |
b2view rendering |
src/blosc2/b2view/model.py |
Remote browser model |
src/blosc2/b2view/cli.py |
b2view CLI |
src/blosc2/b2objects.py |
Caterva2 objects |
src/blosc2/__init__.py |
Public API exports |
RELEASE_NOTES.md |
Release documentation |
pyproject.toml |
Packaging and dependencies |
plans/remote-proxy-v3.md |
Remote proxy plan |
plans/remote-proxy-v11.md |
Remote proxy plan |
examples/vlmeta.py |
Metadata example |
examples/remote/store-browse.py |
Remote store browsing |
examples/remote/s3-access.py |
Remote S3 access |
examples/remote/rw-fsspec.py |
fsspec read/write example |
examples/remote/proxy-carray.py |
Proxy CArray example |
examples/remote/fsspec-cat2-access.py |
Caterva2 fsspec access |
examples/remote/concurrent-fsspec.py |
Concurrent fsspec access |
examples/remote/c2array-traffic.py |
C2Array traffic example |
examples/remote/c2array-get-slice.py |
C2Array slicing |
examples/remote/c2array_expr.py |
C2Array expressions |
doc/tutorials/06.remote_proxy.ipynb |
Remote proxy tutorial |
doc/reference/zarrndsource.rst |
Zarr source reference |
doc/reference/tree_store.rst |
Tree-store reference |
doc/reference/schunk.rst |
Schunk reference |
doc/reference/remotestore.rst |
RemoteStore reference |
doc/reference/remotearray.rst |
RemoteArray reference |
doc/reference/ref.rst |
Reference API |
doc/reference/ndarray.rst |
NDArray reference |
doc/reference/msgpack_serialization.rst |
Serialization reference |
doc/reference/lazyarray.rst |
Lazy-array reference |
doc/reference/hdf5ndsource.rst |
HDF5 source reference |
doc/reference/fsspecndsource.rst |
fsspec source reference |
doc/reference/classes.rst |
Class reference |
doc/reference/c2array.rst |
C2Array reference |
doc/reference/b2zndsource.rst |
B2Z source reference |
doc/guides/b2view.rst |
b2view guide |
doc/getting_started/installation.rst |
Installation documentation |
CMakeLists.txt |
Build configuration |
bench/remote_array_traffic.py |
Remote traffic benchmark |
bench/remote_array_traffic.md |
Benchmark documentation |
.github/workflows/build.yml |
CI workflow |
Review details
Suppressed comments (9)
doc/reference/remotearray.rst:110
- The documentation says authorized B2Z sparse attachment is unsupported, but
RemoteArray.with_sparse_cacheis implemented for this source and the B2Z tests exercise it successfully. This prevents users from discovering the supported persistent sparse-cache path; update the capability statement to describe the eligible external NDArray leaves and retain only the Caterva2 limitation.
src/blosc2/b2view/app.py:2257 - The remote browser is closed only in a daemon thread during unmount.
RemoteStore.close()publishes the cache manifest, soapp.run()can return and terminate the process before this thread finishes; fetched payloads may then be left without an active manifest and be missed on the next run. Make shutdown wait for the closer (or otherwise guarantee the close/publish completes before exit).
src/blosc2/b2view/cli.py:62 - The PR description promises a configurable cache budget for b2view, but this adds only
--cache-dir; there is no--max-cache-bytesargument or value forwarded toStoreBrowser, whose remote store still hardcodes 64 MiB. Users therefore cannot configure the advertised shared-cache limit. Add the option and thread it through the CLI, app, and browser/store construction.
src/blosc2/b2view/model.py:305 - The PR description advertises a configurable b2view cache budget via
--max-cache-bytes, but this path hard-codes every remote store to64 << 20andb2view/cli.pyexposes no corresponding option. Users therefore cannot configure the advertised limit; add the CLI/app plumbing and pass the selected value here, or remove the claim.
src/blosc2/cli/b2nd_to_zarr.py:397 - Valid Blosc2 empty arrays can have a zero-valued chunk extent (for example, shape
(0, 100)with chunks(0, 9)). The resulting zero copy step makesrange(0, s, step)raise, and passing the inherited zero chunk extent to Zarr is invalid, so this converter cannot handle an otherwise valid empty NDArray. Normalize output chunk extents to positive Zarr values and special-case empty batch generation.
src/blosc2/cli/b2nd_to_zarr.py:302 - This slice verification also uses
np.array_equalwith its defaultequal_nan=False, so a matching slice containing NaNs is reported as corrupt. Passequal_nan=Truehere, consistently with the sample and full-array checks.
src/blosc2/remote_array.py:394 - When a Zarr dataset is opened from a URL containing a query or fragment (for example a signed HTTP URL), this appends the dataset after the query string, producing
...?token=.../datasetinstead of adding it to the URL path. The resulting object cannot be opened and signed URLs/fsspec options are lost; append the dataset to the parsed path and rebuild the URL while preserving its query and fragment, with a regression test for such a URL.
src/blosc2/remote_array.py:220 - The validation accepts
B2ZNDSourcebelow, but this error message omits it and tells callers that only Fsspec, Zarr, or HDF5 sources are valid. Include the B2Z source in the message so an invalid authorized-source error accurately describes the supported API.
src/blosc2/remote_store.py:1112 - Each refresh publishes a new generation into the disk cache, but unlike initial construction it never calls
discard_old_generations. Repeated refreshes therefore retain every prior.b2dgeneration and its leaf payloads indefinitely, allowing the configured cache directory to grow without bound. Discard old generations after publishing the replacement manifest.
- Files reviewed: 100/112 changed files
- Comments generated: 9
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
- Ensure b2view shutdown waits for the remote browser closes before exit
- Add --max-cache-bytes CLI option to b2view and thread through store opening
- Handle empty arrays with zero chunk extents and NaNs in b2nd-to-zarr
- Preserve query and fragment parameters when resolving remote Zarr dataset URLs
- Discard obsolete disk generations upon RemoteStore.refresh()
- Include B2ZNDSource in authorized source validation error message
- Update B2Z sparse cache capability statement in RemoteArray docs
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.
This branch introduces comprehensive remote data access capabilities to Python-Blosc2. Users can now open, navigate, and slice remote arrays and multi-dataset containers (.b2nd, .b2z, Zarr, HDF5, and Caterva2) without pre-downloading entire datasets. Data chunks are fetched lazily on demand and managed through configurable, shared cache budgets.
Key Features
1. High-Level Remote APIs
• RemoteArray: Direct, slice-on-demand access to standalone remote files or individual container datasets.
• RemoteStore: Tree-like container discovery and navigation (store.keys(), store["group/dataset"]) across B2Z, Zarr, and HDF5 archives, sharing a single cache budget across all leaves.
• blosc2.open(..., lazy=True): Unified entry point for remote URLs (https://, s3://, gcs://, abfs://, etc.) with storage_options.
2. Remote Format Adapters (proxy_source.py)
• b2z_source.py: Native Blosc2 chunk/block range reads from external ZIP_STORED NDArray members in .b2z archives without unzipping.
• zarr_source.py: Zero-copy/converted reads from remote Zarr v2 and v3 arrays (including scalar, empty, and fixed dtypes).
• hdf5_source.py: Remote HDF5 slicing via kerchunk byte-range pre-indexing, with reference map reuse across container leaves and sessions (blosc2.available_datasets).
• proxy_source.py: Standard byte-range fetches over arbitrary fsspec protocols.
3. Unified Caching & Portable Snapshots
• init.py: Support for NONE, MEMORY, and persistent DISK caching with LRU eviction and memory bounds.
• TreeStore-backed disk layout: Generations are persisted as standard .b2d structures.
• Portable reference exports: RemoteStore.save("snapshot.b2z") serializes remote manifests with optional retained caches.
• In-place snapshot reads: Opening exported .b2z reference snapshots via blosc2.open() runs directly in-place with zero disk overhead for immutable caches, falling back to transient memory reads on misses.
• Caterva2 integration: Sparse-cache backend support via with_sparse_cache().
4. User Metadata & CLI / TUI Tools
• Standardized .attrs interface: Exposes .attrs as the recommended user metadata interface across arrays, containers, and proxy sources (aliasing .vlmeta while preserving Caterva2 server attributes).
• b2view TUI enhancement: Terminal browsing of remote containers and hierarchies with persistent disk caching (--cache-dir) and configurable cache budgets (--max-cache-bytes).
• b2nd-to-zarr CLI: New command-line tool in b2nd_to_zarr.py for converting .b2nd files to Zarr format.
5. Maintenance & Upgrades
• Bundled C-Blosc2 updated to 3.3.4.
• Comprehensive remote benchmarks (bench/remote_array_traffic.py), guide documentation (doc/guides/remote_arrays.md), and reference tests.
Quick Example