Skip to content

Graph projection is O(entire history) with full row materialization — needs incremental projection + streaming reads (prod graph frozen 3 weeks) #489

Description

@bgmcmullen

Summary

Graph projection is a full-history rebuild with full materialization: every run executes SELECT <cols> FROM <sourceDataset> — no WHERE, no watermark (plugins-workspace/context-graph/src/project.js, the LLP 0096 shared scan) — through executeQuerySql, which collects every row into a JS array (result.rows) before the rule loop starts. Both the run's memory and its time are O(entire history), so the design has a scaling expiry date independent of any budget or host size. On the hyperparam prod server it has already expired: the kernel's 1 GiB heap-growth budget (LLP 0097) now refuses every projection run — correctly, since millions of materialized rows are genuine retention — and the graph has been frozen at 2026-07-10 for three weeks (hypaware-server#143).

The output side is NOT the problem: three months of fleet history projects to only 9,904 nodes / 28,151 edges — the node/edge maps are trivial. It's the input rows that are materialized wholesale.

Why each cheap fix expires

  • Raise the budget for projection runs: retained rows grow linearly with ingest (hyperparam prod: 202k rows on 07-10 → 1.66M+ by 07-22). Any ceiling is walked to within weeks-to-months, and past it lies the OOM class the budget exists to prevent. Band-aid only.
  • Slice the scan by date partition: bounds memory durably (peak retention = one slice's rows; the accumulated node/edge maps stay small). But each run still scans all of history — a full pass costs 17–42s today, grows linearly forever, and runs daily on the same event loop that serves queries and ingest.

Proposed

  1. Incremental projection with a per-scope watermark: each automatic run reads only rows past the last successful run's high-water mark (date partition + a stable tiebreaker). The graph's upsert/first_seen semantics already accommodate append-style updates. O(new data) per run, constant-ish forever.
  2. Streaming or sliced reads for internal callers: projection should iterate rows without materializing the full result — either a streaming interface on executeQuerySql for trusted daemon jobs, or per-date-slice queries with rules applied per slice. This keeps full REBUILDS possible at any history size.
  3. Full rebuild becomes the recovery path, not the daily path: kept for contract changes, run sliced (and ideally admin-triggered/offline), preserving the derived-rebuildable philosophy (LLP 0053) without paying its cost daily.

Context

Found while diagnosing hypaware-server#143 (prod graph frozen at 07-10 across the 07-16 redeploy that shipped the heap budget): the issue-#9 OOM fix and automatic projection (#124) are incompatible as shipped, and the freeze self-perpetuates (scheduler retries daily into the same refusal). The band-aid/slice/watermark ladder is written up there; this issue tracks the durable fix.

Metadata

Metadata

Assignees

No one assigned

    Labels

    neutral:fixDelegate this issue to neutral for an autonomous fix attempt (reproduce -> fix -> PR)neutral:stuckneutral attempted this but cannot complete it autonomously — needs a human

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions