Skip to content

fix: preserve reference layouts in GET_Dataset response - #468

Merged
mattjala merged 1 commit into
HDFGroup:masterfrom
joaopaulosr95:fix/preserve-reference-layout-on-dataset-get
Sep 23, 2026
Merged

mattjala merged 1 commit into
HDFGroup:masterfrom
joaopaulosr95:fix/preserve-reference-layout-on-dataset-get

Conversation

@joaopaulosr95

Copy link
Copy Markdown
Contributor

Summary

Since b6016e0 ("use hdf5-json util classes"), GET_Dataset in hsds/dset_dn.py
overwrites the dataset's creationProperties.layout with the top-level layout key:

if "creationProperties" in dset_json:
    cpl = dset_json["creationProperties"]
else:
    cpl = {}
if "layout" in dset_json:
    cpl["layout"] = dset_json["layout"]
resp_json["creationProperties"] = cpl

Impact

Any dataset whose stored json has both keys — i.e. anything written by an older HSDS or by hsload --link — silently returns zeros/empty strings instead of its data. Metadata, dtype, shape and attributes are all still correct, so it does not look like a failure.

This covers NREL's public HSDS data on s3://nrel-pds-hsds. Reading NSRDB through 1.0.x returns an all-zero array with no error of any kind; the same read on 0.9.4 returns the real series.

Why CI didn't catch it

POST_Dataset in 1.0.x never stores a top-level layout key (it logs "unexpected key for POST Dataset: 'layout'"). So for datasets created by this version "layout" in dset_json is False, the overwrite never happens, and tests/integ/dataset_test.py::testChunkedRefIndirectDataset — which does assert that class, file_uri and chunk_table survive a GET — passes.

The bug only reaches datasets in the older on-disk shape, which the test suite never constructs.

Reproducer

Against any 1.0.x server, using the public NREL bucket (no credentials needed):

import h5pyd
f = h5pyd.File("/nrel/nsrdb/GOES/tmy/v4.0.0/nsrdb_tmy-2025.h5", "r",
               endpoint=ENDPOINT, bucket="nrel-pds-hsds")
print(f["ghi"].id.dcpl_json["layout"]["class"])
print(f["air_temperature"][:, 1092614].min(), f["air_temperature"][:, 1092614].max())
0.9.4 1.0.0 / 1.0.1
layout class H5D_CHUNKED_REF_INDIRECT (+ file_uri, chunk_table) H5D_CHUNKED
air_temperature min/max -84 / 377 0 / 0
time_index[:3] b'2025-01-01 00:30:00', … b'', b'', b''

The stored object confirms which one is right — s3://nrel-pds-hsds/db/02161936-77b022e7/d/bb36-de0d1a-f7d448/.dataset.json has creationProperties.layout.class == "H5D_CHUNKED_REF_INDIRECT" with file_uri and chunk_table, and a separate top-level layout of {"class": "H5D_CHUNKED", "dims": [2000, 500]}. 1.0.x reports the latter, contradicting its own stored metadata.

The change

GET_Dataset now delegates to a new getDatasetCreationProps() in hsds/util/dsetUtil.py, which keeps 1.0.x's behaviour of reporting the resolved chunk shape except when the creationProperties layout is one of CHUNK_REF_LAYOUTS, where it is preserved intact.

