DSA Interview Prep · บทเรียน

การออกแบบตัวจำกัดอัตราและฟีด Twitter

ประยุกต์กรอบงานกับปัญหาการออกแบบมาตรฐานสองกรณี ได้แก่ การจำกัดอัตราแบบถังโทเคน/หน้าต่างเลื่อน และฟีดข่าวแบบกระจายเมื่อเขียนเทียบกับกระจายเมื่ออ่าน

บทเรียน 4 จาก 413 ขั้นตอน

การออกแบบตัวจำกัดอัตราและฟีด Twitter เป็นบทเรียน DSA Interview Prep ฟรีบน CoddyKit นี่คือบทเรียนที่ 4 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน DSA Interview Prep และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส DSA Interview Prep มีบทเรียนทั้งหมด 4 บทเรียน

เหตุใดการจำกัดอัตราจึงจำเป็น

การจำกัดอัตรา ควบคุมจำนวนคำขอที่ไคลเอ็นต์สามารถส่งไปยังส่วนติดต่อโปรแกรมประยุกต์ได้ภายในช่วงเวลาที่กำหนด หากไม่มีการจำกัดอัตรา ไคลเอ็นต์ที่ทำงานผิดปกติเพียงรายเดียว (หรือการโจมตีแบบปฏิเสธการให้บริการแบบกระจาย) อาจใช้ทรัพยากรเซิร์ฟเวอร์จนเต็มและทำให้บริการของผู้ใช้ทุกคนแย่ลง การจำกัดอัตรายังช่วยป้องกันการโจมตีแบบลองรหัสผ่านจำนวนมาก ป้องกันการดึงข้อมูลจากส่วนติดต่อโปรแกรมประยุกต์ และบังคับใช้การใช้งานทรัพยากรร่วมกันอย่างเป็นธรรม

ขอบเขตการจำกัดอัตราที่ใช้กันทั่วไป ได้แก่ ต่อรหัสผู้ใช้ ID ต่อกุญแจส่วนติดต่อโปรแกรมประยุกต์ ต่อที่อยู่ IP ต่อปลายทาง หรือใช้หลายรูปแบบร่วมกัน ตัวอย่างขีดจำกัดทั่วไปคือ 100 คำขอต่อนาทีต่อผู้ใช้ และ 1,000 คำขอต่อชั่วโมงต่อกุญแจส่วนติดต่อโปรแกรมประยุกต์ ตัวจำกัดอัตราต้องทำงานรวดเร็ว (เพิ่มเวลาไม่ถึง 1 มิลลิวินาที) และทำงานแบบกระจาย (ใช้ขีดจำกัดเดียวกันกับชุดจำลองเซิร์ฟเวอร์ส่วนติดต่อโปรแกรมประยุกต์ทั้งหมด)

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

อัลกอริทึมการจำกัดอัตรา 1: ถังโทเคน

อัลกอริทึม ถังโทเคน จะดูแลถังที่มีโทเคนได้สูงสุด N โทเคน โทเคนจะถูกเพิ่มเข้ามาด้วยอัตราคงที่ (เช่น 10 โทเคนต่อวินาที) คำขอแต่ละรายการใช้โทเคนหนึ่งรายการ หากถังว่าง คำขอจะถูกปฏิเสธ หากจำนวนโทเคนยังต่ำกว่าความจุ คำขอจะได้รับอนุญาตและโทเคนจะถูกใช้ไป

ถังโทเคนรองรับ การรับคำขอเป็นชุดใหญ่ หากไม่มีคำขอเข้ามาเป็นเวลา 5 วินาที ถังจะเติมจนมี N โทเคน จากนั้นสามารถรับคำขอ N รายการได้ทันที วิธีนี้เหมาะกับส่วนติดต่อโปรแกรมประยุกต์ที่ยอมรับคำขอจำนวนมากเป็นครั้งคราวได้ พารามิเตอร์สองตัวคือความจุ (ขนาดชุดคำขอ) และอัตราการเติมโทเคน

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

อัลกอริทึมการจำกัดอัตรา 2: บันทึกหน้าต่างเลื่อน

