Experiment benchmarks ingestion memory run 01 - kmosoti/FabricO11y GitHub Wiki

Historical report imported 2026-10-10 from docs/experiments/benchmarks/ingestion-memory-run-01.md. Original SHA-256: 813ab2f881458e0359b4ddf0937aa46e274c0e0e851b03ce2e7e8078590cb98f. Measurements and outcomes retain their stated revisions. Links and local environment labels are normalized; $FABRIC_DATA is the mounted experiment drive. See evidence policy.

Ingestion memory and pending-journal query run 01

Executed locally on 2026-10-04 on milestone/streaming-segment-output, with candidate source captured in each population's source-snapshot.tar.gz and working-tree.diff. The memory protocol (local checkout reference docs/experiments/benchmarks/ingestion-memory-protocol.md; not present at origin/main) and pending-query protocol (local checkout reference docs/experiments/benchmarks/pending-journal-query-protocol.md; not present at origin/main) were registered before their respective measurements. This is a finite implementation screen, not completion of the bounded-sealer milestone or target-host qualification.

Mechanism and measurement

The production worker calls segment::build_sealed (local checkout reference crates/fabric-server/src/segment/bounded.rs; not present at origin/main) instead of retaining the complete journal and all projected rows. One frame feeds byte-capped sorted runs for logs, metrics and spans. Merges have at most sixteen inputs and multiple passes. Arrow chunks are flushed explicitly; raw Batches remain exact. Publication, sync and checkpoint/reclaim order are unchanged. A failed build removes its private directory and keeps the journal.

The reference is the retained whole-file read_sealed plus build. Trials use fresh processes and three alternating pairs at each size: seed 42, one node, shuffled timestamps, 128 logs per Batch, 1,024-byte bodies and empty attributes. Nominal sizes refer to body volume; actual journal sizes below include encoding. Fixture creation and answer grading are outside build timing. Incremental Rust heap is the peak above the retained-fixture baseline. Process RSS includes fixture generation and allocator retention and is a separate metric. The probe's global allocator overlaps queries in the pending population, so those allocation values are not reported as builder-only memory.

Memory results

All eighteen measured trials exited 0, preserved raw Batch custody and exact source rows in order, and reported zero spill leftovers. Values are medians of three repetitions; heap was identical across repetitions of each cell.

Actual journal Reference heap Candidate heap Heap reduction Reference wall Candidate wall Wall increase
16.29 MiB 80.79 MiB 29.49 MiB 63.5% 339.53 ms 377.13 ms 11.1%
65.17 MiB 300.82 MiB 30.79 MiB 89.8% 1,347.58 ms 1,450.97 ms 7.7%
260.69 MiB 1,185.29 MiB 32.06 MiB 97.3% 5,306.62 ms 6,633.34 ms 25.0%

Candidate heap increased 4.15% from the middle to largest fixture, below this screen's 80 MiB ceiling. The registered 50% reduction at the middle size was met. This does not establish BS-3 on its different four-shape acceptance suite. Median CPU time was 302.29/335.98, 1,253.98/1,368.37 and 5,039.96/6,319.64 ms (reference/candidate). Whole-process median peak RSS was 153.83/113.27, 558.38/354.50 and 2,183.34/1,145.50 MiB.

The candidate writes more row groups when byte caps close a chunk before 8,192 rows. File and filter hashes therefore differ on these fixtures; exact rows, raw bytes and schemas are preserved. The differential unit fixture separately compares every table/filter byte where byte caps do not change boundaries. Additional disk traffic and CPU are the cost of reducing retained heap; raw per-process I/O counters are in each measurement, and are not physical-device traffic measurements. Spill-only byte attribution is not separately measured.

Query access while sealing

Six measured trials exited 0 on a 65,536-log fixture with 511 Batches in a sealed journal and the final Batch still active. Both Scan and Walk were queried at a fixed snapshot before, during and after building the sealed Segment. Broad 20-row pages and a selective one-row marker in the active journal were graded against the unchanged Python oracle's expected rows, shape and envelope checks.

All 171 sampled first pages matched exact content, order, continuation presence and completeness. Each trial had 20 or 21 during-build samples. Changed, missing and duplicated row controls were rejected. This is first-page grading; full pagination remains the responsibility of the unchanged HTTP history integration tests.

Phase Plan Samples Median engine wall Maximum engine wall
Before Scan 12 90.01 ms 122.86 ms
Before Walk 12 245.65 ms 453.49 ms
During Scan 63 71.28 ms 121.07 ms
During Walk 60 56.00 ms 80.00 ms
After Scan 12 60.39 ms 68.09 ms
After Walk 12 26.50 ms 58.39 ms

