Performance budgets
The four limits MainSDK must stay within, how we measure them, and why a regression fails the release.
Status Status is where the work is: Planned (in a milestone, not started), Building (tickets in progress), Preview (usable, may change), then the stability it ships at.
Stability The promise an interface ships with.
- Stable Follows semantic versioning; breaking changes need a new major, 12 months of overlap and a codemod.
- Beta Usable in production; may change before it becomes stable.
- Experimental May change or go away; needs an explicit opt-in in main.yaml.
MainSDK sits on every call your app makes, so its speed is a published, tested number, not a claim. Four budgets hold in normal mode and in degraded mode, and a release that breaks one fails (ADR-0003).
The four budgets
| Budget | Limit | Measured as |
|---|---|---|
| Policy decision | under 1 ms at p99 | In process, Cedar, a reference set of 200 policies and 10,000 decision inputs |
| Interface overhead | under 2 ms at p99 | SDK call minus the raw provider call, against a local stub provider, 10,000 calls |
| Browser subset | under 150 KB gzipped | The browser entry of mainsdk and everything it imports, excluding the opt-in, lazily loaded TypeScript Cedar engine chunk |
| Cold start | under 20 ms added | Process start to first decision, with and without mainsdk loaded |
p50 and p99 are both recorded. Every number is measured on the pure TypeScript core (ADR-0066): the open harness and the budget gate come from spec fail-static and the browser size gate from spec portable-core, both at 0.4.
Normal and degraded mode
flowchart LR
subgraph normal["Normal mode (0.4)"]
n1["decide"]
n2["overhead"]
n3["browser size"]
n4["cold start"]
end
subgraph degraded["Degraded mode (0.4)"]
d0["Cloud blocked +<br/>TUF cache stale +<br/>telemetry buffer full"]
d1["100,000 decisions: p99 under 1 ms"]
d2["10,000 sends on a 50 ms fake: under 2 ms added"]
d3["Cold start under 20 ms,<br/>no wait on TUF or Cloud"]
d0 --> d1 & d2 & d3
end
normal --> gate{"tools/check stage"}
degraded --> gate
gate -- regression --> fail["Fail: names budget,<br/>measured value, baseline"]
gate -- pass --> rel["Results file attached to the release"]The degraded scenarios prove the budgets hold when our services are down. With every MainSDK host blocked, a sample Next.js app serves 1,000 requests with the same responses and p99 latency within 5% of the unblocked run. With the Cloud unreachable, writing 50,000 log records never takes longer than 0.1 ms per write at p99.
How a regression is caught
$ tools/check stage
budget exceeded: decide p99- The harness runs in CI on every release candidate.
- A regression past any limit, in normal or degraded mode, fails
tools/check stageand the release. - The browser size check fails the build at 150 KB or more, and reports the Cedar engine chunk on its own line.
- Every release carries a results file with all four budgets, normal and degraded, and a link to the harness.
Reproduce a number yourself
The benchmark method and harness are open source. Results are written as JSON.
pnpm --filter mainsdk bench -- --suite decide --json
pnpm --filter mainsdk bench -- --suite overhead,coldstart --json
pnpm --filter mainsdk sizeOne engine or one driver
pnpm --filter @mainsdk/testing bench --engine cedar --json
pnpm --filter @mainsdk/testing bench --driver resend --json --region euEach run prints one BenchResult: the subject and its version, the region (us, eu or in), the hardware line, the release, p50 and p99 in milliseconds, throughput, memory, cold start and, for a driver, overheadP99Ms. The settings are fixed so a number can be reproduced: the engine decides over a reference set of 200 policies and 10,000 seeded inputs after a 2,000-call warm-up; a driver is called 10,000 times with 200 calls in flight against a recorded provider stub that answers after 50 ms, and the overhead is the p99 of email.send minus the p99 of posting the same body straight to the stub in the same run. The hardware line comes from the machine that ran it (BENCH_HARDWARE overrides it for a named reference machine), and BENCH_REGION and BENCH_RELEASE set the region and release.
On every release the pipeline runs every engine and driver from each of the three regions on a fixed reference machine, fails when any cell is missing, and fails the budget job when a result breaks a budget, with a line such as policy.decide p99 1.4 ms > 1 ms. The public performance pages and the status page are built from those results and from health checks, and the status page is hosted apart from MainSDK Cloud.
Adapters and drivers
Framework adapters (Next.js, Hono, Express, Node workers) stay within the same overhead and cold-start budgets. A driver's own provider latency is not MainSDK overhead; the overhead budget measures only what we add on top.
Later
| What | Phase |
|---|---|
| Budgets measured on the core paths and every interface | 1 (0.4) |
| Public benchmark pages (0.7): p50 and p99, throughput, memory and cold start for every engine and driver, measured from US, EU and India on every release, with history, and MainSDK against calling each provider's SDK directly | 2 |
| Per-driver performance shown in the driver marketplace | 3 |
Next
- Core and bindings: what the decide and cold-start numbers measure.
- Request lifecycle: the path the overhead budget covers.
- Fail static: why the budgets must hold when our services are down.
Was this page helpful?