บันทึกหน้าต่างเลื่อน จะจัดเก็บเวลาประทับของคำขอทุกรายการไว้ในชุดที่เรียงลำดับ สำหรับคำขอใหม่แต่ละรายการ ให้ remove เวลาประทับที่เก่ากว่าจุดเริ่มต้นของหน้าต่าง จากนั้นตรวจสอบว่าจำนวนเวลาประทับที่เหลือต่ำกว่าขีดจำกัดหรือไม่ หากใช่ ให้เพิ่มเวลาประทับปัจจุบันและ allow มิฉะนั้นให้ปฏิเสธ

วิธีนี้แม่นยำ เพราะนับจำนวนคำขอที่เกิดขึ้นจริงในช่วง N วินาทีล่าสุดได้อย่างถูกต้อง ข้อแลกเปลี่ยนคือใช้หน่วยความจำสูง (มีหนึ่งรายการต่อคำขอต่อผู้ใช้) สำหรับขีดจำกัด 1,000 คำขอต่อนาทีที่มีผู้ใช้ 100K ราย กรณีเลวร้ายที่สุดจะมีรายการบันทึก 100M รายการ จึงไม่เหมาะกับปริมาณการรับส่งข้อมูลที่สูงมาก เว้นแต่จะใช้ร่วมกับการแบ่งส่วนข้อมูล

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)

อัลกอริทึมการจำกัดอัตรา 3: ตัวนับหน้าต่างเลื่อน

ตัวนับหน้าต่างเลื่อน ประมาณค่าหน้าต่างเลื่อนโดยใช้ถังสองถัง ได้แก่ นาทีปัจจุบันและนาทีก่อนหน้า แล้วถ่วงน้ำหนักตามสัดส่วนเวลาที่ผ่านไปในนาทีปัจจุบัน วิธีนี้ลดการใช้หน่วยความจำจาก O(requests) เหลือ O(1) ต่อผู้ใช้ ขณะยังคงประมาณจำนวนคำขอในหน้าต่างเลื่อนได้ใกล้เคียงกับค่าจริง

สูตร: estimated_count = prev_count × (1 - fraction_of_window_elapsed) + curr_count หากค่าประมาณนี้เกินขีดจำกัด ให้ปฏิเสธคำขอ นี่คืออัลกอริทึมที่ Cloudflare และคองใช้ในระบบขนาดใหญ่ เนื่องจากใช้หน่วยความจำ O(1) ต่อผู้ใช้และมีความแม่นยำสูง

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)

การจำกัดอัตราแบบกระจายด้วยรีดิส

สำหรับระบบแบบกระจายที่มีเซิร์ฟเวอร์แอปพลิเคชันหลายเครื่อง การจำกัดอัตราต้องทำจากศูนย์กลาง มิฉะนั้นแต่ละเซิร์ฟเวอร์จะนับจำนวนของตนเอง และขีดจำกัดจะถูกคูณด้วยจำนวนเซิร์ฟเวอร์โดยปริยาย รีดิส ที่ใช้การดำเนินการแบบอะตอมิกเป็นวิธีมาตรฐาน: ใช้ INCR และ EXPIRE สำหรับตัวนับหน้าต่างคงที่ หรือใช้ ZADD และ ZCOUNT สำหรับบันทึกหน้าต่างเลื่อน

แนวทางใช้สคริปต์ลัวทำให้การดำเนินการรีดิสหลายรายการเป็นอะตอมิก ป้องกันสภาวะการแข่งขันที่เซิร์ฟเวอร์สองเครื่องเพิ่มค่าพร้อมกันขณะจำนวนยังต่ำกว่าขีดจำกัดเพียงเล็กน้อย รีดิสจะประมวลผลสคริปต์ลัวเป็นคำสั่งเดียว จึงรับประกันความเป็นอะตอมิกโดยไม่ต้องใช้การล็อกแบบกระจาย

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

การออกแบบฟีดทวิตเตอร์: ข้อกำหนด