These aggregate timing cells mix reference/candidate and query shapes; they show observed response costs, not an isolated acceleration claim. Before samples include cold initialization. They are engine timings, not HTTP p99 or Spool-ACK-to-first-queryable latency. The owner's 30-second headroom means data may take time to become queryable; it does not request an intentional wait. No timer, delay or product freshness guarantee was added. Existing queries can already read committed active and uncovered sealed journals.

Reproduction and retained evidence

Commands ran through python3 tools/resource_group.py --, with verified cgroup memory.max=21474836480, memory.swap.max=0, data-drive builds and scratch.

python3 tools/resource_group.py -- cargo build --offline --locked -p fabric-server --example responsibility_probe --features responsibility-alloc-probe --release
python3 tools/resource_group.py -- python3 -B tools/bench/run_ingestion_memory.py --phase preflight --destination docs/experiments/benchmarks/data/ingestion-memory-run-01/preflight-corrected
python3 tools/resource_group.py -- python3 -B tools/bench/run_ingestion_memory.py --phase memory --destination docs/experiments/benchmarks/data/ingestion-memory-run-01/memory
python3 tools/resource_group.py -- python3 -B tools/bench/run_ingestion_memory.py --phase pending --destination docs/experiments/benchmarks/data/ingestion-memory-run-01/pending

Each command above exited 0. Memory service runtime was 4 min 34.8 s with 3.5 GiB cgroup peak; pending runtime was 1 min 3.0 s with 1.2 GiB peak; swap was zero. The cgroup includes fixture generation and Python grading. This resource cap is an experiment safeguard, not a proof of a 20 GiB production requirement.

Memory summary (local evidence omitted from stage) and pending summary (local evidence omitted from stage) retain raw measurements, query verdicts, binary SHA-256, commands, source snapshots and working-tree diffs. Per-trial cleanup receipts record deleted logical scratch bytes. Source fixtures are gzip-archived by hash on the mounted data drive at $FABRIC_DATA/evidence/ingestion-memory-fixtures; paths/hashes are in trial summaries. They are intentionally retained evidence, not temporary files. Build caches remain on that drive.

Thirty completed preflight/measured trial cleanup receipts all reported owned scratch removed: 12,512,145,815 logical bytes (11.65 GiB cumulative). This is cumulative cleanup, not peak disk usage. Failed preflight scratch was retained separately as evidence.

The first preflight (local evidence omitted from stage) failed because the full-page-chain oracle was incorrectly applied to a sampled first page. Its exit/failure and owned scratch were retained, then the grading correction was registered before measured trials. The corrected preflight (local evidence omitted from stage) ran four trials with negative controls and exited 0. The initial Arrow writer test also found double-finalization; a deterministic empty-table regression now preserves that failure. A corrupt-frame regression checks cleanup after valid rows have already spilled, and a 16 KiB-row test checks byte admission.

Limits and next evidence

The four-shape bounded-sealer suite, injected write/full-disk failures, every crash stage, registered sealer mutants, unchanged soak and remote forwarding were not run here. No installed defaults changed. Per-node freshness, filter and Parquet metadata grow; frame decoding can expand; multiple workers multiply buffer ownership. Scan's decoded journal tail and Walk's tail index remain separate backlog memory risks. The next consumer measurement needs native Spool ACK timestamps and first exact query visibility under sustained arrivals, with the proposed 30-second allowance measured explicitly.

The earlier workstation OOM occurred in query_spec during semantic-mutant verification, according to the kernel log; it was not a measured ingestion trial. Source inspection identifies non-progressing pagination as a plausible cause, but the exact mutant allocation was not reproduced in this screen. That continuation remains interrupted. Whole-workload cgroup containment now covers verification as well as benchmark subprocesses.

Candidate verification

The final python3 tools/resource_group.py -- cargo xtask checks --profile fast exited 0: formatting, compilation, workspace tests with all features, Clippy, layers, core purity, harness/oracle controls, properties, fuzz corpus and simulation. Runtime was 2 min 33.8 s; cgroup peak 705.1 MiB, swap zero. Preserved receipts and checked sources (local evidence omitted from stage) record this working-tree candidate. Earlier attempts exited 1 for a regression test import and lint findings; they were corrected without weakening checks.

Manual documentation check python3 tools/resource_group.py -- /tmp/fabric-bun-runtime/bun-linux-x64/bun tools/docs/check.mjs exited 0. Containment tests (python3 -B tools/test_resource_group.py) and hook contract tests (python3 -B tools/docs/test_hooks.py, retained Bun on PATH), each through the cgroup launcher, exited 0 with six and ten tests. The automatic Bun documentation CI and Stop hook remain disabled as requested.