Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
792657c
Move into ZarrCore.jl
asinghvi17 Aug 17, 2026
9808610
Fix S3Ext
asinghvi17 Aug 17, 2026
eee1ad4
Decrease amount of CI
asinghvi17 Aug 17, 2026
aaf5a24
Remove HTTP 2 compat due to CI
mkitti Aug 17, 2026
0af8387
Restrict HTTP.jl to v1, add CHANGELOG
mkitti Aug 17, 2026
61d8904
Merge remote-tracking branch 'origin/zarrcore' into zarrcore
asinghvi17 Aug 17, 2026
241c2e9
Update changelog
asinghvi17 Aug 17, 2026
14f721e
Update claude md
asinghvi17 Aug 17, 2026
3da09d2
Fix docs
asinghvi17 Aug 17, 2026
ba87f30
Fix zarrcore project
asinghvi17 Aug 17, 2026
9b157ac
fixup and make things public in zarrcore
asinghvi17 Aug 17, 2026
eab9563
rename things to ZarrCore in tests
asinghvi17 Aug 17, 2026
997ed62
ai wants to drop numpy compat, not sure why but ok
asinghvi17 Aug 17, 2026
ef3416c
restore numpy compat
asinghvi17 Aug 17, 2026
04f5320
fix underlying problem
asinghvi17 Aug 17, 2026
a9e467c
Merge remote-tracking branch 'origin/main' into zarrcore
asinghvi17 Aug 17, 2026
9e157d3
Fix CI and docs for the ZarrCore split
asinghvi17 Aug 17, 2026
558aefb
CI: dev ZarrCore explicitly so LTS (Julia 1.10) can resolve it
asinghvi17 Aug 17, 2026
3077f5b
CI: fix Windows shell handling in the fixture step
asinghvi17 Aug 17, 2026
052a96e
Fix public API vanishing from `Zarr` on Julia 1.10
asinghvi17 Aug 17, 2026
932baa5
CI: drop the test manifest so Pkg.test can sandbox on LTS
asinghvi17 Aug 17, 2026
e2d0c60
implement irregular chunk extension
meggart Aug 17, 2026
30fe3c4
Remove `@public` macro and split user and dev public names
felixcremer Aug 18, 2026
62af4bb
Fix public
mkitti Aug 18, 2026
9f4bb98
Import "public" symbols in Zarr from ZarrCore for 1.10
mkitti Aug 18, 2026
b7b86c2
Fixes towards passing tests
meggart Aug 18, 2026
dc0d698
Fix public tests and DateTime64 tests
mkitti Aug 18, 2026
3bfa8b3
Remove commented out tests
mkitti Aug 18, 2026
e08569e
Remove DateTime64 import from Zarr
mkitti Aug 18, 2026
417917d
Fix package name to DateTimes64
mkitti Aug 18, 2026
086f82f
Merge remote-tracking branch 'origin/main' into zarrcore
asinghvi17 Aug 18, 2026
5d00d07
Test ZarrCore.typestr instead of Zarr.typestr
felixcremer Aug 19, 2026
ac01283
Merge branch 'main' into zarrcore
felixcremer Aug 19, 2026
3f24e92
Add typrstr, fill_value_* to public_names_zarr.jl
mkitti Aug 19, 2026
4cf520a
Use public names from ZarrCore not Zarr
felixcremer Aug 19, 2026
4c4bef0
Fix path typo
felixcremer Aug 19, 2026
e69220f
Fix path typo again
felixcremer Aug 19, 2026
f142f74
Make tests almost pass
meggart Aug 20, 2026
b4a8ac3
Fix remaining unit tests
meggart Aug 21, 2026
00ad670
towards passing round trip
meggart Aug 21, 2026
ad2a847
AI-supported update of docs and Changelog
meggart Aug 21, 2026
563cda8
Merge branch 'zarrcore' into irregular_chunks
meggart Aug 21, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 35 additions & 4 deletions .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,15 @@ jobs:
- 'pre'
os:
- ubuntu-latest
- macOS-latest
- windows-latest
arch:
- 'default'
include:
- os: macOS-latest
version: '1'
arch: 'default'
- os: windows-latest
version: '1'
arch: 'default'
steps:
- uses: actions/checkout@v7
- uses: julia-actions/setup-julia@v3
Expand All @@ -31,6 +36,18 @@ jobs:
arch: ${{ matrix.arch }}
show-versioninfo: true
- uses: julia-actions/cache@v3
# Pkg only understands `[sources]`/`[workspace]` from Julia 1.11 on, so on
# LTS (1.10) the unregistered path dependency on ZarrCore is invisible and
# resolution fails with "expected package ZarrCore to be registered".
# Dev'ing it explicitly fixes that; on 1.11+ it is a no-op because
# `[sources]` already points at the same path. This only affects developing
# from a checkout -- once both packages are registered, users resolve
# ZarrCore normally on any supported Julia.
- name: Develop ZarrCore (Pkg < 1.11 ignores [sources])
# bash on every platform: PowerShell mangles the embedded double quotes
# when forwarding them to a native executable.
shell: bash
run: julia --project=. -e 'using Pkg; Pkg.develop(path="lib/ZarrCore")'
- uses: julia-actions/julia-buildpkg@v1
env:
PYTHON:
Expand All @@ -41,11 +58,25 @@ jobs:
with:
timeout_minutes: 5
max_attempts: 3
# bash on every platform, for the same quoting reason as above (and so
# `rm`-style POSIX commands added here behave consistently).
shell: bash
command: |
julia --project=test -e 'using Pkg; Pkg.develop(path=pwd()); Pkg.resolve(); Pkg.instantiate()'
julia --project=test -e 'using Pkg; Pkg.develop([PackageSpec(path=pwd()), PackageSpec(path="lib/ZarrCore")]); Pkg.resolve(); Pkg.instantiate()'
julia --project=test test/v3_julia.jl
julia --project=test test/v3_python.jl
rm test/Manifest.toml
# The step above dev's ZarrCore into the test environment so the fixture
# scripts can `using Zarr` on LTS, which makes ZarrCore a *direct* dep of
# test/Project.toml. `Pkg.test` then builds its sandbox by merging the
# active manifest (where ZarrCore is also dev'd, hence fixed) with the
# fixed deps of the test manifest, and refuses to merge a package that
# appears in both: "ERROR: can not merge projects". Dropping the test
# manifest leaves nothing to merge; the sandbox resolves ZarrCore from the
# active manifest instead. On 1.11+ the workspace keeps the only manifest
# at the repo root, so this is a no-op there.
- name: Drop the test manifest so Pkg.test can build its sandbox
shell: bash
run: rm -f test/Manifest.toml
- uses: julia-actions/julia-runtest@v1
env:
PYTHON:
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
Manifest.toml
Manifest-v1.12.toml
docs/build
*.zarr
.CondaPkg
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
# Changelog

