惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Security Archives - TechRepublic
Security Archives - TechRepublic
Project Zero
Project Zero
K
Kaspersky official blog
G
Google Developers Blog
T
Threat Research - Cisco Blogs
T
The Blog of Author Tim Ferriss
Cyberwarzone
Cyberwarzone
Y
Y Combinator Blog
Recorded Future
Recorded Future
Blog — PlanetScale
Blog — PlanetScale
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Cisco Talos Blog
Cisco Talos Blog
Latest news
Latest news
Microsoft Security Blog
Microsoft Security Blog
H
Help Net Security
S
Schneier on Security
P
Palo Alto Networks Blog
H
Hacker News: Front Page
N
News and Events Feed by Topic
N
Netflix TechBlog - Medium
博客园 - Franky
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
SecWiki News
SecWiki News
Cloudbric
Cloudbric
TaoSecurity Blog
TaoSecurity Blog
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
The Hacker News
The Hacker News
C
Check Point Blog
L
LangChain Blog
腾讯CDC
小众软件
小众软件
T
Tenable Blog
Google DeepMind News
Google DeepMind News
GbyAI
GbyAI
L
LINUX DO - 最新话题
A
About on SuperTechFans
Google Online Security Blog
Google Online Security Blog
C
Cisco Blogs
Recent Announcements
Recent Announcements
Hacker News: Ask HN
Hacker News: Ask HN
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Vercel News
Vercel News
雷峰网
雷峰网
美团技术团队
D
DataBreaches.Net
Martin Fowler
Martin Fowler
Help Net Security
Help Net Security
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
F
Full Disclosure
博客园_首页

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant Common SOC 2 Failures (Real World) Stop Vibe-Checking Your AI App: A Practical Guide to Evals How to Use SonarQube and SonarScanner Locally to Level Up Your Code Quality Your Next To-Do App Is Dead — I Replaced Mine with an OpenClaw AI Sign a Nostr event in 60 lines of Python using coincurve — no nostr-sdk, no nbxplorer, no rust toolchain ITGC Audit Explained Like You’re in Big 4 Patch Tuesday abril 2026: Microsoft parcha 163 vulnerabilidades y un zero-day en SharePoint Stop scraping everything: a better way to track competitor price changes Listing on MCPize + the Official MCP Registry while routing payments OUTSIDE the marketplace — how I kept 100% of my x402 revenue Building an AI-Powered Risk Intelligence System Using Serverless Architecture Why We Ripped Function Overloading Out of Our AI Toolchain Testing AI-Generated Code: How to Actually Know If It Works SaaS Churn Is Killing Your Business. Here Is What to Do About It (Without a Support Team) The Speed of AI Is No Longer Linear - And Self-Improving Models Are Why How to Implement RBAC for MCP Tools: A Practical Guide for Engineering Teams From Standard Quote to Persuasive Proposal: AI Automation for Arborists I built a CLI that scaffolds complete multi-tenant SaaS apps Axios CVE-2025–62718: The Silent SSRF Bug That Could Be Hiding in Your Node.js App Right Now The dashboard that ended our friendship Data Pipelines Explained Simply (and How to Build Them with Python) The Hidden Cost of AI Systems Nobody Talks About. undefined vs undeclared, and how typeof behaves Switching from file-based jobs to NATS/Kafka in Rust without changing code io_uring Adventures: Rust Servers That Love Syscalls Why Agentic AI is Killing the Traditional Database The POUR principles of web accessibility for developers and designers Quantum Neural Network 3D — A Deep Dive into Interactive WebGL Visualization How To Install Caveman In Codex On macOS And Windows Automation Pipeline Reliability: Why Your Workflow Breaks When Nobody Is Watching I Built an 'Open World' AI Coding Agent — It Works From ANY Folder From Freelancing to Product: A Tech Service Company's SaaS Transformation China's AI Giants: Adding Tencent Hunyuan & ByteDance Doubao to AI University (74 Providers) On the Vibe Coders and Their Lies clerk: Auto-Summarize Your Claude Code Sessions AI Weekly — 2026/04/10–04/17 | The Model Lockdown Is Here, but the Toolchain Is the Real Battleground AI 週報 — 2026/04/10–2026/04/17 模型封鎖潮來了,但工具鏈才是真戰場 Maybe this is how Open-Source apps are born... 🚀 Fine-Tune LLMs with LoRA and QLoRA: 2026 Guide tRPC v11 + Next.js App Router: End-to-End Type Safety Without the Boilerplate ShadCN UI in 2026: Why I Stopped Installing Component Libraries and Started Owning My Components SaaS Billing in React Server Components: Stripe + Supabase Without a Single `useEffect` Join our DEV Weekend Challenge — $1,000 in Prizes Across TEN winners! Submissions Due April 20 at 6:59 AM UTC. Implementing FSRS Spaced Repetition in Flutter + Supabase — Adding Memory Science to an AI Learning App "I Texted My Localhost From the Train — Claude Code Fixed the Bug Before I Got Home" I Built a Sales Prep AI and It Went Deeper Than Expected Design to Code #2: One JSON, Eleven Outputs Solving the 100M-Row Problem: A Summary Table Pattern for High-Volume Push Notification Logs Flutter Web With Wasm: What Actually Changes For Developers I Built 50 Royalty-Free Soundtracks for My Side Project in a Weekend Using AI Music Generation The Vibe Coding Security Checklist: 7 Things to Check Before You Ship Stop Letting Googlebot Guess Fix Your React App's SEO Right Desconstruindo o Streaming do LinkedIn: Como Criar um Engine de Extração de Vídeo de Alta Performance com HLS e FFmpeg (EDA Part-1) EDA (Exploratory Data Analysis) Explained With Real Life — Why Looking at Your Data Is the Most Important Step in Machine Learning Brand Relationship Management at Scale: Our 4-Touch Outreach System for 200+ Brands Why String.fromEnvironment() Might Return an Empty String in Dart JGuardrails 1.0.0 — Hardening Java LLM Apps Against Jailbreaks, Toxicity, and Prompt Injection Plan and Schedule a Full Week of Threads Content From One Claude Conversation Coding Cat Oran Ep3, Five Tables Changed Everything Updated: BFF Pattern I'm done watching freelancers get buried by 200 proposals. So I'm building the alternative. This is my first post BFS Algorithm in Java Step by Step Tutorial with Examples Tracking LLM Pricing Monthly: An Open Dataset for 22 AI Models How We Measure Content ROI on a Comparison Site: Revenue Attribution Without Perfect Data Introducing Nova AI Ops: The AI-Native Operating System for SRE Teams I built a free desktop video downloader for Windows — Grabbit How Talkie OCR Helps Vision-Impaired & Dyslexic Users Read the World Around Them VRCFaceTracking安装和iPhone面捕配置教程,有bug Even CrowdStrike Can't See Your Agents The Automation Gold Rush: What n8n Workflows and Claude Are Opening Up for Developers Right Now
Building an Error Monitoring Tool Without Pricing Overages
Jijo Bose · 2026-05-27 · via DEV Community

