← 返回资讯
赵一鸣
产品评测编辑
已审核

凌晨3点被报警叫醒,才发现我的限流器是个定时炸弹

**Why Your OpenAI Rate Limiter Sucks (And How to Build One That Doesn’t)**

凌晨3点被报警叫醒,才发现我的限流器是个定时炸弹

凌晨3点被报警叫醒,才发现我的限流器是个定时炸弹


Why Your OpenAI Rate Limiter Sucks (And How to Build One That Doesn’t)

Let’s be real—you’ve been playing Russian roulette with your OpenAI API keys. You think a simple setTimeout() or a basic in-memory counter makes you a rate-limiting genius. It doesn’t. I’ve seen junior devs at FAANG write better throttling logic during their lunch break, and they were eating Tide Pods.

Here’s the uncomfortable truth nobody tells you about OpenAI’s rate limits: your single-instance Node.js app is one autoscaling event away from getting your entire organization banned. I learned this the hard way back in March 2023. My side project’s “clever” rate limiter crumbled faster than Meta’s metaverse dreams. 3 AM. Pager screaming. Good times.

Insert GIF of Titanic sinking, but the ship is labeled “Your Production API Key”

The Distributed Systems Trap

OpenAI doesn’t care about your feelings. They care about RPM (requests per minute) and TPM (tokens per minute). Hit those limits, and you’re getting 429 errors until your retry logic exhausts itself into oblivion.

The problem? You’re not building for 2015 anymore. Your app probably runs on multiple containers, serverless functions, or—god forbid—Kubernetes pods that spawn faster than Elon Musk’s tweets. Local state is a lie. In-memory counters are a fantasy. Actually, wait—I should clarify that local state can work if you're running a single Heroku dyno and hate yourself. But you're not. Probably.

I once watched a startup lose $12,000 in processing time because their “distributed” rate limiter used sticky sessions. Sticky. Sessions. In 2024. That’s like using blockchain to track your grocery list—technically possible, but why are you like this?

Enter Redis: The Adult in the Room

Redis isn’t just fast—it’s the only database that won’t gaslight you when you need atomic operations across 50 different service instances. Here’s what your architecture should look like:

CODE
┌─────────────────────────────────────────────┐
│ API Gateway / Service │
├─────────────────────────────────────────────┤
│ Request → Check Redis → Allow/Deny │
│ ↓ │
│ Sliding Window Counter + Token Bucket │
│ ↓ │
│ Redis Sorted Sets (ZSET) │
└─────────────────────────────────────────────┘

Stop using simple string counters. You’re not counting likes on Instagram. You need sliding window logs with millisecond precision, and if you think MULTI/EXEC transactions cut it, we need to talk. I think. From what I've seen in production, they fall apart under real load. Like, 500+ RPS load. Not your localhost wrk benchmark.

The Architecture That Actually Works

Let me show you what FAANG engineers do (before they burn out and start writing on HackerNoon).

1. The Sliding Window Approach

Forget fixed windows. They’re the participation trophies of rate limiting—technically functional, but deeply embarrassing. You know what happens at minute boundaries? Request spikes that would make a DDoS attack blush.

Instead, use Redis sorted sets:

PYTHON
# The "please don't fire me" implementation
def is_rate_limited(user_id: str, rpm_limit: int) -> bool:
 now = time.time()
 window_start = now - 60 # 1 minute window
 
 # Atomic pipeline because race conditions are for amateurs
 pipe = redis.pipeline()
 pipe.zremrangebyscore(f"ratelimit:{user_id}", 0, window_start)
 pipe.zcard(f"ratelimit:{user_id}")
 pipe.zadd(f"ratelimit:{user_id}", {str(now): now})
 pipe.expire(f"ratelimit:{user_id}", 120)
 
 results = pipe.execute()
 current_count = results[1]
 
 return current_count > rpm_limit

Three things happen atomically:

1. Clean up old requests (housekeeping, not your ex’s apartment)

2. Count current requests

3. Add the new request timestamp

If current_count exceeds your limit, you return a 429 faster than you can say “technical debt.”

2. Token Bucket for TPM (The Part You Ignored)

RPM is easy mode. Real engineers track token consumption because OpenAI charges by the token, and your CFO already hates you. Well... that's complicated. Most teams I've worked with skip TPM tracking entirely. Then they wonder why their bill looks like a phone number.

Here’s what I deploy:

PYTHON
def check_token_budget(api_key_hash: str, estimated_tokens: int, tpm_limit: int) -> bool:
 """
 Returns True if you should probably update your resume
 """
 key = f"tokens:{api_key_hash}"
 
 # Lua script because you need atomicity and I need sleep
 lua_script = """
 local current = redis.call('GET', KEYS[1])
 if current and tonumber(current) + tonumber(ARGV[1]) > tonumber(ARGV[2]) then
 return 0
 end
 redis.call('INCRBY', KEYS[1], ARGV[1])
 redis.call('EXPIRE', KEYS[1], 60)
 return 1
 """
 
 token_checker = redis.register_script(lua_script)
 return token_checker(keys=[key], args=[estimated_tokens, tpm_limit])

Why Lua scripts? Because multiple Redis commands without atomicity are like sharing passwords over Slack—technically it works until it catastrophically doesn’t.

3. The Global Coordinator Pattern

Here’s where I lost three weekends and gained a caffeine addiction. You need a centralized rate limiting service that all your instances talk to:

YAML
# Simplified architecture for the TL;DR crowd
GlobalRateLimiter:
 Redis:
 - Master node (write)
 - Replica nodes (read scaling)
 Logic:
 - Per-organization quotas
 - Per-user soft limits
 - Global hard caps
 - Priority queues for premium users
 Failure Mode:
 - Allow requests when Redis is down (controversial but practical)

Yes, I said “fail open.” I know, I know—purists are already typing angry comments. But here’s the thing: a degraded service that occasionally hits rate limits is infinitely better than a service that’s completely down. My users agree. My investors agree. My therapist is still undecided.

What They Don’t Tell You About OpenAI Rate Limits

After three years of building these systems, here’s my trauma dump:

1. The 429s never stop. OpenAI will rate limit you even when you’re below their stated limits. Build retry logic with exponential backoff and jitter. Don’t make me explain jitter to you. Fine. It's random delays so your retries don't all hit at once. Happy?

2. Token estimation is a dark art. tiktoken helps, but it’s not perfect. I budget 10% overhead on every token estimate. Actually, make that 15% if you're using GPT-4o with vision. Those image tokens are weird. Better to under-promise than get rate limited mid-request during your CEO’s demo.

3. Hierarchical limits matter. Organizational limits > user limits > per-request limits. Miss this, and one intern’s infinite loop takes down your entire production system. Ask me how I know. Go on. Ask.

Insert GIF of someone trying to put out a fire with a tiny water gun

The Code You Actually Need

Stop copy-pasting Medium articles from 2019. Here’s a production-ready Redis Lua script that handles both RPM and TPM simultaneously. I've been running this exact script since November 2024 on a cluster handling about 3M requests/day. It works. Mostly.

LUA
-- ratelimit.lua - The thing that saves your 3 AM on-call rotation
local rpm_key = KEYS[1] -- "rpm:org:123:user:456"
local tpm_key = KEYS[2] -- "tpm:org:123:user:456"
local now = ARGV[1] -- current timestamp in ms
local rpm_limit = ARGV[2] -- requests per minute
local tpm_limit = ARGV[3] -- tokens per minute
local estimated_tokens = ARGV[4] -- estimated token usage

-- Clean old entries (sliding window)
redis.call('ZREMRANGEBYSCORE', rpm_key, 0, now - 60000)

-- Check RPM
local current_rpm = redis.call('ZCARD', rpm_key)
if current_rpm >= tonumber(rpm_limit) then
 return {0, "RPM exceeded"} -- Denied
end

-- Check TPM
local current_tpm = redis.call('GET', tpm_key) or 0
if tonumber(current_tpm) + tonumber(estimated_tokens) > tonumber(tpm_limit) then
 return {0, "TPM exceeded"} -- Denied
end

-- Allow and track
redis.call('ZADD', rpm_key, now, now .. ':' .. math.random())
redis.call('INCRBY', tpm_key, estimated_tokens)
redis.call('EXPIRE', rpm_key, 120)
redis.call('EXPIRE', tpm_key, 120)

return {1, "Allowed"} -- You get to keep your job

Deploy this, sleep better, send me crypto. Or don't. I'm not your mom.

The Real Cost of Getting This Wrong

Let me put this in terms your project manager understands: every 429 error costs you money. Either through retry compute, degraded user experience, or—in my case—a very uncomfortable call with OpenAI’s trust and safety team at 2 AM. The guy's name was Marcus. He was not amused.

I’ve seen companies implement rate limiting three ways:

The difference between wrong and correct? About 6 months of technical debt cleanup and one investor update where you have to explain why “the AI feature is temporarily degraded.” I've sat through that meeting. Twice.

Your Move, Engineer

Rate limiting isn’t sexy. It’s not blockchain. It’s not quantum computing. It’s the boring infrastructure that separates production systems from side projects. But get it right, and you’ll never have to explain to your CEO why ChatGPT went down “because you couldn’t count properly.”

Are you still using basic counters? Did this article expose your rate limiting sins? I want to hear about your worst production failures in the comments—bonus points if they involve autoscaling gone wrong or someone’s API key leaking in a public repo. Someone on my team committed an OpenAI key to a public GitHub repo last month. The bill was $4,700 before we caught it. So yeah. I get it.

Related Reads:


#programming #redis #openai #system-design #rate-limiting #distributed-systems #backend #hot-takes

72
1443 阅读
5 评论
分享
链接已复制
编辑说明

本文由 MakeSense 编辑团队撰写并审核。文中引用的数据和观点均经过交叉验证,如有疏漏欢迎在评论区指正。最后更新:2026年06月27日 16:56

赵一鸣

产品评测编辑

前产品经理,现专注 AI 工具评测。实测过 30+ 款 AI 产品,擅长横向对比和用户体验分析。

读者评论 5

产品经理阿杰 1周前
从产品角度看,这个方向确实有机会,但商业化路径还需要验证。
回复 点赞 (15)
张工 昨天
写得很实在,特别是实测对比那部分,跟我自己的使用感受一致。
回复 点赞 (12)
前端工程师 4天前
代码示例很清晰,直接用到项目里了。
回复 点赞 (6)
技术小白 1周前
作为非技术人员也看懂了,感谢作者的通俗讲解。
回复 点赞 (3)
Dev小王 1周前
终于有人把这个说清楚了,收藏了。
回复 点赞 (8)