ให้เราออกแบบระบบ ฟีดข่าวแบบทวิตเตอร์ ข้อกำหนดด้านฟังก์ชัน: ผู้ใช้สามารถโพสต์ทวีต (สูงสุด 280 อักขระ) ติดตามผู้ใช้รายอื่น และดูฟีดทวีตจากบุคคลที่ตนติดตาม โดยเรียงตามความใหม่ ข้อกำหนดที่ไม่ใช่ด้านฟังก์ชัน: ผู้ใช้งานประจำวัน 300 ล้านราย ทวีต 500 ล้านรายการต่อวัน ฟีดต้องโหลดเสร็จภายใน 2 วินาที และมีอัตราส่วนการอ่านต่อการเขียนประมาณ 100:1

การประมาณความจุ: 500 ล้านทวีตต่อวัน ÷ 86400 ≈ 5,800 ทวีตต่อวินาที การอ่าน ≈ 580,000 ครั้งต่อวินาที ทวีตแต่ละรายการมีขนาดประมาณ 300 ไบต์; 500 ล้าน × 300 ไบต์ = พื้นที่จัดเก็บทวีตใหม่ 150 GB ต่อวัน การรวบรวมฟีดคือความท้าทายหลักด้านวิศวกรรม

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

การกระจายเมื่อเขียน: ฟีดที่คำนวณไว้ล่วงหน้า

ในการ กระจายเมื่อเขียน เมื่อผู้ใช้ A โพสต์ทวีต ระบบจะกระจายทวีตนั้นไปยังฟีดของผู้ติดตามทุกคนทันที เมื่อผู้ติดตามร้องขอฟีด ฟีดนั้นจะถูกคำนวณไว้ล่วงหน้าและจัดเก็บในรีดิสแล้ว การอ่านจึงเป็นเพียงการอ่านรายการรีดิสอย่างง่ายด้วย O(k) เมื่อ k คือขนาดฟีด (โดยทั่วไปจำกัดไว้ที่ 1,000 ทวีต)

ความท้าทายคือผู้มีชื่อเสียงที่มีผู้ติดตามหลายล้านรายจะสร้างการกระจายข้อมูลจำนวนมหาศาล จัสติน บีเบอร์โพสต์ทวีตหนึ่งรายการอาจต้องเขียนลงฟีดของผู้ติดตามมากกว่า 100M รายพร้อมกัน ซึ่งเป็นปัญหาจริงที่ทวิตเตอร์เคยพบและเรียกว่า “ปัญหาผู้มีชื่อเสียง” บริการกระจายเมื่อเขียนต้องทำงานแบบอะซิงโครนัสและใช้คิวเพื่อรองรับปริมาณที่พุ่งสูงขึ้น

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

การกระจายแบบผสม: แก้ปัญหาผู้มีชื่อเสียง

แนวทางแบบผสม ผสานการกระจายเมื่อเขียนสำหรับผู้ใช้ทั่วไปเข้ากับการกระจายเมื่ออ่านสำหรับผู้มีชื่อเสียง ผู้ใช้จะถูกจัดประเภทเป็นผู้มีชื่อเสียงหากจำนวนผู้ติดตามเกินเกณฑ์ที่กำหนด (เช่น ผู้ติดตาม 1 ล้านราย) สำหรับผู้ใช้ทั่วไป ทวีตจะถูกส่งไปยังฟีดของผู้ติดตามทุกคนในเวลาที่โพสต์ ส่วนผู้มีชื่อเสียง ทวีตของพวกเขาจะไม่ถูกส่งไปล่วงหน้า แต่เมื่อผู้ติดตามอ่านฟีด ระบบจะดึงทวีตล่าสุดของผู้มีชื่อเสียงและผสานเข้ากับฟีดที่คำนวณไว้ล่วงหน้า

โมเดลแบบผสมนี้ใกล้เคียงกับสิ่งที่ทวิตเตอร์ใช้งานจริง ขั้นตอนการผสานทำงานได้รวดเร็ว เพราะผู้มีชื่อเสียงโพสต์ไม่บ่อย และการผสานมีความซับซ้อน O(f) เมื่อ 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)

ฟีดทวิตเตอร์: สถาปัตยกรรมสมบูรณ์

