Remote cache design

This note describes the implemented cache model for remote native Blosc2 arrays, B2Z containers, HDF5 files, and Zarr stores. For usage, see Working with Remote Data, Working with Remote Arrays, and Working with Remote Tables. Cache layouts and metadata fields described here are private implementation details, not portable format contracts.

Shared model

Opening a remote object discovers enough metadata to describe it. Payload reads are deferred until indexing or computation needs them, except where bounded prefetch also retrieves payload. Three kinds of cached information must be distinguished:

Layer

Purpose

Current implementation

Discovery metadata

Reconstruct readers, locate data, and navigate containers

Array carriers and store manifests; format-specific headers, indexes, or metadata objects

Compressed payload

Reuse fetched or converted data

Native Blosc2 chunks/blocks, or chunks converted from HDF5/Zarr

Complete source object

Reuse original bytes across dataset scopes

HDF5 files and B2Z archives up to 8 MiB

A warm metadata cache does not imply a warm payload cache. Reopening may avoid discovery requests but still fetch data for a preview. HDF5 and B2Z complete-source caches bridge that gap for small files; the other routes do not provide a general persistent copy of the remote source.

Identity and ownership

Managed cache paths distinguish source URLs and non-reversible storage_options fingerprints. Dataset-scoped entries also distinguish the selected leaf or subtree. Source stamps and recorded geometry determine whether existing payload can be reused; their inputs differ by format. A stamp is not a universal content checksum or an automatic remote freshness check.

A standalone RemoteArray owns its cache carrier. A RemoteStore discovery owner shares transport, metadata, traffic, and a cache coordinator across its leaves and table columns. Store disk caches retain a manifest and payload under an active generation. Ordinary store disk caches have exclusive ownership; the separate sparse shared-cache path uses operation-scoped locking. Dependent handles keep the owner alive until its last user closes.

Policies, accounting, and persistence

MEMORY retains payload in RAM; DISK retains it across sessions; NONE does not retain payload between operations. NONE does not mean that readers discard all discovery state or temporary buffers. Array disk caching supports an explicit cache_path or a managed cache_dir; stores use cache_dir.

max_cache_bytes bounds retained compressed payload, with LRU enforcement after operations. Store-owned readers share the owner’s allowance. The default bound is 256 MiB; DISK also accepts None for unbounded retention. This is not a limit on decompressed results, temporary conversion buffers, discovery metadata, or total filesystem usage. The HDF5 source-copy exclusion is described below.

Traffic measures transport activity at the reader’s instrumentation points, not local cache reads. Discovery and payload reads share an owner’s counter. It is not a packet-level HTTP trace: backend metadata probes, retries, and batched operations need not map one-to-one to its request count.

Portable reference exports are distinct from disposable runtime caches. They record source locators and optionally warm payload, not a promise that all source data is local. Immutable references do not grow an on-disk cache; mutable references use a writable runtime cache without modifying the original archive.

Freshness and refresh

Remote reads assume immutable sources by default. For standalone .b2nd and Caterva2 sources, assume_immutable=False enables identity checking and cache invalidation. The B2Z, HDF5, and Zarr adapters do not support that mutable-source mode; publishing new data at a new URL remains the simplest safe contract.

Explicit table/store refresh prepares new discovery before replacing the active generation and invalidating derived caches. Failure during preparation leaves the previous generation usable. Existing child handles of a refreshed store become stale and must be reacquired. Other independently opened scopes are not automatically refreshed; HDF5/B2Z shared-source version checks add the specific reopen behavior described below. Immutable reference snapshots cannot refresh.

Native Blosc2 arrays and B2Z containers

Standalone .b2nd

FsspecNDSource uses the native frame reader in proxy_source.py. Opening reads the frame header; chunk offsets and payload ranges are obtained as needed. Native compressed chunks and, where supported, individual blocks can be fetched without decoding an entire array or translating it to another storage format. The cache retains that compressed payload, not a copy of the source file.

Source identity uses transport information, including HTTP validators when available. Reopening a disk carrier does not guarantee zero bootstrap traffic for this route. The immutable-source policy avoids repeated identity checks before each data operation; it is not the HDF5 whole-file persistence mechanism.

.b2z archives

B2ZArchive discovers ZIP members through bounded byte ranges. Lazy NDArray reads address external ZIP_STORED members and translate native frame offsets to archive offsets. Table/store discovery shares the archive reader and metadata across leaves, rather than opening each member as an unrelated remote file.

Persisted archive metadata records object identity and captured discovery ranges. Leaf carriers can also store a b2z-frame bootstrap seed, allowing a reader to be reconstructed without fetching its header again. A populated table/store cache trusts the saved archive identity on reopen; older caches may need an identity lookup when upgrading their metadata.

Cold discovery eagerly downloads archives up to 8 MiB and retains the bytes for all members. HTTP discovery first requests the ZIP tail to learn the object size; if the tail contains the whole archive it is reused, otherwise a second request fetches the complete small archive. Larger archives keep the range-read path. With an explicit DISK cache directory, the shared source copy survives reopening. All archive read paths, including parallel table reads, can use these bytes.

Bounded member prefetch can also contain a complete small member. Its payload is transferred to the normal chunk cache, while the persistent leaf bootstrap keeps the metadata it needs. This remains useful for large archives without a source copy. Replacing an archive at the same URL requires refresh or cache replacement.

Zarr stores

