Real-World Case Studies
Interview Context
DS/MLE 面試越來越常出現 system design 環節 — 不一定要你設計完整的 Twitter,但會問「你的模型 serving 架構長怎樣?」「資料存哪裡?為什麼?」。這一章用經典案例(Instagram、Netflix、Airbnb、S3)建立你的 design vocabulary,讓你在面試中能用具體的 pattern 和 tradeoff 回答,而不是空泛地說「加個 cache」。
What You Should Understand
- 能用 4-step framework(requirements → high-level design → deep dive → wrap-up)結構化地回答任何 design 題
- 能做 back-of-envelope estimation:從 DAU 推出 QPS 和 storage,並用數字證明 design 決策
- 理解 news feed 的 fanout-on-write vs fanout-on-read tradeoff,以及 celebrity problem 的 hybrid 解法
- 能講出 Netflix 和 Airbnb 從 monolith 到 microservices 的演化故事,以及「何時該拆」的判斷準則
- 理解 object storage(S3)和 file system 的差異,以及為什麼 data lake 建在 object storage 上
- 掌握網站效能的 checklist:CDN、compression、caching、lazy loading、connection reuse、query optimization
A Framework for Design Interviews
System design 面試沒有標準答案,但有標準流程。面試官評的不是你背了多少架構圖,而是你能不能像資深工程師一樣:先問清楚需求、用數字支撐決策、主動講 tradeoff。
The 4-Step Method
以 45 分鐘的面試為例:
| Step | Time | What You Do | What It Signals |
|---|---|---|---|
| 1. Requirements & Scale Estimation | 5-10 min | Clarify functional + non-functional requirements; estimate QPS, storage, read/write ratio | 你不會拿到題目就埋頭畫圖 |
| 2. High-Level Design | 10-15 min | Draw major components: client, API, services, database, cache, queue | 你有完整的 system vocabulary |
| 3. Deep Dive | 15-20 min | Pick 1-2 components and go deep: schema, sharding, consistency, failure modes | 你有真正的深度,不只是名詞 |
| 4. Wrap-up & Tradeoffs | 5 min | Summarize, list bottlenecks, discuss what you would improve with more time | 你知道 design 永遠不完美 |
Step 1: Requirements Come in Two Kinds
| Type | Questions to Ask | Example (News Feed) |
|---|---|---|
| Functional | What features are in scope? Who are the users? | Post photos, follow users, view feed |
| Non-functional | Scale? Latency? Availability vs consistency? | 500M DAU, feed loads in 200ms, eventual consistency OK |
直覺:functional requirements 決定你要畫哪些盒子,non-functional requirements 決定盒子怎麼接、要幾個。同樣是 news feed,10K users 和 500M users 是完全不同的系統。
Back-of-Envelope Estimation
估算不用精確 — 面試官要看的是你的推理鏈和數量級是否合理。常用的錨點數字:
| Quantity | Value | Memory Aid |
|---|---|---|
| Seconds per day | 86,400(約 10 的 5 次方) | 1 day ≈ 100K seconds |
| 1M requests/day | ≈ 12 QPS | Divide by 100K |
| Peak traffic | 2-5x average | Depends on product |
| Photo size | 200KB-2MB | After compression |
| One char / one int | 1 byte / 4 bytes | For row-size estimates |
估算的真正目的
估算不是算術表演,而是決策依據。算出 60K read QPS 之後,你才有理由說「單台 database 撐不住,需要 cache layer + read replicas」。每個數字都應該接一個 design 決策,否則就是在浪費面試時間。
What Interviewers Actually Score
| Signal | Weak Answer | Strong Answer |
|---|---|---|
| Problem navigation | Jumps straight to solution | Asks clarifying questions first |
| Quantitative reasoning | "We need a big database" | "60K QPS reads → cache + replicas" |
| Tradeoff awareness | "Use Kafka because it's popular" | "Queue decouples upload from processing, at the cost of eventual consistency" |
| Depth on demand | Only buzzwords | Can explain how the chosen component works internally |
| Communication | Silent drawing | Thinks out loud, checks in with interviewer |
Design Instagram / News Feed
場景:設計一個照片分享 app — 使用者上傳照片、追蹤別人、滑動 feed 看被追蹤者的最新貼文。這是最經典的 design 題,因為它同時考 storage、read-heavy scaling、和 fanout 這個核心 tradeoff。
Step 1: Requirements and Estimation
假設:500M DAU,每人每天上傳 0.2 張照片、刷 10 次 feed,照片平均 2MB。
| Quantity | Calculation | Result |
|---|---|---|
| Upload QPS | 500M × 0.2 / 86,400 | ≈ 1,200 writes/sec(peak ≈ 3,500) |
| Feed read QPS | 500M × 10 / 86,400 | ≈ 58,000 reads/sec |
| Read/write ratio | 58,000 / 1,200 | ≈ 50:1 — 極度 read-heavy |
| Storage per day | 100M photos × 2MB | ≈ 200TB/day |
| Storage per year | 200TB × 365 | ≈ 73PB — 不可能放在 database |
兩個數字直接決定了架構:(1)73PB/year → 照片必須放 object storage,不是 database;(2)50:1 read ratio → feed 必須靠 cache 而不是每次都查 DB。
The Photo Upload Path
Client → API Gateway → Upload Service → Object Storage (S3)
│
└→ Metadata DB (photo_id, user_id, caption, s3_key)
└→ Queue → Async workers (thumbnails, ML tagging, fanout)
| Component | Choice | Why |
|---|---|---|
| Photo bytes | Object storage + CDN | Cheap, durable, infinitely scalable; CDN serves from edge |
| Photo metadata | Relational DB or wide-column store | Structured, queryable; only KBs per photo |
| Thumbnail generation | Async via queue | 不能讓 user 等 resize 完成才收到 upload 成功 |
| Feed fanout | Async via queue | 寫入 follower timelines 可以慢幾秒 |
直覺:database 存指標(pointer),object storage 存 bytes。DB row 裡放的是 s3_key,不是 2MB 的照片。這個 pattern 在 ML 系統中完全一樣 — feature store 存 metadata,training data 和 model artifacts 放 object storage。
Feed Generation: Push vs Pull
這是這題的核心 deep dive。瓶頸:user 打開 app 時,系統要從他追蹤的 500 個帳號中撈出最新貼文、排序、回傳 — 在 200ms 內完成,每秒 58,000 次。
| Approach | How It Works | Pros | Cons | Best For |
|---|---|---|---|---|
| Fanout-on-write (push) | 發文時,把 post_id 寫進每個 follower 的 timeline cache | Read 是 O(1) lookup — 超快 | 名人發文 = 幾百萬次寫入(write amplification);不活躍用戶浪費儲存 | 一般用戶(followers 少) |
| Fanout-on-read (pull) | 讀 feed 時,即時查所有 followees 的最新 posts 再合併 | 寫入便宜;沒有 celebrity 爆炸 | Read 昂貴又慢 — 每次都要 merge 幾百個來源 | 名人、不活躍用戶 |
| Hybrid | 一般用戶 push;名人的 posts 在 read time 才 merge 進來 | 兩者的優點 | 系統複雜度較高 | Production(Instagram/Twitter 實際做法) |
Celebrity problem(名人問題):一個有 100M followers 的帳號發文,純 push 模式要做 100M 次 cache 寫入 — 這會壓垮 fanout pipeline,其他人的貼文也跟著延遲。解法:帳號超過某個 follower 門檻(例如 10K)就標記為 celebrity,發文不 fanout;讀 feed 時把「預先 push 好的 timeline」和「celebrity 的最新貼文」merge 起來。
Timeline Cache
| Design Decision | Choice | Reasoning |
|---|---|---|
| What to store | List of post_ids per user(不是完整 post 內容) | 內容另外查,避免重複儲存與更新異常 |
| Where | Redis sorted set(score = timestamp) | O(log n) insert, O(1) range read |
| Size cap | 最新 500-800 posts per user | 幾乎沒人往回滑超過這個量;控制 memory |
| Miss handling | Fall back to pull(查 DB 重建) | Cache 不是 source of truth,可以隨時重建 |
如果面試被問到
常見 follow-ups:(1)「名人發文瞬間 feed 會塞爆嗎?」→ 講 hybrid fanout + rate limiting。(2)「feed 要嚴格按時間排序嗎?」→ 引導到 eventual consistency:晚幾秒看到貼文完全可接受,換來的是 scalability。(3)「如果要加 ML ranking 呢?」→ timeline cache 存 candidate posts,read path 加一層 ranking service(這就接回 ML pipelines 那一章的 candidate generation → ranking 架構)。
Netflix's Scaling Evolution
場景:Netflix 從 DVD 租借轉型成串流服務,用戶從百萬級長到兩億級。它的架構演化是面試中「monolith vs microservices」的最佳素材 — 因為它示範了何時該拆,而不是「一開始就拆」。
The Evolution Stages
| Stage | Architecture | Trigger for Change |
|---|---|---|
| 1. Monolith in own datacenter | Classic 3-tier: client → monolithic API → database | 2008 年資料庫嚴重故障,服務中斷數天 — 單點風險不可接受 |
| 2. Move to cloud (AWS) | Same app, rented infrastructure | 自建 datacenter 跟不上成長速度;把「管機器」外包 |
| 3. Microservices | Monolith 逐步拆成數百個獨立 services | 團隊變多後,monolith 的 deploy 互相卡住,engineering velocity 掉 |
| 4. API Gateway (Zuul) | Gateway 統一處理 routing、auth、rate limiting | Client 不該知道幾百個 services 的位置 — 降低 client-service coupling |
| 5. Resilience layer | Circuit breakers, bulkheads, chaos engineering | 幾百個 services 之後,「某個 service 掛掉」從例外變成日常 |
教訓:每一步演化都是被具體的痛逼出來的,不是為了追技術潮流。面試中講「scale when you must, not when you can」會比背 microservices 的好處更加分。
Open Connect: Build Your Own CDN
瓶頸:影片串流佔了 Netflix 流量的 95% 以上。用第三方 CDN 成本極高,而且尖峰時段(晚上八點)品質不穩。
解法:Netflix 打造自己的 CDN — Open Connect。核心想法:
| Design Choice | How | Why It Works |
|---|---|---|
| Appliances inside ISPs | 把裝滿熱門影片的 cache server 直接放進各地 ISP 機房 | 影片流量根本不用出 ISP 的網路 — latency 和 transit cost 同時下降 |
| Predictive pre-loading | 每天離峰時段,根據觀看預測把明天的熱門內容推到各節點 | 影片目錄有限且可預測 — cache hit rate 極高 |
| Control plane on AWS, data plane on Open Connect | 推薦、搜尋、帳號等 API 在 AWS;影片 bytes 走自建 CDN | 各自 scale:API 是小流量高邏輯,影片是大流量零邏輯 |
直覺:這是 cache 的極致版本 — 把資料搬到離 user 最近的地方。DS 的類比:與其每次即時算推薦,不如離峰時段 batch 預計算好放進 cache。
Stateless Services and Regional Failover
| Principle | Implementation | Payoff |
|---|---|---|
| Stateless services | Session state 放外部 cache,任何 instance 可以服務任何 request | Instance 可以隨意增減、掛了直接換 |
| Active-active regions | 多個 AWS region 同時服務流量 | 整個 region 掛掉時,DNS 把流量導去其他 region |
| Chaos engineering | 主動在 production 隨機關掉 instances(Chaos Monkey) | 逼所有團隊把 failure 當成常態來設計 |
如果面試被問到
(1)「monolith 一定不好嗎?」→ 不是。Netflix 的 monolith 撐過了最早期 — 拆分是在 deploy 速度和故障隔離變成瓶頸之後。小團隊先拆 microservices 反而是自找麻煩。(2)「為什麼影片不放 AWS CloudFront?」→ 流量規模大到自建更省,而且放進 ISP 是第三方 CDN 做不到的 last-mile 優化。(3)「stateless 對 ML serving 的啟示?」→ model server 也應該 stateless:model weights 從 registry 載入,feature 從 feature store 查,任何 instance 等價 → 可以水平擴展。
Netflix Distributed Counter
場景:Netflix 要計數海量事件 — 某影片的觀看次數、某功能的互動數 — 每秒幾萬次遞增,還要能低延遲讀回。「counter」聽起來簡單,卻是分散式系統的經典難題。
Why Counting at Scale Is Hard
| Problem | What Happens | Why Naive Solutions Fail |
|---|---|---|
| Hot key | 熱門影片的 counter 集中在單一 row/partition | 所有寫入打到同一台 node — 單機吞吐量上限就是系統上限 |
| Lock contention | 每次 increment 都要 read-modify-write 同一筆資料 | Lock 排隊 → latency 暴增 |
| Exactly-once | Client retry 造成重複計數;訊息丟失造成少算 | 網路不可靠,「剛好一次」需要額外機制 |
| Read latency | 即時 aggregate 幾百萬筆 events 太慢 | 讀的時候才算 = 每次讀都是一次大查詢 |
直覺:問題的本質是把所有壓力集中在一個點。解法方向必然是:把寫入攤開(partition)、把計算延後(aggregate lazily)、把讀取預先算好(cache)。
The Buffering / Aggregation Approach
Netflix 的 Distributed Counter Abstraction 分成四層:
| Layer | Responsibility | Key Technique |
|---|---|---|
| 1. Client API | AddCount / GetCount / ClearCount | 簡單的抽象介面,背後複雜度全部隱藏 |
| 2. Event log | 每次 increment 先寫成一筆 event(append-only),不直接更新 counter | 帶 event ID 做 idempotency(retry 不會重複計數);按時間分 bucket 避免 contention |
| 3. Rollup pipeline | 背景批次把 events aggregate 成 counter 值,寫入 rollup store | 在 immutable time window 上聚合 — 寫入和計算解耦 |
| 4. Read cache | Aggregated 值放進 cache 供快速讀取 | 讀到稍舊的值沒關係;stale 時觸發背景 refresh |
核心 pattern:把「更新 counter」變成「append event + 稍後聚合」。append 天然可以 partition、可以平行,contention 消失了。這和 data engineering 的 event sourcing、以及 streaming aggregation(Kafka → windowed aggregation → serving store)是同一個思想。
Accuracy vs Latency Tradeoff
| Mode | Guarantee | Cost | Use When |
|---|---|---|---|
| Best-effort counting | 近似值,可能少算或重複 | 最便宜、最快 | 顯示用的「讚數」— 差幾個沒人在乎 |
| Eventually consistent (rollup) | 最終精確(idempotent events + 完整聚合) | Aggregation pipeline 的延遲(秒級) | 觀看數、billing 相關計數 |
| Real-time exact | 隨時精確 | 極貴 — 需要 distributed transactions | 幾乎不值得;重新想需求 |
面試常見誤區
被問「怎麼設計 view counter」時,直接回答「Redis INCR」只答對了小規模的情況。強答案要先問「需要多精確?多即時?」— 然後指出 hot key 和 exactly-once 的難點,再給出 event log + async aggregation 的架構。先釐清 accuracy requirement 再選架構,這正是 DS 面試官想看的思維。
如果面試被問到:(1)「怎麼保證不重複計數?」→ 每個 event 帶 unique ID,聚合時 dedupe — 這就是 idempotency。(2)「user 看到的數字會延遲多久?」→ 秒級,取決於 rollup 頻率;讀 cache 保證快但可能 stale。(3)「這跟 A/B test 的 metrics pipeline 有什麼關係?」→ 一模一樣:event log(曝光/點擊)→ batch aggregation → metrics store,實驗平台就是一個大型 distributed counter。
Airbnb's Architectural Evolution
場景:Airbnb 用一個 Ruby on Rails monolith(內部叫 Monorail)從 0 做到全球規模。隨著工程師從十位數長到千位數,Monorail 從加速器變成瓶頸 — 這個 migration 故事是「怎麼拆 monolith」的教科書案例。
Why the Monolith Stopped Working
| Symptom | Root Cause |
|---|---|
| Deploy 要排隊、常被別人的 bug block | 幾百個工程師 commit 到同一個 codebase、同一條 deploy pipeline |
| 改一個小功能要理解整個系統 | Code ownership 界線模糊 — 誰都能改任何地方 |
| 局部功能故障拖垮整站 | 所有功能跑在同一個 process,沒有 isolation |
| 無法針對熱點單獨 scale | Monolith 只能整包複製 |
注意:這些全是組織規模造成的問題,不是技術本身。小團隊的 monolith 沒有這些病 — 這是回答「要不要拆」時最關鍵的判斷。
The SOA Migration
Airbnb 把系統拆成 service-oriented architecture(SOA),並且給 service 分了明確的類型:
| Service Type | Responsibility | Example |
|---|---|---|
| Data service | 擁有某個核心資料實體的讀寫 | Listing service, user service |
| Derived data service | 從多個 data services 計算衍生資料 | Pricing, availability, search index |
| Presentation service | 為前端組合多個 services 的結果 | Checkout page aggregator |
Service boundaries around business domains:service 的邊界照著業務領域切(listings、bookings、payments、reviews),不是照技術層切(不要搞出一個「database service」和一個「logic service」)。這樣每個 service 有清楚的 owner 團隊、清楚的職責,才能獨立開發部署。
Migration Lessons
| Lesson | Practice |
|---|---|
| Incremental, not big-bang | 一次搬一個 domain;Monorail 和新 services 長期共存 |
| Compare before switching | 新舊路徑同時跑(dual-read / shadow traffic),驗證結果一致才切流量 |
| Freeze the monolith gradually | 先禁止往 Monorail 加新功能,讓它自然萎縮 |
| Invest in shared infra first | Service framework、observability、CI/CD 先到位,否則每個團隊重造輪子 |
如果面試被問到
(1)「migration 期間怎麼確保正確性?」→ shadow traffic + 結果 diff,跟 ML 的 shadow deployment 完全同構。(2)「service 邊界切錯會怎樣?」→ 兩個 services 之間出現大量互相呼叫(chatty communication)、每個 feature 都要同時改多個 services — 這是邊界沒對齊 business domain 的訊號。(3)「DS 團隊在這種架構下拿資料會遇到什麼?」→ 資料散在各 service 的 DB,需要 data platform 把它們 ETL 進 warehouse/lake — 這就是為什麼大公司都有 data engineering 團隊。
How Amazon S3 Works (Conceptually)
場景:S3 是幾乎所有 data stack 的地基 — training data、feature snapshots、model artifacts、data lake 全放在上面。理解它的設計,你才能回答「為什麼 data lake 建在 S3 而不是資料庫」。
Object Storage vs File System
| Aspect | File System | Object Storage (S3) |
|---|---|---|
| Structure | 階層目錄樹 | Flat namespace: bucket + key |
| Access | Open, seek, partial in-place write | GET / PUT / DELETE whole objects(HTTP API) |
| Update | 可以就地修改檔案的一部分 | Object 是 immutable — 修改 = 整個重新上傳 |
| Metadata | 固定(owner, permissions, mtime) | 每個 object 可帶自訂 key-value metadata |
| Scale | 單機或叢集,有目錄瓶頸 | 幾乎無上限,天生分散式 |
| Cost | 高(block storage) | 低,且有 storage classes 分層 |
Key 看起來像路徑(datasets/2026/07/part-001.parquet),但那只是命名慣例 — S3 沒有真正的資料夾,ListObjects 用 prefix 過濾而已。
Durability: Replication and Erasure Coding
S3 宣稱 11 個 9 的 durability(99.999999999%)。做法的核心概念:
| Technique | How | Tradeoff |
|---|---|---|
| Replication | 完整複製 3 份,放不同 Availability Zones | 簡單、讀快;儲存成本 3x |
| Erasure coding | 把 object 切成 k 個 data shards + m 個 parity shards,任意 k 個就能重建 | 儲存成本約 1.5x 就達到同等 durability;重建需要計算 |
| Continuous auditing | 背景不斷 checksum、偵測 bit rot、自動修復 | Durability 是「持續維護」出來的,不是寫入時一次搞定 |
直覺:erasure coding 就像 RAID 的一般化 — 用 parity 資訊換取「壞幾顆碟也不掉資料」,而成本遠低於整份複製。
Consistency Model
早期 S3 是 eventual consistency(剛寫入的 object 可能讀不到),這曾是 data pipeline 的地雷 — job A 寫完,job B 立刻 list 卻看不到新檔案。現在 S3 提供 strong read-after-write consistency:PUT 成功後,任何 GET/LIST 都保證看到最新版本。但跨 object 的操作仍然沒有 transaction — 這正是 Delta Lake / Iceberg 這類 table format 存在的理由(在 object storage 上補上 ACID 和 schema)。
Why Data Teams Build Lakes on S3
| Reason | Explanation |
|---|---|
| Storage 和 compute 分離 | 資料放 S3,Spark/Trino/Athena 隨用隨開 — 不用為了存資料養一個永遠開機的 cluster |
| 無限便宜的容量 | PB 級 raw data 放 DB 成本爆炸;S3 冷資料還能降級到更便宜的 class |
| 開放格式 | Parquet on S3 誰都能讀 — 不被單一 engine 綁死 |
| 天生的 sharing point | 所有團隊、所有工具讀同一份資料 — single source of truth |
如果面試被問到:(1)「為什麼 training data 放 S3 不放 PostgreSQL?」→ 容量成本、throughput scan 效能、storage/compute 分離。(2)「S3 上怎麼做到『更新一筆資料』?」→ object immutable,所以靠 table format(Delta/Iceberg)用新檔案 + metadata 版本來模擬 update。(3)「11 個 9 的 durability 等於不會丟資料?」→ durability 不等於 availability,也擋不住你自己誤刪 — 所以還需要 versioning 和 lifecycle policy。
Making Websites Fast
場景:這一節是前面所有章節的收斂 — 一個「網站太慢」的問題,解法散落在 CDN、cache、database、network 各章。面試中被問「怎麼讓系統變快」時,你需要一張有層次的 checklist,而不是隨機丟名詞。
The Performance Checklist
依照 request 的旅程排序 — 由外而內:
| Layer | Technique | What It Saves | Links Back To |
|---|---|---|---|
| Edge | CDN for static assets | 地理距離造成的 latency | Netflix Open Connect — 同一個思想 |
| Transport | Compression (gzip/brotli), smaller payloads | Bytes over the wire | 網路頻寬是有限資源 |
| Transport | Connection reuse (keep-alive, HTTP/2 multiplexing) | 重複 TCP/TLS handshake 的 round trips | Handshake 要 1-3 個 RTT — 重用連線直接省掉 |
| Browser | Caching headers (Cache-Control, ETag) | 重複下載沒變的資源 | Cache 的第一站其實在 client |
| Browser | Lazy loading(圖片、下方內容延後載入) | 首屏不需要的資源 | 先渲染看得到的 — priority-based loading |
| App | Async processing for slow work | User 等待非關鍵工作 | Instagram 的 thumbnail queue |
| Database | Indexes, fix N+1 queries, pagination | Full table scans、重複查詢 | 慢的網站九成卡在 DB |
| Database | Query result caching (Redis) | 重複計算相同結果 | Timeline cache 就是這個 pattern |
Diagnosing Before Optimizing
| Step | Question | Tool/Method |
|---|---|---|
| 1. Measure | 慢在哪一段?DNS、connect、server、render? | Waterfall chart, APM tracing |
| 2. Find the bottleneck | p95/p99 最差的是哪個 endpoint? | Latency percentiles per route |
| 3. Fix the biggest first | 哪個修復的 impact/effort 比最高? | 通常是 missing index 或 N+1 |
| 4. Verify | 改完後 p95 真的降了嗎? | 對照 deploy 前後的 metrics |
和 DS 工作的連結
這個「先量測、找瓶頸、修最大的、驗證」循環就是 profiling 思維 — 和你優化慢的 pandas pipeline 或慢的 model inference 完全相同。而且 latency 分析天生是統計問題:平均值會騙人,要看 p95/p99 和分布(回想 Statistics 章節的 skewed distributions — latency 幾乎永遠是 right-skewed)。
如果面試被問到:(1)「加了 cache 之後要注意什麼?」→ invalidation 策略、cache stampede(大量 miss 同時打 DB)、資料 staleness 是否可接受。(2)「為什麼 p99 比 median 重要?」→ 高流量下,1% 的慢請求每天影響幾十萬人次,而且慢請求常集中在重度使用者身上。(3)「dashboard 查詢很慢怎麼辦?」→ 同一張 checklist:pre-aggregate(batch 預計算)、加 index、cache 查詢結果、限制掃描範圍(partition pruning)。
Real-World Use Cases
把 4-step framework 用在三個 DS 味的 design 題上 — 這些是 DS/MLE 面試中最可能真正遇到的題型。
Case 1: 設計一個推薦系統的 Serving 架構
Step 1 — Requirements:50M DAU 的電商,首頁要顯示個人化推薦。Latency budget 200ms,每秒尖峰 20K requests。推薦「稍微舊一點」可以接受(分鐘級)。
Step 2 — High-level design:經典的兩段式 — candidate generation(從百萬商品撈幾百個)→ ranking(精排到 top 20)。Embeddings 離線 batch 算好放 object storage,載入 ANN index;user features 放 online feature store。
Step 3 — Deep dive(fanout 思維的重演):
| Design Choice | Push (precompute) | Pull (on-the-fly) |
|---|---|---|
| How | 離線為每個 user 算好推薦清單,存 cache | Request 進來才跑 retrieval + ranking |
| Pros | Serving 只是 cache lookup — 快又穩 | 永遠最新;能用 real-time context(現在正在看什麼) |
| Cons | 不活躍用戶白算;無法反應 session 行為 | Latency 壓力大;尖峰時 compute 成本高 |
| Verdict | Hybrid:candidates 預計算,ranking 用 real-time features 線上做 | — |
Step 4 — Wrap-up:瓶頸在 feature fetching(回想 ML pipelines 章);cold start user 走 popularity fallback;用 A/B test 驗證上線效果。
面試 follow-up:「模型更新怎麼不中斷服務?」→ blue-green model deployment + registry rollback。「怎麼處理爆紅商品?」→ 這就是 hot key,cache + 把 popular items 直接放進所有人的 candidates。
Case 2: 設計即時詐欺偵測系統
Step 1 — Requirements:支付平台,每秒 5K 交易,必須在交易完成前判斷 — hard latency budget 100ms 以內。漏掉 fraud 賠錢,誤殺好交易傷害體驗 — 需要可調的 threshold。
Step 2 — High-level design:Payment API → fraud scoring service(synchronous path);同時交易 events 進 stream(Kafka)→ 更新 near-real-time features(例如「這張卡過去 10 分鐘的交易次數」)。
Step 3 — Deep dive — feature latency 的分層:
| Feature Type | Example | Where Computed | Freshness |
|---|---|---|---|
| Request-time | Amount, merchant, device | In the request itself | Immediate |
| Near-real-time | Txn count last 10 min | Stream aggregation → online store | Seconds |
| Batch | User's 90-day spending pattern | Nightly job → online store | Daily |
Netflix distributed counter 的教訓直接用上:velocity features(次數統計)就是 streaming counters — event log + windowed aggregation,不要同步 read-modify-write。
Step 4 — Wrap-up:labels 延遲 30-90 天(chargeback)→ 監控用 proxy metrics;模型每天 retrain;決策要留 audit log。
面試 follow-up:「scoring service 掛了怎麼辦?」→ fail-open(放行 + 事後審查)或 fail-closed(拒絕)— 這是 business 決策,要主動問。「threshold 怎麼定?」→ 用 precision-recall tradeoff 接上 cost matrix(回到 evaluation metrics 章)。
Case 3: 設計 Metrics / 實驗平台(A/B Testing Platform)
Step 1 — Requirements:全公司的 A/B testing 平台 — 分流要一致(同一 user 永遠進同組)、metrics 要準(不能重複計數)、實驗結果 dashboard 可以接受小時級延遲。
Step 2 — High-level design:三個子系統 — (1)assignment service:hash(user_id, experiment_id) 決定分組,deterministic 所以不用存每次結果;(2)event pipeline:曝光和轉換 events 進 stream,落地到 data lake;(3)analysis engine:batch 計算各組 metrics + 統計檢定,結果存 metrics store 供 dashboard 查。
Step 3 — Deep dive — 為什麼 metrics pipeline 就是 distributed counter:
| Requirement | Technique | Case Study It Comes From |
|---|---|---|
| 不重複計曝光 | Event ID + dedup in aggregation | Netflix counter 的 idempotency |
| 高吞吐寫入 | Append-only event log, partitioned | Event buffering pattern |
| Dashboard 快速讀 | Pre-aggregated results in serving store | Rollup + read cache |
| 原始資料可回溯 | Raw events 留在 data lake (S3 + Parquet) | S3 as source of truth |
Step 4 — Wrap-up:統計部分接回 A/B testing 章(sample size、sequential testing 的 peeking 問題);平台要防 sample ratio mismatch(SRM)— assignment 和 logging 的比例對不上就是 pipeline bug。
面試 follow-up:「怎麼確保同一 user 永遠同組?」→ deterministic hashing,不是查表。「實驗互相干擾怎麼辦?」→ layered experiment design(每層獨立 hash)。「metrics 算錯一天怎麼辦?」→ raw events 還在 lake,重跑 backfill 即可 — 這就是保留 raw data 的價值。
Hands-on: Estimation and Fanout in Python
Back-of-Envelope Estimation Helper
# Back-of-envelope estimation: turn assumptions into design decisions
SECONDS_PER_DAY = 86_400
def avg_qps(dau: int, actions_per_user_per_day: float) -> float:
# average queries per second for one action type
return dau * actions_per_user_per_day / SECONDS_PER_DAY
def peak_qps(avg: float, peak_factor: float = 3.0) -> float:
# peak traffic is typically 2-5x the daily average
return avg * peak_factor
def daily_storage_gb(items_per_day: float, item_size_kb: float) -> float:
# new storage needed per day, in GB
return items_per_day * item_size_kb / 1_000_000
# --- Instagram-style numbers ---
DAU = 500_000_000
upload_qps = avg_qps(DAU, 0.2) # ~1,157 writes/sec
feed_qps = avg_qps(DAU, 10) # ~57,870 reads/sec
read_write_ratio = feed_qps / upload_qps # ~50:1 -> read-heavy -> cache
photos_per_day = DAU * 0.2 # 100M photos/day
storage_per_day = daily_storage_gb(photos_per_day, 2_000) # ~200,000 GB = 200 TB
storage_per_year_pb = storage_per_day * 365 / 1_000_000 # ~73 PB -> object storage
# Decision chain (what you say in the interview):
# 57K read QPS -> timeline cache + read replicas, not direct DB reads
# 73 PB/year -> photos in object storage + CDN; DB stores only metadata
Fanout-on-Write vs Fanout-on-Read Simulation
from collections import defaultdict
import heapq
# --- Shared state ---
follower_index = defaultdict(set) # author_id -> set of follower ids
posts_by_author = defaultdict(list) # author_id -> [(timestamp, post_id), ...]
timeline_cache = defaultdict(list) # user_id -> [(timestamp, post_id), ...] (push model)
CELEBRITY_THRESHOLD = 10_000 # hybrid: skip fanout above this
def publish_push(author_id, post_id, ts):
# Fanout-on-write: O(followers) work at post time, O(1) reads later
posts_by_author[author_id].append((ts, post_id))
if len(follower_index[author_id]) >= CELEBRITY_THRESHOLD:
return # celebrity: do NOT fan out (hybrid strategy)
for follower in follower_index[author_id]:
timeline_cache[follower].append((ts, post_id))
def read_feed_pull(user_id, followees, k=20):
# Fanout-on-read: merge latest posts from every followee at read time
# Expensive: O(followees) lookups per feed load
merged = heapq.merge(
*(reversed(posts_by_author[f]) for f in followees),
key=lambda x: x[0], reverse=True,
)
return [post for _, post in zip(range(k), merged)]
def read_feed_hybrid(user_id, followees, k=20):
# Precomputed timeline for regular authors + on-the-fly merge for celebrities
celebrity_followees = [
f for f in followees
if len(follower_index[f]) >= CELEBRITY_THRESHOLD
]
candidates = list(timeline_cache[user_id]) # cheap: already fanned out
for celeb in celebrity_followees: # expensive part is small
candidates.extend(posts_by_author[celeb][-k:])
candidates.sort(key=lambda x: x[0], reverse=True) # rank by recency
return [post for _, post in candidates[:k]]
# Cost intuition:
# push -> write cost = followers count; read cost = 1 cache lookup
# pull -> write cost = 1 append; read cost = followees count
# hybrid-> bounded write cost AND bounded read cost
Interview Signals
What interviewers listen for:
- 你拿到題目先問 requirements 和 scale,而不是直接畫架構圖
- 你的每個 design 決策都有數字支撐(QPS、storage、latency budget),估算的推理鏈清楚
- 你主動講 tradeoff:fanout push vs pull、consistency vs availability、precompute vs on-the-fly,並根據場景選邊
- 你能引用真實案例的 pattern(Netflix 的 event log + rollup、Airbnb 的 incremental migration)而不是只講抽象名詞
- 你能把 system design 連回 DS 工作:feature freshness 分層、shadow deployment 驗證、raw events 保留在 lake 供 backfill
Practice
Flashcards
Flashcards (1/10)
System design 面試的 4-step framework 是什麼?
(1)Requirements & scale estimation:釐清 functional/non-functional 需求,估 QPS 和 storage。(2)High-level design:畫出主要 components。(3)Deep dive:挑 1-2 個關鍵 component 深入(schema、sharding、failure modes)。(4)Wrap-up:總結瓶頸和 tradeoffs。不要跳過第一步直接畫圖。
Quiz
System design 面試中,拿到「設計 news feed」題目後的第一步應該是?