Rate Limiter und Twitter-Feed entwerfen
Wenden Sie das Framework auf zwei klassische Designprobleme an: Rate Limiting mit Token Bucket oder Sliding Window sowie einen Newsfeed mit Fan-out-on-Write oder Fan-out-on-Read.
Rate Limiter und Twitter-Feed entwerfen ist eine kostenlose DSA Interview Prep-Lektion auf CoddyKit. Dies ist Lektion 4 von 4. Du kannst die komplette Lektion unten kostenlos lesen – dann übst du sie direkt im Browser mit einem integrierten Code-Editor und einem KI-Tutor rund um die Uhr. Sie ist Teil des DSA Interview Prep-Lernpfads, und dein Fortschritt wird über Web und CoddyKit-App synchronisiert. Der DSA Interview Prep-Kurs umfasst insgesamt 4 Lektionen.
Warum Rate Limiting unverzichtbar ist
Rate Limiting begrenzt die Anzahl der Anfragen, die ein Client innerhalb eines bestimmten Zeitfensters an eine API stellen kann. Ohne diese Begrenzung kann ein einzelner fehlerhafter Client (oder ein DDoS-Angriff) die Serverressourcen ausschöpfen und dadurch den Dienst für alle Benutzer beeinträchtigen. Rate Limiting schützt außerdem vor Brute-Force-Angriffen, verhindert API-Scraping und setzt eine faire Nutzung gemeinsam verwendeter Ressourcen durch.
Übliche Granularitäten beim Rate Limiting sind: pro Benutzer-ID, pro API-Key, pro IP-Adresse, pro Endpunkt oder eine Kombination daraus. Typische Limits sind 100 Anfragen pro Minute und Benutzer oder 1000 pro Stunde und API-Key. Der Rate Limiter muss schnell sein (weniger als 1 ms zusätzlicher Aufwand) und verteilt arbeiten (über alle API-Server-Replikate hinweg konsistente Limits).
# Rate limiting scenarios
use_cases = [
('API authentication endpoint', '5 attempts per 15 min per IP', 'Brute-force protection'),
('Public search API', '100 requests per minute per key', 'Fair use enforcement'),
('Email sending', '50 emails per hour per user', 'Spam prevention'),
('Payment processing', '10 transactions per second per account', 'Fraud prevention'),
('File upload', '5 uploads per minute per user', 'Resource quota'),
('Notification service', '1000 pushes per second globally', 'Cost control'),
]
print(f'{'Endpoint/Feature':35s} {'Limit':40s} {'Reason'}')
print('-'*95)
for endpoint, limit, reason in use_cases:
print(f'{endpoint:35s} {limit:40s} {reason}')Rate-Limiting-Algorithmus 1: Token-Bucket
Der Token-Bucket-Algorithmus verwaltet einen Bucket mit einer maximalen Kapazität von N Tokens. Tokens werden mit einer festen Rate hinzugefügt (z. B. 10 pro Sekunde). Jede Anfrage verbraucht ein Token. Ist der Bucket leer, wird die Anfrage abgelehnt. Liegt die Anzahl der Tokens unter der Kapazität, wird die Anfrage angenommen und ein Token verbraucht.
Token-Buckets ermöglichen Bursts: Wenn 5 Sekunden lang keine Anfragen eingehen, füllt sich der Bucket auf bis zu N Tokens. Anschließend können sofort N Anfragen eingehen. Das eignet sich für APIs, bei denen gelegentliche Lastspitzen akzeptabel sind. Die beiden Parameter sind die Kapazität (Burst-Größe) und die Nachfüllrate.
import time
class TokenBucket:
def __init__(self, capacity, refill_rate):
self.capacity = capacity # max tokens (burst size)
self.refill_rate = refill_rate # tokens added per second
self.tokens = capacity # start full
self.last_refill = time.time()
def allow(self):
now = time.time()
elapsed = now - self.last_refill
# Refill tokens based on elapsed time
self.tokens = min(self.capacity,
self.tokens + elapsed * self.refill_rate)
self.last_refill = now
if self.tokens >= 1:
self.tokens -= 1
return True # request allowed
return False # rate limited
bucket = TokenBucket(capacity=5, refill_rate=2) # 2 tokens/sec, burst=5
for i in range(8):
allowed = bucket.allow()
print(f'Request {i+1}: {"ALLOWED" if allowed else "REJECTED"} (tokens={bucket.tokens:.1f})')
time.sleep(0.1) # 0.1s between requestsRate-Limiting-Algorithmus 2: Sliding-Window-Log
Das Sliding-Window-Log speichert für jede Anfrage einen Zeitstempel in einer sortierten Menge. Bei jeder neuen Anfrage werden zunächst Zeitstempel entfernt, die älter als der Beginn des Zeitfensters sind. Anschließend wird geprüft, ob die Anzahl der verbleibenden Zeitstempel unter dem Limit liegt. Falls ja, wird der aktuelle Zeitstempel hinzugefügt und die Anfrage zugelassen; andernfalls wird sie abgelehnt.
Dieses Verfahren ist präzise – es zählt exakt, wie viele Anfragen in den letzten N Sekunden eingegangen sind. Der Nachteil ist der hohe Speicherbedarf (ein Eintrag pro Anfrage und Benutzer). Bei einem Limit von 1000 Anfragen pro Minute und 100.000 Benutzern beträgt der ungünstigste Fall 100 Millionen Log-Einträge. Für sehr hohen Datenverkehr ist das ohne Sharding nicht geeignet.
import time
from collections import deque
class SlidingWindowLog:
def __init__(self, limit, window_seconds):
self.limit = limit
self.window = window_seconds
self.logs = {} # user_id -> deque of timestamps
def allow(self, user_id):
now = time.time()
if user_id not in self.logs:
self.logs[user_id] = deque()
log = self.logs[user_id]
window_start = now - self.window
# Remove expired timestamps
while log and log[0] <= window_start:
log.popleft()
# Check limit
if len(log) < self.limit:
log.append(now)
return True
return False
limiter = SlidingWindowLog(limit=3, window_seconds=10)
for i in range(5):
allowed = limiter.allow('user123')
print(f'Request {i+1}: {"ALLOWED" if allowed else "REJECTED"}')
time.sleep(0.5)Rate-Limiting-Algorithmus 3: Sliding-Window-Counter
Der Sliding-Window-Counter nähert das Sliding Window mithilfe von zwei Buckets an – der aktuellen und der vorherigen Minute –, die abhängig davon gewichtet werden, wie weit die aktuelle Minute fortgeschritten ist. Dadurch sinkt der Speicherbedarf von O(requests) auf O(1) pro Benutzer, während der exakte Wert des Sliding Windows eng angenähert wird.
Formel: estimated_count = prev_count × (1 - fraction_of_window_elapsed) + curr_count. Überschreitet dieser geschätzte Wert das Limit, wird die Anfrage abgelehnt. Aufgrund des Speicherbedarfs von O(1) pro Benutzer und der hohen Genauigkeit wird dieser Algorithmus bei hoher Last von Cloudflare und Kong eingesetzt.
import time
import math
class SlidingWindowCounter:
def __init__(self, limit, window_seconds=60):
self.limit = limit
self.window = window_seconds
self.buckets = {} # user_id -> {prev_count, curr_count, curr_window_start}
def allow(self, user_id):
now = time.time()
window_start = int(now // self.window) * self.window
if user_id not in self.buckets or self.buckets[user_id]['window'] < window_start - self.window:
self.buckets[user_id] = {'prev': 0, 'curr': 0, 'window': window_start}
elif self.buckets[user_id]['window'] < window_start:
self.buckets[user_id] = {'prev': self.buckets[user_id]['curr'], 'curr': 0, 'window': window_start}
b = self.buckets[user_id]
fraction = (now - window_start) / self.window
estimated = b['prev'] * (1 - fraction) + b['curr']
if estimated < self.limit:
b['curr'] += 1
return True
return False
limiter = SlidingWindowCounter(limit=5, window_seconds=10)
for i in range(7):
print(f'Request {i+1}: {"OK" if limiter.allow("user1") else "RATE LIMITED"}')
time.sleep(0.3)Verteiltes Rate Limiting mit Redis
In einem verteilten System mit mehreren App-Servern muss Rate Limiting zentralisiert erfolgen. Andernfalls erfasst jeder Server seinen eigenen Zähler, und die Limits vervielfachen sich effektiv mit der Anzahl der Server. Redis mit atomaren Operationen ist die Standardlösung: Verwenden Sie INCR und EXPIRE für einen Zähler mit festem Zeitfenster oder ZADD und ZCOUNT für ein Sliding-Window-Log.
Mit einem Lua-Skript lassen sich mehrere Redis-Operationen atomar ausführen. Dadurch werden Race Conditions verhindert, bei denen zwei Server gleichzeitig knapp unter dem Limit erhöhen. Redis verarbeitet Lua-Skripte als einen einzigen Befehl und gewährleistet so Atomizität ohne verteilte Sperren.
# Distributed rate limiting with Redis (pseudocode / simulation)
# Fixed window counter using Redis INCR + EXPIRE
def redis_fixed_window(redis_client, user_id, limit, window_sec):
key = f'rl:{user_id}:{int(time.time() // window_sec)}'
count = redis_client.incr(key) # atomic increment
if count == 1:
redis_client.expire(key, window_sec) # set TTL on first request
return count <= limit
# Sliding window with sorted set
def redis_sliding_window(redis_client, user_id, limit, window_sec):
now = time.time()
key = f'rl:{user_id}'
# Remove old entries, count recent, add current
# Atomic with Lua: multi-step operation
lua_script = '''
local key = KEYS[1]
local now = ARGV[1]
local window = ARGV[2]
local limit = ARGV[3]
redis.call('ZREMRANGEBYSCORE', key, '-inf', now - window)
local count = redis.call('ZCARD', key)
if count < tonumber(limit) then
redis.call('ZADD', key, now, now)
redis.call('EXPIRE', key, window)
return 1 -- allowed
end
return 0 -- rejected
'''
print('Redis Lua script ensures atomicity across ZREM + ZCARD + ZADD')Twitter-Feed entwerfen: Anforderungen
Entwerfen wir ein System für einen Twitter-ähnlichen Newsfeed. Funktionale Anforderungen: Benutzer können Tweets (bis zu 280 Zeichen) veröffentlichen, anderen Benutzern folgen und einen nach Aktualität sortierten Feed mit Tweets der Benutzer anzeigen, denen sie folgen. Nichtfunktionale Anforderungen: 300 Millionen täglich aktive Benutzer, 500 Millionen Tweets pro Tag, der Feed muss in weniger als 2 Sekunden geladen werden, und das Lese-/Schreibverhältnis beträgt etwa 100:1.
Kapazitätsschätzungen: 500 Millionen Tweets/Tag ÷ 86400 ≈ 5800 Tweets/Sekunde. Lesezugriffe ≈ 580.000/Sekunde. Jeder Tweet ist etwa 300 Byte groß; 500 Millionen × 300 Byte = 150 GB neuer Tweet-Speicher pro Tag. Die Aggregation des Feeds ist die zentrale technische Herausforderung.
# Twitter feed requirements and estimates
reqs = {
'Functional': [
'Post tweet (text, image, video)',
'Follow/unfollow users',
'View home feed (tweets from followees, newest first)',
'View user timeline (all tweets by one user)',
'Like and retweet',
'Search tweets (basic keyword)',
],
'Non-functional': [
'300M DAU, 500M tweets/day => 5800 writes/sec',
'100:1 read:write => 580K feed reads/sec',
'Feed load < 2 seconds (p95)',
'99.99% availability',
'Tweets retained indefinitely (tweets never deleted by default)',
],
'Estimates': [
'Storage: 500M tweets * 300B = 150 GB/day, 54 TB/year',
'Media: separate object store (S3), CDN-served',
'Feed cache: 300M users * top-100-tweets * 100B = 3 TB (hot feeds in Redis)',
],
}
for category, items in reqs.items():
print(f'{category}:')
for item in items: print(f' - {item}')
print()Fan-out beim Schreiben: Vorberechnete Feeds
Beim Fan-out beim Schreiben verteilt das System einen Tweet unmittelbar auf den Feed jedes Followers, sobald Benutzer A ihn veröffentlicht. Wenn ein Follower seinen Feed anfordert, ist dieser bereits vorberechnet und in Redis gespeichert – ein einfacher Lesezugriff auf eine Redis-Liste mit O(k), wobei k der Feed-Größe entspricht (typischerweise auf 1000 Tweets begrenzt).
Die Herausforderung sind Prominente mit Millionen von Followern, die massive Fan-out-Operationen erzeugen. Wenn Justin Bieber einen Tweet veröffentlicht, muss dieser gleichzeitig in mehr als 100 Millionen Follower-Feeds geschrieben werden – ein reales Problem, mit dem Twitter konfrontiert war und das als „Celebrity-Problem“ bezeichnet wurde. Der Fan-out-Schreibdienst muss asynchron und warteschlangenbasiert arbeiten, um solche Lastspitzen zu bewältigen.
# Fan-out on write (push model)
fan_out_steps = [
'1. User posts tweet => write to tweets table (source of truth)',
'2. Publish event to message queue (Kafka topic: tweet-created)',
'3. Fan-out workers consume from queue:',
' a. Fetch list of followers from follows table',
' b. For each follower: LPUSH feed:{follower_id} tweet_id',
' c. Trim feed to last 1000 tweets: LTRIM feed:{follower_id} 0 999',
'4. Feed read: LRANGE feed:{user_id} 0 99 => hydrate tweet_ids => response',
]
for step in fan_out_steps:
print(step)
print('\nPros:')
print(' - Feed reads are O(1): just read from Redis list')
print(' - Feed is always sorted by recency automatically')
print('\nCons:')
print(' - Celebrities with 100M followers => 100M Redis writes per tweet')
print(' - Fan-out lag: followers may see tweet 10-30 seconds late at peak')
print(' - Inactive users waste Redis storage for precomputed feeds')Hybrides Fan-out: Das Celebrity-Problem lösen
Der hybride Ansatz kombiniert Fan-out beim Schreiben für reguläre Benutzer mit Fan-out beim Lesen für Prominente. Ein Benutzer wird als Prominenter eingestuft, wenn seine Followerzahl einen Schwellenwert überschreitet (z. B. 1 Million Follower). Bei regulären Benutzern werden Tweets zum Zeitpunkt der Veröffentlichung in alle Follower-Feeds übertragen. Bei Prominenten werden die Tweets NICHT übertragen. Stattdessen ruft das System beim Lesen des Feeds durch einen Follower die aktuellen Tweets des Prominenten ab und führt sie mit dem vorberechneten Feed zusammen.
Dieses hybride Modell entspricht weitgehend dem, was Twitter tatsächlich verwendet. Der Zusammenführungsschritt ist schnell, weil Prominente selten posten und die Zusammenführung O(f) benötigt, wobei f der Anzahl der Prominenten-Konten entspricht, denen der Benutzer folgt (typischerweise eine kleine Zahl).
# Hybrid fan-out implementation sketch
CELEBRITY_THRESHOLD = 1_000_000 # followers > 1M => celebrity
def on_post_tweet(user_id, tweet_id, follower_count):
if follower_count <= CELEBRITY_THRESHOLD:
# Fan-out to all followers (async via Kafka)
print(f'User {user_id}: fan-out tweet {tweet_id} to {follower_count} followers')
# => queue to fan-out workers
else:
print(f'Celebrity {user_id}: tweet {tweet_id} stored in timeline only')
# => only write to tweets table + user timeline
# => followers get it on demand when reading feed
def get_home_feed(user_id, followees):
# 1. Get precomputed feed (fan-out on write tweets)
precomputed = f'LRANGE feed:{user_id} 0 499' # up to 500 tweets
# 2. Find celebrity followees
celebrity_followees = [u for u in followees if is_celebrity(u)]
# 3. Fetch recent tweets from celebrities (fan-out on read)
celebrity_tweets = []
for celeb in celebrity_followees:
tweets = f'GET tweets WHERE user_id={celeb} ORDER BY created_at DESC LIMIT 20'
celebrity_tweets.extend(tweets)
# 4. Merge and sort by recency
combined = merge_and_sort(precomputed, celebrity_tweets)
return combined[:100]
print('on_post_tweet for regular user:')
on_post_tweet('user123', 'tweet_abc', 500)
print('on_post_tweet for celebrity:')
on_post_tweet('celebrity456', 'tweet_xyz', 50_000_000)Twitter-Feed: Vollständige Architektur
Die vollständige Architektur eines Twitter-Feeds kombiniert mehrere Systeme:
- Tweet-Service: schreibt Tweets in Cassandra (hoher Schreibdurchsatz, Zeitreihendaten)
- Fan-out-Service: asynchrone Worker (Kafka-Consumer), die Tweet-IDs in die Follower-Feeds in Redis schreiben
- Feed-Service: liest aus dem Redis-Feed, reichert Tweet-IDs mit vollständigen Tweet-Objekten an und führt Tweets von Prominenten zusammen
- Follow-Service: verwaltet den sozialen Graphen (wer wem folgt) in einer Graphdatenbank oder einem geshardeten SQL-System
- Timeline-Service: stellt die eigenen Tweets eines Benutzers bereit (getrennt vom Home-Feed)
# Twitter architecture summary
architecture = '''
[User] --> [API Gateway + Load Balancer]
|
+-----------+-----------+
| | |
[Tweet Svc] [Feed Svc] [Follow Svc]
| | |
[Cassandra] [Redis Feeds] [Graph DB]
| |
[Kafka] <-- [Fan-out
| Workers]
[S3 + CDN] (tweet_ids
(media) => follower
feed lists)
Key design choices:
- Tweets stored in Cassandra (PRIMARY KEY (user_id, created_at))
- Feed stored in Redis as list of tweet_ids per user (LPUSH/LTRIM/LRANGE)
- Fan-out via Kafka + workers (decoupled, retryable, scalable)
- Hybrid: regular users = push; celebrities = pull-on-read
- Hydration: tweet_ids -> full tweet objects via Cassandra read
'''
print(architecture)Header und Fehlerantworten des Rate Limiters
Ein gut konzipierter Rate Limiter teilt Clients seine Limits über HTTP-Response-Header mit. Dadurch können Clients eine Retry-After-Logik implementieren und Dashboards die Nutzung anzeigen. Standard-Header:
X-RateLimit-Limit: maximal zulässige Anzahl von Anfragen im ZeitfensterX-RateLimit-Remaining: verbleibende Anfragen im aktuellen ZeitfensterX-RateLimit-Reset: Unix-Zeitstempel, zu dem das Zeitfenster zurückgesetzt wirdRetry-After: Anzahl der Sekunden, die vor einem erneuten Versuch gewartet werden soll (bei einer 429-Antwort)
Der HTTP-Statuscode für rate-limitierte Antworten lautet 429 Too Many Requests.
# Rate limit response headers
def build_rate_limit_headers(limit, remaining, reset_timestamp, retry_after=None):
headers = {
'X-RateLimit-Limit': str(limit),
'X-RateLimit-Remaining': str(max(0, remaining)),
'X-RateLimit-Reset': str(int(reset_timestamp)),
}
if retry_after is not None:
headers['Retry-After'] = str(retry_after)
return headers
import time
# Simulated response for allowed request
headers = build_rate_limit_headers(
limit=100,
remaining=73,
reset_timestamp=time.time() + 45
)
print('Allowed request headers:')
for k, v in headers.items():
print(f' {k}: {v}')
# Rate limited response
headers_429 = build_rate_limit_headers(
limit=100,
remaining=0,
reset_timestamp=time.time() + 30,
retry_after=30
)
print('\n429 Too Many Requests headers:')
for k, v in headers_429.items():
print(f' {k}: {v}')Vergleich von Rate-Limiting-Algorithmen
Zusammenfassender Vergleich aller Rate-Limiting-Algorithmen, damit Sie in Vorstellungsgesprächen die passende Lösung auswählen können:
- Token Bucket: Ermöglicht Bursts und hat eine gleichmäßige Nachfüllrate. Am besten für APIs geeignet, bei denen gelegentliche Bursts akzeptabel sind (die häufigste Wahl).
- Leaky Bucket: Verarbeitet Anfragen unabhängig vom Burst mit einer festen Ausgaberate. Am besten geeignet, um den Datenverkehr in einen konstanten Datenstrom zu formen.
- Fixed Window Counter: Am einfachsten, O(1)-Speicher. Problem: Burst mit dem doppelten Limit an der Fenstergrenze (z. B. 100 um 11:59 Uhr + 100 um 12:00 Uhr).
- Sliding Window Log: Am genauesten, ohne Spitzen an Fenstergrenzen. Problem: O(requests)-Speicher.
- Sliding Window Counter: Nähert sich dem Sliding Window Log mit O(1)-Speicher an. Wird von Cloudflare verwendet.
# Algorithm comparison matrix
comparison = [
('Token Bucket', 'Allows bursts', 'O(1)', 'Most APIs, default choice'),
('Leaky Bucket', 'Smooth output rate', 'O(1)', 'Traffic shaping, message queues'),
('Fixed Window Counter', 'Very simple', 'O(1)', 'Low-traffic, approximate OK'),
('Sliding Window Log', 'Most accurate', 'O(requests)', 'High-accuracy, low traffic'),
('Sliding Window Counter','Approximate+fast', 'O(1)', 'High-traffic, Cloudflare-style'),
]
print(f'{'Algorithm':30s} {'Burst Handling':20s} {'Memory':15s} {'Use Case'}')
print('-'*85)
for name, burst, mem, use in comparison:
print(f'{name:30s} {burst:20s} {mem:15s} {use}')Kurzer Test
Testen Sie Ihr Verständnis der Konzepte von Data Structures & Algorithms — Coding Interview Prep aus dieser Lektion.
Zusammenfassung der Lektion
In dieser Lektion haben Sie gelernt: Rate Limiter verwenden Token Bucket (ermöglicht Bursts), Sliding Window Counter (O(1)-Speicher) oder Sliding Window Log (am genauesten), um die Anfrageraten zu steuern, und atomare Redis-Operationen ermöglichen verteiltes Rate Limiting, der Twitter-Feed verwendet Fan-out on write, um Follower-Feeds in Redis vorab zu berechnen und schnelle Lesezugriffe zu ermöglichen, mit einem hybriden Pull-Modell für Celebrity-Accounts, um eine massive Schreibverstärkung zu vermeiden. Als Nächstes beginnen wir mit dem Capstone-Abschnitt und einem Mustererkennungs-Spickzettel, der Problemsignale den Algorithmusmustern zuordnet, mit denen sie am schnellsten gelöst werden können.
Häufig gestellte Fragen
Ist die Lektion „Rate Limiter und Twitter-Feed entwerfen“ kostenlos?
Ja — der vollständige Text von „Rate Limiter und Twitter-Feed entwerfen“ ist hier im Web kostenlos zu lesen. Um sie interaktiv zu üben (integrierter Code-Editor und 24/7 KI-Tutor) und den Rest des DSA Interview Prep-Kurses freizuschalten, upgrade auf CoddyKit PRO. Der DSA Interview Prep-Kurs umfasst insgesamt 4 Lektionen.
Was lerne ich in „Rate Limiter und Twitter-Feed entwerfen“?
Wenden Sie das Framework auf zwei klassische Designprobleme an: Rate Limiting mit Token Bucket oder Sliding Window sowie einen Newsfeed mit Fan-out-on-Write oder Fan-out-on-Read. Du übst DSA Interview Prep mit praktischem Code, den du direkt im Browser ausführst, und ein 24/7 KI-Tutor beantwortet deine Fragen während du die Lektion bearbeitest.
Brauche ich Erfahrung, um DSA Interview Prep zu starten?
Keine Vorkenntnisse erforderlich. DSA Interview Prep auf CoddyKit ist für Anfänger bis fortgeschrittene Lernende strukturiert, sodass du hier starten oder von Anfang an beginnen und in deinem eigenen Tempo voranschreiten kannst. Dies ist Lektion 4 von 4.
Wie lange dauert die Lektion „Rate Limiter und Twitter-Feed entwerfen“?
Die meisten CoddyKit-Lektionen dauern etwa 5–10 Minuten. Jede ist kompakt und interaktiv, sodass du stetig Fortschritte machst und genau dort weitermachst, wo du aufgehört hast – im Web und in der App.
Kann ich in dieser DSA Interview Prep-Lektion Code schreiben und ausführen?
Ja. Jede DSA Interview Prep-Lektion enthält einen integrierten Code-Editor, sodass du echten Code direkt in deinem Browser schreibst und ausführst und sofort KI-Feedback erhältst — ohne lokale Einrichtung erforderlich.
Alle Lektionen in diesem Kurs
- Das Framework für System-Design-Interviews
- Skalierbare Datenspeicherung: SQL vs. NoSQL
- Caching, CDNs und Load Balancing
- Rate Limiter und Twitter-Feed entwerfen