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 分鐘的面試為例:

StepTimeWhat You DoWhat It Signals
1. Requirements & Scale Estimation5-10 minClarify functional + non-functional requirements; estimate QPS, storage, read/write ratio你不會拿到題目就埋頭畫圖
2. High-Level Design10-15 minDraw major components: client, API, services, database, cache, queue你有完整的 system vocabulary
3. Deep Dive15-20 minPick 1-2 components and go deep: schema, sharding, consistency, failure modes你有真正的深度,不只是名詞
4. Wrap-up & Tradeoffs5 minSummarize, list bottlenecks, discuss what you would improve with more time你知道 design 永遠不完美

Step 1: Requirements Come in Two Kinds

TypeQuestions to AskExample (News Feed)
FunctionalWhat features are in scope? Who are the users?Post photos, follow users, view feed
Non-functionalScale? 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

估算不用精確 — 面試官要看的是你的推理鏈和數量級是否合理。常用的錨點數字:

QuantityValueMemory Aid
Seconds per day86,400(約 10 的 5 次方)1 day ≈ 100K seconds
1M requests/day≈ 12 QPSDivide by 100K
Peak traffic2-5x averageDepends on product
Photo size200KB-2MBAfter compression
One char / one int1 byte / 4 bytesFor row-size estimates

估算的真正目的

估算不是算術表演,而是決策依據。算出 60K read QPS 之後,你才有理由說「單台 database 撐不住,需要 cache layer + read replicas」。每個數字都應該接一個 design 決策,否則就是在浪費面試時間。

What Interviewers Actually Score

SignalWeak AnswerStrong Answer
Problem navigationJumps straight to solutionAsks 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 demandOnly buzzwordsCan explain how the chosen component works internally
CommunicationSilent drawingThinks 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。

QuantityCalculationResult
Upload QPS500M × 0.2 / 86,400≈ 1,200 writes/sec(peak ≈ 3,500)
Feed read QPS500M × 10 / 86,400≈ 58,000 reads/sec
Read/write ratio58,000 / 1,200≈ 50:1 — 極度 read-heavy
Storage per day100M photos × 2MB≈ 200TB/day
Storage per year200TB × 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)
ComponentChoiceWhy
Photo bytesObject storage + CDNCheap, durable, infinitely scalable; CDN serves from edge
Photo metadataRelational DB or wide-column storeStructured, queryable; only KBs per photo
Thumbnail generationAsync via queue不能讓 user 等 resize 完成才收到 upload 成功
Feed fanoutAsync 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 次。

ApproachHow It WorksProsConsBest For
Fanout-on-write (push)發文時,把 post_id 寫進每個 follower 的 timeline cacheRead 是 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 DecisionChoiceReasoning
What to storeList of post_ids per user(不是完整 post 內容)內容另外查,避免重複儲存與更新異常
WhereRedis sorted set(score = timestamp)O(log n) insert, O(1) range read
Size cap最新 500-800 posts per user幾乎沒人往回滑超過這個量;控制 memory
Miss handlingFall 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

StageArchitectureTrigger for Change
1. Monolith in own datacenterClassic 3-tier: client → monolithic API → database2008 年資料庫嚴重故障,服務中斷數天 — 單點風險不可接受
2. Move to cloud (AWS)Same app, rented infrastructure自建 datacenter 跟不上成長速度;把「管機器」外包
3. MicroservicesMonolith 逐步拆成數百個獨立 services團隊變多後,monolith 的 deploy 互相卡住,engineering velocity 掉
4. API Gateway (Zuul)Gateway 統一處理 routing、auth、rate limitingClient 不該知道幾百個 services 的位置 — 降低 client-service coupling
5. Resilience layerCircuit 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 ChoiceHowWhy 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

PrincipleImplementationPayoff
Stateless servicesSession state 放外部 cache,任何 instance 可以服務任何 requestInstance 可以隨意增減、掛了直接換
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

ProblemWhat HappensWhy Naive Solutions Fail
Hot key熱門影片的 counter 集中在單一 row/partition所有寫入打到同一台 node — 單機吞吐量上限就是系統上限
Lock contention每次 increment 都要 read-modify-write 同一筆資料Lock 排隊 → latency 暴增
Exactly-onceClient retry 造成重複計數;訊息丟失造成少算網路不可靠,「剛好一次」需要額外機制
Read latency即時 aggregate 幾百萬筆 events 太慢讀的時候才算 = 每次讀都是一次大查詢

