You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Design: memory-bounded adaptive chunking for large raster→H3 hex
Type: design / architecture (not a bug). Goal: make cng-datasets raster scale to large/fine imports with uniform small memory requests automatically, instead of a few oversized big-instance pods that need bespoke handling.
Surfaced by data-workflows #453 (Annual NLCD, res-10 CONUS × 19 yr) and complementary to #172 (multi-year output structure). Applies to any large raster hex — e.g. the 300 m global res-8 layers already flagged as needing 64Gi.
Problem (measured)
The hex unit of parallelism is one h0 base cell (--h0-index 0–121, the only chunk knob). RAM scales with the largest chunk's H3-cell count, and h0 is too coarse at fine resolutions:
res-10 densest CONUS h0 ≈ 282 M cells; default exact-extract peaks at ~140 GiB (live kubectl top, #453).
The build requested 256Gi — ~1.8× the real peak — and NRP 256Gi nodes have only ~250Gi allocatable, so the request barely places → chronic FailedScheduling.
The profile is bimodal: ~116/122 h0 are empty ocean (finish in seconds, ~GiB), a handful of land h0 cost everything. A flat request oversizes the many to survive the few.
Net: today large imports require either an oversized flat request (wastes RAM, starves scheduling) or bespoke two-tier / node-taint handling. We want neither.
Root cause
RAM ≈ f(cells in the single largest chunk), chunk = h0. Coarse chunk + fine resolution = a few enormous chunks. Fix the chunk granularity and the memory problem dissolves.
What exists today (from cng-datasets raster --help)
Chunking: only --h0-index (0–121). No sub-h0 chunking.
--method {exact-extract | warp-centroid} — exact-extract (default) is area-weighted, one row/cell, memory-heavy; warp-centroid is documented "fast and low-memory" (point-samples pixel→cell centroid, one row per warped pixel).
--local-cache-dir / --no-local-cache (each pod localizes the whole COG today, e.g. 1.5 GiB/pod).
Design levers (best → band-aid)
A. Finer H3-parent chunking (the real fix). Chunk by h1 (7 children) or h2 (49) instead of h0. H3 nests perfectly, so sub-cells tile an h0 with no seam artifacts / no dedup (unlike arbitrary bbox tiling of exact-extract, where a res-10 cell straddling a bbox edge would be split/double-counted). Estimated peak (proportional to cell count — needs empirical confirmation): h1 ≈ 20 GiB, h2 ≈ 3 GiB. Makes RAM uniform and small across all pods; density no longer needs to be known; no two-tier. Needs --chunk-resolution N / --parent-index.
B. --method=warp-centroid (low-mem, exists now). For categorical/mode (e.g. NLCD) centroid-sampling ~15 pixels/cell ≈ area-weighted mode. May be a near-free RAM win. Caveats: semantics differ slightly (not for area-weighted continuous layers where exact fractional coverage matters), and it may emit a large per-pixel intermediate needing a GROUP BY. A/B test required (peak RAM + value agreement vs exact-extract).
C. Windowed COG reads. At finer chunking each small pod should read only its sub-region of the COG (COG range reads), not localize the full file — otherwise per-pod I/O explodes with pod count (h2 → thousands of pods each pulling 1.5 GiB).
D. Execution backend. k8s indexed Jobs are fine at h0 (~2318 pods) but finer chunking blows the ~200-pod namespace guideline (h2 ≈ 6000 chunks/COG). This is the documented Armada regime (no pod cap, external queue, --chunk-size 1 so one slow chunk never blocks). Small uniform pods also schedule on any node. So: fine chunking ⇒ Armada backend.
E. Band-aid (superseded by A): two-tier memory (big request only for populated h0). Helps scheduling, not peak RAM.
Target behavior
raster-workflow picks a chunk resolution so the largest chunk stays under a memory budget (e.g. --max-hex-memory 96Gi → auto-select h1/h2/h3), emits uniform small-request chunks, reads windowed COG, and routes to Armada when the chunk count is large — no per-import custom handling. Target: < 120 GiB, ideally < 32 GiB, uniform.
How we proceed (phased — design + test)
Phase 0 — empirical de-risk (no tool changes; can run on NRP now):
warp-centroid vs exact-extract on one dense h0: peak RAM + confirm mode values match.
RAM-vs-granularity: measure peak for a sub-region of one h0 (via --target-extent, for the RAM curve only — not production data) to confirm the ~1/7, ~1/49 scaling before committing to the interface.
Phase 2 — implement + test:correctness gate — sub-chunked hex must be bit-identical (values + coverage) to the h0-chunk baseline on a fixture; performance (RAM/pod, wall-clock, total I/O); edge cases (seam h0, all-nodata chunks, points/lines).
Phase 3 — adopt as the default path for large/fine imports in raster-workflow; keep h0-chunk as a fallback.
Open questions
Auto chunk-resolution from a memory budget, or explicit --chunk-resolution? (auto is the "no custom handling" goal.)
Does warp-centroid already give correct mode/min/max per cell, or only per-pixel rows needing aggregation? (determines whether B is a method or a pipeline stage.)
Windowed COG read: GDAL -projwin/range reads per chunk — worth it vs a one-time COG→tiles pre-split?
Design: memory-bounded adaptive chunking for large raster→H3 hex
Type: design / architecture (not a bug). Goal: make
cng-datasets rasterscale to large/fine imports with uniform small memory requests automatically, instead of a few oversized big-instance pods that need bespoke handling.Surfaced by data-workflows #453 (Annual NLCD, res-10 CONUS × 19 yr) and complementary to #172 (multi-year output structure). Applies to any large raster hex — e.g. the 300 m global res-8 layers already flagged as needing 64Gi.
Problem (measured)
The hex unit of parallelism is one h0 base cell (
--h0-index 0–121, the only chunk knob). RAM scales with the largest chunk's H3-cell count, and h0 is too coarse at fine resolutions:exact-extractpeaks at ~140 GiB (livekubectl top, #453).FailedScheduling.Net: today large imports require either an oversized flat request (wastes RAM, starves scheduling) or bespoke two-tier / node-taint handling. We want neither.
Root cause
RAM ≈ f(cells in the single largest chunk), chunk = h0. Coarse chunk + fine resolution = a few enormous chunks. Fix the chunk granularity and the memory problem dissolves.What exists today (from
cng-datasets raster --help)--h0-index(0–121). No sub-h0 chunking.--method {exact-extract | warp-centroid}—exact-extract(default) is area-weighted, one row/cell, memory-heavy;warp-centroidis documented "fast and low-memory" (point-samples pixel→cell centroid, one row per warped pixel).--target-extent xmin,ymin,xmax,ymaxclip bbox (mosaic clipping).--local-cache-dir/--no-local-cache(each pod localizes the whole COG today, e.g. 1.5 GiB/pod).Design levers (best → band-aid)
A. Finer H3-parent chunking (the real fix). Chunk by h1 (7 children) or h2 (49) instead of h0. H3 nests perfectly, so sub-cells tile an h0 with no seam artifacts / no dedup (unlike arbitrary bbox tiling of
exact-extract, where a res-10 cell straddling a bbox edge would be split/double-counted). Estimated peak (proportional to cell count — needs empirical confirmation): h1 ≈ 20 GiB, h2 ≈ 3 GiB. Makes RAM uniform and small across all pods; density no longer needs to be known; no two-tier. Needs--chunk-resolution N/--parent-index.B.
--method=warp-centroid(low-mem, exists now). For categorical/mode(e.g. NLCD) centroid-sampling ~15 pixels/cell ≈ area-weighted mode. May be a near-free RAM win. Caveats: semantics differ slightly (not for area-weighted continuous layers where exact fractional coverage matters), and it may emit a large per-pixel intermediate needing a GROUP BY. A/B test required (peak RAM + value agreement vs exact-extract).C. Windowed COG reads. At finer chunking each small pod should read only its sub-region of the COG (COG range reads), not localize the full file — otherwise per-pod I/O explodes with pod count (h2 → thousands of pods each pulling 1.5 GiB).
D. Execution backend. k8s indexed Jobs are fine at h0 (~2318 pods) but finer chunking blows the ~200-pod namespace guideline (h2 ≈ 6000 chunks/COG). This is the documented Armada regime (no pod cap, external queue,
--chunk-size 1so one slow chunk never blocks). Small uniform pods also schedule on any node. So: fine chunking ⇒ Armada backend.E. Band-aid (superseded by A): two-tier memory (big request only for populated h0). Helps scheduling, not peak RAM.
Target behavior
raster-workflowpicks a chunk resolution so the largest chunk stays under a memory budget (e.g.--max-hex-memory 96Gi→ auto-select h1/h2/h3), emits uniform small-request chunks, reads windowed COG, and routes to Armada when the chunk count is large — no per-import custom handling. Target: < 120 GiB, ideally < 32 GiB, uniform.How we proceed (phased — design + test)
warp-centroidvsexact-extracton one dense h0: peak RAM + confirmmodevalues match.--target-extent, for the RAM curve only — not production data) to confirm the ~1/7, ~1/49 scaling before committing to the interface.--chunk-resolution/ auto from--max-hex-memory; windowed COG read; Armada auto-route; how sub-chunks compose parents/partitions; preserve the antimeridian/pole handling (Raster Plan A: h0 boundary polyfill double-counts cells, ~16% overcount globally #88/_split_antimeridian (#88 fix) throws GEOSException on polar antimeridian cells #92) under sub-chunking.raster-workflow; keep h0-chunk as a fallback.Open questions
--chunk-resolution? (auto is the "no custom handling" goal.)warp-centroidalready give correctmode/min/maxper cell, or only per-pixel rows needing aggregation? (determines whether B is a method or a pipeline stage.)-projwin/range reads per chunk — worth it vs a one-time COG→tiles pre-split?year=pattern (raster-workflow: built-in multi-year / time-series pattern (per-year COGs + year=-partitioned unified hex) #172): chunk res is orthogonal (per year × sub-chunk).Refs: data-workflows #453 (evidence + measured RAM), boettiger-lab/datasets #172 (multi-year output structure, complementary), AGENTS memory model ("RAM ∝ largest chunk's cell count").