Passa al contenuto principale

ADR-0007: Pipeline CDR — NATS JetStream come bus durevole

Status

Accepted — supersede l'indicazione del docx v5.2 cap. 4.2 ("NATS queue + worker") chiarendone l'edizione (JetStream, non core NATS) e dello STARTUP_PROGETTO.md §13 ("arq su Redis") chiarendone lo scope (job UI/admin, non CDR).

Superseded by: ADR-0016 per l'implementazione concreta della pipeline CDR e ADR-0019 per il placement NATS sul tier stateful.

Context

Il flusso CDR di Akira ha caratteristiche peculiari:

  • Volumetria: target di sostenibilità 10k CDR/s (peak), con burst durante eventi commerciali.
  • Origine: Kamailio modulo acc_json emette un record JSON ad ogni evento di accounting (200 OK, BYE, CANCEL, failure).
  • Destinazione: TimescaleDB hypertable cdr_* (chunk 1 giorno).
  • Durabilità richiesta: se il DB rallenta o va giù, non possiamo perdere CDR (impatto fatturazione diretto). Buffer minimo 24h, target 48h.
  • Ordering: best-effort, non strict (i CDR hanno timestamp self-contained).
  • Backpressure: se il consumer è lento, il broker deve assorbire — Kamailio non può essere bloccato.

Le opzioni preesistenti nel progetto:

  • arq + Redis (raccomandato in STARTUP §13 per job): Redis è in-memory, persistenza AOF non garantisce durabilità a 24-48h volumi 10k/s senza dimensionamenti enormi. Non è il tool giusto per CDR.
  • NATS core: at-most-once, no durabilità → inadatto.
  • Kafka: durabilità eccellente ma operationally pesante (Zookeeper/KRaft, retention complessa, dimensione team operations).
  • INSERT diretto da Kamailio: nessun buffer → se DB lento, perdita CDR o blocco Kamailio.

Decision

Adottare NATS JetStream come bus durevole dedicato per la pipeline CDR. arq+Redis resta invariato per job UI/admin.

Topologia pipeline

Kamailio (acc_json)

▼ publish subject "cdr.raw"
NATS JetStream stream "AKIRA_CDR"
│ retention=24h staging / 48h prod, MaxMsgs=10M
▼ pull consumer "cdr-worker"
cdr-worker (Python, asyncpg COPY)

▼ INSERT batch (5000 row chunk)
TimescaleDB hypertable cdr_*

Subject naming

  • cdr.raw — pubblicato da Kamailio, schema JSON nativo acc_json.
  • cdr.> — wildcard inclusa nello stream AKIRA_CDR per future estensioni della pipeline senza ricreare lo stream.
  • cdr.rated — pubblicato post-rating engine (Fase 2), per consumer downstream (alerting, balance update).
  • cdr.failed — DLQ per record che hanno fallito parsing/insert N volte.

Stream config

ParamStagingProd
MaxAge24h48h
MaxMsgs10M50M
Replicas13
Storagefilefile
Retentionlimitslimits
Discardoldold

Backpressure & alerting

  • Se backlog (num_pending) del consumer cdr-worker > 30s di throughput medio → alert Prometheus.
  • Se backlog > 5 min → alert critico, valutare scale-out worker.
  • Se stream MaxMsgs raggiunto al 80% → alert capacità.

Deployment

  • Staging: NATS server single-node su akira-cache-01-staging, tier stateful definito da ADR-0019, JetStream file storage persistente.
  • Produzione (Fase 2 Opzione A): cluster NATS 3 nodi su VM dedicate (nats-01, nats-02, nats-03), 200GB SSD ciascuna, replicas=3.

arq + Redis resta per

  • Generazione PDF report.
  • Future Tariff applier (cron applica tariffe schedulate).
  • IMAP poller (cap. 16.6, 49.2.4, 58.9).
  • Pattern Analyzer scheduled. Superseded by ADR-0028: Pattern Analyzer scheduled runs use Postgres-backed pattern_runs claim/lease with a systemd-managed worker; NATS is only an optional wakeup, not the durable job state.
  • Telegram outbound queue (rate limit + retry).
  • Email outbound (Open Ticket auto-email).

Consequences

Positive

  • Durabilità reale: 24-48h buffer permette di tollerare DB down totale senza perdita CDR.
  • Disaccoppiamento: Kamailio pubblica fire-and-forget, nessun back-pressure sul signaling path.
  • Scalabilità consumer: pull-based, possiamo aggiungere worker se backlog cresce.
  • Replay: in caso di bug nel cdr-worker, possiamo ri-consumare da timestamp X (durable consumer cursor).

Negative

  • +1 sistema da operare: NATS server (staging) o cluster (prod).
  • Operational complexity: monitoring backlog, replication health, disk usage.
  • Curva apprendimento: il team deve familiarizzare con JetStream (subject vs stream vs consumer).

Neutral

  • 2 broker in produzione (Redis per arq, NATS per CDR) — è una decisione consapevole: ognuno fa il suo mestiere.

Implementation hints

Client Python

  • Libreria: nats-py (async-native, supporta JetStream).
  • Connessione condivisa per app, singleton.
  • Pull consumer con fetch(batch=500, timeout=2s) loop.

Batch insert TimescaleDB

  • Usare asyncpg.connection.copy_records_to_table (COPY) — ordini di grandezza più veloce di INSERT.
  • Chunk size 5000 record o flush a 2s timeout (whichever first).
  • Ack JetStream dopo commit DB (at-least-once → idempotenza su cdr_uuid PK / UPSERT ON CONFLICT DO NOTHING).

Kamailio side

  • Modulo acc_json configurato per publish via TCP a NATS bridge (script Lua o Python sidecar che fa il publish).
  • Alternative: scrivere su file rotativo + filebeat-style sidecar che pubblica (più robusto se NATS down brevemente).

Subject naming convention

  • cdr.raw — input grezzo.
  • cdr.> — subject set dello stream AKIRA_CDR, cosi' cdr.processed, cdr.failed e cdr.audit possono essere introdotti senza migrazione stream.
  • cdr.rated — dopo rating.
  • cdr.failed — DLQ (max 3 retry su raw, poi qui).

Stream config Ansible

  • Role: roles/nats/.
  • Stream config in roles/nats/files/streams/cdr.json.
  • Applicato via nats stream add --config task.

Alternatives considered

  • arq + Redis per CDR: scartato — Redis AOF a 10k/s + 24h retention richiede RAM enorme e fsync killer per throughput.
  • Kafka: scartato — overkill per il team size, operational burden alto, valutabile se cresciamo a > 100k/s.
  • Direct INSERT da Kamailio: scartato — no backpressure, no durabilità, accoppia Kamailio al DB.
  • NATS core (no JetStream): scartato — at-most-once, perdita CDR garantita su failure.
  • RabbitMQ: scartato — più pesante di NATS JetStream, throughput inferiore per use case durable, team meno familiare.
  • PostgreSQL LISTEN/NOTIFY: scartato — non è broker, non ha durabilità sui messaggi.