Season 1 Episode 2

Rate Limiter

Rate limiters are critical components that control the flow of requests to protect systems from overload and ensure fair resource allocation. This episode explores design patterns, algorithms, and real-world implementations used by major tech companies.

🎧 Listen

A concise written overview — no audio required

Rate Limiter: The Invisible Bouncer Protecting Every API You've Ever Used

Every time you scroll Twitter during the Super Bowl, smash the like button on Instagram, or fire off API calls to Stripe, there's an invisible system deciding whether you're allowed to continue. That system is a rate limiter — and designing one that's fast, fair, and fault-tolerant is one of the most practical system design challenges you'll face.

Why Rate Limiting Matters

Rate limiting isn't just about stopping malicious actors. It's about protecting shared resources from legitimate overuse. A single user with a buggy retry loop can take down your service for everyone. A sudden mobile app reconnection storm can saturate your database. Rate limiting is traffic control for your infrastructure.

The core tension: you want to block abuse without punishing legitimate bursts. A user sending 50 cached requests after reconnecting? Probably fine. That same user sustaining 50 requests per second for an hour? That's a problem.

The Four Algorithms You Need to Know

AlgorithmBurst HandlingMemoryPrecisionBest For
Token BucketExcellentLowGoodAPIs with burst tolerance
Leaky BucketSmooths outputLowGoodSteady-rate processing
Fixed WindowPoor (boundary exploit)Very LowLowSimple use cases
Sliding WindowGoodHigherHighFair, accurate limiting

Token Bucket is the most popular choice. Tokens accumulate at a steady rate (e.g., 10/second) up to a maximum bucket size. Each request costs a token. Bursts are naturally supported — if you've been idle, your bucket is full.

Fixed Window is simple but has a well-known exploit: send 1000 requests at 11:59:59 and another 1000 at 12:00:01 — technically both are within their respective windows, but you just doubled your effective rate.

Sliding Window fixes this by tracking requests across a rolling time period, but requires more memory per user — you need timestamps, not just counters.

Leaky Bucket processes requests at a fixed rate regardless of input burst, acting like a queue with constant drain. Great for smoothing traffic to downstream services.

The Interview Framework

When you get this in an interview, start here:

  1. Clarify requirements — Per-user? Per-IP? Per-endpoint? What's the scale (1K QPS or 100K)?
  2. Non-functional requirements — Latency budget (adding 50ms per request is unacceptable), availability requirements, fail-open vs fail-closed policy
  3. Back-of-envelope math — 10M users × 8 bytes per counter = 80MB. Fits easily in Redis.
  4. High-level architecture — API Gateway → Rate Limit Middleware → Redis counters → Your service
  5. Deep dive — Distributed coordination, algorithm choice, failure modes

Key insight: The rate limiter sits on the hot path of every request. Anything you add here multiplies across your entire QPS.

The Distributed Problem

Here's where "just use Redis" stops being a complete answer. With multiple API servers, you need coordination:

Centralized (Redis): All servers check/increment the same counter. Atomic operations (INCR + EXPIRE, or Lua scripts) prevent race conditions. Trade-off: every request adds a network hop to Redis.

Local + Periodic Sync: Each server counts locally and syncs to a central store periodically. Lower latency, but you lose precision — a user could exceed limits by hitting different servers before sync occurs.

Batched Updates: Buffer several requests worth of counter increments and flush them in batches. Reduces Redis load but introduces a small accuracy window.

The thundering herd is real: 100 servers simultaneously incrementing the same Redis key during a traffic spike. Solutions include request coalescing, Redis clustering, and client-side jitter.

Fail-Open vs Fail-Closed

When your rate limiting infrastructure goes down:

  • Fail-open: Allow all requests through. Preserves availability but removes protection. Most companies choose this — a brief period without rate limiting is better than dropping all traffic.
  • Fail-closed: Reject all requests. Safer against attacks but causes an outage for legitimate users.

The pragmatic approach: fail-open with local fallback counters and circuit breakers. If Redis is unreachable, apply conservative in-memory limits until connectivity recovers.

How the Big Players Built It

