DSA Interview Prep · Lektion

Designa Rate Limiter och Twitter Feed

Tillämpa ramverket på två klassiska designproblem: hastighetsbegränsning med token-bucket/sliding-window och ett nyhetsflöde med fan-out-on-write kontra fan-out-on-read.

Lektion 4 av 413 steg

Designa Rate Limiter och Twitter Feed är en gratis lektion i DSA Interview Prep på CoddyKit. Detta är lektion 4 av 4. Du kan läsa vilka 3 lektioner som helst i den här lärvägen kostnadsfritt i sin helhet – därefter låser CoddyKit PRO upp alla lektioner, plus praktisk övning med en inbyggd kodredigerare och en AI-lärare dygnet runt. Den ingår i lärvägen för DSA Interview Prep, och Era framsteg synkroniseras mellan webben och CoddyKit-appen. Kursen i DSA Interview Prep innehåller totalt 4 lektioner.

Varför rate limiting är avgörande

Rate limiting begränsar hur många begäranden en klient kan skicka till ett API under ett visst tidsfönster. Utan detta kan en enda klient som beter sig felaktigt (eller en DDoS-attack) överbelasta serverresurserna och försämra tjänsten för alla användare. Rate limiting skyddar också mot brute-force-attacker, förhindrar API-skrapning och upprätthåller rättvis användning av delade resurser.

Vanliga nivåer för rate limiting: per användar-ID, per API-nyckel, per IP-adress, per endpoint eller en kombination av dessa. Typiska gränser: 100 begäranden per minut och användare, 1000 per timme och API-nyckel. Rate limiter måste vara snabb (med <1ms overhead) och distribuerad (konsekvent över alla API-serverrepliker).

# 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-algoritm 1: Token bucket

Algoritmen token bucket underhåller en bucket med en maximal kapacitet på N tokens. Tokens fylls på med en fast hastighet (t.ex. 10 per sekund). Varje begäran förbrukar en token. Om bucketen är tom avslås begäran. Om den inte är full accepteras begäran och tokenen förbrukas.

Token bucket tillåter bursttrafik: om inga begäranden kommer in under 5 sekunder fylls bucketen upp till N tokens, och därefter kan N begäranden komma in omedelbart. Detta passar API:er där tillfälliga bursts är acceptabla. De två parametrarna är kapacitet (burststorlek) och påfyllnadshastighet.

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 requests

Rate limiting-algoritm 2: Sliding window-logg

Sliding window-logg lagrar en tidsstämpel för varje begäran i en sorterad mängd. För varje ny begäran tas tidsstämplar som är äldre än fönstrets början bort, varefter antalet återstående tidsstämplar kontrolleras mot gränsen. Om antalet ligger under gränsen läggs den aktuella tidsstämpeln till och begäran tillåts; annars avslås den.

Detta är exakt — algoritmen räknar precis hur många begäranden som inträffade under de senaste N sekunderna. Nackdelen är hög minnesanvändning (en post per begäran och användare). Med en gräns på 1000 begäranden/minut för 100K användare är värsta fallet 100M loggposter. Det lämpar sig inte för mycket hög trafik om det inte kombineras 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 limiting-algoritm 3: Sliding window-räknare

Sliding window-räknaren approximerar sliding window med hjälp av två buckets — den aktuella minuten och föregående minut — viktade efter hur långt in i den aktuella minuten vi befinner oss. Detta minskar minnesanvändningen från O(requests) till O(1) per användare, samtidigt som antalet nära approximerar det exakta värdet för sliding window.

Formel: estimated_count = prev_count × (1 - fraction_of_window_elapsed) + curr_count. Om det uppskattade antalet överskrider gränsen avslås begäran. Detta är algoritmen som Cloudflare och Kong använder i stor skala tack vare O(1) minne per användare och hög noggrannhet.

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)

Distribuerad rate limiting med Redis

I ett distribuerat system med flera appservrar måste rate limiting vara centraliserad — annars håller varje server reda på sitt eget antal och gränserna multipliceras i praktiken med antalet servrar. Redis med atomiska operationer är standardlösningen: använd INCR och EXPIRE för en räknare med fast fönster, eller ZADD och ZCOUNT för en sliding window-logg.

