|
@@ -168,3 +168,85 @@ counter plus a smoke assertion that publish rate ≥ receive rate, so the
|
|
|
next class of "system looks healthy but publishes are silently failing"
|
|
next class of "system looks healthy but publishes are silently failing"
|
|
|
bug gets caught at smoke time. The `M11_NATS_INVESTIGATION.md` doc
|
|
bug gets caught at smoke time. The `M11_NATS_INVESTIGATION.md` doc
|
|
|
proposes this in the medium-term section.
|
|
proposes this in the medium-term section.
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## F2 verification — 2026-06-16 16:55 EDT (smoke `m11-f2-*`)
|
|
|
|
|
+
|
|
|
|
|
+**What F2 adds:** the M11 NATS investigation's medium-term layer — a
|
|
|
|
|
+publish-success counter on ingestd, a smoke assertion that the publish
|
|
|
|
|
+path tracks the receive path, and PromQL alerts that would have fired
|
|
|
|
|
+on the original NATS issue.
|
|
|
|
|
+
|
|
|
|
|
+**Changes:**
|
|
|
|
|
+
|
|
|
|
|
+| Change | File |
|
|
|
|
|
+|---|---|
|
|
|
|
|
+| `ba_ingestd_nats_publish_total{result=ok|error}` counter | `internal/observability/metrics.go` + `internal/pipeline/pipeline.go` (4 sites: 2 submission errors + observeAsyncAck ok + observeAsyncAck error) |
|
|
|
|
|
+| `reg.MustRegister` includes the new counter | `internal/observability/metrics.go` (549d1e3 — the original commit forgot this) |
|
|
|
|
|
+| `assert_nats_publish_rate_near(target, tolerance, window)` | `scripts/m11_lib.py` |
|
|
|
|
|
+| Per-sample `publish_ok` column in the soak log | `scripts/m11_smoke.py` |
|
|
|
|
|
+| `publish_ok` column in the final summary table | `scripts/m11_smoke.py` (dc71b38) |
|
|
|
|
|
+| 4 PromQL alerts (NATS storage + publish path) | `deploy/prometheus/rules/nats.yml` (new) |
|
|
|
|
|
+| `rule_files` + rule volume mount | `deploy/prometheus/prometheus.yml` + `docker-compose.yml` |
|
|
|
|
|
+
|
|
|
|
|
+**Smoke run (`m11-f2-*`):**
|
|
|
|
|
+
|
|
|
|
|
+```
|
|
|
|
|
+[0m] rate=7443/s publish_ok=7454/s p99=nanms dlq=0 streams=32
|
|
|
|
|
+[1m] rate=7610/s publish_ok=7614/s p99=nanms dlq=0 streams=32
|
|
|
|
|
+[1m] rate=7133/s publish_ok=7133/s p99=nanms dlq=0 streams=32
|
|
|
|
|
+[2m] rate=7114/s publish_ok=7115/s p99=nanms dlq=0 streams=32
|
|
|
|
|
+[2m] rate=7093/s publish_ok=7094/s p99=nanms dlq=0 streams=32
|
|
|
|
|
+[3m] rate=7130/s publish_ok=7129/s p99=nanms dlq=0 streams=32
|
|
|
|
|
+[3m] rate=7141/s publish_ok=7144/s p99=nanms dlq=0 streams=32
|
|
|
|
|
+[4m] rate=7087/s publish_ok=7087/s p99=nanms dlq=0 streams=32
|
|
|
|
|
+[4m] rate=7134/s publish_ok=7135/s p99=nanms dlq=0 streams=32
|
|
|
|
|
+[5m] rate=7147/s publish_ok=7147/s p99=25.0ms dlq=0 streams=32
|
|
|
|
|
+[5m] rate=7132/s publish_ok=7133/s p99=nanms dlq=0 streams=32
|
|
|
|
|
+[6m] rate=7098/s publish_ok=7099/s p99=25.0ms dlq=0 streams=32
|
|
|
|
|
+[6m] rate=7081/s publish_ok=7081/s p99=25.0ms dlq=0 streams=32
|
|
|
|
|
+[7m] rate=7052/s publish_ok=7053/s p99=25.0ms dlq=0 streams=32
|
|
|
|
|
+[7m] rate=7122/s publish_ok=7123/s p99=nanms dlq=0 streams=32
|
|
|
|
|
+[8m] rate=7080/s publish_ok=7081/s p99=nanms dlq=0 streams=32
|
|
|
|
|
+[8m] rate=7110/s publish_ok=7110/s p99=nanms dlq=0 streams=32
|
|
|
|
|
+[9m] rate=7080/s publish_ok=7077/s p99=nanms dlq=0 streams=32
|
|
|
|
|
+[9m] rate=7048/s publish_ok=7048/s p99=nanms dlq=0 streams=32
|
|
|
|
|
+[10m] rate=7163/s publish_ok=7162/s p99=25.0ms dlq=0 streams=32
|
|
|
|
|
+
|
|
|
|
|
+Step 4 — multi-stream backpressure (16 streams × 1k/s):
|
|
|
|
|
+ ✅ 16-stream backpressure loadgen ran without crashes
|
|
|
|
|
+ ✅ rate-limited acks observed: 33 (backpressure working)
|
|
|
|
|
+Step 5 — DLQ invariant: 0 over 10min soak
|
|
|
|
|
+Step 6 — teardown: named volumes preserved
|
|
|
|
|
+
|
|
|
|
|
+🎉 M11 smoke: all checks complete.
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**Every sample shows `rate ≈ publish_ok` within 1/s** — the publish path
|
|
|
|
|
+tracks the receive path, which is the F2 invariant. The F2 assertion
|
|
|
|
|
+would have failed the smoke immediately if the publish path diverged
|
|
|
|
|
+from the receive path by more than 10% of target (600/s threshold),
|
|
|
|
|
+which is exactly the failure mode the M11 10-min soak missed.
|
|
|
|
|
+
|
|
|
|
|
+**Prometheus rules now loaded:**
|
|
|
|
|
+
|
|
|
|
|
+```
|
|
|
|
|
+nats_publish_path: 2 rules
|
|
|
|
|
+ IngestdNatsPublishErrorsHigh (state=inactive)
|
|
|
|
|
+ IngestdReceivePublishMismatch (state=inactive)
|
|
|
|
|
+nats_resource_limits: 2 rules
|
|
|
|
|
+ NatsJetStreamStorageHigh (state=inactive)
|
|
|
|
|
+ NatsJetStreamStorageCritical (state=inactive)
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**Status:** F2 is shipped. M11 dev-playground gate is now defended by:
|
|
|
|
|
+1. The smoke (which would fail the next time a publish path diverges
|
|
|
|
|
+ silently from the receive path)
|
|
|
|
|
+2. The alerts (which would page on-call well before the system went red)
|
|
|
|
|
+3. The F1 cap (which prevents the storage from filling past the limit
|
|
|
|
|
+ in the first place)
|
|
|
|
|
+
|
|
|
|
|
+The combined F1 + F2 closes the M11 NATS investigation's full report.
|
|
|
|
|
+The remaining work is M12 (multi-broker NATS, horizontal scaling,
|
|
|
|
|
+50k/s ceiling, prod-shape cluster).
|