Stripe uses Redis with sliding window counters and Lua scripts for atomicity. They batch operations and handle billions of API requests with sub-millisecond latency overhead. Their multi-tier approach applies different limits at the API key, IP, and account levels simultaneously.

Cloudflare does rate limiting at the edge network level — blocking bad traffic before it reaches your origin servers. It's like having bouncers at every entrance to the city, not just your building.

GitHub evolved from simple counters to sophisticated abuse detection. They use a sharded, replicated rate limiter in Redis that differentiates between authenticated and unauthenticated requests, with different limits per endpoint category.

Key Takeaways

  1. Algorithm choice depends on your burst tolerance — Token bucket for most APIs, sliding window when fairness is critical
  2. Distributed coordination is the hard part — Not the algorithm itself
  3. Monitor your false positive rate — If legitimate users are getting rate limited, your thresholds are wrong
  4. Design for failure — Your rate limiter will go down; decide what happens when it does
  5. Layer your defenses — Edge-level, application-level, and database-level limits each catch different problems
  6. Don't add latency to the hot path — Async updates, batching, and local caches are your friends

Rate Limiter:保护你用过的每个 API 的隐形保安

每当你在超级碗期间刷 Twitter,在 Instagram 上疯狂点赞,或者向 Stripe 发起 API 调用时,都有一个隐形系统在决定是否允许你继续操作。这个系统就是 rate limiter——设计一个快速、公平且容错的 rate limiter 是你会遇到的最实用的系统设计挑战之一。

为什么 Rate Limiting 很重要

Rate limiting 不仅仅是为了阻止恶意行为者,它是为了保护共享资源免受合法的过度使用。一个有 bug 的重试循环就能让你的服务对所有人都不可用。移动应用的突然重连风暴可能会让你的数据库饱和。Rate limiting 就是你基础设施的交通管制。

核心矛盾在于:你想要阻止滥用而不惩罚合法的突发流量。用户重连后发送 50 个缓存请求?可能没问题。同一个用户持续一小时每秒发送 50 个请求?那就有问题了。

你需要了解的四种算法

算法突发处理内存占用精度最适合场景
Token Bucket优秀低好能容忍突发的 API
Leaky Bucket平滑输出低好稳定速率处理
Fixed Window差(边界利用)很低低简单用例
Sliding Window好较高高公平、准确的限制

Token Bucket 是最受欢迎的选择。Token 以稳定速率累积(比如每秒 10 个)直到达到最大桶容量。每个请求消耗一个 token。天然支持突发——如果你一直空闲,你的桶就是满的。

Fixed Window 简单但有个众所周知的漏洞:在 11:59:59 发送 1000 个请求,在 12:00:01 再发送 1000 个——技术上都在各自的窗口内,但你实际上把有效速率翻了一倍。

Sliding Window 通过跟踪滚动时间段内的请求来修复这个问题,但每个用户需要更多内存——你需要时间戳,而不仅仅是计数器。

Leaky Bucket 无论输入突发如何,都以固定速率处理请求,就像一个恒定排水的队列。非常适合平滑到下游服务的流量。

面试框架

当你在面试中遇到这个问题时,从这里开始:

  1. 明确需求 — 按用户?按 IP?按端点?规模是多少(1K QPS 还是 100K)?
  2. 非功能性需求 — 延迟预算(每个请求增加 50ms 是不可接受的)、可用性要求、fail-open vs fail-closed 策略
  3. 粗略计算 — 1000 万用户 × 每个计数器 8 字节 = 80MB。轻松放入 Redis。
  4. 高层架构 — API Gateway → Rate Limit Middleware → Redis 计数器 → 你的服务
  5. 深入探讨 — 分布式协调、算法选择、故障模式

关键洞察: Rate limiter 位于每个请求的热路径上。你在这里添加的任何东西都会在整个 QPS 上成倍放大。

分布式问题

这里"直接用 Redis"不再是完整答案。有了多个 API 服务器,你需要协调:

中心化(Redis): 所有服务器检查/递增同一个计数器。原子操作(INCR + EXPIRE,或 Lua 脚本)防止竞态条件。权衡:每个请求都要增加到 Redis 的网络跳数。

本地 + 定期同步: 每个服务器本地计数并定期同步到中央存储。延迟更低,但失去精度——用户可能在同步发生前通过访问不同服务器超出限制。

