ReviewOS

zig-utils/zig-waf

[WAF-54] Implement disk-backed event queues and batched PostgreSQL ingestion

#55
Closed chris opened this 9 days ago ·
9 days ago

Objective

Implement zstd batches, node-local durable queues, idempotency, libpq binary COPY, durable acknowledgements, reconnect, backpressure, and shutdown recovery.

Deliverables

  • Implement the complete scoped behavior described above.
  • Keep request-path work independent from UI, PostgreSQL, and blocking audit I/O.
  • Use Zig 0.17-dev and dependencies pinned through Pantry; do not add Git submodules.
  • Keep public APIs explicit about ownership, resource limits, errors, security policy, and compatibility.
  • Add focused unit, integration, differential, fuzz, benchmark, and documentation coverage as applicable.

Acceptance criteria

  • The scoped implementation and tests are merged on main in small conventional commits.
  • Zig formatting, focused tests, and the relevant CI suite pass.
  • Compatibility claims have executable evidence and no unexplained skips or silent fallbacks.
  • Untrusted inputs have deterministic bounds; hot paths have benchmark evidence.
  • Documentation and the machine-readable compatibility matrix are updated.

Relationships

Blocked by

Blocks

Baselines

  • ModSecurity 3.0.16
  • Coraza 3.7.0
  • OWASP CRS 4.28.0
  • libinjection 4.0.0
  • Zig 0.17-dev resolved and locked through Pantry
9 days ago

Complete. The ingestion path now survives the failures it was scoped against, with each guarantee tested against a live PostgreSQL 18.4.

Durable queue and shutdown recovery — EventSpool is bounded and snapshots to a single file: a plaintext header, then a Zstandard frame holding the events. restore bounds every read against the decoded body and rejects a truncated, corrupt, or oversized snapshot. Snapshots written by earlier formats are still read, because a restart is exactly when a node is upgraded and a pending queue must survive it.

Idempotency — events carry a node-assigned key; a unique index on (event_key, occurred_at) makes a re-sent batch a no-op. Ingestion returns how many events were actually inserted, so the deduplicated count is visible rather than implied.

Reconnect — drainReconnecting resets the connection and retries once, which is only safe because ingestion is idempotent. Verified against a genuinely severed connection (the server terminates its own backend), asserting the plain drain fails with the events retained.

Backpressure — a full queue refuses new events by default; drop_oldest sheds the stale head to keep the recent tail, counting what it discards so the loss is never silent.

zstd batches — facebook.com/zstd through Pantry, statically linked, behind a bounded compress/decompress pair. A snapshot of 200 events differing only in their query string takes under a quarter of its raw size.

COPY — recordBatchCopy streams a batch through a staging table so the conflict handling COPY cannot express is preserved. Text format, not binary, on purpose: the server then parses timestamps exactly as it does everywhere else, where binary would mean reimplementing its timestamptz encoding on the client and silently mis-dating events if the two ever drifted.

Benchmark evidence — zig build bench-ingestion (PostgreSQL 18.4, local socket, µs/event):

batch14162432642561024
statement11162454448454652
COPY717182543936241815

The paths cross at ~24 events, which is where drain switches.

Commits: f88388c (idempotency), 107ed79 (reconnect), 0243452 (COPY), 4a2ce60 (benchmark), 2bf2a2a (keys through the spool), 82e4ce6 (drain threshold), d12212c + d01333e (zstd), 0f4c20b (overflow policy), e0ccfb2 (docs). Documented in docs/fleet-storage.md.

Sign in to comment on this issue.