Design Rate Limiter og Design Twitter Feed
Bruk rammeverket på to klassiske designproblemer: hastighetsbegrensning med token-bucket eller skyvevindu, og en nyhetsstrøm med fan-out-on-write eller fan-out-on-read.
Design Rate Limiter og Design Twitter Feed er en gratis leksjon i Forberedelse til kodeintervjuer på CoddyKit. Dette er leksjon 4 av 4. Du kan lese hele leksjonen gratis nedenfor – og deretter øve praktisk i nettleseren med en innebygd kodeeditor og en AI-veileder som er tilgjengelig døgnet rundt. Den er en del av læringsløpet i Forberedelse til kodeintervjuer, og fremdriften din synkroniseres mellom nettet og CoddyKit-appen. Kurset i Forberedelse til kodeintervjuer inneholder totalt 4 leksjoner.
Hvorfor rate limiting er nødvendig
Rate limiting begrenser antallet forespørsler en klient kan sende til et API i løpet av et gitt tidsvindu. Uten dette kan én klient som oppfører seg dårlig (eller et DDoS-angrep) bruke opp serverressursene og forringe tjenesten for alle brukere. Rate limiting beskytter også mot brute-force-angrep, hindrer API-skraping og håndhever rettferdig bruk av delte ressurser.
Vanlige nivåer for rate limiting er per bruker-ID, per API-nøkkel, per IP-adresse, per endepunkt eller en kombinasjon. Typiske grenser er 100 forespørsler per minutt per bruker og 1000 per time per API-nøkkel. Rate limiteren må være rask (med <1ms ekstra overhead) og distribuert (konsistent på tvers av alle API-serverreplikaer).
# 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-limitingalgoritme 1: token bucket
Token bucket-algoritmen opprettholder en bucket med maksimal kapasitet på N tokens. Tokens fylles på med en fast hastighet (for eksempel 10 per sekund). Hver forespørsel bruker ett token. Hvis bucket-en er tom, avvises forespørselen. Hvis den er under kapasitet, godtas forespørselen og tokenet brukes.
Token bucket tillater bursts: Hvis det ikke kommer noen forespørsler på 5 sekunder, fylles bucket-en med opptil N tokens, og N forespørsler kan komme inn umiddelbart. Dette passer for API-er der sporadiske topper er akseptable. De to parameterne er kapasitet (burst-størrelse) og påfyllingshastighet.
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-limitingalgoritme 2: logg for glidende vindu
Loggen for glidende vindu lagrer et tidsstempel for hver forespørsel i et sortert sett. For hver nye forespørsel fjernes tidsstempler som er eldre enn starten på vinduet, og deretter kontrolleres det om antallet gjenværende tidsstempler er under grensen. Hvis svaret er ja, legges gjeldende tidsstempel til, og forespørselen tillates; ellers avvises den.
Dette er presist – det teller nøyaktig hvor mange forespørsler som kom inn i løpet av de siste N sekundene. Ulempen er høy minnebruk (én oppføring per forespørsel per bruker). Med en grense på 1000 forespørsler per minutt for 100K brukere er verste fall 100M loggoppføringer. Dette egner seg ikke for svært høy trafikk med mindre det kombineres med sharding.
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-limitingalgoritme 3: teller for glidende vindu
Telleren for glidende vindu tilnærmer seg det glidende vinduet ved hjelp av to buckets – det inneværende minuttet og det foregående minuttet – vektet etter hvor langt inn i det inneværende minuttet vi er. Dette reduserer minnebruken fra O(requests) til O(1) per bruker, samtidig som den nøyaktige opptellingen for det glidende vinduet tilnærmes tett.
Formel: estimated_count = prev_count × (1 - fraction_of_window_elapsed) + curr_count. Hvis dette estimerte antallet overskrider grensen, avvises forespørselen. Dette er algoritmen Cloudflare og Kong bruker i stor skala på grunn av O(1)-minne per bruker og høy nøyaktighet.
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)Distribuert rate limiting med Redis
I et distribuert system med flere applikasjonsservere må rate limiting sentraliseres – ellers holder hver server sitt eget antall, og grensene blir i praksis multiplisert med antallet servere. Redis med atomiske operasjoner er standardløsningen: bruk INCR og EXPIRE for en teller med fast vindu, eller ZADD og ZCOUNT for en logg med glidende vindu.
Lua-skript-tilnærmingen gjør flere Redis-operasjoner atomiske og hindrer kappløpssituasjoner der to servere øker telleren samtidig, like under grensen. Redis behandler Lua-skript som én kommando, noe som sikrer atomisitet uten distribuerte låser.
# 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')Utforming av Twitter-feed: krav
La oss utforme et Twitter-lignende nyhetsfeed-system. Funksjonelle krav: brukere kan publisere tweets (på opptil 280 tegn), følge andre brukere og se en feed med tweets fra personer de følger, sortert etter hvor nylig de ble publisert. Ikke-funksjonelle krav: 300M daglig aktive brukere, 500M tweets per dag, feeden må lastes inn på <2 sekunder, og lese-/skriveforholdet er ~100:1.
Kapasitetsestimater: 500M tweets per dag ÷ 86400 ≈ 5800 tweets per sekund. Antall lesinger ≈ 580K per sekund. Hver tweet er ≈ 300 byte; 500M × 300B = 150 GB per dag med lagring av nye tweets. Feed-aggregasjon er den sentrale tekniske utfordringen.
# 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 ved skriving: forhåndsberegnede feeder
Ved fan-out on write distribuerer systemet tweeten umiddelbart til feeden til hver følger når bruker A publiserer den. Når en følger ber om feeden sin, er den allerede forhåndsberegnet og lagret i Redis – en enkel lesing av en Redis-liste med O(k), der k er feed-størrelsen (vanligvis begrenset til 1000 tweets).
Utfordringen er at kjendiser med millioner av følgere fører til massive fan-out-operasjoner. Når Justin Bieber publiserer en tweet, må den skrives til feedene til over 100M følgere samtidig – et reelt problem Twitter møtte og kalte 'celebrity problem'. Fan-out-tjenesten for skriving må være asynkron og købasert for å håndtere slike topper.
# 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')Hybrid fan-out: løser kjendisproblemet
Den hybride tilnærmingen kombinerer fan-out on write for vanlige brukere med fan-out on read for kjendiser. En bruker klassifiseres som en kjendis hvis antallet følgere overstiger en terskel (for eksempel 1 million følgere). For vanlige brukere sendes tweets til alle følgernes feeder ved publisering. For kjendiser sendes tweetene IKKE ut; i stedet henter systemet kjendisens nyeste tweets og fletter dem sammen med den forhåndsberegnede feeden når en følger leser feeden sin.
Denne hybridmodellen ligner på det Twitter faktisk bruker. Flettingen er rask fordi kjendiser publiserer sjelden, og den har kompleksiteten O(f), der f er antallet kjendiskontoer brukeren følger (vanligvis et lite antall).
# 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: komplett arkitektur
Den komplette arkitekturen for Twitter-feeden kombinerer flere systemer:
- Tweet-tjeneste: skriver tweets til Cassandra (høy skrivekapasitet, tidsseriedata)
- Fan-out-tjeneste: asynkrone arbeidere (Kafka-konsumenter) som skyver tweet-ID-er til følgernes feeder i Redis
- Feed-tjeneste: leser feeden i Redis, slår opp tweet-ID-er til fullstendige tweet-objekter og fletter inn kjendis-tweets
- Følge-tjeneste: håndterer den sosiale grafen (hvem som følger hvem) i en grafdatabase eller sharded SQL
- Tidslinjetjeneste: viser brukerens egne tweets (atskilt fra hjemmefeeden)
# 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)Rate-limiter-headere og feilresponser
En godt utformet rate limiter kommuniserer grensene sine til klienter via HTTP-responsheadere. Dette gjør at klienter kan implementere retry-after-logikk, og at dashbord kan vise bruk. Standardheadere:
X-RateLimit-Limit: maksimalt antall tillatte forespørsler i vinduetX-RateLimit-Remaining: antall forespørsler som gjenstår i det gjeldende vinduetX-RateLimit-Reset: Unix-tidsstempel for når vinduet tilbakestillesRetry-After: antall sekunder det må ventes før et nytt forsøk (ved 429-respons)
HTTP-statuskoden for rate-limited-responser er 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}')Sammenligning av algoritmer for hastighetsbegrensning
Oppsummert sammenligning av alle algoritmer for hastighetsbegrensning, slik at du kan velge riktig i intervjuer:
- Token Bucket: tillater trafikkspisser og har jevn påfyllingshastighet. Best for API-er der sporadiske trafikkspisser er akseptable (det vanligste valget).
- Leaky Bucket: behandler forespørsler med en fast utdatahastighet, uavhengig av trafikkspisser. Best for å forme trafikken til en konstant strøm.
- Fixed Window Counter: enklest, med O(1) minnebruk. Problem: en trafikkspiss som er dobbelt så stor som grensen ved vindusgrensen (for eksempel 100 kl. 11:59 + 100 kl. 12:00).
- Sliding Window Log: mest nøyaktig, uten trafikkspisser ved vindusgrensen. Problem: O(requests) minnebruk.
- Sliding Window Counter: tilnærmer seg sliding log med O(1) minnebruk. Brukes av 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}')Hurtigsjekk
Test forståelsen din av konseptene fra Data Structures & Algorithms — Coding Interview Prep i denne leksjonen.
Oppsummering av leksjonen
I denne leksjonen lærte du: rate limitere bruker token bucket (tillater trafikkspisser), sliding window counter (O(1) minne) eller sliding window log (mest nøyaktig) for å kontrollere forespørselshastigheter, og atomiske operasjoner i Redis muliggjør distribuert hastighetsbegrensning, Twitter-feeden bruker fan-out on write for å forhåndsberegne følgerfeeder i Redis for raske lesinger, med en hybrid pull-modell for kjendiskontoer for å unngå massiv skriveforsterkning. Deretter går vi inn i avslutningsdelen med en jukselapp for mønstergjenkjenning som kobler problemsignaler til algoritmemønstrene som løser dem raskest.
Lær deg Forberedelse til kodeintervjuer med en AI-veileder – gratis
Skriv og kjør ekte kode i nettleseren, få umiddelbar hjelp fra en AI-veileder som er tilgjengelig døgnet rundt, og fortsett der du slapp – på nettet eller i appen.
- Kurs
- 90
- Leksjoner
- 360
Ofte stilte spørsmål
Er leksjonen «Design Rate Limiter og Design Twitter Feed» gratis?
Ja – hele teksten i «Design Rate Limiter og Design Twitter Feed» er gratis å lese her på nettet. For å øve interaktivt med en innebygd kodeeditor og en AI-veileder som er tilgjengelig døgnet rundt, og for å låse opp resten av Forberedelse til kodeintervjuer-kurset, kan du oppgradere til CoddyKit PRO. Kurset i Forberedelse til kodeintervjuer inneholder totalt 4 leksjoner.
Hva lærer jeg i «Design Rate Limiter og Design Twitter Feed»?
Bruk rammeverket på to klassiske designproblemer: hastighetsbegrensning med token-bucket eller skyvevindu, og en nyhetsstrøm med fan-out-on-write eller fan-out-on-read. Du øver på Forberedelse til kodeintervjuer med praktisk kode som du kjører direkte i nettleseren, mens en AI-veileder som er tilgjengelig døgnet rundt, svarer på spørsmålene dine mens du jobber deg gjennom leksjonen.
Trenger jeg erfaring for å begynne med Forberedelse til kodeintervjuer?
Ingen tidligere erfaring er nødvendig. Forberedelse til kodeintervjuer på CoddyKit er lagt opp for både nybegynnere og viderekomne, så De kan begynne her eller helt fra start og lære i Deres eget tempo. Dette er leksjon 4 av 4.
Hvor lang tid tar leksjonen «Design Rate Limiter og Design Twitter Feed»?
De fleste CoddyKit-leksjoner tar omtrent 5–10 minutter. Hver leksjon er kort og interaktiv, slik at De gjør jevne fremskritt og kan fortsette akkurat der De slapp – både på nettet og i appen.
Kan jeg skrive og kjøre kode i denne Forberedelse til kodeintervjuer-leksjonen?
Ja. Alle Forberedelse til kodeintervjuer-leksjoner har en innebygd kodeeditor, slik at De kan skrive og kjøre ekte kode direkte i nettleseren og få umiddelbar tilbakemelding fra AI – uten lokal konfigurering.
Alle leksjonene i dette kurset
- Rammeverk for systemdesignintervjuet
- Skalerbar datalagring: SQL eller NoSQL
- Caching, CDN-er og lastbalansering
- Design Rate Limiter og Design Twitter Feed