The bill that arrives after the incident

Picture the worst version of a Tuesday. You ship a deploy, a downstream API starts timing out, and your retry logic turns one failure into forty. A single broken code path is now throwing the same exception in a hot loop. By the time you have rolled back, your app has emitted two million error events in ninety minutes.

Your error monitoring tool ingested every single one of them. It was very good at its job.

Then, a few days later, the second incident arrives: an invoice. The $26/month plan you signed up for has quietly become a $390 bill, because you blew through your included event volume and the meter kept running at some fraction of a cent per event. Nobody asked you. Nobody could ask you, because the events arrived faster than any human could approve them.

This is the part of usage-based monitoring that I find genuinely backwards. The pricing is anti-correlated with your wellbeing. The tool charges you the most at the exact moment you are already having your worst day. A traffic spike, a bad release, a noisy dependency, a retry storm: every one of these is both an operational emergency and a billing event. The product that is supposed to help you through the incident is, at the same time, metering you for the privilege.

I am building an error tracker called ErrSight, and early on I decided it would not work this way. No overage charges. Not "low overage charges," not "overage charges with a generous buffer." None. The ceiling on your plan is a real ceiling, not the starting line for a surprise invoice.

That turns out to be a more interesting engineering problem than it sounds, so let me walk through how it actually works.

Overage billing is a choice, not a law of physics

Before the architecture, it is worth being honest about why overage pricing is so common. It is not because it is the only way. It is because it is the easiest and most profitable way.

It is the easiest because the implementation is trivial: count what arrives, multiply by a rate, send the total at the end of the month. You never have to make a decision in the hot path. You never have to tell a customer "no." You just let everything in and reconcile later.

It is the most profitable because the meter runs before the bill arrives. By the time the customer sees the number, the spend already happened. They cannot decline it. The asymmetry is the entire business model.

Once you see it that way, "no overages" stops being a pricing gimmick and becomes a design constraint. It means moving the decision to the front, into the request path, while the customer can still be protected. Concretely, I wrote down three rules:

  1. When an account is out of quota, stop ingesting and say so clearly. Do not silently accept the data and invoice for it.
  2. Make it architecturally impossible to overshoot the cap, even under a burst of concurrent requests, because bursts are exactly when this matters.
  3. If a customer genuinely needs more capacity, make getting it a deliberate, opt-in decision with a known price, not a default that happens to them.

Everything below is in service of those three rules.

Mechanic 1: stop, do not bill

The ingestion endpoint is the front door. Before it does any real work, it asks one question: is this project allowed to ingest right now? The answer comes from a single method that collapses every "no" reason into one place.

def drop_reason
  return "ingestion_paused"        if ingestion_paused?
  return "events_over_limit"       if organization.over_events_limit?
  return "storage_limit_exceeded"  if storage_limit_exceeded?
  nil
end

Enter fullscreen mode Exit fullscreen mode

If there is a reason to drop, the controller returns an HTTP 429 Too Many Requests with a machine-readable code, and that is the end of it.

when "events_over_limit"
  notify_once(@project.organization_id, "events")   # one email, debounced
  render json: {
    error: "Monthly event limit reached",
    code:  "EVENTS_LIMIT_EXCEEDED"
  }, status: :too_many_requests

Enter fullscreen mode Exit fullscreen mode

Notice what is not here. There is no branch that says "over limit, so accept the event and tack it onto the overage counter." Going over your limit is a 429, not a bigger invoice. The client SDK receives a clear, documented code (EVENTS_LIMIT_EXCEEDED) and can back off, buffer, or surface a warning in your own dashboards. The signal is honest: your data is being dropped, here is exactly why, and your bill is not moving.

The customer also gets one email when they hit the wall. Exactly one. A client hammering the endpoint while over quota could otherwise enqueue thousands of identical "you are over your limit" notifications per minute, so the notification is debounced with an atomic cache write that only the first caller in a one-hour window wins:

def notify_once(org_id, kind)
  key = "quota_notified:#{org_id}:#{kind}"
  if Rails.cache.write(key, true, unless_exist: true, expires_in: 1.hour)
    NotifyQuotaOverageJob.perform_later(org_id, kind)
  end
end

Enter fullscreen mode Exit fullscreen mode

The customer gets told. The customer does not get charged.

Mechanic 2: you literally cannot overshoot the cap

Here is the part that took the most care, and it is the reason "no overages" is harder to build than overage billing.

The naive version of a quota check has a race condition that bursts will find immediately:

# WRONG: two concurrent requests can both pass this check
if organization.total_events_this_month + count <= organization.events_limit
  accept(events)
end

Enter fullscreen mode Exit fullscreen mode

Imagine a project sitting at 49,950 events against a 50,000 limit, so there are 50 events of real headroom left. Two batches of 40 events arrive at the same millisecond, handled by two different Puma workers, possibly on two different replicas. Each batch on its own fits comfortably. But both workers read the same starting count of 49,950, both compute 49,950 + 40 = 49,990, both see that as under the limit, and both commit. The project lands at 50,030. The two batches were 80 events against 50 events of headroom, and the cap leaked by 30. Multiply that by a real burst across many workers and your "hard" limit leaks by thousands of events. Each leaked event is either free (you eat the cost) or billed (the customer eats it). There is no version of the leak that is fair.

A guarantee has to actually be a guarantee, so the reservation happens inside a transaction, serialized by a Postgres advisory lock keyed to the organization and billing period:

def reserve_events!(count:)
  org      = organization
  month    = org.quota_period_start
  lock_key = Zlib.crc32("errsight:quota:#{org.id}:#{month}") % 2**31

  transaction do
    # Every project in this org/period serializes through this lock,
    # so two concurrent bursts cannot both read "under limit" and
    # both commit. The lock releases automatically at transaction end.
    connection.execute("SELECT pg_advisory_xact_lock(#{lock_key})")

    current = Usage.where(organization_id: org.id, month: month).sum(:events_count)
    return false if current + count > org.events_limit   # the ceiling holds

    # reserve `count` against this month's usage, then return true
    bump_usage!(count)
    true
  end
end

Enter fullscreen mode Exit fullscreen mode

pg_advisory_xact_lock gives me a mutex that lives in the database, not in any single Ruby process, which is the only place it can live if the limit is going to hold across many workers and replicas. Two bursts hitting the same account at the same instant now line up behind the lock. The first one reserves its quota and commits. The second one reads the post-commit total, sees there is no room, and gets false. The controller turns that false into a 429. The cap is exact, even at the millisecond boundary, even during the spike that an overage model would have cashed in on.

This is the trade at the heart of "no overages." Overage billing never needs this lock, because it never needs to say no. Choosing to say no means choosing to build the machinery that can say it correctly under load.

Mechanic 3: more capacity is a decision, not an accident

A hard cap with no escape hatch is just a worse product. The point is not to punish growth, it is to make growth a choice the customer makes on purpose, with the price known in advance.

So the limit a project is actually checked against is never just the plan limit. It is the plan limit plus any capacity the customer has deliberately added:

def events_limit
  plan_record.events_limit + active_pack_event_credit
end

def active_pack_event_credit(at: Time.current)
  purchased_packs.where(status: "active")
                 .where("expires_at > ?", at)
                 .sum(:events_credit)
end

Enter fullscreen mode Exit fullscreen mode

There are two ways to add capacity, and both are opt-in:

  • Upgrade the plan. The tiers are flat monthly prices with included volume: Free is 5,000 events a month, Pro is $29 for 50,000, Growth is $79 for 200,000, Business is $199 for 750,000. You always know what the next step costs before you take it.
  • Buy an add-on pack. If you are mostly fine but had one heavy month, a $9 pack adds 50,000 events and 2 GB of storage on a 30-day rolling window. It is a one-time purchase, not a recurring commitment, and it stacks if you need a few.

The crucial difference from an overage line item is when the decision happens. An overage charge is a decision the system makes for you, after the spend, that you discover on an invoice. A pack or an upgrade is a decision you make for yourself, before the spend, at a price you agreed to. Same outcome of "you needed more and you paid for more," opposite relationship with the customer.

A second dial: capping the burn rate, not just the total

A hard monthly ceiling solves the billing problem, but go back to the retry storm from the top of this post: two million events in ninety minutes. Even with overage charges off the table, a spike like that can burn through an entire month of quota before lunch, and then ingestion is capped for the rest of the month and you are flying blind through the part of the incident that matters most. A ceiling on the total is not the same as a ceiling on the rate.

So every project also has a per-minute rate limit, and on paid plans the customer sets it themselves. The plan defines the maximum you are allowed to choose, and you pick any number underneath it. It is enforced by a fixed-window limiter that lives in Postgres rather than in process memory, because a per-worker counter cannot hold a real limit once you are running several Puma workers across replicas:

rate = IngestionRateLimiter.check!(@project, count: events_data.length)
unless rate.allowed
  response.headers["Retry-After"]       = rate.retry_after.to_s
  response.headers["X-RateLimit-Limit"] = rate.limit.to_s
  return render json: {
    error:       "Rate limit exceeded, retry in #{rate.retry_after}s",
    code:        "RATE_LIMIT_EXCEEDED",
    retry_after: rate.retry_after
  }, status: :too_many_requests
end

Enter fullscreen mode Exit fullscreen mode

Now a runaway loop can spend at most the configured number of events per minute. The bad deploy still hurts, but it cannot vaporize your whole month in the first ninety minutes, and the Retry-After header tells a well-behaved SDK exactly how long to back off. The customer ends up inside two ceilings at once: the monthly total they are billed against, and the per-minute rate they chose. As a side effect, it also shields my ingestion path from a single misbehaving client, which is the first thing standing between a customer's spike and my own infrastructure bill. Which brings me to the cost side.