## Unreleased
- Drop HTTP.jl 2.x support
- Support irregular (rectilinear) chunking: `zcreate`, `zzeros` and the `ZArray(a; chunks=...)` constructor now accept a `DiskArrays.GridChunks` object for the `chunks` keyword in addition to a tuple of chunk sizes. Irregular grids round-trip through Zarr v3's `rectilinear` chunk grid; Zarr v2 persists only the maximum chunk size per axis [#326](https://github.com/JuliaIO/Zarr.jl/pull/326)
- Move code to ZarrCore.jl with low dependencies
- Declare an explicit public API [#317](https://github.com/JuliaIO/Zarr.jl/pull/317). Every store, codec, filter and compressor type, and every documented extension point, is now `public`; the set of exported names is unchanged. Internals (`Metadata`, `ZarrFormat`, `is_zarray`, `is_zgroup`, `normalize_path`, `MaxLengthString`, ...) are no longer reachable as `Zarr.x` and must be accessed via `Zarr.ZarrCore.x`

## v0.10.2 - 2026-08-19

Expand Down
42 changes: 35 additions & 7 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,20 @@ julia --project -e 'using Pkg; Pkg.test()'
julia --project=test -e 'using Test, Zarr, JSON; include("test/v3_codecs.jl")'

# Instantiate test dependencies (after Julia version change or first time setup)
julia --project=test -e 'using Pkg; Pkg.instantiate()'
julia --project=test -e 'using Pkg; Pkg.develop(path=pwd()); Pkg.resolve(); Pkg.instantiate()'

# Generate the v3 test fixtures (required before running the suite; CI does this
# in a separate step, `runtests.jl` does not do it for you)
julia --project=test test/v3_julia.jl
julia --project=test test/v3_python.jl
```

Julia version requirement: 1.10+. CI tests against Julia LTS, stable (`1`), nightly, and pre-release on Ubuntu, macOS, and Windows.

Because 1.10 is still supported, the `public` keyword (Julia 1.11+) cannot be used directly. Use the `ZarrCore.@public` macro instead — it expands to `public` on 1.11+ and to nothing on 1.10.

Since there is no `public` on 1.10, `names()` cannot report public names there, so `@public` *also* appends to the calling module's `PUBLIC_NAMES::Vector{Symbol}`. **Any module that uses `@public` must define its own `const PUBLIC_NAMES = Symbol[]`** (per-module, exactly like `public` itself); forgetting it is a load-time `UndefVarError`. The `Zarr` facade unions `names(ZarrCore)` with `ZarrCore.PUBLIC_NAMES`, which is what keeps the public API present on LTS — without it, every public-but-not-exported name silently vanishes from `Zarr` on 1.10 while 1.11+ looks fine.

## Architecture

### Core Type Hierarchy
Expand All @@ -43,16 +52,35 @@ ZArray{T,N,S<:AbstractStore,M<:AbstractMetadata} <: AbstractDiskArray{T,N}
ZGroup{S<:AbstractStore}
```

### Package Layout

The repo is a Pkg workspace with two packages:

- `ZarrCore` (`lib/ZarrCore/`) — the entire implementation. Every type, method and docstring lives here.
- `Zarr` (`src/Zarr.jl`) — a thin facade that re-exports `ZarrCore`'s API. It mirrors the export/public split: names `ZarrCore` exports are re-exported, names it only marks `@public` stay public (not exported). Nothing else is forwarded, so internals must be reached as `Zarr.ZarrCore.foo`.

### Public API Policy

Set by [a maintainer comment on PR #317](https://github.com/JuliaIO/Zarr.jl/pull/317#issuecomment-5314176722). Anything a downstream consumer could need, plus every documented extension point, is part of the public API; everything else is internal and may change.

- **Exported** (in scope after `using Zarr`): `ZArray`, `ZGroup`, `zopen`, `zzeros`, `zcreate`, `zgroup`, `zarrcache`, `storagesize`, `storageratio`, `zinfo`, `DirectoryStore`, `S3Store`, `GCStore`.
- **Public but not exported**: every store, codec, filter and compressor type; the store/filter/compressor/codec/chunk-key-encoding extension interfaces; `typestr`, `fill_value_encoding`, `fill_value_decoding`, `zname`, `writezip`, `consolidate_metadata`, `DateTime64`.
- **Internal**: `Metadata`/`MetadataV2`/`MetadataV3`, `ZarrFormat`, `DV`, `is_zarray`, `is_zgroup`, `normalize_path`, `MaxLengthString`, `ASCIIChar`, `ShapeOnlyArray` (should be removed), `getattrs`/`writeattrs`/`getmetadata`/`writemetadata`, `V2Pipeline`/`V3Pipeline`/`pipeline_encode`/`pipeline_decode!`, and the `store_*` helpers.

Tests follow the same rule: public names are used as `Zarr.foo`, internals as `ZarrCore.foo` (each test file does `import Zarr: ZarrCore`). If a test needs `ZarrCore.` for something a downstream user would plausibly need, that is a signal the name should be made public rather than the test qualified.

### Module/File Layout

- `src/Zarr.jl` — Module entry point, defines `ZarrFormat{V}` (Val-parameterized version tag, default `DV = ZarrFormat(Val(2))`)
All paths below are relative to `lib/ZarrCore/`.

- `src/ZarrCore.jl` — Module entry point, defines `ZarrFormat{V}` (Val-parameterized version tag, default `DV = ZarrFormat(Val(2))`), the `@public` macro, and the export/public declarations
- `src/metadata.jl` — `MetadataV2` struct, type string encoding (`typestr`), fill value encoding/decoding, `Metadata()` constructors for V2; dispatches V3 to `metadata3.jl`
- `src/metadata3.jl` — All V3-specific code: `MetadataV3` struct and constructors, `Metadata3(dict)` parsing, `lower3` serialization, codec pipeline parsing, `get_order`, `JSON.lower(::MetadataV3)`
- `src/chunkencoding.jl` — `ChunkEncoding` struct (separator char + prefix bool), `citostring()` for chunk path generation. V2 default: `'.'` separator, no prefix. V3 default: `'/'` separator, `"c/"` prefix
- `src/chunkkeyencoding.jl` — `ChunkKeyEncoding` struct (separator char + prefix bool), `citostring()` for chunk path generation, plus the `register_chunk_key_encoding` registry. V2 default: `'.'` separator, no prefix. V3 default: `'/'` separator, `"c/"` prefix
- `src/ZArray.jl` — Core array type, `readblock!`/`writeblock!` (DiskArrays interface), `zcreate`, `zzeros`, `zopen`, resize/append
- `src/ZGroup.jl` — Hierarchical group support, `zopen`, `zgroup`, auto-detection of zarr version via `ZarrFormat(store, path)`
- `src/Compressors/` — `Compressor` abstract type, `compressortypes` registry (keyed by spec name string), implementations: `blosc.jl`, `zlib.jl`, `zstd.jl`, `v3.jl` (v3 wrapper `Compressor_v3{C}`)
- `src/Codecs/` — V3 codec system (`Codec` abstract type), `V3/V3.jl` defines `V3Codec{In,Out}` with `BloscV3Codec`, `BytesCodec`, `CRC32cV3Codec`, `GzipV3Codec`, `ShardingCodec`, `TransposeCodec`, `ZstdV3Codec`
- `src/Compressors/` — `Compressor` abstract type, `compressortypes` registry (keyed by spec name string), implementations: `blosc.jl`, `zlib.jl`, `zstd.jl`
- `src/Codecs/` — V3 codec system (`Codec` abstract type), `V3/V3.jl` defines `V3Codec{In,Out}` with `BloscV3Codec`, `BytesCodec`, `CRC32cV3Codec`, `GzipV3Codec`, `ShardingCodec`, `TransposeCodec`, `ZstdV3Codec`, and the `register_codec` registry
- `src/Filters/` — `Filter{T,TENC}` abstract type, implementations for variable-length arrays, strings, Fletcher32, shuffle, delta, quantize
- `src/Storage/Storage.jl` — `AbstractStore` interface, I/O strategy (`SequentialRead`/`ConcurrentRead`), chunk read/write/delete helpers, metadata read/write dispatched on `ZarrFormat{2}` vs `ZarrFormat{3}`

Expand All @@ -73,14 +101,14 @@ New store backends must implement: `getindex(store, key)::Union{Vector{UInt8}, N

V3 support is under active development. Current state:

**Codecs (`src/Codecs/V3/V3.jl`)**
**Codecs (`lib/ZarrCore/src/Codecs/V3/V3.jl`)**
- `BytesCodec` — stores `endian::Symbol` (`:little` or `:big`); encode/decode byte-swap elements when the target endian differs from the system byte order (`Base.ENDIAN_BOM`). Default is `:little`.
- `TransposeCodec` — array→array permutation codec (renamed from `TransposeCodecImpl`)
- `BloscV3Codec` — shuffle stored as integer (0=noshuffle, 1=shuffle, 2=bitshuffle); parsed from spec strings (`"noshuffle"`, `"shuffle"`, `"bitshuffle"`) and serialized back to strings
- Sharding codec (`sharding_indexed`) has struct definitions and encode/decode logic but is not yet wired into the main read/write pipeline (throws `ArgumentError` when encountered)
- `crc32c` codec has encode/decode implementations and is parseable from metadata

**Metadata (`src/metadata3.jl`)**
**Metadata (`lib/ZarrCore/src/metadata3.jl`)**
- `MetadataV3{T,N,P}` has no `order` field; storage order is encoded in the pipeline via `TransposeCodec`
- Two constructors:
- Primary inner constructor: `MetadataV3{T,N,P}(zarr_format, node_type, shape, chunks, dtype, pipeline, fill_value, chunk_encoding)` — takes a pre-built pipeline, no `order` argument
Expand Down
36 changes: 4 additions & 32 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,11 @@ uuid = "0a941bbe-ad1d-11e8-39d9-ab76183a1d99"
version = "0.10.2"
authors = ["Fabian Gans <fgans@bgc-jena.mpg.de>"]

[workspace]
projects = ["test", "lib/ZarrCore"]

[deps]
Blosc = "a74b3585-a348-5f62-a45c-50e91977d574"
CRC32c = "8bf52ea8-c179-5cab-976a-9e18b702a9bc"
ChunkCodecCore = "0b6fb165-00bc-4d37-ab8b-79f91016dbe1"
ChunkCodecLibZlib = "4c0bbee4-addc-4d73-81a0-b6caacae83c8"
ChunkCodecLibZstd = "55437552-ac27-4d47-9aa3-63184e8fd398"
DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8"
DateTimes64 = "b342263e-b350-472a-b1a9-8dfd21b51589"
Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"
DiskArrays = "3c3547ce-8d99-4f5e-a174-61eb10b00ae3"
HTTP = "cd3eb016-35fb-5094-929b-558a96fad6f3"
JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6"
OffsetArrays = "6fe1bfb0-de20-5000-8ca7-80f57d26f881"
OrderedCollections = "bac558e1-5e72-5ebc-8fee-abe8a469f55d"
Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"
URIs = "5c2747f8-b7ea-4ff2-ba2e-563bfd36b1d4"
Unicode = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5"
ZipArchives = "49080126-0e18-4c2a-b176-c102e4b3760c"
ZarrCore = "77f5b75c-4c08-499f-ba13-550b0a0af171"

[weakdeps]
AWSS3 = "1c724243-ef5b-51ab-93f4-b0a88ac62a95"
Expand All @@ -30,19 +17,4 @@ ZarrAWSS3Ext = "AWSS3"

[compat]
AWSS3 = "0.10, 0.11"
Blosc = "0.5, 0.6, 0.7"
CRC32c = "1.10, 1.11"
ChunkCodecCore = "1"
ChunkCodecLibZlib = "1"
ChunkCodecLibZstd = "1"
DataStructures = "0.17, 0.18, 0.19"
DateTimes64 = "1"
DiskArrays = "0.4.21"
HTTP = "2"
JSON = "0.21, 1"
OffsetArrays = "0.11, 1.0"
OrderedCollections = "1.8.2, 2"
URIs = "1"
Unicode = "1.10, 1.11.0"
ZipArchives = "2"
julia = "1.10"
5 changes: 4 additions & 1 deletion docs/make.jl
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
using DocumenterVitepress
using Documenter, Zarr
# All docstrings live in the `ZarrCore` implementation package; `Zarr` is only
# the facade that re-exports its API.
using Zarr: ZarrCore

cp(joinpath(@__DIR__, "..", "CHANGELOG.md"), joinpath(@__DIR__, "src", "changelog.md"), force = true)
cp(joinpath(@__DIR__, "..", "CONTRIBUTING.md"), joinpath(@__DIR__, "src", "contributing.md"), force = true)

makedocs(
modules = [Zarr],
modules = [Zarr, ZarrCore],
clean = false,
doctest = true,
format = DocumenterVitepress.MarkdownVitepress(
Expand Down
1 change: 1 addition & 0 deletions docs/src/.vitepress/config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ const userGuideItems = [
// { text: 'Data Types', link: '/UserGuide/data_types' },
// { text: 'Codecs & Performance', link: '/UserGuide/performance' },
{ text: 'Operations', link: '/UserGuide/operations'},
{ text: 'Chunking', link: '/UserGuide/chunking' },
// { text: 'Sharding', link: '/UserGuide/sharding' },
{ text: 'Missing Values', link: '/UserGuide/missings' },
]
Expand Down
4 changes: 2 additions & 2 deletions docs/src/UserGuide/caching.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@

and wrap it into a `CachedDiskArray`:

````jldoctest cache

Check failure on line 24 in docs/src/UserGuide/caching.md

View workflow job for this annotation

GitHub Actions / Documentation

doctest failure in docs/src/UserGuide/caching.md:24-38 ```jldoctest cache julia> a_lrucache = DiskArrays.cache(a,maxsize=1) 10000×10000 DiskArrays.CachedDiskArray{Float64, 2, ZArray{Float64, 2, DirectoryStore, ZarrCore.MetadataV2{Float64, 2, ZarrCore.BloscCompressor, Nothing}}, LRUCache.LRU{ChunkIndex{2, DiskArrays.OffsetChunks}, OffsetArrays.OffsetMatrix{Float64, Matrix{Float64}}}} Chunked: ( [1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000] [1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000] ) julia> a_lrucache[5000,1] # Precompilation 5000.0 julia> a_lrucache[5001,1] 5001.0 ``` Subexpression: a_lrucache = DiskArrays.cache(a,maxsize=1) Evaluated output: 10000×10000 DiskArrays.CachedDiskArray{Float64, 2, ZArray{Float64, 2, DirectoryStore, ZarrCore.MetadataV2{Float64, 2, ZarrCore.BloscCompressor, Nothing, DiskArrays.GridChunks{2, Tuple{DiskArrays.RegularChunks, DiskArrays.RegularChunks}}}}, LRUCache.LRU{ChunkIndex{2, DiskArrays.OffsetChunks}, OffsetArrays.OffsetMatrix{Float64, Matrix{Float64}}}} Chunked: ( [1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000] [1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000] ) Expected output: 10000×10000 DiskArrays.CachedDiskArray{Float64, 2, ZArray{Float64, 2, DirectoryStore, ZarrCore.MetadataV2{Float64, 2, ZarrCore.BloscCompressor, Nothing}}, LRUCache.LRU{ChunkIndex{2, DiskArrays.OffsetChunks}, OffsetArrays.OffsetMatrix{Float64, Matrix{Float64}}}} Chunked: ( [1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000] [1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000] ) diff = Warning: Diff output requires color. 10000×10000 DiskArrays.CachedDiskArray{Float64, 2, ZArray{Float64, 2, DirectoryStore, ZarrCore.MetadataV2{Float64, 2, ZarrCore.BloscCompressor, Nothing}}, Nothing, DiskArrays.GridChunks{2, Tuple{DiskArrays.RegularChunks, DiskArrays.RegularChunks}}}}, LRUCache.LRU{ChunkIndex{2, DiskArrays.OffsetChunks}, OffsetArrays.OffsetMatrix{Float64, Matrix{Float64}}}} Chunked: ( [1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000] [1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000] )
julia> a_lrucache = DiskArrays.cache(a,maxsize=1)
10000×10000 DiskArrays.CachedDiskArray{Float64, 2, ZArray{Float64, 2, DirectoryStore, Zarr.MetadataV2{Float64, 2, Zarr.BloscCompressor, Nothing}}, LRUCache.LRU{ChunkIndex{2, DiskArrays.OffsetChunks}, OffsetArrays.OffsetMatrix{Float64, Matrix{Float64}}}}
10000×10000 DiskArrays.CachedDiskArray{Float64, 2, ZArray{Float64, 2, DirectoryStore, ZarrCore.MetadataV2{Float64, 2, ZarrCore.BloscCompressor, Nothing}}, LRUCache.LRU{ChunkIndex{2, DiskArrays.OffsetChunks}, OffsetArrays.OffsetMatrix{Float64, Matrix{Float64}}}}

Chunked: (
[1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000]
Expand Down Expand Up @@ -53,9 +53,9 @@

So, if we want to hold an incomplete local copy of any `AbstractDiskArray` you can do

````jldoctest cache

Check failure on line 56 in docs/src/UserGuide/caching.md

View workflow job for this annotation

GitHub Actions / Documentation

doctest failure in docs/src/UserGuide/caching.md:56-64 ```jldoctest cache julia> a_arraycache = zarrcache(a,"./my_persistent_store.zarr") 10000×10000 DiskArrays.CachedDiskArray{Float64, 2, ZArray{Float64, 2, DirectoryStore, ZarrCore.MetadataV2{Float64, 2, ZarrCore.BloscCompressor, Nothing}}, ZarrCore.PermanentZarrCache{Float64, 2, ZArray{Float64, 2, DirectoryStore, ZarrCore.MetadataV2{Float64, 2, ZarrCore.BloscCompressor, Nothing}}}} Chunked: ( [1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000] [1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000] ) ``` Subexpression: a_arraycache = zarrcache(a,"./my_persistent_store.zarr") Evaluated output: 10000×10000 DiskArrays.CachedDiskArray{Float64, 2, ZArray{Float64, 2, DirectoryStore, ZarrCore.MetadataV2{Float64, 2, ZarrCore.BloscCompressor, Nothing, DiskArrays.GridChunks{2, Tuple{DiskArrays.RegularChunks, DiskArrays.RegularChunks}}}}, ZarrCore.PermanentZarrCache{Float64, 2, ZArray{Float64, 2, DirectoryStore, ZarrCore.MetadataV2{Float64, 2, ZarrCore.BloscCompressor, Nothing, DiskArrays.GridChunks{2, Tuple{DiskArrays.RegularChunks, DiskArrays.RegularChunks}}}}}} Chunked: ( [1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000] [1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000] ) Expected output: 10000×10000 DiskArrays.CachedDiskArray{Float64, 2, ZArray{Float64, 2, DirectoryStore, ZarrCore.MetadataV2{Float64, 2, ZarrCore.BloscCompressor, Nothing}}, ZarrCore.PermanentZarrCache{Float64, 2, ZArray{Float64, 2, DirectoryStore, ZarrCore.MetadataV2{Float64, 2, ZarrCore.BloscCompressor, Nothing}}}} Chunked: ( [1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000] [1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000] ) diff = Warning: Diff output requires color. 10000×10000 DiskArrays.CachedDiskArray{Float64, 2, ZArray{Float64, 2, DirectoryStore, ZarrCore.MetadataV2{Float64, 2, ZarrCore.BloscCompressor, Nothing}}, Nothing, DiskArrays.GridChunks{2, Tuple{DiskArrays.RegularChunks, DiskArrays.RegularChunks}}}}, ZarrCore.PermanentZarrCache{Float64, 2, ZArray{Float64, 2, DirectoryStore, ZarrCore.MetadataV2{Float64, 2, ZarrCore.BloscCompressor, Nothing}}}} Nothing, DiskArrays.GridChunks{2, Tuple{DiskArrays.RegularChunks, DiskArrays.RegularChunks}}}}}} Chunked: ( [1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000] [1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000] )
julia> a_arraycache = zarrcache(a,"./my_persistent_store.zarr")
10000×10000 DiskArrays.CachedDiskArray{Float64, 2, ZArray{Float64, 2, DirectoryStore, Zarr.MetadataV2{Float64, 2, Zarr.BloscCompressor, Nothing}}, Zarr.PermanentZarrCache{Float64, 2, ZArray{Float64, 2, DirectoryStore, Zarr.MetadataV2{Float64, 2, Zarr.BloscCompressor, Nothing}}}}
10000×10000 DiskArrays.CachedDiskArray{Float64, 2, ZArray{Float64, 2, DirectoryStore, ZarrCore.MetadataV2{Float64, 2, ZarrCore.BloscCompressor, Nothing}}, ZarrCore.PermanentZarrCache{Float64, 2, ZArray{Float64, 2, DirectoryStore, ZarrCore.MetadataV2{Float64, 2, ZarrCore.BloscCompressor, Nothing}}}}

