Passa al contenuto principale

ADR-0017 - Billing rating engine: integration in cdr-worker

  • Status: Accepted (2026-05-21)
  • Deciders: Massimo Bagnoli, Claude
  • Implementation tasks: TASK-190
  • Supersedes: nessuno
  • Superseded by: nessuno

Context​

The rating engine computes the billed amount for each CDR and applies the corresponding balance debit. Four implementation locations were considered:

  • A separate worker consuming a cdr.processed subject.
  • The existing cdr-worker, immediately after CDR parsing and before acknowledgement.
  • A periodic background job using arq.
  • A PostgreSQL trigger on CDR insert.

Billing must be near real-time so low-balance thresholds and auto-suspension can react quickly. It must also be idempotent under JetStream redelivery.

Decision​

Extend cdr-worker with rating and balance debit logic.

For each parsed CDR, the worker performs the following steps:

  1. Resolve company and destination.
  2. Call get_effective_rate, introduced by TASK-186.
  3. Compute billed_amount using increment seconds, minimum duration and setup fee.
  4. Run one atomic database transaction containing CDR insert, balance update and balance movement insert.

Failed calls have billed_amount=0 and do not create a balance debit.

Rationale​

  • A single transaction keeps the CDR row, balance debit and balance movement consistent.
  • If the worker fails before commit, the transaction rolls back and JetStream redelivers the message.
  • The cdr.call_id unique constraint prevents double billing on retries.
  • The design avoids an extra NATS hop and avoids races between cdr.inserted and balance.updated events.
  • PostgreSQL triggers would hide business logic in the database and make the rating behavior harder to test.
  • Periodic arq jobs are too delayed for real-time threshold enforcement.

Consequences​

Positive​

  • Rating is applied with low latency.
  • Billing state is updated atomically with CDR persistence.
  • JetStream redelivery remains the retry mechanism for failed processing.

Negative​

  • cdr-worker grows beyond ingestion and now owns billing behavior too.
  • Each CDR insert performs rating lookup, balance locking and movement insert work.
  • Slow tariff lookup can directly slow the CDR consumer.

Mitigations​

  • The pilot target of 50 cps accepts the added database work.
  • If tariff lookup becomes a bottleneck, cache get_effective_rate results in Redis with a 5 minute TTL as a GA optimization.
  • Keep the transaction boundary explicit and covered by tests in TASK-190.

Alternatives considered​

Separate worker on cdr.processed​

Rejected. It adds an extra NATS hop and introduces ordering and race concerns between CDR persistence and balance mutation.

Periodic arq batch​

Rejected. Batch latency breaks real-time balance threshold behavior.

PostgreSQL trigger​

Rejected. It couples business logic to database triggers and makes rating logic harder to test and evolve.

References​

  • ADR-0016: CDR pipeline implementation.
  • TASK-186: get_effective_rate helper.
  • TASK-188: balance movements.
  • TASK-190: cdr-worker rating extension.