Skip to content

Add support for handling zarr urls#7113

Open
stephenworsley wants to merge 29 commits into
SciTools:mainfrom
stephenworsley:zarr_url_support
Open

Add support for handling zarr urls#7113
stephenworsley wants to merge 29 commits into
SciTools:mainfrom
stephenworsley:zarr_url_support

Conversation

@stephenworsley

Copy link
Copy Markdown
Contributor

Closes #6967

@stephenworsley
stephenworsley marked this pull request as draft May 28, 2026 13:10
@codecov

codecov Bot commented May 28, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 94.04762% with 5 lines in your changes missing coverage. Please review.
✅ Project coverage is 90.20%. Comparing base (c2ffc9b) to head (1aa431e).
⚠️ Report is 3 commits behind head on main.

Files with missing lines Patch % Lines
lib/iris/fileformats/netcdf/saver.py 83.33% 3 Missing ⚠️
lib/iris/loading.py 86.66% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #7113      +/-   ##
==========================================
+ Coverage   90.18%   90.20%   +0.02%     
==========================================
  Files          91       91              
  Lines       25102    25160      +58     
  Branches     4706     4718      +12     
==========================================
+ Hits        22638    22696      +58     
  Misses       1685     1685              
  Partials      779      779              

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@pp-mo

pp-mo commented Jun 10, 2026

Copy link
Copy Markdown
Member

Some Notes

