Rate Limiter ontwerpen en Twitter-feed ontwerpen
Pas het framework toe op twee canonieke designproblemen: rate limiting met token-bucket/sliding-window en een nieuwsfeed met fan-out-on-write versus fan-out-on-read.
Rate Limiter ontwerpen en Twitter-feed ontwerpen is een gratis Voorbereiding op programmeerinterviews-les op CoddyKit. Dit is les 4 van 4. Je kunt de volledige les hieronder gratis lezen en daarna in de browser praktisch oefenen met een ingebouwde code-editor en een AI-begeleider die 24/7 beschikbaar is. Deze les maakt deel uit van het leertraject Voorbereiding op programmeerinterviews. Je voortgang wordt gesynchroniseerd op het web en in de CoddyKit-app. De cursus Voorbereiding op programmeerinterviews bevat in totaal 4 lessen.
Waarom verzoekbeperking essentieel is
Verzoekbeperking bepaalt hoeveel verzoeken een client binnen een bepaald tijdvenster naar een API mag sturen. Zonder verzoekbeperking kan één client die zich misdraagt (of een DDoS-aanval) serverbronnen uitputten, waardoor de dienstverlening voor alle gebruikers verslechtert. Verzoekbeperking beschermt ook tegen brute-forceaanvallen, voorkomt het scrapen van API's en zorgt voor eerlijk gebruik van gedeelde bronnen.
Veelgebruikte granulariteiten voor verzoekbeperking zijn: per gebruikers-ID, per API-sleutel, per IP-adres, per eindpunt of een combinatie daarvan. Typische limieten zijn 100 verzoeken per minuut per gebruiker en 1000 per uur per API-sleutel. De verzoekbeperker moet snel zijn (minder dan <1ms extra overhead) en gedistribueerd werken (consistent over alle replica's van de API-server).
# 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}')Verzoekbeperkingsalgoritme 1: tokenbucket
Het tokenbucket-algoritme houdt een bak met een maximale capaciteit van N tokens bij. Tokens worden met een vaste snelheid toegevoegd (bijvoorbeeld 10 per seconde). Elk verzoek verbruikt één token. Als de bak leeg is, wordt het verzoek afgewezen. Als de bak niet vol is, wordt het verzoek geaccepteerd en wordt de token verbruikt.
Met een tokenbucket zijn pieken mogelijk: als er 5 seconden lang geen verzoeken binnenkomen, vult de bak zich tot N tokens en kunnen vervolgens onmiddellijk N verzoeken binnenkomen. Dit is geschikt voor API's waarbij incidentele pieken aanvaardbaar zijn. De twee parameters zijn de capaciteit (piekgrootte) en de aanvulsnelheid.
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 requestsVerzoekbeperkingsalgoritme 2: logboek met schuivend venster
Het logboek met schuivend venster slaat voor elk verzoek een tijdstempel op in een gesorteerde verzameling. Bij elk nieuw verzoek worden eerst de tijdstempels verwijderd die ouder zijn dan het begin van het venster. Daarna wordt gecontroleerd of het aantal overgebleven tijdstempels onder de limiet ligt. Zo ja, dan wordt de huidige tijdstempel toegevoegd en wordt het verzoek toegestaan; anders wordt het afgewezen.
Dit is nauwkeurig — het telt precies hoeveel verzoeken er in de laatste N seconden zijn gedaan. Het nadeel is het hoge geheugengebruik (één vermelding per verzoek per gebruiker). Bij een limiet van 1000 verzoeken per minuut voor 100.000 gebruikers zijn dat in het slechtste geval 100 miljoen logboekvermeldingen. Dit is niet geschikt voor zeer veel verkeer, tenzij het met sharding wordt gecombineerd.
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)Verzoekbeperkingsalgoritme 3: teller met schuivend venster
De teller met schuivend venster benadert het schuivende venster met twee bakken — de huidige minuut en de vorige minuut — waarbij de weging afhangt van hoe ver we in de huidige minuut zijn. Hierdoor neemt het geheugen af van O(verzoeken) naar O(1) per gebruiker, terwijl het aantal verzoeken van het exacte schuivende venster nauw wordt benaderd.
Formule: estimated_count = prev_count × (1 - fraction_of_window_elapsed) + curr_count. Als dit geschatte aantal de limiet overschrijdt, wordt het verzoek afgewezen. Dit algoritme wordt op grote schaal gebruikt door Cloudflare en Kong vanwege het geheugengebruik van O(1) per gebruiker en de hoge nauwkeurigheid.
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)Gedistribueerde verzoekbeperking met Redis
In een gedistribueerd systeem met meerdere applicatieservers moet verzoekbeperking centraal worden geregeld — anders houdt elke server zijn eigen teller bij en worden de limieten effectief vermenigvuldigd met het aantal servers. Redis met atomaire bewerkingen is de standaardoplossing: gebruik INCR en EXPIRE voor een teller met een vast venster, of ZADD en ZCOUNT voor een logboek met een schuivend venster.
Met een Lua-script worden meerdere Redis-bewerkingen atomair uitgevoerd. Zo voorkom je racesituaties waarin twee servers gelijktijdig verhogen terwijl ze allebei net onder de limiet zitten. Redis verwerkt Lua-scripts als één opdracht, waardoor atomiciteit is gegarandeerd zonder gedistribueerde vergrendelingen.
# 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')Een Twitter-nieuwsoverzicht ontwerpen: vereisten
We ontwerpen een systeem voor een Twitter-achtig nieuwsoverzicht. Functionele vereisten: gebruikers kunnen tweets plaatsen (maximaal 280 tekens), andere gebruikers volgen en een nieuwsoverzicht bekijken met tweets van de mensen die ze volgen, gesorteerd op recentheid. Niet-functionele vereisten: 300 miljoen dagelijks actieve gebruikers, 500 miljoen tweets per dag, het nieuwsoverzicht moet in <2 seconden laden en de lees-schrijfverhouding is ongeveer 100:1.
Capaciteitsschattingen: 500 miljoen tweets per dag ÷ 86400 ≈ 5800 tweets per seconde. Het aantal leesbewerkingen is ongeveer 580.000 per seconde. Elke tweet is ongeveer 300 bytes; 500 miljoen × 300 B = 150 GB nieuwe tweetopslag per dag. Het samenvoegen van nieuwsoverzichten is de belangrijkste technische uitdaging.
# 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()Uitwaaiering bij schrijven: vooraf berekende nieuwsoverzichten
Bij uitwaaiering bij schrijven verspreidt het systeem een tweet onmiddellijk naar het nieuwsoverzicht van elke volger wanneer gebruiker A een tweet plaatst. Wanneer een volger het nieuwsoverzicht opvraagt, is het al vooraf berekend en opgeslagen in Redis — een eenvoudige lezing van een Redis-lijst met O(k), waarbij k de grootte van het nieuwsoverzicht is (meestal begrensd op 1000 tweets).
De uitdaging zijn bekende personen met miljoenen volgers, die enorme uitwaaieringsbewerkingen veroorzaken. Als Justin Bieber een tweet plaatst, moet die gelijktijdig naar meer dan 100 miljoen nieuwsoverzichten van volgers worden geschreven — een reëel probleem waar Twitter mee te maken kreeg en dat het 'bekendheidsprobleem' werd genoemd. De service voor uitwaaiering bij schrijven moet asynchroon en op wachtrijen gebaseerd zijn om deze pieken te verwerken.
# 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')Hybride uitwaaiering: het bekendheidsprobleem oplossen
De hybride aanpak combineert uitwaaiering bij schrijven voor gewone gebruikers met uitwaaiering bij lezen voor bekende personen. Een gebruiker wordt als bekend persoon ingedeeld wanneer het aantal volgers boven een drempel uitkomt (bijvoorbeeld 1 miljoen volgers). Bij gewone gebruikers worden tweets op het moment van plaatsen naar alle nieuwsoverzichten van volgers gestuurd. Bij bekende personen worden hun tweets NIET verspreid; in plaats daarvan haalt het systeem de recente tweets van de bekende persoon op wanneer een volger het nieuwsoverzicht leest en voegt het die samen met het vooraf berekende nieuwsoverzicht.
Dit hybride model komt dicht in de buurt van wat Twitter daadwerkelijk gebruikt. De samenvoegstap is snel, omdat bekende personen zelden posten en het samenvoegen O(f) kost, waarbij f het aantal accounts van bekende personen is dat de gebruiker volgt (meestal klein).
# 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-nieuwsoverzicht: volledige architectuur
De volledige architectuur van het Twitter-nieuwsoverzicht combineert verschillende systemen:
- Tweetservice: schrijft tweets naar Cassandra (hoge schrijfsnelheid, tijdreeksgegevens)
- Uitwaaieringsservice: asynchrone werkers (Kafka-consumenten) die tweet-ID's naar de nieuwsoverzichten van volgers in Redis sturen
- Nieuwsoverzichtservice: leest het Redis-nieuwsoverzicht, vult tweet-ID's aan tot volledige tweetobjecten en voegt tweets van bekende personen samen
- Volgservice: beheert de sociale grafiek (wie wie volgt) in een grafendatabase of gepartitioneerde SQL-database
- Tijdlijnservice: levert de eigen tweets van een gebruiker (los van het thuisoverzicht)
# 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)Headers en foutantwoorden van de verzoekbeperker
Een goed ontworpen verzoekbeperker communiceert zijn limieten naar clients via HTTP-antwoordheaders. Zo kunnen clients logica voor opnieuw proberen implementeren en kunnen dashboards het gebruik tonen. Standaardheaders:
X-RateLimit-Limit: maximaal toegestane aantal verzoeken in het vensterX-RateLimit-Remaining: resterend aantal verzoeken in het huidige vensterX-RateLimit-Reset: Unix-tijdstempel waarop het venster wordt geresetRetry-After: aantal seconden dat je moet wachten voordat je het opnieuw probeert (bij antwoord 429)
De HTTP-statuscode voor antwoorden waarbij de verzoeklimiet is overschreden, is 429 Te veel verzoeken.
# 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}')Algoritmen voor snelheidsbeperking vergelijken
Samenvattende vergelijking van alle algoritmen voor snelheidsbeperking om je te helpen kiezen tijdens sollicitatiegesprekken:
- Token Bucket: staat pieken toe en heeft een gelijkmatige aanvulsnelheid. Het beste voor API's waarbij incidentele pieken aanvaardbaar zijn (meest gebruikelijke keuze).
- Leaky Bucket: verwerkt aanvragen met een vaste uitvoersnelheid, ongeacht pieken. Het beste om verkeer vorm te geven tot een constante stroom.
- Fixed Window Counter: eenvoudigste, met O(1) ruimtegebruik. Probleem: een piek van twee keer de limiet op de grens van een venster (bijvoorbeeld 100 om 11:59 + 100 om 12:00).
- Sliding Window Log: nauwkeurigst, zonder piek op de venstergrens. Probleem: O(aanvragen) geheugen.
- Sliding Window Counter: benadert het schuivendevensterlogboek met O(1) ruimtegebruik. Wordt gebruikt door Cloudflare.
# 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}')Korte test
Test je begrip van de concepten van Data Structures & Algorithms — Coding Interview Prep uit deze les.
Samenvatting van de les
In deze les heb je geleerd: snelheidsbegrenzers gebruiken een token bucket (staat pieken toe), een teller met een schuivend venster (O(1) geheugen) of een logboek met een schuivend venster (het nauwkeurigst) om de snelheid van aanvragen te beheersen, en atomaire Redis-bewerkingen maken gedistribueerde snelheidsbeperking mogelijk, de Twitter-feed gebruikt fan-out bij schrijven om feeds van volgers vooraf in Redis te berekenen voor snelle leesbewerkingen, met een hybride pullmodel voor accounts van bekende personen om enorme schrijfversterking te voorkomen. Hierna gaan we naar het afsluitende onderdeel met een spiekbrief voor patroonherkenning die probleemsignalen koppelt aan de algoritmepatronen waarmee je ze het snelst oplost.
Leer Voorbereiding op programmeerinterviews met een AI-tutor — gratis
Schrijf echte code en voer die uit in je browser, krijg direct hulp van een AI-tutor die 24/7 beschikbaar is en ga verder waar je gebleven bent op het web of in de app.
- Cursussen
- 90
- Lessen
- 360
Veelgestelde vragen
Is de les “Rate Limiter ontwerpen en Twitter-feed ontwerpen” gratis?
Ja — de volledige tekst van “Rate Limiter ontwerpen en Twitter-feed ontwerpen” kun je hier gratis op het web lezen. Als je interactief wilt oefenen met een ingebouwde code-editor en een AI-begeleider die 24/7 beschikbaar is, en de rest van de cursus Voorbereiding op programmeerinterviews wilt ontgrendelen, kun je upgraden naar CoddyKit PRO. De cursus Voorbereiding op programmeerinterviews bevat in totaal 4 lessen.
Wat leer ik in “Rate Limiter ontwerpen en Twitter-feed ontwerpen”?
Pas het framework toe op twee canonieke designproblemen: rate limiting met token-bucket/sliding-window en een nieuwsfeed met fan-out-on-write versus fan-out-on-read. Je oefent met Voorbereiding op programmeerinterviews door code rechtstreeks in de browser uit te voeren. Een AI-begeleider die 24/7 beschikbaar is beantwoordt je vragen terwijl je de les doorwerkt.
Heb ik ervaring nodig om met Voorbereiding op programmeerinterviews te beginnen?
Ervaring vooraf is niet nodig. Voorbereiding op programmeerinterviews op CoddyKit is opgebouwd voor beginners tot gevorderden, zodat je hier of bij het begin kunt starten en in je eigen tempo kunt leren. Dit is les 4 van 4.
Hoe lang duurt de les “Rate Limiter ontwerpen en Twitter-feed ontwerpen”?
De meeste lessen van CoddyKit duren ongeveer 5–10 minuten. Elke les is kort en interactief, zodat je gestaag vooruitgaat en op het web en in de app precies verdergaat waar je was gebleven.
Kan ik code schrijven en uitvoeren in deze les over Voorbereiding op programmeerinterviews?
Ja. Elke les over Voorbereiding op programmeerinterviews bevat een ingebouwde code-editor, zodat je rechtstreeks in je browser echte code kunt schrijven en uitvoeren en direct feedback van AI krijgt — lokale installatie is niet nodig.
Alle lessen in deze cursus
- Het framework voor system-designinterviews
- Schaalbare gegevensopslag: SQL versus NoSQL
- Caching, CDN's en load balancing
- Rate Limiter ontwerpen en Twitter-feed ontwerpen