直覺:問題的本質是把所有壓力集中在一個點。解法方向必然是:把寫入攤開(partition)、把計算延後(aggregate lazily)、把讀取預先算好(cache)。

The Buffering / Aggregation Approach

Netflix 的 Distributed Counter Abstraction 分成四層:

LayerResponsibilityKey Technique
1. Client APIAddCount / 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 cacheAggregated 值放進 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

ModeGuaranteeCostUse 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

SymptomRoot Cause
Deploy 要排隊、常被別人的 bug block幾百個工程師 commit 到同一個 codebase、同一條 deploy pipeline
改一個小功能要理解整個系統Code ownership 界線模糊 — 誰都能改任何地方
局部功能故障拖垮整站所有功能跑在同一個 process,沒有 isolation
無法針對熱點單獨 scaleMonolith 只能整包複製

注意:這些全是組織規模造成的問題,不是技術本身。小團隊的 monolith 沒有這些病 — 這是回答「要不要拆」時最關鍵的判斷。

The SOA Migration

Airbnb 把系統拆成 service-oriented architecture(SOA),並且給 service 分了明確的類型:

Service TypeResponsibilityExample
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

LessonPractice
Incremental, not big-bang一次搬一個 domain;Monorail 和新 services 長期共存
Compare before switching新舊路徑同時跑(dual-read / shadow traffic),驗證結果一致才切流量
Freeze the monolith gradually先禁止往 Monorail 加新功能,讓它自然萎縮
Invest in shared infra firstService 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

AspectFile SystemObject Storage (S3)
Structure階層目錄樹Flat namespace: bucket + key
AccessOpen, seek, partial in-place writeGET / 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%)。做法的核心概念:

TechniqueHowTradeoff
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

ReasonExplanation
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 的旅程排序 — 由外而內:

LayerTechniqueWhat It SavesLinks Back To
EdgeCDN for static assets地理距離造成的 latencyNetflix Open Connect — 同一個思想
TransportCompression (gzip/brotli), smaller payloadsBytes over the wire網路頻寬是有限資源
TransportConnection reuse (keep-alive, HTTP/2 multiplexing)重複 TCP/TLS handshake 的 round tripsHandshake 要 1-3 個 RTT — 重用連線直接省掉
BrowserCaching headers (Cache-Control, ETag)重複下載沒變的資源Cache 的第一站其實在 client
BrowserLazy loading(圖片、下方內容延後載入)首屏不需要的資源先渲染看得到的 — priority-based loading
AppAsync processing for slow workUser 等待非關鍵工作Instagram 的 thumbnail queue
DatabaseIndexes, fix N+1 queries, paginationFull table scans、重複查詢慢的網站九成卡在 DB
DatabaseQuery result caching (Redis)重複計算相同結果Timeline cache 就是這個 pattern

Diagnosing Before Optimizing

StepQuestionTool/Method
1. Measure慢在哪一段?DNS、connect、server、render?Waterfall chart, APM tracing
2. Find the bottleneckp95/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 ChoicePush (precompute)Pull (on-the-fly)
How離線為每個 user 算好推薦清單,存 cacheRequest 進來才跑 retrieval + ranking
ProsServing 只是 cache lookup — 快又穩永遠最新;能用 real-time context(現在正在看什麼)
Cons不活躍用戶白算;無法反應 session 行為Latency 壓力大;尖峰時 compute 成本高
VerdictHybrid: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 TypeExampleWhere ComputedFreshness
Request-timeAmount, merchant, deviceIn the request itselfImmediate
Near-real-timeTxn count last 10 minStream aggregation → online storeSeconds
BatchUser's 90-day spending patternNightly job → online storeDaily

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

RequirementTechniqueCase Study It Comes From
不重複計曝光Event ID + dedup in aggregationNetflix counter 的 idempotency
高吞吐寫入Append-only event log, partitionedEvent buffering pattern
Dashboard 快速讀Pre-aggregated results in serving storeRollup + 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。不要跳過第一步直接畫圖。

Click card to flip

Quiz

Question 1/10

System design 面試中,拿到「設計 news feed」題目後的第一步應該是?

Mark as Complete

3/5 — Okay