批量更新: 缓冲几个请求的计数器递增并批量刷新。减少 Redis 负载但引入小的准确性窗口。

Thundering herd 是真实存在的:100 个服务器在流量高峰期同时递增同一个 Redis key。解决方案包括请求合并、Redis 集群和客户端抖动。

Fail-Open vs Fail-Closed

当你的 rate limiting 基础设施宕机时:

  • Fail-open: 允许所有请求通过。保持可用性但移除保护。大多数公司选择这个——短暂没有 rate limiting 比丢弃所有流量要好。
  • Fail-closed: 拒绝所有请求。对攻击更安全但对合法用户造成中断。

实用方法:fail-open 配合本地回退计数器和熔断器。如果 Redis 不可达,应用保守的内存限制直到连接恢复。

大公司是怎么构建的

Stripe 使用 Redis 配合 sliding window 计数器和 Lua 脚本保证原子性。他们批量操作并处理数十亿 API 请求,延迟开销在亚毫秒级。他们的多层方法同时在 API key、IP 和账户级别应用不同限制。

Cloudflare 在边缘网络级别做 rate limiting——在坏流量到达你的源服务器之前就阻止它。就像在城市的每个入口都有保安,而不仅仅是你的建筑。

GitHub 从简单计数器演进到复杂的滥用检测。他们使用分片、复制的 Redis rate limiter,区分认证和未认证请求,每个端点类别有不同限制。

关键要点

  1. 算法选择取决于你的突发容忍度 — 大多数 API 用 Token bucket,公平性关键时用 sliding window
  2. 分布式协调是难点 — 不是算法本身
  3. 监控你的误报率 — 如果合法用户被 rate limit,你的阈值就错了
  4. 为故障而设计 — 你的 rate limiter 会宕机;决定它宕机时会发生什么
  5. 分层防御 — 边缘级、应用级和数据库级限制各自捕获不同问题
  6. 不要给热路径增加延迟 — 异步更新、批处理和本地缓存是你的朋友

🏗 Architecture Diagram

↗ Open full screen

Metadata


Segment 1: The Problem (~2 min)

Alex Welcome back to Byte by Design! I'm here with my co-host who definitely tried to DOS our coffee machine this morning...

Blake Hey! I just wanted my caffeine fix. But speaking of DOS attacks, today we're talking rate limiters — basically the bouncer at the club of your API.

Alex *laughs* That's... actually not a terrible analogy. You know what's wild? Every time you hit "like" on Instagram or send a tweet, there's this invisible system deciding whether you're being too spammy.

Blake Right? I never thought about it until I tried to bulk-upload photos once and got that dreaded "Rate limit exceeded" message. Suddenly I'm like, wait, how does Instagram even know I'm going too fast?

Alex Exactly! And here's the thing — it's not just about stopping spam. When you're handling millions of requests per second like Twitter during the Super Bowl, you need rate limiting to prevent your entire system from melting down. One user with a buggy script shouldn't be able to take down your API for everyone else.

Blake Oh that's nasty. So it's like... traffic control for your servers?

Alex Perfect analogy. But here's where it gets tricky — you need to be fair but also handle legitimate bursts. Like, if someone's mobile app reconnects and sends 50 cached requests at once, that's probably okay. But 50 requests per second for an hour? That's suspicious.


Segment 2: Interview Framework (~3 min)

Alex So if you got this in an interview, where do you start?

Blake Okay, requirements gathering time. Functional requirements — we need to limit requests per user, return proper error codes when limits are hit, and... handle different rate limits for different endpoints?

Alex Good start! Don't forget about non-functional stuff. What's our scale? Are we talking 1000 requests per second or 100,000? Latency matters too — adding 50ms to every request for rate limiting is not gonna fly.

Blake Right, and what about availability? If our rate limiter goes down, do we fail open or fail closed?

Alex Ooh, good catch. Let's say we're building for a social media API — 10 million users, 1000 requests per user per hour, peaks at 100K QPS during events. Back-of-envelope math... that's roughly 28 requests per second average, but we need to handle those bursts.

