Concepts
Tollgate answers two questions for a metered service: may this caller do this, and what does it cost? on every request, and what did each account use? for billing. This page defines the terms the rest of the documentation uses. Getting started shows them working together.
Two planes
Everything Tollgate does belongs to one of two planes, and the line between them is the design.
- The request path decides admission. It reads only local, immutable or
atomic state, so it performs no I/O, takes no blocking locks, and reads no
clock for a policy decision: the caller passes
nowin. It lives intollgate-coreandtollgate-admission. - The control plane does everything slow in background tasks: it
distributes account state, allocates quota, and records usage. It is
tollgate-client's runtime on each instance, plus a store backend and, optionally,tollgate-server.
The request path never waits for the control plane. When local state is missing, stale or exhausted, the request is refused, and the control plane catches up in the background.
Accounts, principals and permissions
An account is the unit that holds a balance and is billed. A principal is a stable identity that a verified credential resolves to; an account can have several (one per API key, say), and they share its funding.
Permissions are bits. A route declares the bits it requires, a snapshot
carries the bits a principal holds, and begin checks one against the other.
What each bit means is your product's decision.
Snapshots
An account snapshot is the compiled, immutable form of everything admission needs to know about a principal: the account's status, its permissions, its rate, concurrency and shape limits, its cost table, its enforcement mode, and how long the snapshot is valid. You compile it once per policy change, never per request, and the control plane distributes it to every instance.
Snapshots carry a generation. An instance never moves a principal back to
an older generation, and a revocation is a durable, generation-ordered
tombstone, so a delayed message cannot resurrect a revoked credential. A
snapshot past its validity window is refused (SnapshotExpired) until a
replacement arrives, so a partitioned instance stops admitting rather than
serving stale policy; staleness never triggers an inline fetch. See
snapshot operations.
Cost units and cost tables
A cost unit is Tollgate's only currency. Your product's credits, requests or compute units map onto it at compile time. Arithmetic on cost units is always checked; an overflow is a refusal, never a wrap.
A cost table prices a request's workload, a list of (operation, count) pairs: a fixed charge per request plus a weight per operation. You
index operations densely, so a quote is O(1) in the size of the table and
O(distinct operations) in the request.
Leases
A lease is a block of units that the control plane allocates to one instance from an account's central balance. The instance spends it locally with atomic counters, which is why admission needs no round trip. A lease is the answer to "how can many instances enforce one balance without coordinating per request?": units leave the balance when the lease is granted, so the instances together can never spend more than was allocated.
- A lease carries a fencing token. The store accepts usage and releases only when the token matches the lease record, so a stale holder cannot settle against a lease it no longer owns.
- A lease expires. It is usable only until
expires_atminus a safety margin, and the store reclaims it only afterexpires_atplus a grace period, so an instance never spends a lease the store has already taken back. See lease timing. - The runtime refills a lease when it falls below a low-water mark, and releases unspent units on graceful shutdown. A lease abandoned by a crash is forfeited after its grace period and recorded as settlement loss: executed work can never become spendable again.
A request's life
A request moves through typed stages, and each stage can only be reached from the one before it:
beginlooks the principal up once, pins that snapshot generation for the rest of the request, and checks the route's permission.- Reserve a usage slot, room in the usage queue for this request's billing event, before reading the request body. If the queue is full, the request is shed before any work is done.
admitquotes the workload, takes rate and concurrency tokens, and debits the lease. The result is a pending reservation.acquire_capacityapplies the instance's execution-capacity policy, if one is configured.commit, at the moment execution starts, turns the reservation into a charge. It can fail if the lease stopped being usable in the meantime.- Dropping the committed guard records the usage event into the slot reserved in step 2, on every exit path, including a panic that unwinds.
Cancelling at any stage before commit releases the debit and charges
nothing. After commit, success, failure and timeout are all charged. The
exact contract is in Embedding Tollgate.
Fast lane: reserved execution capacity
Tollgate's fast lane protects execution capacity for priority customers
when best-effort traffic saturates an instance. Enable
ExecutionCapacityMode::Reserved and classify priority accounts as Assured.
An operator sets each account's capacity class through
account administration.
- BestEffort requests can use only the shared pool.
- Assured requests try the shared pool first, then their protected reserve. Best-effort traffic cannot consume that reserve, even while it is idle.
For example, an instance with 100 capacity units and a 20-unit reserve allows best-effort work to occupy at most 80 units. Assured work can use the shared 80 plus the protected 20, subject to available capacity and request size.
The reserve is per instance and does not preempt running work or queue requests. An assured request is still refused if neither eligible pool has enough room, and the usual permission, rate, concurrency and funding rules still apply. The feature protects capacity under contention; it does not guarantee a fixed latency or that every priority request will succeed. See the design record for pool sizing and deployment details.
Refusals
Every refusal is a DenyReason with retry advice, and every refusal charges
zero units. Tollgate fails closed: an unknown principal, a stale snapshot,
a missing permission, an exceeded limit, an exhausted lease or a full usage
queue all deny. There is no slower fallback path, because a fallback that
reached the store would put I/O back on the request path.
Enforcement modes
A snapshot's enforcement mode decides the one question a lease can't answer by itself: what happens when the lease cannot fund the quote?
- Strict (the default) denies. An account never spends past what was allocated to it.
- Elastic admits past the lease, up to a per-instance overage cap, and records every unit it admits that way as overage, billed like any other usage.
Only funding is elastic. An unknown principal, a missing permission or a stale snapshot is refused under either mode.
Usage events and the ledger
A usage event is the billing record of one committed request: its request ID, the account, what funded it (a lease, or overage), the units charged, and when. Events are idempotent by request ID and are written to the store in batches, so a retried batch never bills twice. Usage events, not leases, are the billing truth: a lease bounds what an instance may spend, and the events record what it did. See usage accounting.
Every backend keeps a per-account ledger that must satisfy one equation exactly, at every step:
deposited + overage_recorded
== balance + active grants + settled usage + settlement loss + expired
The in-memory store and the PostgreSQL store assert it in the same test suite.
Budgets and periods
An account can have a budget schedule: an allowance deposited at each
period boundary, such as a monthly allowance that resets. When a period
closes, unspent allowance is expired, not lost: it stays in the ledger as
its own term. A direct-store service runs a PeriodRoller to apply schedules;
with tollgate-server, the server does it. See
account administration.
Readiness
An instance is ready when it has current snapshots, usable leases for the accounts it serves, and a healthy usage writer. Readiness is continuous, not a startup flag: an instance that loses its snapshots, its leases or its accounting becomes unready, so a load balancer can stop sending it traffic it would only refuse.
Topologies
The request path is the same in every deployment. What changes is where the control plane's store lives:
- In-process:
MemoryStore, the reference implementation. For tests, examples and single-process tools. - Direct store: each instance's runtime talks to PostgreSQL through
tollgate-store-postgres. - Via server: instances talk to
tollgate-serverover authenticated TLS withHttpStore, and only the server holds database credentials. See the control-plane security runbook.