Repository navigation
perf(zarr-metadata): read a document in one refine walk, with lazy model keys - #407
Merged
d-v-b merged 14 commits intoOct 22, 2026
Conversation
…file system (zarr-developers#4520) The three docstrings, inherited from Zarr 2, opened with "to the local file system". The functions take a StoreLike and a storage_options argument, and work with every store the library supports. The wording was being read as a design limitation of the functions (see the discussion in zarr-developers#3976), which it is not.
…del keys
Reading a v3 array document took about 8x as long as zarr-python's own
parser, and a consolidated group with 200 arrays 200 times that. The
cost was repeated work, not the compiled type checker: each extension
point was refined to JSON inside `resolve` and the whole document refined
again to build the model's document; every field was frozen into views
and its JSON-text key computed at construction whether or not the model
was ever compared; `Scope.format` recomputed its answer on every call,
and `as_kind` and `kind_of` walked `get_origin` and the MRO on every
field.
Now a document is refined once: `read_array_v3`, `read_array_v2` and the
group reader refine each member where they read it, hand the refined
value to `resolve_refined`, and assemble the document the model holds
from what was refined, in the document's order, each consolidated child
document among them. A model's key and its claims are computed the first
time `==`, `hash` or `claims` asks, as `Keyed._model_key`; a scope's
format is computed once per scope; a kind is looked up once per way it is
named and once per definition class; `well_named` remembers each name;
`json_text` reuses one encoder; `frozen` and `_refine` tell the types
`json.loads` builds apart by type before falling back to the ABC checks;
`_put_back` walks nothing when a configuration holds no nested field;
`one_of` checks a set; and the `get_args` calls in `storage_of`, the
float fill-value rules and `ValidationProblem` are hoisted to constants.
Every problem is reported as before, with the same `loc`, `kind`,
message and `input`: a corpus of 25,780 documents (the three benchmark
documents, the test fixtures, and systematic mutations of each member)
read identically on both sides. The one change in behavior is that a
definition's `canonical` is first asked when a model is first compared,
not when it is built.
Per read, best of ten `timeit` repeats, Python 3.12, PYTHONHASHSEED=0,
documents as zarr 3.4.1 writes them:
ZarrV3ArrayMetadata.from_json 194.1 us -> 72.5 us (2.7x)
ZarrV2ArrayMetadata.from_json 75.0 us -> 18.2 us (4.1x)
ZarrV3GroupMetadata, 200 children 37.4 ms -> 14.7 ms (2.5x)
The benchmark is `tests/benchmarks/read_documents.py`, run as
`python -m tests.benchmarks.read_documents`; a smoke test reads each
case once so it keeps working.
Assisted-by: ClaudeCode:claude-fable-5-1
Assisted-by: ClaudeCode:claude-fable-5-1
--- updated-dependencies: - dependency-name: aiohttp dependency-version: 3.14.4 dependency-type: indirect ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…rs#4535) Bumps [certifi](https://github.com/certifi/python-certifi) from 2026.4.22 to 2026.10.19. - [Commits](certifi/python-certifi@2026.04.22...2026.10.19) --- updated-dependencies: - dependency-name: certifi dependency-version: 2026.10.19 dependency-type: indirect ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…r-developers#4543) * ci(docs): fix the Read the Docs sub-site builds and skip filters The zarr-indexing, zarr-metadata and zarr-http-server docs sites install their docs group with pip on Read the Docs, without a lockfile, so pymdown-extensions floated to 12.3, whose Highlight extension API the pinned mkdocstrings 1.0.6 cannot drive (Highlight.__init__() missing argument 'md'). No zarr-indexing build has succeeded since. Pin pymdown-extensions to 11.0.1, the version the repo-root uv.lock resolves to, and refresh the package lockfiles. The post_checkout skip filters compared the PR head against the tip of main with a two-dot diff, so any PR that was behind main appeared to touch every package and built every site. Compare against the merge base instead, after fetching full history into the shallow checkout. Also exclude zarr-http-server from the root site's filter, as the other two packages already were. Assisted-by: ClaudeCode:claude-fable-5.1 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * docs: add changelog entry for zarr-developers#4543 Assisted-by: ClaudeCode:claude-fable-5.1 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
…rusting Debian (zarr-developers#4547) * ci(big-endian): resolve dependencies from pyproject.toml instead of trusting Debian The s390x job installed zarr with --no-deps into an image whose numpy and numcodecs come from Debian sid, so Debian's package versions defined the test environment and zarr's own constraints were never checked. Debian currently ships numcodecs 0.17.0, which zarr excludes (GHSA-jqc8-qjfv-5m6r), and the job failed through the pickle opt-in tests instead of saying so. Install zarr with its dependencies resolved and run pip check, so an image that does not satisfy pyproject.toml fails at install time. Debian stays a source of prebuilt s390x binaries only. Build a satisfying numcodecs from source in the image, where the compile is cached as a layer, using Debian's meson, Cython and numpy so nothing else compiles under emulation. Assisted-by: ClaudeCode:claude-fable-5.1 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * docs: add changelog entry for zarr-developers#4547 Assisted-by: ClaudeCode:claude-fable-5.1 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Bumps [pillow](https://github.com/python-pillow/Pillow) from 12.3.0 to 13.0.0. - [Release notes](https://github.com/python-pillow/Pillow/releases) - [Changelog](https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst) - [Commits](python-pillow/Pillow@12.3.0...13.0.0) --- updated-dependencies: - dependency-name: pillow dependency-version: 13.0.0 dependency-type: indirect ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [uv](https://github.com/astral-sh/uv) from 0.12.21 to 0.13.4. - [Release notes](https://github.com/astral-sh/uv/releases) - [Changelog](https://github.com/astral-sh/uv/blob/main/CHANGELOG.md) - [Commits](astral-sh/uv@0.12.21...0.13.4) --- updated-dependencies: - dependency-name: uv dependency-version: 0.13.4 dependency-type: direct:development ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Davis Bennett <davis.v.bennett@gmail.com>
--- updated-dependencies: - dependency-name: aiohttp dependency-version: 3.14.4 dependency-type: indirect ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…zarr-developers#4552) Bumps the actions group with 5 updates in the / directory: | Package | From | To | | --- | --- | --- | | [astral-sh/setup-uv](https://github.com/astral-sh/setup-uv) | `10.2.0` | `10.3.0` | | [CodSpeedHQ/action](https://github.com/codspeedhq/action) | `5.2.1` | `5.5.0` | | [actions/upload-artifact](https://github.com/actions/upload-artifact) | `7.0.1` | `7.0.2` | | [actions/download-artifact](https://github.com/actions/download-artifact) | `8.0.1` | `8.0.2` | | [zizmorcore/zizmor-action](https://github.com/zizmorcore/zizmor-action) | `0.6.4` | `0.7.1` | Updates `astral-sh/setup-uv` from 10.2.0 to 10.3.0 - [Release notes](https://github.com/astral-sh/setup-uv/releases) - [Commits](astral-sh/setup-uv@c18668a...1c37ad0) Updates `CodSpeedHQ/action` from 5.2.1 to 5.5.0 - [Release notes](https://github.com/codspeedhq/action/releases) - [Changelog](https://github.com/CodSpeedHQ/action/blob/main/CHANGELOG.md) - [Commits](CodSpeedHQ/action@373d686...821a40c) Updates `actions/upload-artifact` from 7.0.1 to 7.0.2 - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](actions/upload-artifact@043fb46...cf430e0) Updates `actions/download-artifact` from 8.0.1 to 8.0.2 - [Release notes](https://github.com/actions/download-artifact/releases) - [Commits](actions/download-artifact@3e5f45b...9000827) Updates `zizmorcore/zizmor-action` from 0.6.4 to 0.7.1 - [Release notes](https://github.com/zizmorcore/zizmor-action/releases) - [Commits](zizmorcore/zizmor-action@cc914d7...85b2b9f) --- updated-dependencies: - dependency-name: astral-sh/setup-uv dependency-version: 10.3.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: actions - dependency-name: CodSpeedHQ/action dependency-version: 5.5.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: actions - dependency-name: actions/upload-artifact dependency-version: 7.0.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: actions - dependency-name: actions/download-artifact dependency-version: 8.0.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: actions - dependency-name: zizmorcore/zizmor-action dependency-version: 0.7.1 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: actions ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…s, which it lands in Assisted-by: ClaudeCode:claude-fable-5-1
…a-read-perf-52cd33 The docs pins take upstream's resolution of the pymdown-extensions breakage -- mkdocstrings 1.0.6 with pymdown-extensions pinned to 11.0.1, as the root and the sibling packages pin them -- in place of this branch's bump to mkdocstrings 1.0.7, so the stack changes no docs pin. The package lock is regenerated against that. Assisted-by: ClaudeCode:claude-fable-5-1
A v2 `order` or `dimension_separator` that only compares equal to "C" or "." without being a string was written back as null once the document was built of what the read refined; both are now judged as refined, so such a value is refused as any other outside the set is. A document writing the `UNSET` sentinel at `consolidated_metadata` was taken for one without the member and written as null; the member is read whenever the key is present. A `ZarrV3ConsolidatedMetadata` given as the member is read in its document's order, so the group writes it as the member wrote it. `_located` takes the problems a kind's `envelope_problems` yields lazily, as it did before. `one_of` is as it was, since its frozenset drifted at the edges for a 1% gain. The kind caches are bounded. The group reader copies listed models only on the problem path, and the depth rule `_refine` applies inline is stated once, in `_past_the_levels`. A test per case, and one showing that a `canonical` that raises does so when the model is first compared, with its note. Assisted-by: ClaudeCode:claude-fable-5-1
Owner
Author
|
🤖 AI text below 🤖 The agent folded this branch into |
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.
🤖 AI text below 🤖
Reading a metadata document is 2.5 to 4 times faster: a document is refined to JSON once, the model holds what was refined, and a model's key -- what
==andhashcompare -- is computed the first time the model is compared rather than when it is built. A definition'scanonicalis therefore first asked when a model is first compared. Every problem is reported as before, with the sameloc,kind, message andinput.Timings
Per read, best of ten
timeitrepeats, Python 3.12,PYTHONHASHSEED=0, Apple M5, on documents aszarr==3.4.1writes them (zarr.create_array(MemoryStore(), shape=(100, 100), chunks=(10, 10), dtype="f8")as v3 and v2, and a v3 group of 200int32arrays consolidated withzarr.consolidate_metadata), each round-tripped throughjson.dumps/json.loads:ZarrV3ArrayMetadata.from_jsonZarrV2ArrayMetadata.from_jsonZarrV3GroupMetadata.from_json, 200 consolidated arraysFor scale, zarr-python 3.4.1's own
ArrayV3Metadata.from_dictreads the v3 array document in about 23 µs on the same machine. The benchmark is committed astests/benchmarks/read_documents.py; run it from the package directory withPYTHONHASHSEED=0 uv run --group test python -m tests.benchmarks.read_documents(--profileprints a cProfile of the v3 array read). A smoke test reads each case once so the script keeps working.Where the time went, and what changed
Profiling 1000 v3 array reads on the base branch put 55% of the time in
resolvefor the five extension points, 23% in_refine(58 calls per document: each field refined insideresolve, then the whole document refined again to build the model's document), 13% in the model's_adopt(key and claims computed at construction), 6% injson.dumpsfor the eager JSON-text keys, and 5% inScope.format. The compiled type checker was about 4% and is untouched.read_array_v3,read_array_v2and the group reader refine each member where they read it, hand the refined value to the new internalresolve_refined(whatresolveis after it has refined), and assemble the document the model holds from what was refined, in the document's own order.ArrayMembersV3,ArrayMembersV2andGroupMembersV3carry thatdocument; consolidated children's documents are taken from their members, so a group of 200 arrays is no longer refined a second time as a whole.refined_objectis no longer called on the read paths.Keyed._model_keycomputes_key_of()the first time==,hash, or a holding model asks, and keeps it;claimslikewise. A model that is only read pays nothing for them. (This surfaced a latent sharing bug inZarrV2GroupMetadata.__init__, which kept the caller's nested attribute containers; it now holds the refined document's.)Context._formatis computed once per scope in__post_init__;as_kindremembers its answer per way a kind is named;kind_ofwalks the MRO once per definition class;well_namedis anlru_cacheover names;json_textreuses oneJSONEncoder; theget_args(...)calls instorage_of, the float fill-value rules andValidationProblem.__post_init__are module constants._refineandfrozentell the exact typesjson.loadsbuilds apart bytype()before falling back to the ABCisinstancechecks (subclasses, mapping proxies and other sequences take the same path as before);frozenkeeps scalar leaves without a call per leaf;_put_backwalks nothing when a configuration holds no nested field;one_ofchecks afrozensetof(type, value);withinand_locatedreturn at once for no problems.Verification
just test(2543 passed, 1 skipped),just lint,just typecheck(pyright 1.1.x on 3.11, 0 errors) andjust docs-checkall pass.tests/, and systematic mutations of each member and nested member (deleted, replaced by 90 kinds of junk, extra keys,must_understand: false, bad names, NaN, big ints, non-string keys, model entries in consolidated metadata, ...) -- produced byte-identical records of every validator's problems (loc,kind, message,input,ctx), every reading's fields, each model'sto_json/to_key_value, equality and hash round trips, claims and canonical spellings.resolve_refinedagrees withresolveon eight kinds of field, and its two error paths (a field that is not JSON; a scope of the other format); the model key is computed on first comparison (a countingcanonical); mapping proxies,UserDictanddict/listsubclasses refine asdict/listdo;as_kindrefuses an unhashable value as a non-kind;_document_ofrefuses members with no document; each benchmark case reads.Stack bookkeeping
Base is
feat/zarr-metadata-document-context-stack(upstream zarr-developers#4490, head c414f94). This PR is one commit on top of it plus the changelog fragment; rebase onto whatever zarr-developers#4490 becomes before it lands, and renumber the fragment if it is folded into an upstream PR.🤖 Generated with Claude Code