Sentry Performance Quotas Killed Our Error Alerts: A Postmortem on Mixing Traces and Issues
We ran out of Sentry transaction quota mid-month and silently stopped capturing new errors on a critical service. Here's how the quota math actually works and what we changed.
A customer pinged us on a Tuesday afternoon asking why a checkout endpoint had been throwing 500s "since the weekend." Sentry showed nothing new. The last issue on that service was dated four days earlier. That gap wasn't because the service was healthy — it was because we had exhausted our Sentry quota and misunderstood what that actually turns off.
This is the breakdown of what happened, what Sentry's quota model actually does under the hood, and the configuration we now ship by default on every Node and Next.js service.
The setup that bit us
We had roughly a dozen services reporting to a single Sentry organization on the Team plan. Most were small internal tools. Two were customer-facing: a Next.js storefront on Vercel and a Node checkout API on AWS Fargate.
When we enabled Sentry Performance a few months earlier, we set tracesSampleRate: 0.2 on the checkout API because we wanted decent trace coverage for a low-traffic but high-value path. The storefront was set to 0.05. Both felt conservative at the time.
What we didn't do:
- Set per-project spike protection
- Separate error and transaction quotas by project
- Alert on quota consumption itself
- Reconsider sampling when a marketing push 4x'd storefront traffic
The marketing team ran a promo. Storefront transactions spiked. Sentry's org-wide transaction quota drained in about nine days instead of thirty. And here's the part we got wrong: we assumed errors and transactions had independent fates. They mostly do — but spike protection, when it kicks in, drops events across categories in ways that are easy to misread on the dashboard.
What Sentry's quota model actually does
Sentry bills by event category: errors, transactions, replays, attachments, cron monitors, and so on. Each has its own monthly reserved volume. When you blow past reserved volume, you either consume on-demand budget (if configured) or events get rejected at ingest with a 429 and an X-Sentry-Rate-Limit header.
Two mechanisms matter here, and they're not the same thing:
Reserved quota exhaustion
When a category is out of quota and you have no on-demand budget, Sentry rejects new events in that category. Your SDK still tries to send them, gets a 429, and drops the event. The Sentry UI shows a banner, but if nobody's watching the org settings page, nobody sees it.
Critically: errors and transactions are separate categories. Running out of transactions should not stop errors. In our case, it didn't — directly.
Spike protection
Spike protection is a separate feature. When Sentry detects an anomalous burst of events in a short window, it throttles ingest for that category for the rest of the hour. It's designed to protect you from a runaway loop blowing your entire month in an afternoon.
Spike protection is per-category, but it triggers on volume anomalies, not on quota. So you can hit spike protection on transactions even if you still have quota left. And when it triggers, the dropped events show up in your "dropped" stats with a reason of smart_rate_limit — which looks a lot like generic rate limiting if you're skimming.
What actually silenced our alerts
Here's the sequence we reconstructed from Sentry's stats page and our own logs:
- Promo traffic 4x'd storefront transactions. Transaction quota depleted around day 20.
- On-demand budget wasn't enabled, so new transactions started getting 429'd.
- The checkout API started throwing a new class of 500s on day 22 — a race condition exposed by higher concurrency.
- Errors from checkout were being accepted. But our alert rule was built on an issue trigger: "notify when a new issue is seen in production."
- The specific error was being grouped under an existing, resolved issue because the stack trace fingerprint matched a bug we'd "fixed" the previous sprint. Sentry auto-reopens resolved issues when they recur — but our alert rule only fired on new issues, not regressions.
- The regression notification path depended on a separate rule we'd never enabled.
So the quota exhaustion wasn't the direct cause of the silent alerts. It was the trigger that made us look, discover the gap, and realize our alert routing had a hole we'd missed for months. The quota banner in the UI is what finally made someone open the project settings page.
The incident was a composite failure: a sampling choice that didn't scale, a missing spike protection config, an alert rule that didn't cover regressions, and no monitoring on the monitoring tool itself.
The config we ship now
First, dynamic sampling instead of a fixed rate. We sample high-value routes at a known rate and everything else at a floor:
import * as Sentry from '@sentry/node';
Sentry.init({
dsn: process.env.SENTRY_DSN,
environment: process.env.NODE_ENV,
tracesSampler: (ctx) => {
const name = ctx.transactionContext?.name ?? '';
// Always trace checkout and payment paths
if (name.includes('/checkout') || name.includes('/payment')) {
return 1.0;
}
// Health checks and static assets: never
if (name.includes('/health') || name.includes('/_next/static')) {
return 0.0;
}
// Everything else: low floor
return 0.02;
},
// Keep errors uncapped by SDK-side sampling
sampleRate: 1.0,
});
Second, we enable spike protection on every project. It's off by default on older projects, and the setting lives under Project Settings → Security & Privacy, not under Quotas. Easy to miss.
Third, we set up a Sentry Metric Alert that watches quota consumption itself. The idea is simple: if we're on track to burn through monthly quota before day 25, we want to know on day 10, not day 28. You can approximate this with Sentry's own stats API and a scheduled job that posts to Slack if the burn rate is off.
# Rough daily check — run as a scheduled job
curl -s "https://sentry.io/api/0/organizations/$ORG/stats_v2/" \
-H "Authorization: Bearer $SENTRY_TOKEN" \
--data-urlencode "field=sum(quantity)" \
--data-urlencode "category=transaction" \
--data-urlencode "statsPeriod=30d" \
--data-urlencode "interval=1d" \
| jq '.groups[0].totals'
Fourth — and this is the one that actually prevented a repeat — we audited every alert rule and added coverage for regressions and issue frequency changes, not just "new issue." Default Sentry alert rules only cover a slice of what you probably care about.
Why we don't just raise the quota
The knee-jerk fix is to buy more quota or enable unlimited on-demand. We considered it. The problem is that transactions are the cheapest thing to over-collect and the least valuable per event. A 50x cost increase to capture 50x more transactions, 95% of which are duplicates of the same five routes, is bad engineering regardless of the budget.
Sampling smarter beats paying more. The exception is errors — we never sample errors below 100% at the SDK level. If you can't afford all your errors, you have an error volume problem, not a Sentry problem.
What we got wrong about observability vendors in general
This incident changed how we think about observability tools generally, not just Sentry. Three principles we now treat as non-negotiable on new projects:
- The observability tool needs its own health signal. If Sentry stops receiving events, something outside Sentry has to notice. We now have a synthetic error from a canary job every hour; if it doesn't appear in Sentry, PagerDuty fires.
- Alert rules decay. The rule you wrote 18 months ago probably doesn't match how your app fails today. We review alert rules quarterly, same as runbooks.
- Quota is a reliability concern, not a billing concern. Finance owns the invoice. Platform owns the quota config, the sampling strategy, and the burn-rate alerting.
If you're setting up Sentry on a new service in 2026, the defaults are better than they were, but spike protection, dynamic sampling, and regression alerts still need deliberate configuration. The out-of-box experience will get you most of the way to catching errors on a quiet service. It will not protect you from a traffic spike that silently reshapes your signal-to-noise ratio.
Where we'd start
If you suspect your setup has the same gaps, do these four things in order, today:
- Open your Sentry org stats page and look at the last 30 days of dropped events, broken down by reason. If you see
smart_rate_limitorrate_limitedwith non-trivial counts, you have a quota or spike problem. - Confirm spike protection is enabled on every project, not just the org.
- Audit your alert rules. Make sure you have coverage for issue regressions and frequency changes, not only new issues.
- Add a weekly scheduled job that posts current quota burn rate to a Slack channel someone actually reads.
None of this requires a vendor migration or a new tool. It's an afternoon of config work that would have saved us a customer-facing incident and a sheepish email thread. If you want help wiring up observability properly on a service that actually matters, that's part of what we do on our DevOps engagements.
Want a team like ours?
72Technologies builds production software for the kind of teams who actually read this blog.
Start a projectKeep reading
Pulumi vs Terraform in 2026: We Migrated Half Our Stacks and Kept the Rest
We spent nine months running Pulumi and Terraform side by side across production. Here's what we moved, what we didn't, and the specific failure modes that made us stop short of a full migration.

The Terraform State Lock That Froze Our Production Deploys for 6 Hours
A stuck DynamoDB lock turned a routine Friday deploy into a six-hour outage window. Here's what actually went wrong, how we unstuck it safely, and the guardrails we wish we'd had.

OpenTelemetry Sampling at 1%: How We Lost the One Trace That Mattered
Head sampling at 1% feels responsible until a P1 incident hits and the trace you need is in the 99% you threw away. Here's how we rebuilt sampling around tail-based rules and what it actually cost.
