Source code for gedih3.doctor.fused

# Copyright (C) 2026, University of Maryland. All Rights Reserved.
# Authors: Tiago de Conto, Amelia Grace Holcomb
# For commercial licensing inquiries, contact UM Ventures at umdtechtransfer@umd.edu

"""Fused per-partition scan for gh3_doctor's h3db-tree diagnoses.

When the user asks for multiple per-partition diagnoses in one invocation
(e.g. ``--check db`` or ``--check parquet_health,metadata``), this module
turns N separate ``parallel_map`` dispatches with N file opens per
partition into a single dispatch where each partition's parquet listing,
meta JSON, and parquet footers are opened **once** and shared across
every enabled scan.

The shared state is built by :func:`_build_shared_state` at worker entry,
passed as a ``shared`` dict to each diagnosis's ``_scan_partition_*(dir,
shared=None)`` worker, and dropped when the partition's work is done.
Each scan in :data:`_SCAN_REGISTRY` returns the per-partition payload its
diagnosis already produces today (no schema change), so driver-side
``_finalize_check_from_results`` aggregation is identical to the single-
diagnosis path.

Read-amplification savings on a continental DB scale with the parquet
footer reuse — the heaviest reads — between ``parquet_health``,
``geoparquet_bbox``, and ``backfill``.
"""

from __future__ import annotations

import os
from typing import Any, Callable, Dict, Iterable, List, Optional, Tuple

from .inspect import partition_meta_file, partition_parquet_files
from ..utils import json_read, release_arrow_pool


# Worker registry. Each entry is the picklable per-partition scan callable
# for one diagnosis. The fused worker dispatches enabled scans in
# registry-iteration order — order doesn't matter for correctness because
# every scan returns its own keyed result slot.
#
# Diagnoses populate this at import time via :func:`register_scan`.
_SCAN_REGISTRY: Dict[str, Callable[..., Any]] = {}


[docs] def register_scan(name: str, fn: Callable[..., Any]) -> None: """Register a per-partition scan worker under ``name``. The worker must be picklable (module-level), accept ``partition_dir`` as the first positional arg, and accept ``shared=None`` plus any diagnosis-specific keyword args. """ _SCAN_REGISTRY[name] = fn
def _build_shared_state(partition_dir: str, enabled: Iterable[str]) -> Dict[str, Any]: """Open the per-partition state needed by ``enabled`` scans **once**. Returns a dict with whichever keys are relevant: * ``parquet_files`` : list of parquet paths under the partition. Populated whenever any scan that reads parquet is enabled. * ``meta_file`` : path to the merged partition meta JSON, or None. Populated whenever any scan that touches the meta is enabled. * ``meta_dict`` : parsed meta JSON, or None. Populated only when ``backfill`` is enabled (only consumer today). Parquet footers are *not* prefetched here — the per-file ``pq.ParquetFile`` open is deferred to the diagnosis worker so the file handle's lifetime is tightly scoped (open → read footer → close) and worker RSS doesn't carry N footer handles simultaneously. The driver-side win is the *file-listing* and *meta-locate* dedupe; the per-file open itself stays inside each diagnosis as today, since the footer payload is small and dropping ``pq.ParquetFile`` promptly is the established memory plateau pattern (see parquet_health docstring). """ enabled = set(enabled) need_parquet_list = bool(enabled & { 'metadata', 'geoparquet_bbox', 'parquet_health', 'backfill', }) need_meta_file = bool(enabled & {'metadata', 'backfill'}) need_meta_dict = bool(enabled & {'backfill'}) shared: Dict[str, Any] = {'partition_dir': partition_dir} if need_parquet_list: try: shared['parquet_files'] = partition_parquet_files(partition_dir) except Exception: shared['parquet_files'] = [] if need_meta_file: try: shared['meta_file'] = partition_meta_file(partition_dir) except Exception: shared['meta_file'] = None if need_meta_dict: mf = shared.get('meta_file') shared['meta_dict'] = None if mf is not None: try: shared['meta_dict'] = json_read(mf) except Exception: shared['meta_dict'] = None return shared
[docs] def fused_scan_partition( partition_dir: str, *, enabled_scans: List[str], scan_kwargs: Optional[Dict[str, Dict[str, Any]]] = None, ) -> Dict[str, Any]: """Worker: run every enabled per-partition scan on one partition. Returns ``{scan_name: per_partition_result_or_exception}``. A per-scan exception is captured in the result dict (mirroring ``parallel_map``'s exception-passthrough) so one scan's failure on one partition never aborts the others for that same partition. ``scan_kwargs`` carries per-diagnosis broadcast args namespaced by diagnosis name — e.g. ``{'orphans': {'age_seconds': 86400}, 'backfill': {'products': [...], 'expected_by_product': {...}}}``. """ # Force-import the diagnoses package on the worker side. Each # diagnosis module populates ``_SCAN_REGISTRY`` via a # :func:`register_scan` call at import time, but workers receive # this function by pickle-by-reference — they have never imported # the diagnoses submodules, so the worker's ``_SCAN_REGISTRY`` is # empty until we trigger the side-effect imports here. Without this, # every scan returns None and the driver-side fallback fires for # every diagnosis (slow per-diagnosis path). from . import diagnoses # noqa: F401 scan_kwargs = scan_kwargs or {} shared = _build_shared_state(partition_dir, enabled_scans) out: Dict[str, Any] = {} for name in enabled_scans: fn = _SCAN_REGISTRY.get(name) if fn is None: # Diagnosis isn't fused-aware yet — driver falls back to # single-diagnosis dispatch for this name. continue kwargs = scan_kwargs.get(name, {}) try: out[name] = fn(partition_dir, shared=shared, **kwargs) except Exception as e: out[name] = e # Worker memory hygiene — release any pyarrow buffers that may have # been allocated during the per-scan footer reads. try: release_arrow_pool() except Exception: pass return out
[docs] def fused_eligible_names() -> set: """Return the set of diagnosis names that participate in fusion. The runner uses this to decide whether ``--check X,Y`` is fusible. Only diagnoses whose worker has been registered via :func:`register_scan` are eligible. """ return set(_SCAN_REGISTRY)