Lubili

Founders

Migrating a B2B SaaS from Flat-Rate to Metered Billing Without Re-Architecting Your Entire Database

Switch to usage-based SaaS pricing without database locks or race conditions by replacing in-place counters with an asynchronous, append-only event pipeline.

Lubili5 min read

Moving a B2B SaaS platform from seat-based subscriptions to usage-based pricing sounds like a commercial transition, but engineering teams usually inherit the operational fallout. The easiest implementation shortcut is adding counter columns to existing tenant tables, updating records like monthly_api_calls or storage_bytes directly inside application requests. As traffic scales, these synchronous increments trigger database row locks, slow down critical user queries, and lead to lost counts during race conditions.

Metered billing fails when teams treat consumption as an updateable number in their core database; decoupling usage ingestion into an immutable, append-only log ensures accurate billing without degrading application performance.

To adopt consumption pricing safely, you do not need to rebuild your application data model or migrate to a new database engine. You only need to change how consumption data enters and moves through your infrastructure.

Why In-Place Database Counters Fail

When billing depends on mutable database records, operational bugs turn into revenue disputes. The most common pattern involves running queries like UPDATE organizations SET api_usage = api_usage + 1 WHERE id = :id whenever a customer uses a feature.

This approach breaks under normal production conditions for three reasons:

  1. Row Lock Contention: High-frequency API calls from the same customer target the same organization row simultaneously. Transactions queue behind one another, increasing API response times and exhausting database connection pools.
  2. Lack of Auditability: If a customer contests an invoice at the end of the billing cycle, a raw integer offers zero proof. You cannot reconstruct when the consumption occurred, which API keys generated it, or whether network retries duplicated the counts.
  3. Transaction Rollback Mismatches: If a business action fails downstream after the counter increments, your application must either run a rollback or risk charging the customer for a failed operation. Distributed rollback logic across microservices is notoriously fragile.

The Decoupled Event Stream Pattern

The architectural fix is separating the operational database from the metering pipeline. Your core database remains responsible for authentication, subscription tiers, organization settings, and permissions. Usage data moves entirely through an append-only event stream.

Instead of updating an integer, every billable action emits a discrete usage event containing five standard fields: a customer identifier, an event name, a timestamp, a numerical quantity, and a unique idempotency key.

01ApplicationServiceExecutes customeraction and emits ausage event with an…02MessageBufferQueue or streamabsorbshigh-frequency…03Append-OnlyStoreWorker writes raweventssequentially with…04WindowedAggregatorScheduled jobscompile hourlyconsumption…05BillingGatewayAggregatedmetrics sync tothe billing…
Decoupling usage metering from the application request cycle ensures accurate billing calculations without locking primary database tables.

1. Ingestion via Lightweight Buffer

Your core application should not write usage events directly to disk or run heavy validation inline. The application publishes the event payload to an asynchronous buffer, such as a Redis stream, an Amazon SQS queue, or a lightweight HTTP ingestion gateway. The customer receives an immediate response without waiting for billing logic to complete.

2. The Append-Only Event Store

A background worker consumes events from the buffer and writes them to an append-only table. This table can live in a separate schema, a dedicated read-replica, or a cost-effective document store. Because this table only accepts inserts and never updates existing rows, it avoids write locks entirely.

Billing systems must treat usage records like financial ledgers; once an event is recorded with an idempotency key, it should never be modified, only reversed or adjusted by a new entry.

3. Windowed Aggregations

Billing engines such as Stripe Billing, Lago, or Orb do not need microsecond-level updates for every individual action. They need aggregated totals.

Rather than recalculating millions of raw events at midnight on invoice day, scheduled background jobs run windowed rollups. An hourly cron job aggregates all events for a given organization into a summary ledger, then flushes those totals to the billing provider via its API. This keeps third-party API rate limits under control and makes end-of-month invoice generation instantaneous.

Managing Entitlements and Spend Caps

A common objection to asynchronous metering is enforcing account limits. If a customer is on a capped plan or exhausts prepaid credits, how do you block access without reading the raw event log synchronously?

You do this by decoupling entitlement evaluation from event ingestion:

  • Maintain a cached limit balance in a fast key-value store like Redis.
  • The application checks this balance during authentication or request authorization.
  • The asynchronous worker that ingests usage events decrements or updates this cached balance out of band.
  • If an account hits its ceiling, a flag updates on the organization record in the primary database, switching the tenant into a throttled state on their next request.

A latency window of a few seconds between consumption and quota enforcement is acceptable for almost every B2B contract, and it protects the primary database from bearing the load of continuous real-time balance calculations.

Where to Start

Audit your application to identify the exact metrics that determine pricing. Teams often make the mistake of tracking hundreds of operational metrics before deciding what to charge for.

Pick the single metric tied directly to revenue. Configure your application to emit that event into a background queue, store the raw events in a dedicated append-only table, and run an hourly aggregation script that logs the totals. Once that pipeline runs reliably in parallel with your current setup, connect the aggregator output to your billing gateway and deprecate the old subscription counters.

Apply this to your business.

Tell us what you are working on and we will help you turn this into a concrete next step.