Two smaller things fixed along the way:

  • The old code mutated dset_json["creationProperties"] in place. Since dset_json is the meta_cache entry returned by get_metadata_obj, that corrupted the cached metadata for the lifetime of the process, not just the response. The helper returns a copy.
  • CHUNK_REF_LAYOUTS is currently duplicated in dset_lib.py and chunk_crawl.py. I put the shared copy in util/dsetUtil.py (a leaf module the DN can import — dset_lib pulls in servicenode_lib, so it isn't importable from dset_dn). Happy to follow up with a patch pointing the other two at it if you'd like that consolidated.

Tests

tests/unit/dset_util_test.py::testGetDatasetCreationProps covers:

  • layout in creationProperties only → reported as-is
  • both keys, plain chunked → top-level (resolved) shape wins, as in 1.0.x
  • both keys, H5D_CHUNKED_REF_INDIRECT → reference preserved (this is the regression)
  • same for H5D_CHUNKED_REF and H5D_CONTIGUOUS_REF
  • the input dict is not mutated
  • no creationProperties, and neither key, both handled

It fails on master and passes with the change. Full unit suite: 87 passed, 1 skipped.
flake8 clean.

Verification

Built a 1.0.1 image with the patch and read NSRDB through it against the real public bucket. The layout is reported correctly again, and a TMY CSV extracted through the patched 1.0.1 is byte-identical (sha256 7486b799…) to the same extraction through 0.9.4.

@joaopaulosr95
joaopaulosr95 force-pushed the fix/preserve-reference-layout-on-dataset-get branch from fa7bea6 to 0131551 Compare September 18, 2026 15:50
@joaopaulosr95
joaopaulosr95 marked this pull request as ready for review September 18, 2026 16:01
@joaopaulosr95

Copy link
Copy Markdown
Contributor Author

@mattjala this fixes a critical regression. We're currently running a custom build of this in production.

@mattjala mattjala moved this from To be triaged to In progress in HSDS - TRIAGE & TRACK Sep 18, 2026
@mattjala mattjala self-assigned this Sep 18, 2026
mattjala added a commit to mattjala/hsds that referenced this pull request Sep 22, 2026
Fork of HDFGroup#468 which adds a test, and fixes the unit test registration.

testall.py registers dset_dn_test in unit_tests, which otherwise would
never run it, and reflows that tuple to stay under the 99 character
flake8 limit.
@mattjala
mattjala merged commit 0131551 into HDFGroup:master Sep 23, 2026
27 checks passed
@github-project-automation github-project-automation Bot moved this from In progress to Done in HSDS - TRIAGE & TRACK Sep 23, 2026
@joaopaulosr95
joaopaulosr95 deleted the fix/preserve-reference-layout-on-dataset-get branch September 28, 2026 22:45
d-gski added a commit to d-gski/hsds that referenced this pull request Sep 29, 2026
Since 1.0, the chunk shape comes from h5json's getChunkDims, which returns the
full dataset shape for any layout that isn't H5D_CHUNKED*. For a contiguous
reference that makes the whole dataset one chunk: reading a single element
makes the DN range-get, decode and cache the entire dataset from the linked
file.

This regressed in b6016e0 ("use hdf5-json util classes", HDFGroup#450). Through 0.9.x,
POST_Dataset gave every contiguous reference a virtual chunk shape via
getContiguousLayout, and testContiguousRefDataset required it (HDFGroup#393/HDFGroup#396 fixed
its short last chunk on an NREL `meta` dataset). b6016e0 moved layouts under
creationProperties, removed that computation, and switched to h5json's
getChunkDims. h5json's rule holds for data HSDS stores itself, since
generateLayout only picks H5D_CONTIGUOUS below chunk_min, but not for a
reference, whose size comes from the linked file. The read path still assumes
virtual chunks (per-chunk offsets in getChunkLocations, the DN's short-chunk
padding). The reference-layout bug fixed by HDFGroup#468/HDFGroup#469 hid the regression for
domains linked by 0.9.x.

Seen on NREL's NSRDB TMY domains (nrel-pds-hsds). Each `meta` dataset is an
H5D_CONTIGUOUS_REF (MSG: 2,693,287 x 134 B = 361 MB). One-row reads fetched
the whole 361 MB (6-34 s from S3). The two such datasets no longer fit a 512m
chunk cache and evicted each other. Concurrent requests during a fetch each
started another full copy.

Add getChunkDims to hsds.util.dsetUtil, used everywhere in place of h5json's.
It defers to h5json except for H5D_CONTIGUOUS_REF. There it derives virtual
chunks with getContiguousLayout from the dataset's shape, item size and the
min/max_chunk_size config. That is the same split 0.9.x made: 21042 rows for
the MSG `meta` above, as stored with the domain.

Nothing is stored per chunk; each is a range get into the file. So the shape
only has to agree between the SN (chunk index -> byte range) and the DN
(decode + cache). Deriving it from shared inputs gives that on both, so
GET_Dataset keeps reporting the reference layout without dims, as HDFGroup#468/HDFGroup#469
intend.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Development

Successfully merging this pull request may close these issues.

2 participants