Fixing open_mfdataset That Is Slow, Hangs or Will Not Combine
Problem statement
xr.open_mfdataset("folder/*.nc") fails in ways that have little to do with the error message. A single cropped file produces a complaint about monotonic indexes. A download error page produces a missing-backend error. Too many files produces the same missing-backend error. A duplicate file produces no error at all, just an extra day. And parallel=True, the obvious speed-up, crashes.
Reproduced with the 31 daily NOAA OISST v2.1 files for January 2024, each time adding one problem:
- A cropped regional file made by-coordinate combining raise
ValueError: Resulting object does not have monotonic global indexes along dimension lon; a latitude shifted by 0.0001ยฐ in one file raised the same error forlat. - A file with one variable missing combined without error; that variable was filled with NaN for the whole day.
- A duplicate "preliminary" copy of one day gave 32 time steps with a duplicate under nested combining; by-coordinate combining returned 31 without warning.
- An HTML error page saved as
.ncraiseddid not find a match in any of xarray's currently installed IO backendsโ the same message produced, for the full year, by a limit of 64 open files. parallel=Trueon the 366-file year failed in all six attempts: five killed the interpreter with double frees or segmentation faults, the sixth raised an HDF error.
Quick answer
symptom cause fix
"does not have monotonic global indexes along dimension" one file on a different or shifted grid find it (Example 1); regrid or drop
"did not find a match in any ... installed IO backends" a non-NetCDF file, or too many open files check file headers; lower file_cache_maxsize
OSError: NetCDF: HDF error truncated or corrupt file re-download it
extra or duplicated time steps, no error duplicate files in the folder check time duplicates after opening
a variable all NaN for some days, no error files with different variables check variable sets per file
crash, "double free", segfault with parallel=True HDF5 not thread-safe in this build drop parallel=True or use processes
"Could not find any dimension coordinates to use" files without the time coordinate use nested combining with concat_dim
Then scan the folder before opening it (Example 1), and open with join="exact" so mismatches fail immediately instead of being padded.
Step-by-step solution
1. Check the glob matched what you expect
open_mfdataset("nothing_here/*.nc") raised OSError: no files to open. A glob that matched too many files is quieter: a stray regional extract, a preliminary copy or a partial download all match *.nc. Count the files and compare with the number of days.
2. Check that every file is NetCDF
A download that returned an HTML error page, saved under a .nc name, made the whole open fail with ValueError: did not find a match in any of xarray's currently installed IO backends. A truncated download raised OSError: [Errno -101] NetCDF: HDF error. Checking each file's first bytes โ CDF or \x89HDF โ names the bad file directly (Example 1).
3. Find the file on a different grid
By-coordinate combining sorts files by their coordinates and requires one consistent grid. One file cropped to the North Atlantic, or with latitudes shifted by 0.0001ยฐ, produced a ValueError about monotonic global indexes along lon or lat โ the error describes the combined result, not the file responsible. Listing each file's grid size and first coordinate values identifies it. With join="exact", the cropped file raised AlignmentError: cannot align objects with join='exact' instead; with join="override" it still failed because the sizes differed.
4. Check variables per file
A file without the ice variable combined silently: by-coordinate combining filled ice with NaN for that day, and nested combining also succeeded without a message. Compare the set of data variables in each file before combining, or open with data_vars limited to the variables you need.
5. Check time after opening
Nested combining trusts list order and does not look for duplicates: a folder with a preliminary copy of 15 January gave 32 time steps and one duplicate. A shuffled file list gave a time axis that was not monotonic, and slicing it raised KeyError: 'Value based partial slicing on non-monotonic DatetimeIndexes with non-existing keys is not allowed.' After any multi-file open, assert that time is sorted and unique.
6. Handle files without a time coordinate
Files that store a single field with no time variable cannot be ordered by coordinates: one such file raised ValueError: Every dimension requires a corresponding 1D coordinate and index for inferring concatenation order but the coordinate 'time' has no corresponding index, and a folder of them Could not find any dimension coordinates to use to order the Dataset objects for concatenation. Use combine="nested" with a sorted list and add time afterwards, or add it in preprocess from the file name.
7. Raise the open-file limit or shrink the cache
xarray keeps up to 128 files open by default. With the process limited to 64 open files, opening the 366-file year failed โ surprisingly, with the "did not find a match in any of xarray's currently installed IO backends" error, because the backends could not open the files to identify them. xr.set_options(file_cache_maxsize=32) fixed it, and the mean computed as 14.13 ยฐC.
8. Do not use parallel=True with the netCDF4 library in threads
On the full 366-file year, parallel=True failed in six of six attempts, five of them killing the process: double free or corruption, malloc(): unaligned tcache chunk detected, NetCDF: HDF error, NetCDF: Can't open HDF5 attribute and two segmentation faults. Sequential opening took 2.1 s. If opening is the bottleneck, convert the collection to Zarr once โ see opening hundreds of NetCDF files.
Code examples
Example 1 โ scan a folder before combining
import glob
import os
import netCDF4
import pandas as pd
def scan(pattern):
rows = []
for path in sorted(glob.glob(pattern)):
row = {"file": os.path.basename(path)}
with open(path, "rb") as f:
head = f.read(8)
if not (head.startswith(b"CDF") or head.startswith(b"\x89HDF")):
row["problem"] = f"not NetCDF: starts {head[:6]!r}"
else:
try:
with netCDF4.Dataset(path) as nc:
row["variables"] = ",".join(sorted(v for v, var in nc.variables.items() if var.ndim >= 3))
row["grid"] = f"{len(nc.dimensions['lat'])}x{len(nc.dimensions['lon'])} from {float(nc['lat'][0]):.4f}"
row["time"] = float(nc["time"][0]) if "time" in nc.variables else None
except OSError as error:
row["problem"] = str(error).split(":")[0] + ": " + str(error).split(":")[1].strip()
rows.append(row)
table = pd.DataFrame(rows)
print(f"{len(table)} files")
for column in ("problem", "variables", "grid"):
if column in table:
counts = table[column].value_counts(dropna=True)
if column == "problem" or len(counts) > 1:
print(f" {column}:", counts.to_dict())
if "time" in table:
timed = table.dropna(subset=["time"])
if timed.time.duplicated(keep=False).any():
print(" duplicate times:", timed.loc[timed.time.duplicated(keep=False), "file"].tolist())
return table
table = scan("broken/*.nc")
35 files
problem: {"not NetCDF: starts b'<!DOCT'": 1, '[Errno -101] NetCDF: HDF error': 1}
grid: {'720x1440 from -89.8750': 32, '280x320 from 0.1250': 1}
duplicate times: ['oisst-avhrr-v02r01.20240115.nc', 'oisst-avhrr-v02r01.20240115_preliminary.nc']
The folder held January plus four planted problems: a preliminary copy of 15 January, an HTML page and a truncated download named as 1 and 2 February, and a cropped 3 February. The scan names each one.
Example 2 โ open strictly and check the result
import xarray as xr
def open_strict(files, dim="time"):
ds = xr.open_mfdataset(files, combine="by_coords", join="exact", data_vars="minimal", coords="minimal")
index = ds.indexes[dim]
if not index.is_monotonic_increasing:
raise ValueError(f"{dim} is not sorted")
if index.has_duplicates:
raise ValueError(f"{int(index.duplicated().sum())} duplicate {dim} values")
empty = [v for v in ds.data_vars if bool(ds[v].isnull().all(dim=[d for d in ds[v].dims if d != dim]).any())]
if empty:
raise ValueError(f"variables entirely missing on some {dim} steps: {empty}")
return ds
for folder in ("clean", "cropped", "missing_variable"):
try:
ds = open_strict(sorted(glob.glob(f"{folder}/*.nc")))
print(f"{folder}: ok {dict(ds.sizes)}")
except Exception as error:
print(f"{folder}: {type(error).__name__}: {str(error).splitlines()[0][:110]}")
clean: ok {'time': 31, 'zlev': 1, 'lat': 720, 'lon': 1440}
cropped: AlignmentError: cannot align objects with join='exact' where index/labels/sizes are not equal along these coordinates (dimensi
missing_variable: AlignmentError: cannot align objects with join='exact' where index/labels/sizes are not equal along these coordinates (dimensi
With join="exact" the folder with a missing variable failed as well, where the default join had combined it silently, so the NaN check after opening was never reached.
Example 3 โ too many open files
import subprocess
import sys
code = ("import xarray as xr; {opt} ds = xr.open_mfdataset('oisst-2024/*.nc'); "
"print('mean', round(float(ds.sst.mean()), 2))")
for option in ("", "xr.set_options(file_cache_maxsize=32);"):
result = subprocess.run(["bash", "-c", f'ulimit -n 64; {sys.executable} -W ignore -c "{code.format(opt=option)}"'],
capture_output=True, text=True)
message = result.stdout.strip() or [line for line in result.stderr.splitlines() if "Error" in line][-1]
print(f"{option or 'default file cache':42} exit {result.returncode}: {message[:100]}")
default file cache exit 1: ValueError: did not find a match in any of xarray's currently installed IO backends ['netcdf4', 'h5n
xr.set_options(file_cache_maxsize=32); exit 0: mean 14.13
ulimit -n 64 imitates a restrictive environment such as a batch job or a container; on a typical workstation the limit is far higher, so the failure appears only on larger collections.
Explanation
Why the errors point at the wrong thing
open_mfdataset does not report which file caused a problem. A cropped file makes the concatenated longitude index non-monotonic, so the error describes the index. A file no backend can read makes backend detection fail, so the error describes the backends. A process that cannot open more files makes the same detection fail. Scanning files individually turns each of those into a named file and a specific problem.
Why some problems raise nothing
xarray is designed to combine imperfect data: an outer join pads missing coordinates, a missing variable becomes NaN, nested concatenation keeps every step it is given. Those defaults keep workflows running, and they mean a duplicate or incomplete file changes the data without any message. Strict options โ join="exact" and explicit checks after opening โ trade that convenience for early failure.
Why the default join is changing
Opening several of these folders printed FutureWarning: In a future version of xarray the default value for join will change from join='outer' to join='exact'. When that change lands, mismatched grids will fail instead of being padded โ the behaviour Example 2 opts into now.
Why parallel opening is unsafe here
Each file open enters the HDF5 C library. The builds used here are not compiled with thread safety, so concurrent opens from dask's thread pool corrupt shared state. The symptoms vary from run to run because they depend on timing, which is why a crash in one attempt and an HDF error in the next point at the same cause.
Edge cases or notes
- Hangs rather than errors usually mean a network file system or a remote URL; test with a local copy first.
- Hundreds of thousands of files make even the metadata scan slow; build a Kerchunk index or convert.
- Different chunking between files is allowed but produces uneven dask chunks.
- Mixed calendars across model files fail when times are combined; see fixing cftime errors.
- Engine-specific failures can be isolated by passing
engine="netcdf4"explicitly. - Compressed
.nc.gzfiles are not NetCDF until decompressed. - Upcoming default changes to
join,compatanddata_varswill turn some silent cases into errors; set them explicitly to keep behaviour stable.
Internal links
- How to open hundreds of NetCDF files as one dataset โ the working workflow
- Fixing xarray's "did not find a match in any of xarray's installed backends" โ the backend error in detail
- How to open a NetCDF file in Python with xarray โ checking single files
- Fixing cftime and out-of-bounds datetime errors in xarray โ calendar mismatches across files
- Regridding explained: bilinear, conservative and nearest for gridded data โ putting files on one grid
- GRIB, NetCDF or Zarr: choosing a format for gridded data โ converting a collection
- Lazy loading explained: why xarray reads nothing until you ask โ what happens after opening
- NetCDF and gridded data explained: dimensions, variables and attributes โ what the scan is checking
FAQ
Why does open_mfdataset say the index is not monotonic?
One file has a different or shifted grid. A single cropped OISST file raised the error for longitude; a latitude shifted by 0.0001ยฐ raised it for latitude. Scan the files' grids to find it.
Why does open_mfdataset say no backend matches when netCDF4 is installed?
One file is not NetCDF โ often an HTML error page from a failed download โ or the process cannot open more files. Both produced the same message here.
Why does my combined dataset have duplicate dates?
A duplicate or preliminary file is in the folder. Nested combining kept both copies of the day; check index.has_duplicates after opening.
How do I fix "too many open files" in open_mfdataset?
Lower xarray's file cache with xr.set_options(file_cache_maxsize=...) or raise the operating system limit. With a 64-file limit, a cache of 32 let the 366-file year open.
Why does parallel=True crash open_mfdataset?
The HDF5 library underneath netCDF4 is not safe for concurrent use in threads in these builds. Six attempts crashed; open sequentially or use separate processes.
How do I make open_mfdataset fail loudly on mismatched files?
Pass join="exact" and check the time index for order and duplicates after opening. The cropped file then raised an alignment error instead of being padded.