You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: design/chunk-grid.md
+26-64Lines changed: 26 additions & 64 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -31,7 +31,7 @@ Prior iterations on the chunk grid design were based on the Zarr V3 spec's defin
31
31
32
32
1.**Follow the zarr extension proposal.** The implementation should conform to the [rectilinear chunk grid spec](https://github.com/zarr-developers/zarr-extensions/pull/25), not innovate on the metadata format.
33
33
2.**Minimize changes to the public API.** Users creating regular arrays should see no difference. Rectilinear is additive.
34
-
3.**Maintain backwards compatibility.** Existing code using `RegularChunkGrid`, `.chunks`, or `isinstance` checks should continue to work (with deprecation warnings where appropriate).
34
+
3.**Maintain backwards compatibility.** Existing code using `.chunks`, `isinstance` checks, or importing `RegularChunkGrid`/`RectilinearChunkGrid` from `zarr.core.chunk_grids` should continue to work (with deprecation warnings where appropriate).
35
35
4.**Design for future iteration.** The internal architecture should allow refactoring (e.g., metadata/array separation, new dimension types) without breaking the public API.
36
36
5.**Minimize downstream changes.** xarray, VirtualiZarr, Icechunk, Cubed, etc. should need minimal updates.
37
37
6.**Minimize time to stable release.** Ship behind a feature flag, stabilize through real-world usage, promote to stable API.
Both `from_regular` and `from_rectilinear` require`array_shape`, binding the extent per dimension at construction time. This is a core design choice: a chunk grid is a concrete arrangement for a specific array, not an abstract tiling pattern.
248
+
`from_sizes` requires`array_shape`, binding the extent per dimension at construction time. This is a core design choice: a chunk grid is a concrete arrangement for a specific array, not an abstract tiling pattern.
@@ -276,7 +276,7 @@ When `extent < sum(edges)`, the dimension is always stored as `VaryingDimension`
276
276
277
277
Both names deserialize to the same `ChunkGrid` class. The serialized form does not include the array extent — that comes from `shape` in array metadata and is combined with the chunk grid when constructing a behavioral `ChunkGrid` via `ChunkGrid.from_metadata()`.
278
278
279
-
**The `ChunkGrid` does not serialize itself.** The format choice (`"regular"` vs `"rectilinear"`) belongs to `ArrayV3Metadata`. Serialization and deserialization are handled by the metadata-layer chunk grid classes (`RegularChunkGrid` and `RectilinearChunkGrid` in `metadata/v3.py`), which provide `to_dict()` and `from_dict()` methods.
279
+
**The `ChunkGrid` does not serialize itself.** The format choice (`"regular"` vs `"rectilinear"`) belongs to `ArrayV3Metadata`. Serialization and deserialization are handled by the metadata-layer chunk grid classes (`RegularChunkGridMetadata` and `RectilinearChunkGridMetadata` in `metadata/v3.py`), which provide `to_dict()` and `from_dict()` methods.
280
280
281
281
For `create_array`, the format is inferred from the `chunks` argument: a flat tuple produces `"regular"`, a nested list produces `"rectilinear"`. The `_is_rectilinear_chunks()` helper detects nested sequences like `[[10, 20], [5, 5]]`.
282
282
@@ -295,7 +295,7 @@ RLE compression is used when serializing: runs of identical sizes become `[value
295
295
# expand_rle([[10, 3], 5]) -> [10, 10, 10, 5]
296
296
```
297
297
298
-
For a single-element `chunk_shapes` tuple like `(10,)`, `RectilinearChunkGrid.to_dict()` serializes it as a bare integer `10`. Per the rectilinear spec, a bare integer is repeated until the sum >= extent, preserving the full codec buffer size for boundary chunks.
298
+
For a single-element `chunk_shapes` tuple like `(10,)`, `RectilinearChunkGridMetadata.to_dict()` serializes it as a bare integer `10`. Per the rectilinear spec, a bare integer is repeated until the sum >= extent, preserving the full codec buffer size for boundary chunks.
299
299
300
300
**Zero-extent handling:** Regular grids serialize zero-extent dimensions without issue (the format encodes only `chunk_shape`, no edges). Rectilinear grids cannot represent zero-extent dimensions because the spec requires at least one positive-integer edge length per axis.
301
301
@@ -435,17 +435,17 @@ For `VaryingDimension`, `chunk_size == data_size` when `extent == sum(edges)`. W
435
435
436
436
There is no known chunk grid outside the rectilinear family that retains the tessellation properties zarr-python assumes. A `match` on the grid name is sufficient.
437
437
438
-
### Why a single class instead of RegularChunkGrid + RectilinearChunkGrid?
438
+
### Why a single behavioral class instead of RegularChunkGrid + RectilinearChunkGrid?
439
439
440
440
[Discussed in #3534.](https://github.com/zarr-developers/zarr-python/pull/3534)@d-v-b argued that `RegularChunkGrid` is unnecessary since rectilinear is more general; @dcherian argued that downstream libraries need a fast way to detect regular grids without inspecting potentially millions of chunk edges (see [xarray#9808](https://github.com/pydata/xarray/pull/9808)).
441
441
442
442
The resolution: a single `ChunkGrid` class with an `is_regular` property (O(1), cached at construction). This gives downstream code the fast-path detection @dcherian needed without the class hierarchy complexity @d-v-b wanted to avoid. The metadata document's `name` field (`"regular"` vs `"rectilinear"`) is also available for clients who inspect JSON directly.
443
443
444
-
A `RegularChunkGrid` deprecation shim preserves `isinstance` checks for existing code — see [Backwards compatibility](#backwards-compatibility).
444
+
A backwards-compatibility shim in `chunk_grids.py` preserves the old `RegularChunkGrid` / `RectilinearChunkGrid` import paths with deprecation warnings — see [Backwards compatibility](#backwards-compatibility).
445
445
446
446
### Why is ChunkGrid a concrete class instead of a Protocol/ABC?
447
447
448
-
The old design had `ChunkGrid` as an ABC with `RegularChunkGrid` as a subclass. #3534 added `RectilinearChunkGrid` as a second subclass. This branch makes `ChunkGrid` a single concrete class instead.
448
+
The old design had `ChunkGrid` as an ABC with `RegularChunkGrid` as its only subclass. #3534 added `RectilinearChunkGrid` as a second subclass. This branch makes `ChunkGrid` a single concrete class instead, with separate metadata DTOs (`RegularChunkGridMetadata` and `RectilinearChunkGridMetadata` in `metadata/v3.py`) for serialization.
449
449
450
450
All known grids are special cases of rectilinear, so there's no need for a class hierarchy at the grid level. A `ChunkGrid` Protocol/ABC would mean every caller programs against an abstract interface and adding a grid type requires implementing ~15 methods. A single class is simpler.
451
451
@@ -464,12 +464,12 @@ The resolution:
464
464
465
465
### User control over grid serialization format
466
466
467
-
@d-v-b raised in #3534 that users need a way to say "these chunks are regular, but serialize as rectilinear" (e.g., to allow future append/extend workflows without format changes). @jhamman initially made nested-list input always produce `RectilinearChunkGrid`.
467
+
@d-v-b raised in #3534 that users need a way to say "these chunks are regular, but serialize as rectilinear" (e.g., to allow future append/extend workflows without format changes). @jhamman initially made nested-list input always produce `RectilinearChunkGridMetadata`.
468
468
469
-
The current branch resolves this via the metadata-layer chunk grid classes. When metadata is deserialized, the original name (from `{"name": "regular"}` or `{"name": "rectilinear"}`) determines which metadata class is instantiated (`RegularChunkGrid` or `RectilinearChunkGrid`), and that class handles serialization via `to_dict()`. Current inference behavior for `create_array`:
469
+
The current branch resolves this via the metadata-layer chunk grid classes. When metadata is deserialized, the original name (from `{"name": "regular"}` or `{"name": "rectilinear"}`) determines which metadata class is instantiated (`RegularChunkGridMetadata` or `RectilinearChunkGridMetadata`), and that class handles serialization via `to_dict()`. Current inference behavior for `create_array`:
-`chunks=[[10, 10], [20, 20]]` (nested lists with uniform sizes) → `from_rectilinear` collapses to `FixedDimension`, so `is_regular=True` and infers `"regular"`
472
+
-`chunks=[[10, 10], [20, 20]]` (nested lists with uniform sizes) → `from_sizes` collapses to `FixedDimension`, so `is_regular=True` and infers `"regular"`
473
473
474
474
**Open question:** Should uniform nested lists preserve `"rectilinear"` to support future append workflows without a format change? This could be addressed by checking the input form before collapsing, or by allowing users to pass `chunk_grid_name` explicitly through the `create_array` API.
475
475
@@ -493,7 +493,7 @@ An earlier design doc proposed decoupling `ChunkGrid` (behavioral) from `ArrayV3
493
493
494
494
The current implementation partially realizes this separation:
495
495
496
-
-**Metadata DTOs** (`RegularChunkGrid`, `RectilinearChunkGrid` in `metadata/v3.py`): Pure data, frozen dataclasses, no array shape. These live on `ArrayV3Metadata.chunk_grid` and represent only what goes into `zarr.json`.
496
+
-**Metadata DTOs** (`RegularChunkGridMetadata`, `RectilinearChunkGridMetadata` in `metadata/v3.py`): Pure data, frozen dataclasses, no array shape. These live on `ArrayV3Metadata.chunk_grid` and represent only what goes into `zarr.json`.
497
497
-**Behavioral `ChunkGrid`** (`chunk_grids.py`): Shape-bound, supports indexing, iteration, and chunk specs. Lives on `AsyncArray.chunk_grid`, constructed from metadata + `shape` via `ChunkGrid.from_metadata()`.
498
498
499
499
This means `ArrayV3Metadata.chunk_grid` is now a `ChunkGridMetadata` (the DTO union type), **not** the behavioral `ChunkGrid`. Code that previously accessed behavioral methods on `metadata.chunk_grid` (e.g., `all_chunk_coords()`, `__getitem__`) must now use the behavioral grid from the array layer instead.
@@ -510,32 +510,27 @@ The name controls serialization format; each metadata DTO class provides its own
510
510
511
511
### Backwards compatibility
512
512
513
-
A `RegularChunkGrid` deprecation shim preserves the three common usage patterns:
513
+
A module-level `__getattr__`shim in `chunk_grids.py`preserves the common downstream import pattern. Importing the old names emits a `DeprecationWarning` and returns the renamed metadata class:
514
514
515
515
```python
516
-
from zarr.core.chunk_grids import RegularChunkGrid #works (no ImportError)
516
+
from zarr.core.chunk_grids import RegularChunkGrid #DeprecationWarning, returns RegularChunkGridMetadata
517
517
518
-
# Construction emits DeprecationWarning, returns a real ChunkGrid
519
-
grid = RegularChunkGrid(chunk_shape=(10, 20))
520
-
521
-
# isinstance works via __instancecheck__ metaclass
522
-
isinstance(grid, RegularChunkGrid) # True for any regular ChunkGrid
518
+
grid = RegularChunkGrid(chunk_shape=(10, 20)) # works — same as RegularChunkGridMetadata(...)
519
+
isinstance(grid, RegularChunkGrid) # True — RegularChunkGrid is RegularChunkGridMetadata
523
520
```
524
521
525
-
The shim uses `chunk_shape` as extent (matching the old shape-unaware behavior). The deprecation warning directs users to `ChunkGrid.from_regular()`.
526
-
527
-
**Known limitation:** Because the shim binds `extent=chunk_shape`, `RegularChunkGrid(chunk_shape=(100,)).get_nchunks()` returns `1` (one chunk of size 100 in a dimension of extent 100). This is intentional — the old `RegularChunkGrid` was shape-unaware, and the shim preserves that by using the chunk shape as a stand-in extent. Code that relied on constructing a `RegularChunkGrid` and later querying `nchunks` without binding an array shape must migrate to `ChunkGrid.from_regular(array_shape, chunk_shape)`.
522
+
The same shim exists for `RectilinearChunkGrid` → `RectilinearChunkGridMetadata`.
|`isinstance(cg, RegularChunkGrid)`|`isinstance(cg, RegularChunkGridMetadata)` or `cg.is_regular` on the behavioral grid|
531
+
|`isinstance(cg, RectilinearChunkGrid)`|`isinstance(cg, RectilinearChunkGridMetadata)` or `not cg.is_regular`|
532
+
|`cg.chunk_shape`|`cg.chunk_shape` (unchanged on metadata objects)|
533
+
|`cg.chunk_shapes`|`cg.chunk_shapes` (unchanged on metadata objects)|
539
534
| Feature detection via class import | Version check or `hasattr(ChunkGrid, 'is_regular')`|
540
535
541
536
**[xarray#10880](https://github.com/pydata/xarray/pull/10880):** Replace `isinstance` checks with `.is_regular`. Write path simplifies with `chunks=[[...]]` API.
@@ -557,39 +552,6 @@ This implementation builds on prior work:
557
552
-**[#1483](https://github.com/zarr-developers/zarr-python/pull/1483)** — original variable chunking POC.
558
553
-**[#3736](https://github.com/zarr-developers/zarr-python/pull/3736)** — resolved by storing extent per-dimension.
559
554
560
-
### Suggested PR sequence
561
-
562
-
If the design is accepted, the POC branch can be split into 5 incremental PRs. PRs 1–2 are where the design decisions are reviewed; PRs 3–5 are mechanical consequences.
0 commit comments