Since @pp-mo @stephenworsley discussed this yesterday, here's my take on some things we found...

  • I pointed out that I'm not too sure about the approach here , because I think it undermines the intent of the "schemes"

    • if done more rigorously, I think it should be possible to supply a url to a remote zarr dataset, similar to the OpenDAP protocol supported by the netcdf file loader
      • .. but I don't know if there's a server solution that actually supports such url-based remote access ?
    • .. and admittedly the whole framework of "schemes" is a bit clunky :
      Notably, in the case of netcdf files, we can access from a filepath, a url, or an open dataset ...
  • we also found that saving to zarr by this means currently fails for lazy data streaming (though OK for all-real data)

    • since we added "deferred writes" for netcdf (Lazy netcdf saves #5191), our standard approach is to create a variable and not write the data. We then close the file, and the proxies re-open it in modify ("r+") mode, to fill in a chunk of data.
    • .. it looks like this simply doesn't work with netCDF4-python any more if it is writing Zarr :
      when such a file is re-opened, no such variable exists (the metadata is not saved ?)
    • .. it's not clear though whether this is an essential limitation of Zarrr itself (controlling metadata cannot exist when there are no recorded chunks for a given array ??). Or possibly, this is just a problem with the netCDF4-python implementation.
    • we could however fix this by giving the 'lazy stream' operation an extra mode control, to be specifically turned on for Zarr ..
      This would use a "da.store" directly when creating the original file, i.e. a "third way" for this code to function, where the 'store' operation is just da.store -- which is exactly what the code used to do before Lazy netcdf saves #5191.
      N.B. I already found in Lazy netcdf saves #5191 that you can't create a write proxy and just 'da.store' it immediately (? at least for process-based/distributed scheduler? ), since it needs to re-open the file with write privilege, which will then fail (since the file is already, or "still" open for write). So, to store immediately, you must call da.store directly.
  • we need to discuss whether this makes this whole approach too awkward for supporting Zarr via this route?
    @ukmo-ccbunney to reconsider versus alternative mechanisms in Zarr I/O #6961 ??

Comment thread lib/iris/fileformats/netcdf/saver.py

@scitools-ci scitools-ci Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Templating

This PR includes changes that may be worth sharing via templating. For each file listed below, please either:

  • Action the suggestion via a pull request editing/adding the relevant file in the SciTools/.github templates/ directory. 1
  • Raise an issue against the SciTools/.github repo for the above action if you really don't have 10mins spare right now. Include an assignee, to avoid it being forgotten.
  • Dismiss the suggestion if the changes are not suitable for templating.

You will need to dismiss this review before this PR can be merged. Recommend the reviewer does this as their final action before merging, as this text will continually update as commits come in.

Template candidates

The following changed files are not currently templated, but their parent directories suggest they may be good candidates for a new template to be created:

Footnotes

  1. Include this text in the PR body to avoid any notifications about applying the template changes back to the source repo!
    @scitools-templating: please no update notification on: iris

Comment thread lib/iris/io/__init__.py
Comment thread lib/iris/io/__init__.py Outdated
Comment thread lib/iris/tests/integration/netcdf/test_nczarr.py Outdated
Comment thread lib/iris/io/__init__.py Outdated
Comment thread lib/iris/fileformats/__init__.py Outdated
Comment thread lib/iris/fileformats/netcdf/ugrid_load.py
Comment thread lib/iris/tests/stock/__init__.py Outdated
Comment thread lib/iris/tests/unit/fileformats/netcdf/saver/test_Saver.py Outdated
Comment thread lib/iris/tests/unit/fileformats/netcdf/saver/test_Saver.py Outdated
Comment thread lib/iris/tests/unit/io/test__generate_cubes.py
Comment thread docs/src/user_manual/tutorial/loading_iris_cubes.rst
Comment thread docs/src/user_manual/tutorial/loading_iris_cubes.rst
Comment thread lib/iris/io/__init__.py Outdated
Comment thread lib/iris/fileformats/netcdf/saver.py Outdated
Comment thread lib/iris/fileformats/netcdf/loader.py Outdated

@ukmo-ccbunney ukmo-ccbunney left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking good so far. Just a few suggestions and comments.

Comment thread lib/iris/fileformats/netcdf/saver.py Outdated
Comment thread lib/iris/fileformats/netcdf/saver.py Outdated
Comment thread lib/iris/fileformats/netcdf/saver.py Outdated
Comment thread lib/iris/fileformats/netcdf/loader.py Outdated
Comment thread lib/iris/fileformats/__init__.py
Comment thread lib/iris/tests/stock/__init__.py
Comment thread lib/iris/tests/test_io_init.py
Comment thread docs/src/user_manual/tutorial/loading_iris_cubes.rst
Comment thread docs/src/user_manual/tutorial/loading_iris_cubes.rst
Comment thread lib/iris/tests/test_io_init.py Outdated

@ukmo-ccbunney ukmo-ccbunney left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a thought I meant to add to the original review...

Comment thread lib/iris/tests/integration/netcdf/test_nczarr.py
trexfeathers and others added 7 commits July 24, 2026 14:14
Co-authored-by: Chris Bunney <48915820+ukmo-ccbunney@users.noreply.github.com>
Co-authored-by: Chris Bunney <48915820+ukmo-ccbunney@users.noreply.github.com>
Co-authored-by: Chris Bunney <48915820+ukmo-ccbunney@users.noreply.github.com>
Co-authored-by: Chris Bunney <48915820+ukmo-ccbunney@users.noreply.github.com>
Co-authored-by: Chris Bunney <48915820+ukmo-ccbunney@users.noreply.github.com>
Co-authored-by: Chris Bunney <48915820+ukmo-ccbunney@users.noreply.github.com>

@ukmo-ccbunney ukmo-ccbunney left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK. Everything mostly looks good. I've made a few suggested changes to fix the failing test.

I've also pushed up some whatsnew fragments that Pak Lam and myself put together, see 90236d2

Comment thread lib/iris/tests/unit/io/test__generate_cubes.py Outdated
Comment thread lib/iris/tests/results/file_load/known_loaders.txt Outdated
Comment thread lib/iris/tests/results/file_load/known_loaders.txt
@ukmo-ccbunney
ukmo-ccbunney marked this pull request as ready for review July 24, 2026 15:26
trexfeathers and others added 3 commits July 24, 2026 16:30
Co-authored-by: Chris Bunney <48915820+ukmo-ccbunney@users.noreply.github.com>
Co-authored-by: Chris Bunney <48915820+ukmo-ccbunney@users.noreply.github.com>
Co-authored-by: Chris Bunney <48915820+ukmo-ccbunney@users.noreply.github.com>

@ukmo-ccbunney ukmo-ccbunney left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Spotted a slight oddity...

assert (output_path / ".zattrs").is_file()
assert (output_path / ".zgroup").is_file()

loaded = iris.load(output_uri)[0]

@ukmo-ccbunney ukmo-ccbunney Jul 24, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've noticed that this zarr file isn't loading into a single cube.

If you load this via iris.load_cube() it fails to merge into a single cube.
Testing on my VDI shows that it actually loads into two cubes - the second being the surface_altitude cube.
The equality test still passes (as it takes the first cube via slice [0]), so surface_altitude is getting added as an AuxCoord on the first cube ok, so it is a bit odd.

Furthermore, when accessing the data on my VDI, I get the following errors thrown from the HDF5 layer, although they don't actually stop me from loading the data (so more warnings). I don't see the same warnings in the CI tests though (I'm running the same version of netcdf)

HDF5-DIAG: Error detected in HDF5 (1.14.6) thread 1:
  #000: H5F.c line 496 in H5Fis_accessible(): unable to determine if file is accessible as HDF5
    major: File accessibility
    minor: Not an HDF5 file
  #001: H5VLcallback.c line 3913 in H5VL_file_specific(): file specific failed
    major: Virtual Object Layer
    minor: Can't operate on object
  #002: H5VLcallback.c line 3848 in H5VL__file_specific(): file specific failed
    major: Virtual Object Layer
    minor: Can't operate on object
  #003: H5VLnative_file.c line 344 in H5VL__native_file_specific(): error in HDF5 file check
    major: File accessibility
    minor: Can't get value
  #004: H5Fint.c line 1055 in H5F__is_hdf5(): unable to open file
    major: File accessibility
    minor: Unable to initialize object
  #005: H5FD.c line 787 in H5FD_open(): can't open file
    major: Virtual File Layer
    minor: Unable to open file
  #006: H5FDsec2.c line 323 in H5FD__sec2_open(): unable to open file: name = 'file:///tmp/4dwe.zarr#mode=nczarr', errno = 2, error message = 'No such file or directory', flags = 0, o_flags = 0
    major: File accessibility
    minor: Unable to open file

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, after some investigation, it has become apparent that the realistic_4d_w_everything cube doesn't round trip if you save and load it as a netcdf file either - the same merge errors apply.

The same applied with iris.COMBINE_POLICY.set("comprehensive")

So this is not a NcZarr loading issue.

I can't immediately see why the file doesn't round trip, but it is clearly no the fault of the nczarr loading, so we can ignore that in the context of this PR.

The issue of the HDF warnings still applies, but this is just noise, rather than an error (the data is still read correctly) and it is not showing in the CI tests - only locally for me. I would be interested if @trexfeathers sees similar noise when running locally?

Other than the HDF5 noise, I am happy with the the implementation of the NcZarr functionality.

@ukmo-ccbunney ukmo-ccbunney Jul 25, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After a bit of poking, I've narrowed down the HDF noise to the netCDF4.Dataset call in _ThreadSafeWrapper.__getitem__ where the dataset is repeatedly opened/closed.

Furthermore, it is only a problem when dask is used - if i force the data to be loaded as concrete data (by upping the minimum data size threshold) then there are no HDF warnings on the call the netCDF4.Dataset. Assumedly this has something to do with dask making lots of concurrent calls to op the dataset due to multiple chunks trying to read from it?

If i set dask.config.set('scheduler', 'single-threaded') the error also goes away, which gives further evidence that it is a dask concurrency issue.

At the moment, I don't know WHY it is a problem for NcZarr and OK for NetCDF...

@ukmo-ccbunney ukmo-ccbunney Jul 25, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right - I've got Copilot on board now to help me with the dask interaction.

It has suggested the following test that nicely proves that the HDF5 noise issue is actually not related to dask or Iris, but is generated when any worker-thread makes a call to netcdf4.Dataset with a NCZarr URI:

>>> from concurrent.futures import ThreadPoolExecutor
>>> import netCDF4

>>> uri = "file:///tmp/4dwe.zarr#mode=nczarr,file"

>>> def f():
>>>     ds = netCDF4.Dataset(uri)
>>>     ds.close()

>>> f()  # main thread
>>> with ThreadPoolExecutor(max_workers=1) as ex:
>>>     ex.submit(f).result()  # worker thread

HDF5-DIAG: Error detected in HDF5 (1.14.6) thread 3:
  #000: H5F.c line 496 in H5Fis_accessible(): unable to determine if file is accessible as HDF5
    major: File accessibility
    minor: Not an HDF5 file

As the tests pass, I think we can consider this just slightly annoying "noise" as a result of Dask accessing NCZarr datasets via it's worker thread(s).

If necessary, to quieten the warnings we could recommend either:

  • filtering the HDF5 warnings
  • Using the single-threaded dask scheduler (although potential performance impact?)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

Implicit Zarr support via nczarr

4 participants