สถาปัตยกรรมฟีดทวิตเตอร์ที่สมบูรณ์ผสานระบบหลายส่วนเข้าด้วยกัน:

  • บริการทวีต: เขียนทวีตลงแคสแซนดรา (รองรับอัตราการเขียนสูงและข้อมูลอนุกรมเวลา)
  • บริการกระจายข้อมูล: ผู้ปฏิบัติงานแบบอะซิงโครนัส (ผู้บริโภคคาฟคา) ที่ส่งรหัสทวีตไปยังฟีดของผู้ติดตามในรีดิส
  • บริการฟีด: อ่านฟีดจากรีดิส แปลงรหัสทวีตให้เป็นออบเจ็กต์ทวีตแบบเต็ม และผสานทวีตของผู้มีชื่อเสียง
  • บริการติดตาม: จัดการกราฟสังคม (ใครติดตามใคร) ในฐานข้อมูลกราฟหรือฐานข้อมูล SQL ที่แบ่งส่วน
  • บริการไทม์ไลน์: ให้บริการทวีตของผู้ใช้เอง (แยกจากฟีดหลัก)
# 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)

ส่วนหัวของตัวจำกัดอัตราและคำตอบข้อผิดพลาด

ตัวจำกัดอัตราที่ออกแบบมาอย่างดีจะแจ้งขีดจำกัดให้ไคลเอ็นต์ทราบผ่านส่วนหัวของคำตอบจากโปรโตคอลเว็บ ทำให้ไคลเอ็นต์สามารถใช้ตรรกะรอแล้วลองใหม่ และแดชบอร์ดสามารถแสดงการใช้งานได้ ส่วนหัวมาตรฐานมีดังนี้:

  • X-RateLimit-Limit: จำนวนคำขอสูงสุดที่อนุญาตในช่วงเวลา
  • X-RateLimit-Remaining: จำนวนคำขอที่เหลืออยู่ในช่วงเวลาปัจจุบัน
  • X-RateLimit-Reset: เวลาประทับแบบยูนิกซ์ที่ช่วงเวลาจะเริ่มใหม่
  • Retry-After: จำนวนวินาทีที่ต้องรอก่อนลองใหม่ (เมื่อได้รับคำตอบ 429)

รหัสสถานะของโปรโตคอลเว็บสำหรับคำตอบที่ถูกจำกัดอัตราคือ 429 คำขอมากเกินไป

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

การเปรียบเทียบอัลกอริทึมจำกัดอัตรา

การเปรียบเทียบโดยสรุปของอัลกอริทึมจำกัดอัตราทั้งหมด เพื่อช่วยให้คุณเลือกใช้ในการสัมภาษณ์:

  • ถังโทเคน: รองรับการพุ่งขึ้นของคำขอ และมีอัตราการเติมกลับที่ราบรื่น เหมาะที่สุดสำหรับส่วนติดต่อโปรแกรมประยุกต์ที่ยอมรับการพุ่งขึ้นเป็นครั้งคราวได้ (เป็นตัวเลือกที่พบบ่อยที่สุด)
  • ถังรั่ว: ประมวลผลคำขอด้วยอัตราข้อมูลออกคงที่ ไม่ว่าคำขอจะพุ่งขึ้นมากเพียงใด เหมาะที่สุดสำหรับปรับรูปแบบการรับส่งข้อมูลให้เป็นกระแสคงที่
  • ตัวนับหน้าต่างคงที่: เรียบง่ายที่สุด ใช้พื้นที่ O(1) ปัญหาคืออาจเกิดการพุ่งขึ้นเป็นสองเท่าของขีดจำกัดตรงรอยต่อของหน้าต่าง (เช่น 100 คำขอเวลา 11:59 + 100 คำขอเวลา 12:00)
  • บันทึกหน้าต่างเลื่อน: แม่นยำที่สุด ไม่มีการพุ่งขึ้นตรงรอยต่อ ปัญหาคือใช้หน่วยความจำ O(จำนวนคำขอ)
  • ตัวนับหน้าต่างเลื่อน: ประมาณค่าบันทึกหน้าต่างเลื่อนโดยใช้พื้นที่ O(1) คลาวด์แฟลร์นำวิธีนี้ไปใช้
