A multidim VRT mosaic over netCDF, the byte table behind it, and kerchunk-Parquet Zarr.
Many archives are thousands of netCDF or HDF5 files. Two ways to read such a collection as one array, without copying it:
a multidimensional VRT (gdal mdim mosaic) stitches the files along a dimension, and GDAL reads each file through its own driver;
a byte-reference table records, for every chunk of every array, which file holds it and at what byte offset and size; any reader that knows the codec can then fetch chunks directly. kerchunk’s Parquet references are one form of this; the table below is another.
Both are small text or tabular artifacts that describe data rather than holding it.
A mosaic of three days
Three consecutive daily files of NOAA OISST v2.1, as NOAA publishes them, and a multidim VRT that gdal mdim mosaic wrote over them:
Data: NOAA 0.25-degree Daily Optimum Interpolation Sea Surface Temperature (OISST), Version 2.1; doi:10.25921/RE9P-PT57; licence: CC0-1.0; days 20250101, 20250102, 20250103. Built by scripts/fixtures/make_oisst.py; details in docs/fixtures/oisst/provenance.json.
Sea surface temperature on each day of the mosaic, each day from its own netCDF file. Grey is land and sea ice.
time arrives as plain numbers here because gdal mdim mosaic carries the values over but not their units, so xarray has nothing to decode them with. Opening one of the source files directly gives decoded times; the Virtual paths page does that.
The byte table
The same three files, described chunk by chunk. The table was read from the files with GDAL (MDArray.GetRawBlockInfo) when the fixture was built, and GDAL’s Parquet driver reads it back:
def read_table(path): ds = gdal.OpenEx(path, gdal.OF_VECTOR) # keep the dataset alive while lyr = ds.GetLayer(0) # its layer is in use defn = lyr.GetLayerDefn() cols = [defn.GetFieldDefn(i).GetName() for i inrange(defn.GetFieldCount())]return pd.DataFrame([{c: f.GetField(c) for c in cols} for f in lyr])refs = read_table(data("oisst/refs_sst.parquet"))refs
time
zlev
lat
lon
present
path
offset
size
info
0
0
0
0
0
True
nc/oisst-avhrr-v02r01.20250101.nc
58888
667949
COMPRESSION=DEFLATE; FILTER=SHUFFLE; ENDIANNES...
1
1
0
0
0
True
nc/oisst-avhrr-v02r01.20250102.nc
58888
667794
COMPRESSION=DEFLATE; FILTER=SHUFFLE; ENDIANNES...
2
2
0
0
0
True
nc/oisst-avhrr-v02r01.20250103.nc
58888
668444
COMPRESSION=DEFLATE; FILTER=SHUFFLE; ENDIANNES...
One row per chunk: the chunk’s index along each dimension, the file it lives in, its byte offset and size, and how it is encoded, as GDAL reports it. OISST stores each day’s field as a single chunk:
tables = {t["file"]: t for t in data_json("oisst/provenance.json")["products"]["byte_tables"]}chunks = tables["refs_sst.parquet"]["chunks"]dict(zip(tables["refs_sst.parquet"]["dims"], chunks))
{'time': 1, 'zlev': 1, 'lat': 720, 'lon': 1440}
Decoding a chunk by hand
With a row of the table, a chunk is a range read and whatever the info column lists: here a zlib inflate and a byte unshuffle. No netCDF library is involved:
row = refs.iloc[1]info =set(x.strip() for x in row["info"].split(";"))print(sorted(info))f = gdal.VSIFOpenL(data("oisst/"+ row["path"]), "rb")gdal.VSIFSeekL(f, int(row["offset"]), 0)raw = gdal.VSIFReadL(1, int(row["size"]), f)gdal.VSIFCloseL(f)buf = zlib.decompress(raw) if"COMPRESSION=DEFLATE"in info else rawitemsize =2# int16if"FILTER=SHUFFLE"in info: # bytes grouped by position n =len(buf) // itemsize buf = np.frombuffer(buf, np.uint8).reshape(itemsize, n).T.copy().tobytes()order ="<"if"ENDIANNESS=LITTLE"in info else">"chunk = np.frombuffer(buf, f"{order}i2").reshape(chunks)f"{len(raw):,} bytes on disk, {chunk.nbytes:,} bytes decoded, shape {chunk.shape}"
That equality is what makes a byte table a faithful replacement for the files’ own metadata: the files can stay where they are, and the table travels.
kerchunk-Parquet Zarr
GDAL’s Zarr driver reads kerchunk references stored as Parquet, so a reference store opens like any Zarr. This one is a pinned commit of the BRAN2023 ocean reanalysis temp variable: about 13 TB of netCDF on NCI’s THREDDS server, described by a few megabytes of Parquet on GitHub.
NoteLive: BRAN2023 ocean temp as kerchunk-Parquet Zarr (GitHub, pinned commit)
Reading values fetches the referenced byte ranges from THREDDS, which rate-limits parallel requests; the BRAN2023 gallery page shows that read and the settings it needs.