Metoden med Lua-skript gör flera Redis-operationer atomiska och förhindrar race conditions där två servrar ökar räknaren samtidigt precis under gränsen. Redis kör Lua-skript som ett enda kommando, vilket garanterar atomicitet utan distribuerade lås.

# 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')

Design av Twitter-flöde: Krav

Låt oss designa ett Twitter-liknande nyhetsflöde. Funktionella krav: användare kan publicera tweets (högst 280 tecken), följa andra användare och visa ett flöde med tweets från personer de följer, sorterat efter aktualitet. Icke-funktionella krav: 300M dagligt aktiva användare, 500M tweets/dag, flödet måste läsas in på <2 sekunder och läs-skriv-förhållandet är cirka 100:1.

Kapacitetsuppskattningar: 500M tweets/dag ÷ 86400 ≈ 5800 tweets/sekund. Läsningar ≈ 580K/sekund. Varje tweet är cirka 300 byte; 500M × 300B = 150 GB/dag med ny tweetlagring. Att aggregera flödet är den centrala tekniska utmaningen.

# 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 vid skrivning: Förberäknade flöden

Vid fan-out vid skrivning distribuerar systemet omedelbart en tweet till alla följares flöden när användare A publicerar den. När en följare begär sitt flöde är det redan förberäknat och lagrat i Redis — en enkel läsning från en Redis-lista med O(k), där k är flödesstorleken (vanligtvis begränsad till 1000 tweets).

Utmaningen är att celebriteter med miljontals följare skapar massiva fan-out-operationer. När Justin Bieber publicerar en tweet måste den skrivas till fler än 100M följarflöden samtidigt — ett verkligt problem som Twitter ställdes inför och kallade 'celebrity-problemet'. Tjänsten för fan-out vid skrivning måste vara asynkron och köbaserad för att hantera dessa toppar.

# 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ös celebrity-problemet

Hybridmetoden kombinerar fan-out vid skrivning för vanliga användare och fan-out vid läsning för celebriteter. En användare klassificeras som celebritet om antalet följare överstiger en tröskel (t.ex. 1 miljon följare). För vanliga användare pushas tweets till alla följares flöden när de publiceras. För celebriteter pushas deras tweets INTE; i stället hämtar systemet deras senaste tweets när en följare läser sitt flöde och slår ihop dem med det förberäknade flödet.

Den här hybridmodellen ligger nära det Twitter faktiskt använder. Sammanslagningssteget är snabbt eftersom celebriteter publicerar sällan och sammanslagningen har komplexiteten O(f), där f är antalet celebritetskonton som användaren följer (vanligtvis få).

# 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-flöde: Komplett arkitektur

Den kompletta arkitekturen för Twitter-flödet kombinerar flera system:

  • Tweet service: skriver tweets till Cassandra (hög skrivkapacitet, tidsseriedata)
  • Fan-out service: asynkrona arbetare (Kafka-konsumenter) som pushar tweet-ID:n till följares flöden i Redis
  • Feed service: läser från Redis-flödet, hydraterar tweet-ID:n till fullständiga tweet-objekt och slår ihop celebriteters tweets
  • Follow service: hanterar den sociala grafen (vem som följer vem) i en grafdatabas eller en shardad SQL-databas
  • Timeline service: levererar en användares egna tweets (separat från hemflödet)
# 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-headers och felsvar

En väl utformad rate limiter kommunicerar sina gränser till klienter via HTTP-headers i svaren. Det gör att klienter kan implementera retry-after-logik och att instrumentpaneler kan visa användningen. Standardheaders:

  • X-RateLimit-Limit: maximalt antal begäranden som tillåts under fönstret
  • X-RateLimit-Remaining: antal begäranden som återstår i det aktuella fönstret
  • X-RateLimit-Reset: Unix-tidsstämpel då fönstret återställs
  • Retry-After: antal sekunder att vänta innan nästa försök (vid ett 429-svar)

HTTP-statuskoden för svar som begränsats av rate limiting är 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}')

Jämförelse av algoritmer för hastighetsbegränsning

