Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
33 changes: 22 additions & 11 deletions deployment/aws/lambda_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@
Event payload (default / process mode):
{
"chunk_idx": int,
"parent_morton": int,
"parent_order": int,
"child_order": int,
"shard_key": int, # grid-agnostic shard identifier
"parent_order": int, # HEALPix only (omit for other grids)
"child_order": int, # HEALPix only (omit for other grids)
"granule_urls": [str, ...],
"store_path": str, # e.g. "s3://bucket/prefix.zarr"
"s3_credentials": { # creds for reading NSIDC source data
Expand Down Expand Up @@ -182,7 +182,7 @@ def _handle_process(event: Dict[str, Any], context: Any) -> Dict[str, Any]:
json.dumps(
{
"event_type": "lambda_invocation",
"parent_morton": event.get("parent_morton"),
"shard_key": event.get("shard_key"),
"granule_count": len(event.get("granule_urls", [])),
"child_order": event.get("child_order"),
"request_id": context.aws_request_id,
Expand All @@ -192,11 +192,13 @@ def _handle_process(event: Dict[str, Any], context: Any) -> Dict[str, Any]:
)

try:
# Validate required parameters
# Validate required parameters. ``child_order`` is HEALPix-specific and
# only required once the grid is known to be HEALPix (checked below);
# ``parent_order`` is forwarded by the orchestrator for every grid (None
# for non-HEALPix), so its key is always present.
required_params = [
"parent_morton",
"shard_key",
"parent_order",
"child_order",
"granule_urls",
"store_path",
"s3_credentials",
Expand Down Expand Up @@ -228,13 +230,22 @@ def _handle_process(event: Dict[str, Any], context: Any) -> Dict[str, Any]:
if config is None:
from zagg.config import default_config
config = default_config("atl06")
grid = from_config(config, parent_order=event["parent_order"])

# child_order is required for HEALPix runs (drives the leaf order); it is
# absent/unused for non-HEALPix grids.
grid_type = config.output.get("grid", {}).get("type", "healpix")
if grid_type == "healpix" and "child_order" not in event:
error_msg = "Missing required parameters: child_order"
logger.error(error_msg)
return {"statusCode": 400, "body": json.dumps({"error": error_msg})}

grid = from_config(config, parent_order=event.get("parent_order"))

# Process the shard using cloud-agnostic function
from zagg.processing import process_shard
df_out, metadata = process_shard(
grid,
event["parent_morton"],
event["shard_key"],
event["granule_urls"],
s3_credentials=s3_creds,
config=config,
Expand Down Expand Up @@ -274,7 +285,7 @@ def _handle_process(event: Dict[str, Any], context: Any) -> Dict[str, Any]:
json.dumps(
{
"event_type": "processing_complete",
"parent_morton": metadata["parent_morton"],
"shard_key": metadata["shard_key"],
"cells_with_data": metadata["cells_with_data"],
"total_obs": metadata["total_obs"],
"duration_s": metadata["duration_s"],
Expand Down Expand Up @@ -302,7 +313,7 @@ def _handle_process(event: Dict[str, Any], context: Any) -> Dict[str, Any]:
"body": json.dumps(
{
"error": f"Unhandled exception: {str(e)}",
"parent_morton": event.get("parent_morton"),
"shard_key": event.get("shard_key"),
"request_id": context.aws_request_id,
}
),
Expand Down
26 changes: 13 additions & 13 deletions docs/deployment/lambda.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ The Lambda function processes a single morton cell (order 6) by:

```json
{
"parent_morton": 123456,
"shard_key": 123456,
"parent_order": 6,
"child_order": 12,
"granule_urls": [
Expand All @@ -74,24 +74,24 @@ The Lambda function processes a single morton cell (order 6) by:

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `parent_morton` | int | Yes | Morton index of parent cell (order 6) |
| `parent_order` | int | Yes | Order of parent cell (typically 6) |
| `child_order` | int | Yes | Order of child cells for statistics (typically 12) |
| `shard_key` | int | Yes | Grid-agnostic shard identifier (HEALPix: the parent-cell morton index) |
| `parent_order` | int | Yes | Order of parent cell (typically 6); HEALPix-only (`null` for other grids) |
| `child_order` | int | HEALPix only | Order of child cells for statistics (typically 12); omitted for non-HEALPix grids |
| `granule_urls` | list | Yes | Pre-computed list of S3 URLs from catalog |
| `store_path` | str | Yes | Output Zarr store path (e.g. `s3://bucket/prefix.zarr`) |
| `s3_credentials` | dict | Yes | NSIDC S3 credentials for reading source data |
| `output_credentials` | dict | No | Explicit credentials for *writing* the output store. Omit to use the execution role (in-account writes). Supply to write an external / S3-compatible target. Keys: `accessKeyId`, `secretAccessKey`, optional `sessionToken`/`endpointUrl`/`region`. |

!!! note "Shard vs. `parent_order` naming"
The unit of work is a **shard** — one parent (order-6) HEALPix cell. The
!!! note "Grid-neutral event fields"
The unit of work is a **shard** — for HEALPix, one parent (order-6) cell. The
orchestrator and the catalog use that vocabulary (`python -m zagg.catalog`
emits a shard map with `shard_keys` + a `grid_signature`). The Lambda
**event** schema, however, still carries the HEALPix-shaped field names
`parent_morton` / `parent_order` / `child_order` shown above (the handler
requires them — see `deployment/aws/lambda_handler.py`). A grid-neutral
rename of those event fields is tracked in
[#24](https://github.com/englacial/zagg/issues/24), not done here, so the
field names above are the current, accurate ones.
**event** schema uses the grid-neutral field name `shard_key` (the shard
identifier for any grid; for HEALPix it is the parent-cell morton index).
`parent_order`/`child_order` are HEALPix-specific: `parent_order` is
forwarded for every grid (`null` for non-HEALPix), while `child_order` is
only required/sent for HEALPix runs. See `deployment/aws/lambda_handler.py`.
This rename landed via [#24](https://github.com/englacial/zagg/issues/24).

### S3 Credentials

Expand All @@ -105,7 +105,7 @@ s3_creds = get_nsidc_s3_credentials()

# Pass to each Lambda invocation
event = {
"parent_morton": -6134114,
"shard_key": -6134114,
"parent_order": 6,
"child_order": 12,
"granule_urls": [...],
Expand Down
2 changes: 1 addition & 1 deletion src/zagg/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ def get_nsidc_s3_credentials() -> dict:

# Pass to Lambda invocation
event = {
"parent_morton": -6134114,
"shard_key": -6134114,
"s3_credentials": creds,
# ... other params
}
Expand Down
20 changes: 10 additions & 10 deletions src/zagg/processing.py
Original file line number Diff line number Diff line change
Expand Up @@ -487,7 +487,7 @@ def _kernel_aggregate(


def _read_group(
h5obj, group: str, data_source: dict, parent_morton: int, grid, arrow: bool = False
h5obj, group: str, data_source: dict, shard_key: int, grid, arrow: bool = False
):
"""Read and spatially filter one HDF5 group.

Expand All @@ -513,7 +513,7 @@ def _read_group(

# Assign points to leaf cells, then filter to the current shard.
leaf_ids = grid.assign(lats, lons)
mask_spatial = grid.shards_of(leaf_ids) == parent_morton
mask_spatial = grid.shards_of(leaf_ids) == shard_key

if np.sum(mask_spatial) == 0:
return None
Expand Down Expand Up @@ -629,8 +629,8 @@ def process_shard(
raise ValueError(f"handoff must be 'pandas', 'arrow', or 'arrow-kernel', got {handoff!r}")
data_source = config.data_source

parent_morton = int(shard_key)
logger.info(f"Processing shard: {parent_morton}")
shard_key = int(shard_key)
logger.info(f"Processing shard: {shard_key}")
start_time = datetime.now()

# Resolve driver
Expand All @@ -648,7 +648,7 @@ def process_shard(

# Prepare metadata
metadata: ProcessingMetadata = {
"parent_morton": parent_morton,
"shard_key": shard_key,
"cells_with_data": 0,
"total_obs": 0,
"granule_count": len(granule_urls),
Expand All @@ -659,7 +659,7 @@ def process_shard(

# Check for granules
if not granule_urls:
logger.info(f" No granules provided for morton {parent_morton} - skipping")
logger.info(f" No granules provided for shard {shard_key} - skipping")
metadata["error"] = "No granules found"
metadata["duration_s"] = (datetime.now() - start_time).total_seconds()
return pd.DataFrame(), metadata
Expand Down Expand Up @@ -701,7 +701,7 @@ def process_shard(

for g in data_source["groups"]:
try:
chunk = _read_group(h5obj, g, data_source, parent_morton, grid, arrow=use_arrow)
chunk = _read_group(h5obj, g, data_source, shard_key, grid, arrow=use_arrow)
if chunk is not None:
all_reads.append(chunk)
except Exception as e:
Expand All @@ -718,12 +718,12 @@ def process_shard(
metadata["files_processed"] = files_processed

if not all_reads:
logger.info(f" No data after filtering for morton {parent_morton} - skipping")
logger.info(f" No data after filtering for shard {shard_key} - skipping")
metadata["error"] = "No data after filtering"
metadata["duration_s"] = (datetime.now() - start_time).total_seconds()
return pd.DataFrame(), metadata

children = grid.children(parent_morton)
children = grid.children(shard_key)
data_vars = get_data_vars(config)

if handoff == "arrow-kernel":
Expand Down Expand Up @@ -790,7 +790,7 @@ def process_shard(
df_out[col_name] = vals

duration = (datetime.now() - start_time).total_seconds()
logger.info(f"Completed shard {parent_morton} in {duration:.1f}s")
logger.info(f"Completed shard {shard_key} in {duration:.1f}s")

metadata["cells_with_data"] = cells_with_data
metadata["total_obs"] = int(stats_arrays["count"].sum())
Expand Down
19 changes: 12 additions & 7 deletions src/zagg/runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,9 @@ def agg(
if not store_path:
raise ValueError("No store path specified (pass store= or set output.store: in config)")

child_order = get_child_order(config)
# child_order is HEALPix-specific (leaf order); other grids don't define it.
grid_type = config.output.get("grid", {}).get("type", "healpix")
child_order = get_child_order(config) if grid_type == "healpix" else None
_maybe_warn_dense(get_layout(config))

# Resolve driver: kwarg > config > default
Expand Down Expand Up @@ -506,7 +508,7 @@ def _run_lambda(config, catalog_data, store_path, child_order, *,
cells_with_data += 1
elif error not in ("No granules found", "No data after filtering"):
cells_error += 1
logger.warning(f" [{i}/{len(cells)}] morton {result.get('morton')}: {error}")
logger.warning(f" [{i}/{len(cells)}] shard {result.get('shard_key')}: {error}")

if i % 50 == 0:
elapsed = time.time() - start_time
Expand Down Expand Up @@ -631,7 +633,7 @@ def _invoke_lambda_finalize(lambda_client, function_name, store_path,


def _invoke_lambda_cell(
lambda_client, chunk_idx, parent_morton, parent_order, child_order,
lambda_client, chunk_idx, shard_key, parent_order, child_order,
granule_urls, store_path, s3_credentials, *,
function_name, config_dict, output_creds_event=None, max_retries=3,
max_workers=None,
Expand All @@ -645,9 +647,8 @@ def _invoke_lambda_cell(

event = {
"chunk_idx": chunk_idx,
"parent_morton": parent_morton,
"shard_key": shard_key,
"parent_order": parent_order,
"child_order": child_order,
"granule_urls": granule_urls,
"store_path": store_path,
"s3_credentials": {
Expand All @@ -656,6 +657,10 @@ def _invoke_lambda_cell(
"sessionToken": s3_credentials["sessionToken"],
},
}
# child_order is HEALPix-specific; only forward it when set (non-HEALPix
# grids leave it None and the handler doesn't require it).
if child_order is not None:
event["child_order"] = child_order
if config_dict is not None:
event["config"] = config_dict
if output_creds_event is not None:
Expand Down Expand Up @@ -692,7 +697,7 @@ def _invoke_lambda_cell(
body = {}

return {
"morton": parent_morton,
"shard_key": shard_key,
"status_code": result.get("statusCode"),
"body": body,
"wall_time": time.time() - wall_start,
Expand All @@ -717,7 +722,7 @@ def _invoke_lambda_cell(
break

return {
"morton": parent_morton,
"shard_key": shard_key,
"status_code": None,
"body": {},
"wall_time": time.time() - wall_start,
Expand Down
2 changes: 1 addition & 1 deletion src/zagg/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@


class ProcessingMetadata(TypedDict):
parent_morton: int
shard_key: int
cells_with_data: int
total_obs: int
granule_count: int
Expand Down
Loading
Loading