Merancang Pembatas Laju dan Merancang Feed Twitter
Terapkan kerangka tersebut pada dua masalah desain kanonis: pembatasan laju token-bucket/sliding-window dan feed berita fan-out-on-write vs fan-out-on-read
Merancang Pembatas Laju dan Merancang Feed Twitter adalah pelajaran DSA Interview Prep gratis di CoddyKit. Ini adalah pelajaran 4 dari 4. Kamu bisa membaca pelajaran lengkapnya di bawah secara gratis — lalu praktikkan langsung di browser dengan editor kode bawaan dan tutor AI 24/7. Ini adalah bagian dari jalur belajar DSA Interview Prep, dan progresmu tersinkronisasi di web dan aplikasi CoddyKit. Kursus DSA Interview Prep mencakup 4 pelajaran total.
Mengapa Pembatasan Laju Penting
Pembatasan laju mengendalikan jumlah permintaan yang dapat dibuat klien ke antarmuka pemrograman aplikasi dalam jangka waktu tertentu. Tanpanya, satu klien yang berperilaku buruk (atau serangan penolakan layanan terdistribusi) dapat memenuhi sumber daya peladen dan menurunkan kualitas layanan bagi semua pengguna. Pembatasan laju juga melindungi dari serangan tebak kata sandi, mencegah pengambilan data otomatis dari antarmuka pemrograman aplikasi, dan menegakkan penggunaan yang adil atas sumber daya bersama.
Tingkat penerapan pembatasan laju yang umum: per ID pengguna, per kunci antarmuka pemrograman aplikasi, per alamat IP, per titik akhir, atau gabungan beberapa tingkat tersebut. Batas yang umum: 100 permintaan per menit per pengguna, 1000 permintaan per jam per kunci antarmuka pemrograman aplikasi. Pembatas laju harus cepat (menambahkan overhead <1 milidetik) dan terdistribusi (konsisten di semua replika peladen aplikasi).
# 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}')Algoritme Pembatasan Laju 1: Wadah Token
Algoritme wadah token mempertahankan wadah dengan kapasitas maksimum N token. Token ditambahkan pada laju tetap (misalnya, 10 per detik). Setiap permintaan menghabiskan satu token. Jika wadah kosong, permintaan ditolak. Jika jumlah token masih di bawah kapasitas, permintaan diterima dan token tersebut digunakan.
Wadah token memungkinkan lonjakan: jika tidak ada permintaan selama 5 detik, wadah terisi hingga N token, lalu N permintaan dapat masuk sekaligus. Algoritme ini sesuai untuk antarmuka pemrograman aplikasi yang sesekali menerima lonjakan. Kedua parameternya adalah kapasitas (ukuran lonjakan) dan laju pengisian ulang.
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 requestsAlgoritme Pembatasan Laju 2: Catatan Jendela Bergeser
Catatan jendela bergeser menyimpan stempel waktu untuk setiap permintaan dalam himpunan terurut. Untuk setiap permintaan baru, hapus stempel waktu yang lebih lama dari awal jendela, lalu periksa apakah jumlah stempel waktu yang tersisa berada di bawah batas. Jika ya, tambahkan stempel waktu saat ini dan allow; jika tidak, tolak.
Metode ini tepat—metode ini menghitung persis jumlah permintaan yang terjadi dalam N detik terakhir. Komprominya adalah penggunaan memori yang tinggi (satu entri untuk setiap permintaan bagi setiap pengguna). Untuk batas 1000 permintaan per menit dengan 100 ribu pengguna, kondisi terburuknya adalah 100 juta entri catatan. Metode ini tidak sesuai untuk lalu lintas yang sangat tinggi kecuali digabungkan dengan pemartisian.
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)Algoritme Pembatasan Laju 3: Penghitung Jendela Bergeser
Penghitung jendela bergeser memperkirakan jendela bergeser menggunakan dua wadah—menit saat ini dan menit sebelumnya—dengan bobot berdasarkan seberapa jauh kita berada di dalam menit saat ini. Metode ini mengurangi penggunaan memori dari O(requests) menjadi O(1) per pengguna, sekaligus memberikan perkiraan yang mendekati hitungan jendela bergeser yang tepat.
Rumus: estimated_count = prev_count × (1 - fraction_of_window_elapsed) + curr_count. Jika perkiraan hitungan ini melebihi batas, permintaan ditolak. Algoritme ini digunakan oleh Cloudflare dan Kong dalam skala besar karena penggunaan memorinya O(1) per pengguna dan tingkat keakuratannya tinggi.
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)Pembatasan Laju Terdistribusi dengan Redis
Dalam sistem terdistribusi dengan beberapa peladen aplikasi, pembatasan laju harus dipusatkan—jika tidak, setiap peladen akan menghitung jumlahnya sendiri dan batas efektifnya menjadi berlipat ganda sesuai jumlah peladen. Redis dengan operasi atomik adalah solusi standar: gunakan INCR dan EXPIRE untuk penghitung jendela tetap, atau ZADD dan ZCOUNT untuk catatan jendela bergeser.
Pendekatan skrip Lua membuat beberapa operasi Redis menjadi atomik dan mencegah kondisi balapan ketika dua peladen menambah jumlah secara bersamaan tepat di bawah batas. Redis memproses skrip Lua sebagai satu perintah sehingga menjamin keatomikan tanpa kunci terdistribusi.
# 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')Merancang Umpan Twitter: Persyaratan
Mari kita merancang sistem umpan berita mirip Twitter. Persyaratan fungsional: pengguna dapat menerbitkan kiriman hingga 280 karakter, mengikuti pengguna lain, dan melihat umpan kiriman dari orang yang mereka ikuti, diurutkan berdasarkan waktu terbaru. Persyaratan nonfungsional: 300 juta pengguna aktif harian, 500 juta kiriman per hari, umpan harus dimuat dalam <2 detik, dengan rasio baca:tulis sekitar 100:1.
Perkiraan kapasitas: 500 juta kiriman per hari ÷ 86400 ≈ 5800 kiriman per detik. Pembacaan ≈ 580 ribu per detik. Setiap kiriman berukuran sekitar 300 byte; 500 juta × 300 byte = 150 GB penyimpanan kiriman baru per hari. Agregasi umpan adalah tantangan utama dalam rekayasa sistem.
# 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()Distribusi Saat Penulisan: Umpan yang Telah Dihitung Sebelumnya
Dalam distribusi saat penulisan, ketika pengguna A menerbitkan kiriman, sistem segera mendistribusikannya ke umpan setiap pengikut. Ketika pengikut meminta umpannya, umpan tersebut sudah dihitung sebelumnya dan disimpan di Redis—pembacaan sederhana dari daftar Redis dengan O(k), dengan k sebagai ukuran umpan (biasanya dibatasi hingga 1000 kiriman).
Tantangannya adalah selebritas dengan jutaan pengikut yang menghasilkan operasi distribusi dalam jumlah sangat besar. Ketika Justin Bieber menerbitkan kiriman, sistem harus menulisnya ke lebih dari 100 juta umpan pengikut secara bersamaan—masalah nyata yang pernah dihadapi Twitter dan disebut sebagai “masalah selebritas”. Layanan distribusi saat penulisan harus berjalan secara asinkron dan berbasis antrean untuk menangani lonjakan ini.
# 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')Distribusi Hibrida: Mengatasi Masalah Selebritas
Pendekatan hibrida menggabungkan distribusi saat penulisan untuk pengguna biasa dan distribusi saat pembacaan untuk selebritas. Seorang pengguna diklasifikasikan sebagai selebritas jika jumlah pengikutnya melebihi ambang batas (misalnya, 1 juta pengikut). Untuk pengguna biasa, kiriman didorong ke semua umpan pengikut saat diterbitkan. Untuk selebritas, kiriman mereka NOT didorong; sebaliknya, ketika seorang pengikut membaca umpannya, sistem mengambil kiriman terbaru selebritas tersebut dan menggabungkannya dengan umpan yang telah dihitung sebelumnya.
Model hibrida ini mendekati cara Twitter yang sebenarnya. Langkah penggabungan berlangsung cepat karena selebritas jarang menerbitkan kiriman dan penggabungan tersebut memiliki kompleksitas O(f), dengan f sebagai jumlah akun selebritas yang diikuti pengguna (biasanya sedikit).
# 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)Umpan Twitter: Arsitektur Lengkap
Arsitektur umpan Twitter yang lengkap menggabungkan beberapa sistem:
- Layanan Kiriman: menulis kiriman ke Cassandra (kapasitas tulis tinggi, deret waktu)
- Layanan Distribusi: pekerja asinkron (konsumen Kafka) yang mendorong ID kiriman ke umpan pengikut di Redis
- Layanan Umpan: membaca umpan dari Redis, melengkapi ID kiriman menjadi objek kiriman lengkap, dan menggabungkan kiriman selebritas
- Layanan Pengikutan: mengelola graf sosial (siapa mengikuti siapa) dalam basis data graf atau SQL yang dipartisi
- Layanan Linimasa: menyajikan kiriman milik pengguna sendiri (terpisah dari umpan beranda)
# 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)Tajuk Pembatas Laju dan Respons Galat
Pembatas laju yang dirancang dengan baik mengomunikasikan batasnya kepada klien melalui tajuk respons HTTP. Hal ini memungkinkan klien menerapkan logika untuk mencoba lagi dan dasbor menampilkan penggunaan. Tajuk standar:
X-RateLimit-Limit: jumlah maksimum permintaan yang diizinkan dalam jendelaX-RateLimit-Remaining: jumlah permintaan yang tersisa dalam jendela saat iniX-RateLimit-Reset: stempel waktu Unix saat jendela diatur ulangRetry-After: jumlah detik yang harus ditunggu sebelum mencoba lagi (pada respons 429)
Kode status HTTP untuk respons yang terkena pembatasan laju adalah 429 Terlalu Banyak Permintaan.
# 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}')Membandingkan Algoritme Pembatasan Laju
Perbandingan singkat semua algoritme pembatasan laju untuk membantu Anda memilih saat wawancara:
- Ember Token: memungkinkan lonjakan permintaan dan memiliki laju pengisian ulang yang lancar. Paling cocok untuk antarmuka pemrograman aplikasi yang sesekali menerima lonjakan (pilihan yang paling umum).
- Ember Bocor: memproses permintaan pada laju keluaran tetap, terlepas dari adanya lonjakan. Paling cocok untuk membentuk lalu lintas menjadi aliran konstan.
- Penghitung Jendela Tetap: paling sederhana, dengan ruang O(1). Masalahnya adalah lonjakan sebesar dua kali batas pada batas jendela (misalnya, 100 pada 11:59 + 100 pada 12:00).
- Log Jendela Geser: paling akurat, tanpa lonjakan pada batas. Masalahnya adalah memori O(jumlah permintaan).
- Penghitung Jendela Geser: mendekati log jendela geser dengan ruang O(1). Digunakan oleh 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}')Uji Cepat
Uji pemahaman Anda tentang konsep Struktur Data & Algoritme — Persiapan Wawancara Pemrograman dari pelajaran ini.
Rangkuman Pelajaran
Dalam pelajaran ini Anda mempelajari: pembatas laju menggunakan ember token (memungkinkan lonjakan), penghitung jendela geser (memori O(1)), atau log jendela geser (paling akurat) untuk mengendalikan laju permintaan, dan operasi atomik Redis memungkinkan pembatasan laju terdistribusi, umpan Twitter menggunakan penyebaran saat penulisan untuk melakukan pra-perhitungan umpan pengikut di Redis agar pembacaan cepat, dengan model penarikan hibrida untuk akun selebritas guna menghindari penggandaan penulisan yang sangat besar. Berikutnya, kita memasuki bagian proyek akhir dengan lembar contekan pengenalan pola yang memetakan sinyal soal ke pola algoritme yang menyelesaikannya dengan paling cepat.
Pertanyaan yang Sering Diajukan
Apakah pelajaran “Merancang Pembatas Laju dan Merancang Feed Twitter” gratis?
Ya — teks lengkap “Merancang Pembatas Laju dan Merancang Feed Twitter” gratis dibaca di sini di web. Untuk praktiknya secara interaktif (editor kode bawaan dan tutor AI 24/7) dan buka sisa kursus DSA Interview Prep, upgrade ke CoddyKit PRO. Kursus DSA Interview Prep mencakup 4 pelajaran total.
Apa yang akan aku pelajari di “Merancang Pembatas Laju dan Merancang Feed Twitter”?
Terapkan kerangka tersebut pada dua masalah desain kanonis: pembatasan laju token-bucket/sliding-window dan feed berita fan-out-on-write vs fan-out-on-read Kamu berlatih DSA Interview Prep dengan kode praktik yang langsung kamu jalankan di browser, dan tutor AI 24/7 menjawab pertanyaanmu saat kamu mengerjakan pelajaran ini.
Apakah aku perlu pengalaman untuk memulai DSA Interview Prep?
Tidak diperlukan pengalaman sebelumnya. DSA Interview Prep di CoddyKit dirancang untuk pemula hingga pelajar tingkat lanjut, jadi kamu bisa memulai di sini atau dari awal dan belajar sesuai kecepatan kamu sendiri. Ini adalah pelajaran 4 dari 4.
Berapa lama pelajaran “Merancang Pembatas Laju dan Merancang Feed Twitter” memakan waktu?
Sebagian besar pelajaran CoddyKit memakan waktu sekitar 5–10 menit. Setiap pelajaran ringkas dan interaktif, jadi kamu membuat kemajuan stabil dan melanjutkan dari tempat kamu tinggalkan di web dan aplikasi.
Bisakah aku menulis dan menjalankan kode dalam pelajaran DSA Interview Prep ini?
Ya. Setiap pelajaran DSA Interview Prep menyertakan editor kode bawaan, jadi kamu menulis dan menjalankan kode nyata langsung di browser dan mendapatkan umpan balik AI instan — tidak diperlukan penyiapan lokal.
Semua pelajaran dalam kursus ini
- Kerangka Wawancara Desain Sistem
- Penyimpanan Data Skalabel: SQL vs NoSQL
- Caching, CDN, dan Penyeimbangan Beban
- Merancang Pembatas Laju dan Merancang Feed Twitter