Skip to main content
Back to DevlogsEngineering field notes
SaaSPostgreSQLTransactionsArchitecture

A Subscription Limit Is a Concurrency Problem

Why checking a plan before a write is not enough—and how Sofreh makes usage limits atomic without scattering billing logic everywhere.

Published on August 28, 2026
~3 min read

The Check-Then-Write Trap

Suppose a plan allows ten menus and a restaurant currently has nine. Two browser tabs create a menu at the same time. Both requests count nine, both conclude that one more is allowed, and both write. Every individual check was correct; the final state is not.

This time-of-check to time-of-use race appears anywhere a subscription caps a resource: menus, items, categories, staff members, materials, active campaigns, or monthly orders. UI gating cannot solve it. The frontend may make the experience clearer, but only the server can make the invariant true.

One Lock per Tenant and Limit

Sofreh wraps limit-sensitive actions in withLimitIncrement. The helper starts a serializable database transaction and acquires a PostgreSQL transaction-scoped advisory lock derived from the establishment ID and limit key. Requests creating different resources—or resources for different restaurants—continue independently. Only operations competing for the same allowance wait for each other.

Once the lock is held, the service loads effective entitlements, calculates current usage, projects the requested increment, and rejects an overflow before calling the supplied write action. The check and mutation share both the lock and transaction. There is no gap for another request to slip through.

Entitlements Are a Snapshot, Not a Join Graph

A plan is assembled from many feature records, but authorization happens on nearly every meaningful write. Joining subscription, plan, and feature tables each time would turn billing structure into hot-path latency and make historical behavior harder to reason about.

Sofreh materializes effective entitlements onto the subscription as a typed snapshot: boolean capabilities and numeric limits with a consistent shape. Plan changes rebuild that snapshot; ordinary checks read it directly. The server remains authoritative, while the client uses the same shape to explain locked features without being trusted to enforce them.

A Downgrade Asks a Different Question

Creating a resource asks, “will this operation exceed the limit?” Downgrading asks, “is the existing account already incompatible with the new limit?” Treating both as the same check leads either to silent overages or destructive automatic deletion.

Before a plan transition, Sofreh compares the complete usage snapshot with the proposed entitlements and rejects incompatible changes with a conflict response. The restaurant can reduce usage intentionally before trying again. This keeps subscription enforcement strict without letting billing code delete operational data.

A limit is a domain invariant, not a disabled button.

That distinction shaped the final abstraction: the UI communicates, the service coordinates, PostgreSQL serializes, and the write itself remains domain-specific. Billing policy becomes reusable infrastructure instead of repeated conditional logic.