The economics that let me say yes to this

There is a reason a lot of founders would call "no overages" financially reckless, and they would be right if you ignore the cost side. If your own costs scale linearly and without bound, then capping the customer's bill while your infrastructure bill runs free is a great way to go broke on your most successful day. "No overages" only works if you have first made your costs predictable.

For an error tracker, the dominant cost driver is storage. Error events are write-heavy, append-mostly, time-ordered, and they pile up fast. So the events table is a TimescaleDB hypertable partitioned on time, with columnar compression that kicks in automatically after a week:

SELECT create_hypertable('events', 'occurred_at', migrate_data => true);

ALTER TABLE events SET (
  timescaledb.compress,
  timescaledb.compress_segmentby = 'project_id',
  timescaledb.compress_orderby   = 'occurred_at DESC, id'
);

-- Compress any chunk older than 7 days.
SELECT add_compression_policy('events', INTERVAL '7 days');

Enter fullscreen mode Exit fullscreen mode

Segmenting by project_id and ordering by time means the recent, hot data stays fast to query for the dashboard, while everything older than a week gets squeezed into compressed columnar chunks. Error events compress extremely well, because they are full of repeating values: the same fingerprints, the same stack frames, the same environment strings, over and over. That repetition is exactly what columnar compression eats for breakfast.

The second lever is retention. Every plan has a retention window (7 days on Free, up to 90 on the higher tiers), and a background job prunes anything past it and re-derives usage so the numbers stay honest:

cutoff = org.retention_days.days.ago
count, bytes = EventRepository.prune_older_than!(project_id: id, cutoff: cutoff)

Enter fullscreen mode Exit fullscreen mode

Compression bounds the cost of the data you keep. Retention bounds how much data you keep at all. Together they turn storage from an unbounded liability into a known, modeled number per plan. Once I can predict my cost per account, I can confidently promise a fixed price to the account.

I extended the same logic to hosting. The app runs on a platform with a hard spending cap and per-second billing, which suits a workload that is quiet most of the time and spiky during incidents. I am not going to ask customers to live with a predictable bill while I refuse to give myself one. The predictability has to go all the way down, or the promise on the pricing page is just optimism.

What this costs me, honestly

I want to be straight about the trade-offs, because "no overages" is not free for the person offering it.

I leave money on the table. Every overage charge I do not send is revenue I did not collect. The spiky months that would have been the most lucrative under metered billing are exactly the months I am choosing to cap. That is real money, and pretending otherwise would be dishonest.

A customer who hits the wall is a worse short-term outcome for them than being billed silently. Dropped events during an incident is a genuinely bad moment. I mitigate it with clear 429 codes, an immediate email, and one-click add-on packs, but the honest version is that a hard cap can bite. The bet is that being told "you are out of room, here is the button" is more respectful than being billed for data you never agreed to pay for, and that developers, of all customers, would rather have the explicit signal.

I had to build the hard version. The advisory lock, the atomic reservation, the debounced notifications, the usage reconciliation after pruning: none of that exists in a system that just counts and multiplies at month end. Saying "no" correctly is more code than never saying it at all.

I think it is worth every bit of that, because of one rule of thumb I keep coming back to:

Your billing model should never be anti-correlated with your customer's worst day.

If the only way your pricing makes its best money is by charging customers more during their outages, their spikes, and their emergencies, then your incentives are quietly pointed away from theirs. I would rather have a model where my best day and my customer's calm month are the same thing, and where their disaster does not show up as a line item on my invoice to them.

Wrapping up

"No overages" sounded like a marketing decision when I started. It turned into an architecture: a hard quota ceiling enforced by a database-level lock so it cannot leak under load, an honest 429 instead of a silent meter, opt-in capacity for the people who genuinely need it, and TimescaleDB compression plus retention to keep my own costs bounded enough that I can afford the promise.

If you have built quota or billing systems that try to stay on the customer's side, I would love to hear how you handled the boundary cases. The lock-and-reserve pattern is the cleanest answer I found, but I doubt it is the only one.

If you would rather see the result than the plumbing, the tool is live at errsight.com.