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.