Skip to content
Fig. 00 — Note 03 · Billing hot pathJuly 2026 · 7 min

Billing hot path

Metering streaming AI inference without slowing it down

Every token is money and every request is a transaction, and the ledger can't be on the hot path. A two-phase billing engine for an AI model gateway.

Notes / metering-streaming-inferencefawzyatwa.com
Note sheetFile 03 / 07 · Notes
Date

July 2026

Reading time

7 min

Slugwriting / metering-streaming-inference
Share this note/writing/metering-streaming-inference
LinkedInX

An inference API with usage-based billing has an uncomfortable property. Every request is a small financial transaction. Before a single token streams, the platform must know the caller can pay for it. After the stream ends, it must charge for exactly what was used, exactly once. And it must do all of this in single-digit milliseconds, because billing overhead is pure tax on every call.

On one of the platforms I built, the shape of the problem came down to three constraints that rule out all the comfortable answers.

Why the obvious designs fail

Read-then-write double-spends. The naive check (read the balance, compare, decrement) is a race. Two concurrent requests both read a balance of 10, both approve a cost of 8, and you've sold 16 credits of inference for 10 credits of money. Load makes it worse precisely when it matters.

The ledger is too slow for the request path. A SQL transaction with row locks fixes correctness and costs tens of milliseconds under contention, on every call forever. The financial system of record and the enforcement point want different data stores.

Streams don't know their cost up front. An LLM response might be ten tokens or ten thousand. You can't charge the exact amount at request time because the exact amount doesn't exist yet.

Reserve, stream, settle

This is the shape hotels and fuel pumps landed on decades ago: a two-phase transaction. At request time, reserve a conservative estimate. After the stream ends, settle for actual usage. Release the hold, and deduct the real cost.

The part that makes it fast is where the phases run. Both are single atomic scripts against an in-memory store. Authorization checks balance − reserved ≥ cost and increments the reservation in one round-trip, with no locks, no transactions, and no second network hop. The store executes scripts serially, so the race disappears by construction rather than by coordination.

Fig. 01two-phase credit billing · reserve, stream, settle
PHASE 1 — AUTHORIZE · SYNCPHASE 2 — SETTLE · ASYNC, AFTER THE STREAM ENDSstreaming proxyinferencemodel serverbilling apifail-closedin-memory storebalances · reservationsusage eventsqueueusage workerledgeracid storestream while reserved1 · authorize2 · reserve — one atomic op3 · usage4releaseidempotent replay — a claim-or-replay script

Settlement is asynchronous. The client's stream ends the moment the model stops, not after bookkeeping. The usage event fans out through a queue, and a worker deducts actual usage, releases the reservation, and writes the durable record. The whole authorize path was budgeted at single-digit milliseconds p50 end to end, itemized per step. That's a design budget, not a production benchmark. Holding it is exactly why nothing heavier than the in-memory store is allowed on the path.

Three speeds of data

The reservation trick only works if you let each store do the one thing it's best at. The architecture runs on three speeds. An in-memory store enforces: balances and reservations, sub-millisecond. A wide-column key-value store counts: usage counters, idempotency records, a warm fallback, single-digit milliseconds. A relational database accounts: the ACID ledger that auditors and refunds live on. The enforcement layer runs minutes ahead of the relational store, and a compaction cron reconciles them.

Fig. 02three speeds · enforce, count, account
IN-MEMORY · SUB-MSWIDE-COLUMN KV · 1–5MSRELATIONAL · ACIDauthorize pathevery inference requestbalances · reservationsenforcementcounters · idempotencyttl tiers · fallbackfinancial ledgerwallets · transactionsreserve · settlededup · countersreconcile — compaction cronmatch the store to the latency the caller needs

At-least-once, exactly-once-billed

Queues redeliver. A settlement event processed twice is a customer charged twice, so a third atomic script makes settlement claim-or-replay. The first delivery claims the settlement, and every redelivery replays to the same result. The same discipline repeats at every async boundary (conditional puts keyed on transaction IDs, dedup on webhook events), which is what lets standard at-least-once queues carry the async tier without making queue-level deduplication the billing boundary.

The failure stance

One last decision makes the whole thing coherent. The billing path fails closed. If its stores are unreachable, the API returns 503 rather than serve unbilled inference. The developer console makes the opposite choice. Its caches fall through to the database, because a cache outage should not take down customers. Failure semantics are a product decision per service, and writing them down before the incident is most of the value.

Provenance of numbers:Ttarget (design budget)Iimplemented