Sammanfattande jämförelse av alla algoritmer för hastighetsbegränsning som hjälper dig att välja under intervjuer:

  • Token Bucket: tillåter trafiktoppar och har en jämn påfyllnadstakt. Passar bäst för API:er där tillfälliga trafiktoppar är acceptabla (det vanligaste valet).
  • Leaky Bucket: behandlar anrop med en fast utgående takt oavsett trafiktoppar. Passar bäst för att forma trafiken till en konstant dataström.
  • Fixed Window Counter: enklast, O(1)-utrymme. Problem: en trafik­topp som är dubbelt så stor som gränsen vid fönstergränsen (t.ex. 100 kl. 11:59 + 100 kl. 12:00).
  • Sliding Window Log: mest exakt, ingen topp vid fönstergränsen. Problem: minne O(antal anrop).
  • Sliding Window Counter: approximerar Sliding Window Log med O(1)-utrymme. Används 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}')

Snabbkontroll

Testa din förståelse av begreppen i Data Structures & Algorithms — Coding Interview Prep från den här lektionen.

Sammanfattning av lektionen

I den här lektionen lärde du dig: hastighetsbegränsare använder Token Bucket (tillåter trafiktoppar), Sliding Window Counter (O(1)-minne) eller Sliding Window Log (mest exakt) för att styra anropsfrekvenser, och Redis atomiska operationer möjliggör distribuerad hastighetsbegränsning, Twitter-flödet använder fan-out on write för att beräkna följarflöden i Redis i förväg för snabba läsningar, med en hybrid pull-modell för kändiskonton för att undvika massiv skrivförstärkning. Härnäst går vi in i capstone-avsnittet med en fusklapp för mönsterigenkänning som kopplar problemsignaler till de algoritmmönster som löser dem snabbast.

Gratis att börja

Lär dig Python med en AI-lärare – gratis

Skriv och kör riktig kod i webbläsaren, få omedelbar hjälp av en AI-lärare dygnet runt och fortsätt där du slutade – på webben eller i appen.

Kurser
30
Lektioner
120

Vanliga frågor

Är lektionen ”Designa Rate Limiter och Twitter Feed” gratis?

Ja – du kan läsa vilka 3 lektioner som helst i lärvägen DSA Interview Prep, inklusive ”Designa Rate Limiter och Twitter Feed”, kostnadsfritt i sin helhet här på webben. Därefter låser CoddyKit PRO upp alla lektioner, plus interaktiv övning med en inbyggd kodredigerare och en AI-lärare dygnet runt. Kursen i DSA Interview Prep innehåller totalt 4 lektioner.

Vad lär jag mig i ”Designa Rate Limiter och Twitter Feed”?

Tillämpa ramverket på två klassiska designproblem: hastighetsbegränsning med token-bucket/sliding-window och ett nyhetsflöde med fan-out-on-write kontra fan-out-on-read. Ni övar på DSA Interview Prep med praktisk kod som körs direkt i webbläsaren, medan en AI-handledare som är tillgänglig dygnet runt svarar på Era frågor under lektionen.

Behöver jag någon erfarenhet för att börja lära mig DSA Interview Prep?

Du behöver inga förkunskaper. Utbildningen i DSA Interview Prep på CoddyKit är upplagd för allt från nybörjare till avancerade elever, så att du kan börja här eller från början och gå fram i din egen takt. Detta är lektion 4 av 4.

Hur lång tid tar lektionen ”Designa Rate Limiter och Twitter Feed”?

De flesta CoddyKit-lektioner tar cirka 5–10 minuter. Varje lektion är kort och interaktiv, så att du gör stadiga framsteg och kan fortsätta precis där du slutade – på webben eller i appen.

Kan jag skriva och köra kod i den här DSA Interview Prep-lektionen?

Ja. Varje DSA Interview Prep-lektion innehåller en inbyggd kodredigerare, så att du kan skriva och köra riktig kod direkt i webbläsaren och få omedelbar AI-feedback – utan lokal installation.

Alla lektioner i den här kursen

  1. Ramverk för systemdesignintervjuer
  2. Skalbar datalagring: SQL eller NoSQL
  3. Caching, CDN:er och lastbalansering
  4. Designa Rate Limiter och Twitter Feed
← Tillbaka till DSA Interview Prep