# 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}')

ตรวจสอบความเข้าใจอย่างรวดเร็ว

ทดสอบความเข้าใจแนวคิดโครงสร้างข้อมูลและอัลกอริทึม — การเตรียมตัวสัมภาษณ์การเขียนโปรแกรมจากบทเรียนนี้

ทบทวนบทเรียน

ในบทเรียนนี้ คุณได้เรียนรู้ว่า ตัวจำกัดอัตราใช้ถังโทเคน (รองรับการพุ่งขึ้นของคำขอ) ตัวนับหน้าต่างเลื่อน (ใช้หน่วยความจำ O(1)) หรือบันทึกหน้าต่างเลื่อน (แม่นยำที่สุด) เพื่อควบคุมอัตราคำขอ และการดำเนินการแบบอะตอมิกของเรดิสช่วยให้จำกัดอัตราแบบกระจายได้ รวมถึง ฟีดของทวิตเตอร์ใช้การกระจายข้อมูลเมื่อเขียน เพื่อคำนวณฟีดของผู้ติดตามล่วงหน้าไว้ในเรดิสสำหรับการอ่านที่รวดเร็ว และใช้แบบจำลองดึงข้อมูลแบบผสมสำหรับบัญชีบุคคลที่มีชื่อเสียง เพื่อหลีกเลี่ยงการขยายจำนวนการเขียนอย่างมหาศาล บทถัดไป เราจะเข้าสู่ส่วนโครงงานสรุป พร้อมเอกสารสรุปการรู้จำรูปแบบที่จับคู่สัญญาณของโจทย์กับรูปแบบอัลกอริทึมที่แก้โจทย์เหล่านั้นได้เร็วที่สุด

เริ่มต้นได้ฟรี

เรียนรู้ Python ด้วย AI tutor — ฟรี

เขียนและเรียกใช้โค้ดจริงในเบราว์เซอร์ของคุณ รับความช่วยเหลือทันทีจาก AI tutor 24/7 และเรียนรู้ต่อจากที่คุณหยุดบนเว็บหรือในแอป

คอร์ส
30
บทเรียน
120

คำถามที่พบบ่อย

บทเรียน “การออกแบบตัวจำกัดอัตราและฟีด Twitter” ฟรีหรือไม่

ใช่ — ข้อความเต็มของ “การออกแบบตัวจำกัดอัตราและฟีด Twitter” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส DSA Interview Prep ให้อัปเกรดเป็น CoddyKit PRO คอร์ส DSA Interview Prep มีบทเรียนทั้งหมด 4 บทเรียน

คุณจะเรียนรู้อะไรในบทเรียน “การออกแบบตัวจำกัดอัตราและฟีด Twitter”

ประยุกต์กรอบงานกับปัญหาการออกแบบมาตรฐานสองกรณี ได้แก่ การจำกัดอัตราแบบถังโทเคน/หน้าต่างเลื่อน และฟีดข่าวแบบกระจายเมื่อเขียนเทียบกับกระจายเมื่ออ่าน คุณปฏิบัติ DSA Interview Prep ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน

คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน DSA Interview Prep หรือไม่

ไม่จำเป็นต้องมีประสบการณ์มาก่อน DSA Interview Prep บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 4 จากทั้งหมด 4 บทเรียน

บทเรียน “การออกแบบตัวจำกัดอัตราและฟีด Twitter” ใช้เวลานานแค่ไหน

บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย

ฉันเขียนและรันโค้ดในบทเรียน DSA Interview Prep นี้ได้ไหม

ได้ บทเรียน DSA Interview Prep ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ

บทเรียนทั้งหมดในหลักสูตรนี้

  1. กรอบการสัมภาษณ์ด้านการออกแบบระบบ
  2. การจัดเก็บข้อมูลที่ขยายขนาดได้: SQL กับ NoSQL
  3. แคช, CDN และการกระจายโหลด
  4. การออกแบบตัวจำกัดอัตราและฟีด Twitter
← กลับไปที่ DSA Interview Prep