Blake For storage, if we're tracking per-user counters... 10 million users times maybe 8 bytes per counter, that's only 80MB. Pretty manageable in Redis.

Alex Nice! High-level architecture — API gateway with rate limiting middleware, Redis for counter storage, and probably some async processing for non-critical updates. The key is keeping the hot path fast.

Blake Wait, but what about distributed systems? If I have multiple API servers, they all need to coordinate on these limits, right?

Alex *laughs* And there's the million-dollar question! That's where things get spicy.


Segment 3: Deep Dive (~3 min)

Alex Okay, now let's get into the hard parts. First big decision — which algorithm? Token bucket, sliding window, fixed window, or leaky bucket?

Blake I always get these confused. Token bucket is like... you get tokens at a steady rate and spend them on requests?

Alex Exactly! It's great for handling bursts — you can save up tokens and then spend them all at once. But sliding window is more memory-intensive but fairer. Fixed window is simple but has that boundary problem where you can get 2x your limit by hitting the boundary just right.

Blake Oh that's sneaky! So someone could send 1000 requests at 11:59:59 and another 1000 at 12:00:01?

Alex Bingo! Now, distributed coordination — this is where it gets nasty. You could use Redis with atomic operations, but now you're adding network latency to every request. Or you could do local counting with periodic sync, but then you lose accuracy.

Blake Hmm, what about the thundering herd problem? If 100 servers all try to update the same Redis counter at once...

Alex *groans* Don't even get me started. You need batching, you need proper Redis clustering, maybe even eventual consistency. Stripe actually does some clever stuff here with multiple Redis instances and reconciliation.

Blake And what happens when Redis goes down? Do we just... let everyone through?

Alex That's the classic fail-open vs fail-closed dilemma. Most companies fail open because availability trumps perfect rate limiting. But then you need local fallbacks, circuit breakers, the whole nine yards.


Segment 4: How They Actually Built It (~2 min)

Alex So how does Stripe actually handle this in production? They're processing billions of API requests with sub-millisecond latency impact.

Blake I read their blog post — they use Redis with sliding window counters, but the clever part is how they batch operations and use Lua scripts for atomicity.

Alex Right! And Cloudflare takes it even further — they do rate limiting at their edge network level. So they're blocking bad traffic before it even hits your servers. That's like having bouncers at every entrance to the city, not just your building.

Blake That's actually brilliant. GitHub has a fun story too — they started with simple counters but had to evolve to handle sophisticated abuse patterns. Turns out people get really creative when trying to scrape GitHub repos.

Alex *laughs* Oh I bet. Twitter's approach during high-traffic events is fascinating too. They basically have multiple tiers — aggressive limiting for suspicious patterns, but they relax limits for verified accounts during major news events.

Blake Makes sense. You don't want to rate limit the President's Twitter account during a crisis because your algorithm thinks they're tweeting too much.

Alex Exactly! The real world is messier than the textbook version. You need business logic, special cases, and a lot of monitoring to see how your limits affect real user behavior.


Segment 5: Interview Tips (~1 min)

Alex Alright, rapid-fire interview tips for this one...

Blake First — always ask about the specific use case. Rate limiting for a payment API is very different from rate limiting for social media posts.

Alex Yes! And don't just jump to Redis. Ask about latency requirements first. Sometimes in-memory with periodic sync is fine, sometimes you need a full distributed consensus system.

Blake Common mistake — forgetting about monitoring. If you can't see your rate limit hit rates and false positives, you're flying blind.

Alex And please, for the love of all that's holy, don't implement synchronous Redis calls on every request without thinking about batching or async updates.

Blake *laughs* "Just throw Redis at it" — the battle cry of engineers everywhere.

Alex Key takeaway — rate limiting is easy in theory, brutal in practice. It's all about the trade-offs between accuracy, latency, and complexity.

Blake And remember, the best rate limiter is one that users never notice... until they really need it.

Alex Alright, that wraps up Episode 2 of Byte by Design! If you found this useful, share it with a fellow engineer who's prepping for system design interviews.

Blake And next time, we're diving into chat systems — WebSockets, message queues, and all the fun challenges of building real-time messaging at scale. See you then!

Alex See you next week!


References

Architecture Diagram