Rechunker
repository·master·Indexed 41 days ago
https://github.com/pangeo-data/rechunkerA Python library for rechunking large-scale chunked arrays, such as Zarr and TileDB, optimized for parallel execution via Dask. It uses a Push/Pull Consolidated algorithm to transition data from a source to a target chunking scheme while respecting memory limits (max_mem) and minimizing tasks. The library supports rechunking single arrays, Zarr groups, and Xarray datasets, with multiple executor backends including Dask, Beam, Prefect, Pywren, and Python.
What's inside Rechunker
- Rechunker is a Python package designed for efficient and scalable manipulation of chunk structures in chunked array formats like Zarr and TileDB. It allows you to take an input array (or a group of arrays) from a persistent storage device (e.g., a local filesystem or cloud storage bucket) and write it to a new location with a different chunking scheme. Rechunker is built to operate within parallel execution frameworks, specifically Dask, to handle large-scale data processing.
Execute a rechunking plan with the Rechunked object
masterCalling
rechunker.rechunkreturns aRechunkedobject. This object represents a plan, but it does not perform any work immediately. To start the rechunking process, you must explicitly call the.execute()method on theRechunkedobject.Important Lifecycle Management:
- Execution: The operation only begins when
.execute()is called. - Cleanup: You are responsible for manually deleting the intermediate store once the
.execute()method has finished to free up storage space.
# Conceptual workflow rechunked_obj = rechunker.rechunk(...) rechunked_obj.execute() # Manually delete intermediate store here- Execution: The operation only begins when
How the Push / Pull Consolidated algorithm works
masterRechunker uses the Push / Pull Consolidated algorithm to rechunk datasets while satisfying three primary constraints: respecting memory limits, minimizing the number of required tasks (always less than N source chunks + M target chunks), and ensuring the process is embarrassingly parallel (avoiding write locks and inter-worker communication).
The algorithm follows these steps:
- Input Configuration: The user provides a source array (shape, chunk structure, and data type), the desired
target_chunksstructure, and amax_memthreshold. - Determine Write Batches: The algorithm calculates the largest batch of data a single worker can write without exceeding
max_mem. These are calledwrite_chunks. - Determine Read Batches: The algorithm calculates the largest batch of data a single worker can read given
max_mem, attempting to align these withwrite_chunkswhere possible. These are calledread_chunks. - Direct Copy Path: If
write_chunks == read_chunks, the algorithm performs a direct copy from the source to the target, avoiding intermediate datasets. - Intermediate Path: If they do not match, the algorithm defines intermediate chunks as the minimum of
write_chunksandread_chunksalong each axis. Data is first copied from the source to an intermediate array, and then from the intermediate array to the target.
- Input Configuration: The user provides a source array (shape, chunk structure, and data type), the desired
Execute rechunker plans with different executors
masterRechunker plans can be executed using various executors. The default executor is
dask, which runs the plan on a Dask cluster. For debugging purposes or simple local tasks, you can use thepythonexecutor, which executes the plan as a standard Python for loop.Note that most executors require installing additional optional dependencies beyond the core
rechunkerpackage.import zarr from rechunker import rechunk source = zarr.ones((4, 4), chunks=(2, 2), store="source.zarr") intermediate = "intermediate.zarr" target = "target.zarr" # Using the 'python' executor for simple execution rechunked = rechunk( source, target_chunks=(4, 1), target_store=target, max_mem=256000000, temp_store=intermediate, executor="python" ) rechunked.execute()How the `Rechunked` object works
masterA
Rechunkedobject is a handle for a deferred rechunking operation. It encapsulates the execution engine (executor), the scheduling plan, and the paths to the source, intermediate, and target data.When you call
rechunk(), no data is moved immediately; instead, aRechunkedobject is returned. The actual computation is triggered only when.execute()is called. This allows you to inspect the plan or pass the handle around before committing to the heavy computation.source = zarr.ones((4, 4), chunks=(2, 2), store="source.zarr") rechunked = rechunk(source, target_chunks=(4, 1), target_store="target.zarr", max_mem=256000, temp_store="temp.zarr") # The plan can be inspected (type depends on executor) print(rechunked.plan) # The actual work happens here final_array = rechunked.execute()How intermediate chunks are calculated
masterThe rechunking algorithm uses a 'Greatest Common Denominator' (GCD) approach to find intermediate chunks.
- GCD Chunks: It first identifies the smallest possible chunks that evenly fit into both the source and target chunks using the GCD of the dimensions. These are guaranteed to be smaller than both source and target chunks.
- Aggregation: To improve performance, the algorithm attempts to increase these GCD chunks by aggregating them along axes. It checks how many source chunks can be combined before exceeding the
max_memlimit. - Optimization: It iterates through possible aggregation axes (starting from the highest axis) to maximize the chunk size while respecting the memory constraint and the target chunk requirements.
Construct execution workflows with Stage and Pipeline
masterRechunker uses a staged approach to define execution workflows.
Stage
A
Stageis a frozen dataclass representing a single step in a workflow. It contains:function: AStageFunction, which can be a function taking no arguments (exceptconfig) or a function taking a singleconfigargument.name: A string identifier for the stage.mappable: An optionalIterableused for mapping operations.
Pipeline
A
Pipelineis a frozen dataclass that groups a sequence ofStageobjects together with an optionalconfigobject.ParallelPipelines
A
ParallelPipelinestype is aTupleof one or morePipelineobjects intended to be executed in parallel.from rechunker.types import Stage, Pipeline def my_stage_func(config=None): pass stage = Stage(function=my_stage_func, name="my_stage") pipeline = Pipeline(stages=[stage], config={"param": 1}) parallel_pipelines = (pipeline,)Rechunk a single Zarr array
masterTo rechunk a single array, use the
rechunkfunction. You must provide the source array, the desiredtarget_chunks(as a tuple or a dictionary mapping dimension names to sizes), themax_memallowed for intermediate steps, atarget_storefor the final data, and atemp_storefor intermediate data.Note that
rechunkreturns aRechunkedobject, which represents a plan. The actual rechunking process is only performed when you call the.execute()method on this object. Rechunking can be performed using the default Dask multi-threaded scheduler or by providing a DaskClient(e.g., from aLocalClusterorGatewayCluster) to use a distributed cluster.from rechunker import rechunk target_chunks = {"time": 2920, "lat": 25, "lon": 1} max_mem = "1MB" target_store = "air_rechunked.zarr" temp_store = "air_rechunked-tmp.zarr" # Create the plan array_plan = rechunk( source_array, target_chunks, max_mem, target_store, temp_store=temp_store ) # Execute the plan result = array_plan.execute()Rechunk data in cloud storage (GCS)
masterRechunker can be used to rechunk data stored in cloud buckets (like Google Cloud Storage) by using a filesystem mapper (e.g.,
gcsfs).- Use
gcsfs.GCSFileSystemto create a filesystem object. - Use
gcs.get_mapper(url)to create a mapper for the source and target locations. - Pass these mappers to the
rechunkfunction as thetarget_storeandtemp_store. - For large-scale cloud rechunking, it is recommended to use a distributed Dask cluster (e.g., via
Dask Gateway).
import gcsfs from rechunker import rechunk from dask.distributed import Client # Setup cluster and filesystem client = Client() # or a GatewayCluster gcs = gcsfs.GCSFileSystem(requester_pays=True) # Define locations using mappers source_store = gcs.get_mapper("gs://pangeo-cmems-duacs") target_store = gcs.get_mapper("gs://your-bucket/target.zarr") temp_store = gcs.get_mapper("gs://your-bucket/temp.zarr") # Open source array from group import zarr group = zarr.open_consolidated(source_store, mode="r") source_array = group["sla"] # Plan and execute plan = rechunk(source_array, (8901, 72, 72), "1GB", target_store, temp_store=temp_store) plan.execute()- Use
Rechunk a Zarr Group or Xarray Dataset
masterRechunker supports rechunking entire groups (to preserve metadata/coordinates) or Xarray Datasets.
When rechunking a group,
target_chunksmust be a dictionary where keys are variable names. For each variable, you can specify:- A dictionary of dimension-to-chunksize mappings (e.g.,
{"time": 2920, "lat": 25, "lon": 1}). Noneto indicate that the specific array should not be rechunked.
If you provide a simple dictionary mapping dimension names to chunk sizes (e.g.,
{"time": 2920, "lat": 25, "lon": 1}) directly to therechunkfunction when passing an Xarray Dataset, rechunker will apply those consistent chunks to all variables in the dataset.# Rechunking a group with specific settings per variable target_chunks = { "air": {"time": 2920, "lat": 25, "lon": 1}, "time": None, # don't rechunk this array "lon": None, "lat": None, } array_plan = rechunk( source_group, target_chunks, max_mem, target_store, temp_store=temp_store ) array_plan.execute()- A dictionary of dimension-to-chunksize mappings (e.g.,
Use the rechunker.rechunk function
masterThe primary entry point for the library is therechunker.rechunkfunction. This function is used to define a rechunking plan for your data.Configure Zarr options for target and temporary stores
masterWhen rechunking, you can pass additional Zarr configuration via
target_optionsandtemp_options.Supported Zarr options include:
compressorfiltersordercache_metadatacache_attrsoverwritewrite_empty_chunks
If the source is an
xarray.Dataset,target_optionsare used to encode variables similarly to how theencodingparameter works inDataset.to_zarr().