ADR-0016 - CDR pipeline implementation: NATS + sidecar + worker
- Status: Accepted (2026-05-21)
- Deciders: Massimo Bagnoli, Claude
- Implementation tasks: TASK-111, TASK-112, TASK-113
- Supersedes: nessuno
- Superseded by: nessuno
Context
ADR-0007 and ADR-0010 define the asynchronous CDR pipeline pattern, but they do not fully document the concrete Wave 9-11 implementation. The smoke test on 2026-05-21 showed that the pattern existed as architectural guidance while the deployable pieces were still missing: no NATS role, no Kamailio bridge role and no CDR worker role.
This ADR records the implementation decision retroactively for audit trail and GA readiness.
Decision
Implement the CDR pipeline as three separate components.
NATS JetStream broker
infra/roles/nats deploys nats:2.10-alpine on
akira-cache-01-staging, the stateful tier selected by ADR-0019.
The pilot deployment creates JetStream stream AKIRA_CDR with subjects
cdr.raw and cdr.>, single-node topology and replicas=1. GA preparation is
covered by TASK-202 with a 3-node NATS cluster.
kam-cdr-bridge sidecar
infra/roles/kam_cdr_bridge deploys the bridge on sip-01 and sip-02,
co-located with Kamailio.
The sidecar follows the offline wheelhouse install pattern already used by
kam-sync in TASK-106. It tails /var/log/kamailio/acc/cdr.jsonl and publishes
records to NATS subject cdr.raw.
TASK-192 extends the same bridge to publish live-call events on
call.events.<phase> for the Live Calls SSE path.
cdr-worker
infra/roles/cdr_worker runs a durable JetStream consumer on stream
AKIRA_CDR and subject cdr.raw.
The worker inserts records into the TimescaleDB cdr hypertable and is
idempotent through the cdr.call_id unique constraint. TASK-190 extends this
worker with rating-engine execution and balance debit, documented by ADR-0017.
Rationale
- The design is compliant with ADR-0010: database writes stay out of the signaling path.
- JetStream provides durable acknowledgement and replay, so worker restarts do not drop CDR records.
- The sidecar pattern keeps the bridge close to Kamailio while preserving process isolation.
- NATS belongs on the stateful tier because JetStream persists messages on disk; ADR-0019 formalizes that placement.
Consequences
Positive
- The pilot has a small operational surface: one stream, one consumer and a single-node NATS server.
- CDR ingestion, live-call events and rating can share the same event boundary without putting work in Kamailio.
- Worker redelivery plus database uniqueness gives at-least-once processing without double insertion.
Negative
- The pipeline adds NATS, a bridge and a worker to the deployment surface.
- The single-node pilot is not HA until TASK-202 completes.
- Operational runbooks must cover NATS backlog, bridge lag and worker failures.
Alternatives considered
Kafka
Rejected. Kafka is operationally heavier than needed for the pilot scale and team footprint.
PostgreSQL NOTIFY/LISTEN
Rejected. It does not provide the durable replay and consumer backlog model required for billing-sensitive CDR processing.
Direct insert from a Kamailio plugin
Rejected. It would couple SIP signaling to database latency and violates ADR-0010.
Redis Streams
Rejected for this path. Redis remains useful elsewhere, but Akira already uses NATS as the durable event bus for CDR and future event flows.
References
- ADR-0007: CDR pipeline with NATS JetStream.
- ADR-0010: Kamailio CDR emit pattern.
- ADR-0017: billing rating engine integration in
cdr-worker. - ADR-0018: live calls SSE event stream.
- ADR-0019: NATS placement on the stateful tier.
- TASK-111: NATS role.
- TASK-112:
kam-cdr-bridgerole andacc_jsonfix. - TASK-113:
cdr-workerrole.