ZarrNDSource delegates Zarr v2/v3 metadata and codec handling to Zarr. A counting store wrapper retains metadata objects such as .zarray, .zgroup, .zattrs, .zmetadata, and zarr.json, including remembered missing metadata keys. Standalone carriers can restore this metadata; store discovery shares and persists it in its manifest. Resolving a selected array directly avoids an unnecessary parent listing, but unseen hierarchy paths can still require reads.

On a payload miss, Zarr reads and decodes the required source chunk data. The adapter converts the resulting values into a Blosc2-compressed logical chunk, which enters the ordinary payload cache. Repeated reads can therefore bypass both remote transfer and Zarr decoding. The persisted payload is not a mirror of the original Zarr objects. Sharded layouts and codec details remain Zarr’s responsibility, so a logical chunk read need not equal one whole-object GET.

The source stamp includes location, geometry, dtype, conversion layout, and the storage-options fingerprint. It does not detect changed values under unchanged metadata. Source immutability and explicit refresh/cache replacement are thus essential. There is no whole-store download or shared raw-source cache, and peak memory can exceed the retained-payload budget during decode/conversion.

HDF5 files

The native index and source cache are Blosc2 mechanisms, not HDF5 conventions or fsspec cache formats.

Discovery and reads

Remote discovery uses h5py to record dataset metadata and allocated chunk byte ranges. Selecting a dataset avoids traversing unrelated siblings; PyTables index groups are discovered lazily. Opening a hierarchy discovers its nodes, but allocated-chunk maps are deferred until a leaf is read.

A standalone RemoteArray stores its native index in the carrier’s schunk.vlmeta["hdf5-index"]. A RemoteStore shares discovery metadata across its leaves and persists it in a manifest. Supported filter pipelines decode fsspec range reads directly; other pipelines use a retained h5py reader.

For objects up to 8 MiB, discovery fetches the complete file once: one bounded transfer avoids many latency-bound metadata reads. Retaining these bytes lets later reads and PyTables index conversion reuse the same download.

Shared complete-source cache (HDF5 and B2Z)

The complete source bytes, discovery metadata, and converted Blosc2 chunks have different lifetimes. Source bytes are shared across dataset scopes; metadata and converted payload remain in their existing array/store caches.

With an explicit cache_dir and CachePolicy.DISK, source bytes persist under cache_dir/hdf5-sources/ or cache_dir/b2z-sources/. Both readers reuse the integrity, publication, and locking helpers in remote_source_cache.py. The source identity reuses fsspec_cache_path(): normalized base URL plus the non-reversible storage_options fingerprint, without the dataset scope. Different datasets in one file share a single copy; different cache directories, URLs, or credential fingerprints remain isolated.

Each source has a .hdf5-source or .b2z-source file, a JSON marker, and a publication lock. The marker records its schema version, byte size, SHA-256, and version token. These names and fields are private implementation details.

Source copies do not count against max_cache_bytes. The 8 MiB ceiling is per file; aggregate source-cache disk usage is unbounded. They survive individual dataset-generation cleanup. Close active cache users before removing the corresponding hdf5-sources/ or b2z-sources/ directory to clear source copies, or the whole cache directory to reset all caches. There is no automatic source eviction or freshness check.

MEMORY/NONE policies can retain prefetched source bytes for the current session, but do not persist them. cache_path-only opens and portable references without an explicit shared root do not gain a persistent source copy. Saved metadata alone does not force a whole-archive download on portable B2Z reference reopen. Zarr does not use this source cache.

Publication and reuse

Normal carrier/manifest initialization precedes source publication. Existing atomic-write helpers publish the blob and then its marker. A short source-level file lock serializes version checks and publication, not downloads. No store/array lock is acquired while holding the source lock. A stale opener must retry rather than overwrite a newer refreshed source.

Readers validate marker schema, file size, bounded reads, and SHA-256. Missing, corrupt, or interrupted file/marker pairs are cache misses, so ordinary remote reads remain available. Local source reads do not contribute to Traffic. Valid bytes can also supply discovery for a new dataset scope without a remote size lookup or download.

Version compatibility and refresh

Generated HDF5 indexes and B2Z discovery metadata record source_sha256 when complete bytes are available. Small B2Z member stamps derive from that content identity rather than transport-dependent header fields. An index incompatible with the shared source must be rebuilt, and its derived payload invalidated. Legacy small-source disk metadata without a digest is rebound by rebuilding discovery and invalidating derived payload once.

Table/store refresh bypasses the retained bytes and prepares fresh discovery. A preparation failure leaves the old live generation usable. Successful refresh publishes replacement metadata and source bytes; other scopes check compatibility on their next open. Existing handles may retain their old immutable snapshot.

If the refreshed file exceeds 8 MiB, publication replaces the old marker with a new version token and null checksum, then removes the old blob. Discovery metadata record this token as source_cache_version, so sibling scopes invalidate old metadata and payload even though there is no replacement cached blob.

Explicit hdf5_index= never causes a complete download just to populate this cache. It may reuse verified cached bytes; a recorded digest mismatch is an error requiring a regenerated sidecar, not silent replacement of the supplied index. Legacy explicit indexes without a digest retain the immutable-URL trust contract: matching sizes alone cannot prove version compatibility.

Boundaries and future work

The common model does not imply identical bootstrap costs or invalidation mechanisms across formats. The 8 MiB complete-source threshold, checksum marker, and cross-scope source-version reconciliation apply to HDF5 and B2Z, not to standalone .b2nd files or Zarr stores.

Zarr would require an object-level policy rather than this single-file design. Automatic freshness checks for containers, aggregate source-cache eviction, and a global disk-space budget are not implemented by this design.