Skip to content

Commit 2e847d3

Browse files
committed
Bugfix for sharded ingestion
1 parent cc08076 commit 2e847d3

3 files changed

Lines changed: 66 additions & 43 deletions

File tree

src/mdio/builder/templates/base.py

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from __future__ import annotations
44

55
import copy
6+
import logging
67
from abc import ABC
78
from abc import abstractmethod
89
from typing import TYPE_CHECKING
@@ -25,6 +26,8 @@
2526
from mdio.builder.schemas.v1.dataset import Dataset
2627
from mdio.builder.templates.types import SeismicDataDomain
2728

29+
logger = logging.getLogger(__name__)
30+
2831

2932
class AbstractDatasetTemplate(ABC):
3033
"""Abstract base class that defines the template method for Dataset building factory.
@@ -313,15 +316,26 @@ def _add_coordinates(self) -> None:
313316
if "same name twice" not in str(exc):
314317
raise
315318

316-
def _create_chunk_grid(self, exclude_vertical: bool = False) -> RegularChunkGrid | ShardedChunkGrid:
319+
def _create_chunk_grid(
320+
self,
321+
exclude_vertical: bool = False,
322+
is_shardable: bool = True,
323+
variable_name: str | None = None,
324+
) -> RegularChunkGrid | ShardedChunkGrid:
317325
"""Create the appropriate chunk grid based on sharding configuration.
318326
319327
Args:
320328
exclude_vertical: If True, excludes the vertical (last) dimension from shapes.
321329
Used for headers and other non-sample variables.
330+
is_shardable: If False, the variable's dtype doesn't support sharding (e.g.,
331+
structured dtypes, void/bytes). When sharding is configured but
332+
the type is not shardable, a RegularChunkGrid with chunk_shape
333+
equal to shard_shape is returned instead.
334+
variable_name: Optional variable name for logging purposes.
322335
323336
Returns:
324-
RegularChunkGrid if sharding is disabled, ShardedChunkGrid if enabled.
337+
RegularChunkGrid if sharding is disabled or type is non-shardable,
338+
ShardedChunkGrid if sharding is enabled and type supports it.
325339
"""
326340
chunk_shape = self.full_chunk_shape[:-1] if exclude_vertical else self.full_chunk_shape
327341
shard_shape = self.full_shard_shape
@@ -330,8 +344,21 @@ def _create_chunk_grid(self, exclude_vertical: bool = False) -> RegularChunkGrid
330344
# No sharding - use regular chunk grid
331345
return RegularChunkGrid(configuration=RegularChunkShape(chunk_shape=chunk_shape))
332346

333-
# Sharding enabled - create sharded chunk grid
347+
# Adjust shard shape for vertical exclusion
334348
shard_shape = shard_shape[:-1] if exclude_vertical else shard_shape
349+
350+
# When sharding is enabled but type is non-shardable, use shard shape as chunk shape
351+
if not is_shardable:
352+
var_desc = f"Variable '{variable_name}'" if variable_name else "Variable"
353+
logger.warning(
354+
"Sharding is not supported for this dtype. %s will use regular "
355+
"chunking with chunk shape %s (shard shape) instead of sharding.",
356+
var_desc,
357+
shard_shape,
358+
)
359+
return RegularChunkGrid(configuration=RegularChunkShape(chunk_shape=shard_shape))
360+
361+
# Sharding enabled and type supports it - create sharded chunk grid
335362
return ShardedChunkGrid(configuration=ShardedChunkShape(shard_shape=shard_shape, chunk_shape=chunk_shape))
336363

337364
def _add_trace_mask(self) -> None:
@@ -345,8 +372,13 @@ def _add_trace_mask(self) -> None:
345372
)
346373

347374
def _add_trace_headers(self, header_dtype: StructuredType) -> None:
348-
"""Add trace mask variables."""
349-
chunk_grid = self._create_chunk_grid(exclude_vertical=True)
375+
"""Add trace headers variable with structured dtype."""
376+
# Structured dtypes don't support Zarr sharding codec, so use regular chunking
377+
chunk_grid = self._create_chunk_grid(
378+
exclude_vertical=True,
379+
is_shardable=False,
380+
variable_name="headers",
381+
)
350382
self._builder.add_variable(
351383
name="headers",
352384
dimensions=self.spatial_dimension_names,

src/mdio/builder/xarray_builder.py

Lines changed: 23 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
"""Convert MDIO v1 schema Dataset to Xarray DataSet and write it in Zarr."""
22

3-
import logging
4-
53
import numcodecs
64
import numpy as np
75
import zarr
@@ -27,8 +25,6 @@
2725
from mdio.converters.type_converter import to_numpy_dtype
2826
from mdio.core.zarr_io import zarr_warnings_suppress_unstable_numcodecs_v3
2927

30-
logger = logging.getLogger(__name__)
31-
3228

3329
def _import_numcodecs_zfpy() -> "type[numcodecs.ZFPY]":
3430
"""Helper to import the optional dependency at runtime."""
@@ -212,18 +208,31 @@ def to_xarray_dataset(mdio_ds: Dataset) -> xr_Dataset: # noqa: PLR0912, PLR0915
212208
dtype = to_numpy_dtype(v.data_type)
213209
original_chunks = _get_zarr_chunks(v, all_named_dims=all_named_dims)
214210

211+
# Determine the final encoding chunks early to ensure Dask array alignment
212+
# For non-shardable types with sharding configured, we'll use shard_shape as chunk_shape
213+
shard_shape = _get_zarr_shards(v)
214+
is_non_shardable = isinstance(v.data_type, StructuredType) or dtype.kind == "V"
215+
zarr_format = zarr.config.get("default_zarr_format")
216+
217+
# Determine what the final encoding chunks will be
218+
if shard_shape is not None and zarr_format == ZarrFormat.V3 and is_non_shardable:
219+
encoding_chunks = shard_shape
220+
else:
221+
encoding_chunks = original_chunks
222+
215223
# For efficient lazy array creation with Dask use larger chunks to minimize the task graph size
216-
# Initialize with original chunks for lazy array creation
217-
lazy_chunks = original_chunks
218-
if shape != original_chunks:
219-
# Compute automatic chunk sizes based on heuristics, respecting original chunks where possible
220-
auto_chunks = normalize_chunks("auto", shape=shape, dtype=dtype, previous_chunks=original_chunks)
224+
# Initialize with encoding chunks for lazy array creation to ensure alignment
225+
lazy_chunks = encoding_chunks
226+
if shape != encoding_chunks:
227+
# Compute automatic chunk sizes based on heuristics, respecting encoding chunks where possible
228+
auto_chunks = normalize_chunks("auto", shape=shape, dtype=dtype, previous_chunks=encoding_chunks)
221229

222230
# Extract the primary (uniform) chunk size for each dimension, ignoring any variable remainder chunks
223231
uniform_auto = tuple(dim_chunks[0] for dim_chunks in auto_chunks)
224232

225-
# Ensure creation chunks are at least as large as the original chunks to avoid splitting chunks
226-
lazy_chunks = tuple(max(auto, orig) for auto, orig in zip(uniform_auto, original_chunks, strict=True))
233+
# Ensure creation chunks are at least as large as the encoding chunks to avoid splitting chunks
234+
# This is critical for proper Zarr write alignment
235+
lazy_chunks = tuple(max(auto, enc) for auto, enc in zip(uniform_auto, encoding_chunks, strict=True))
227236

228237
data = dask_array.full(shape=shape, dtype=dtype, chunks=lazy_chunks, fill_value=_get_fill_value(v.data_type))
229238

@@ -238,36 +247,13 @@ def to_xarray_dataset(mdio_ds: Dataset) -> xr_Dataset: # noqa: PLR0912, PLR0915
238247
if v.long_name:
239248
data_array.attrs["long_name"] = v.long_name
240249

241-
zarr_format = zarr.config.get("default_zarr_format")
250+
# Use pre-computed zarr_format from above
242251
fill_value_key = "_FillValue" if zarr_format == ZarrFormat.V2 else "fill_value"
243252
fill_value = _get_fill_value(v.data_type) if v.name != "headers" else None
244253

245-
# Handle sharding for Zarr v3
246-
# Note: Sharding is only supported for simple scalar types, not structured types
247-
# (e.g., headers) or void/bytes types (e.g., raw_headers) because Zarr's sharding
248-
# codec cannot handle these dtypes. For non-shardable types, we use the shard shape
249-
# as the chunk shape to maintain consistent I/O patterns across all variables.
250-
shard_shape = _get_zarr_shards(v)
251-
# Check for non-shardable types: structured types and void/bytes types (dtype.kind == 'V')
252-
is_non_shardable = isinstance(v.data_type, StructuredType) or dtype.kind == "V"
253-
254-
# When sharding is configured for Zarr v3 but variable has non-shardable dtype,
255-
# use shard shape as chunk shape to maintain consistent I/O patterns
256-
if shard_shape is not None and zarr_format == ZarrFormat.V3 and is_non_shardable:
257-
dtype_desc = "structured" if isinstance(v.data_type, StructuredType) else "void/bytes"
258-
logger.warning(
259-
"Sharding is not supported for %s dtypes. Variable '%s' will use regular "
260-
"chunking with chunk shape %s (shard shape) instead of sharding.",
261-
dtype_desc,
262-
v.name,
263-
shard_shape,
264-
)
265-
chunks_to_use = shard_shape
266-
else:
267-
chunks_to_use = original_chunks
268-
254+
# Use pre-computed encoding_chunks from above (handles non-shardable types)
269255
encoding = {
270-
"chunks": chunks_to_use,
256+
"chunks": encoding_chunks,
271257
fill_value_key: fill_value,
272258
}
273259

src/mdio/converters/segy.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -609,7 +609,12 @@ def enhanced_add_variables() -> None:
609609

610610
# Create chunk grid metadata using template's method to respect sharding config
611611
# exclude_vertical=True because raw_headers don't have the sample dimension
612-
chunk_grid = mdio_template._create_chunk_grid(exclude_vertical=True)
612+
# is_shardable=False because void/bytes dtypes don't support Zarr sharding codec
613+
chunk_grid = mdio_template._create_chunk_grid(
614+
exclude_vertical=True,
615+
is_shardable=False,
616+
variable_name="raw_headers",
617+
)
613618

614619
# Add the raw headers variable using the builder's add_variable method
615620
mdio_template._builder.add_variable(

0 commit comments

Comments
 (0)