Chunked: (
[1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000]
Expand Down
110 changes: 110 additions & 0 deletions docs/src/UserGuide/chunking.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
# Chunking and Irregular Chunk Grids

A Zarr array is divided into chunks, each of which is compressed and stored (and
transferred) independently. The chunk size along every axis is therefore one of
the main knobs controlling read/write performance and storage granularity.

By default chunks have a uniform size along each axis (regular chunking), which
is what the plain tuple form of the `chunks` keyword gives you:

````jldoctest regular
julia> using Zarr

julia> z = zzeros(Int, 100, 100; chunks=(10, 20))
ZArray{Int64} of size 100 x 100
````

Here the array is divided into chunks of 10×20 elements, evenly tiling the
array.

## Controlling the chunk grid with `GridChunks`

Passing a tuple only fixes the chunk size; to fully control the chunk grid you
can instead pass a `DiskArrays.GridChunks` object as the `chunks` keyword to
[`zcreate`](@ref), [`zzeros`](@ref) or the `ZArray(a::AbstractArray, ...; chunks=...)`
constructor. This is also what makes *irregular* (rectilinear) chunking
possible, where chunk sizes vary along an axis instead of being uniform.

The chunk types and the `GridChunks` wrapper come from the
[DiskArrays.jl](https://github.com/JuliaIO/DiskArrays.jl) package, which Zarr
builds on. Bring them into scope with:

```julia
using DiskArrays: GridChunks, IrregularChunks, RegularChunks
```

A `GridChunks` holds one chunk specification per axis, either a
`RegularChunks` or an `IrregularChunks`. Irregular chunks along an axis are
described by their edge lengths:

````jldoctest irregular

Check failure on line 40 in docs/src/UserGuide/chunking.md

View workflow job for this annotation

GitHub Actions / Documentation

doctest failure in docs/src/UserGuide/chunking.md:40-58 ```jldoctest irregular julia> using Zarr julia> using DiskArrays: GridChunks, IrregularChunks, RegularChunks julia> chunks = GridChunks(RegularChunks(2, 0, 5), IrregularChunks(chunksizes=[3, 4, 5, 6, 2])); julia> z = zcreate(Int, 5, 20; zarr_format=3, chunks=chunks) ZArray{Int64} of size 5 x 20 julia> eachchunk(z) GridChunks( RegularChunks(2, 0, 5)IrregularChunks([0, 3, 7, 12, 18, 20])) julia> z[:, :] = reshape(1:100, 5, 20); julia> z[:, :] == reshape(1:100, 5, 20) true ``` Subexpression: eachchunk(z) Evaluated output: ERROR: UndefVarError: `eachchunk` not defined in `Main` Suggestion: check for spelling errors or missing imports. Hint: a global variable of this name also exists in DiskArrays. Stacktrace: [1] top-level scope @ none:1 Expected output: GridChunks( RegularChunks(2, 0, 5)IrregularChunks([0, 3, 7, 12, 18, 20])) diff = Warning: Diff output requires color. GridChunks( RegularChunks(2, 0, 5)IrregularChunks([0, 3, 7, 12, 18, 20]))ERROR: UndefVarError: `eachchunk` not defined in `Main` Suggestion: check for spelling errors or missing imports. Hint: a global variable of this name also exists in DiskArrays. Stacktrace: [1] top-level scope @ none:1
julia> using Zarr

julia> using DiskArrays: GridChunks, IrregularChunks, RegularChunks

julia> chunks = GridChunks(RegularChunks(2, 0, 5), IrregularChunks(chunksizes=[3, 4, 5, 6, 2]));

julia> z = zcreate(Int, 5, 20; zarr_format=3, chunks=chunks)
ZArray{Int64} of size 5 x 20

julia> eachchunk(z)
GridChunks(
RegularChunks(2, 0, 5)IrregularChunks([0, 3, 7, 12, 18, 20]))

julia> z[:, :] = reshape(1:100, 5, 20);

julia> z[:, :] == reshape(1:100, 5, 20)
true
````

The second axis is split into chunks of 3, 4, 5, 6 and 2 elements (20 in total),
while the first axis keeps regular chunks of size 2.

`zzeros` works the same way; for grids that are not uniformly regular it fills
the array element by element rather than reusing a single encoded chunk:

````jldoctest irregular
julia> zz = zzeros(Int, 10, 10; zarr_format=3, chunks=GridChunks(RegularChunks(5, 0, 10), IrregularChunks(chunksizes=[3, 3, 4])));

julia> zz[1, 1]
0
````

## Persistence and interoperability

Irregular chunk grids are stored in the array metadata, so they survive
round-tripping through a store. Zarr v3 records them as a `rectilinear` chunk
grid with `kind: "inline"`, where each axis is either a single chunk size
(regular) or a list of edge lengths. Reopening the array restores the exact
grid:

````jldoctest irregular

Check failure on line 81 in docs/src/UserGuide/chunking.md

View workflow job for this annotation

GitHub Actions / Documentation

doctest failure in docs/src/UserGuide/chunking.md:81-91 ```jldoctest irregular julia> dir = joinpath(mktempdir(), "irregular.zarr"); julia> z = zcreate(Int, 5, 20; zarr_format=3, chunks=chunks, path=dir); julia> z2 = zopen(dir); julia> eachchunk(z2) GridChunks( RegularChunks(2, 0, 5)IrregularChunks([0, 3, 7, 12, 18, 20])) ``` Subexpression: eachchunk(z2) Evaluated output: ERROR: UndefVarError: `eachchunk` not defined in `Main` Suggestion: check for spelling errors or missing imports. Hint: a global variable of this name also exists in DiskArrays. Stacktrace: [1] top-level scope @ none:1 Expected output: GridChunks( RegularChunks(2, 0, 5)IrregularChunks([0, 3, 7, 12, 18, 20])) diff = Warning: Diff output requires color. GridChunks( RegularChunks(2, 0, 5)IrregularChunks([0, 3, 7, 12, 18, 20]))ERROR: UndefVarError: `eachchunk` not defined in `Main` Suggestion: check for spelling errors or missing imports. Hint: a global variable of this name also exists in DiskArrays. Stacktrace: [1] top-level scope @ none:1
julia> dir = joinpath(mktempdir(), "irregular.zarr");

julia> z = zcreate(Int, 5, 20; zarr_format=3, chunks=chunks, path=dir);

julia> z2 = zopen(dir);

julia> eachchunk(z2)
GridChunks(
RegularChunks(2, 0, 5)IrregularChunks([0, 3, 7, 12, 18, 20]))
````

::: warning

Zarr v2 has no concept of irregular chunk grids. If you create an array with an
irregular grid and persist it as v2, the metadata only records the *maximum*
chunk size along each axis, and the array is reopened as a regular grid of that
size. Irregular chunking is therefore only preserved end-to-end with Zarr v3
(`zarr_format=3`).

:::

::: warning

`resize!` and `append!` are not supported for arrays whose grid contains
`IrregularChunks`; shrinking such an array throws an `ArgumentError`. Resizing
an irregular grid would require redefining the per-axis edge lengths, which is
not implemented.

:::
Loading
Loading