System Design Fundamentals

Interview Context

System design 面試的第一關通常不是畫架構圖,而是考你有沒有「量級感」:面試官丟一句「設計一個推薦 API」,就看你會不會先問 QPS、latency budget、availability target。這一頁的概念(scalability、percentiles、nines、CAP、estimation、核心演算法)是所有後續章節的共同語言 — 答不出 p99 和 99.99% 代表什麼,後面的設計都是空談。

What You Should Understand

  • 能清楚比較 vertical vs horizontal scaling,並解釋為什麼 stateless service 是水平擴展的前提
  • 能區分 latency 和 throughput,並用 p50/p95/p99 percentiles 描述系統效能(而不是只講平均值)
  • 能定義 SLA/SLO/SLI,背出 nines 對應的年停機時間,並說明 redundancy 與 failover 的設計
  • 能用 CAP theorem 解釋 CP vs AP 的取捨,舉出真實資料庫的例子
  • 能在 2 分鐘內做 back-of-the-envelope estimation:QPS、storage、bandwidth,且熟記 latency numbers
  • 能解釋 consistent hashing、bloom filter、token bucket 等面試常考演算法的直覺與取捨

Scalability

Scalability is the ability of a system to handle growing load by adding resources. There are two fundamental directions:

DimensionVertical Scaling (Scale Up)Horizontal Scaling (Scale Out)
HowBigger machine: more CPU, RAM, diskMore machines behind a load balancer
CeilingHard hardware limitNearly unlimited (add nodes)
Cost curveSuper-linear(頂級硬體貴得不成比例)Roughly linear(commodity hardware)
Single point of failureYes — one machine dies, service diesNo — traffic reroutes to healthy nodes
ComplexityLow(不用改架構)High(需要 load balancing、state 管理、data partitioning)
Best forEarly stage, simple apps, relational DB 撐得住時Large scale, high availability requirements

直覺:vertical scaling 像把一台卡車換成更大的卡車;horizontal scaling 像改用一整個車隊。車隊需要調度系統(load balancer)、每台車不能記住「只有它知道」的事(stateless),但車隊可以無限加車、壞一台也不影響出貨。

Stateless Services: The Prerequisite for Horizontal Scaling

A stateless service keeps no client session data in local memory or disk — every request can be served by any instance.

  • Stateful 的問題:如果 user session 存在 server A 的記憶體,load balancer 就必須用 sticky session 把同一個 user 永遠導到 A。A 掛掉 → session 消失;A 過熱 → 無法把流量分給 B。
  • Stateless 的做法:把 state externalize 到 shared store(Redis 存 session、DB 存資料、S3 存檔案)。Web tier 的每台機器變成可以隨意增減、隨意替換的「牛」而不是「寵物」。
  • Auto-scaling 因此成為可能:流量高峰加機器、離峰砍機器,因為任何一台新機器都能立刻服務任何 request。

The Scale Cube

AKF Scale Cube 把 scaling 分成三個軸:X 軸 = cloning(複製同樣的 instance,配 load balancer)、Y 軸 = functional decomposition(按功能拆成 microservices)、Z 軸 = data partitioning(按 user id 或 key 做 sharding)。多數系統先做 X(最便宜),資料量大了做 Z,組織與程式碼複雜了才做 Y。面試中能講出「先 X 後 Z 再 Y」是加分項。

Why Horizontal Wins at Scale

  1. Fault tolerance: N 台機器壞 1 台,剩 N-1 台照常服務;一台大機器壞了就是全掛。
  2. Cost: commodity server 的性價比遠勝頂規機器;cloud 上 horizontal scaling 還能隨流量彈性計費。
  3. No ceiling: 世界上最大的單機也撐不起 Google 級的流量;水平擴展理論上沒有上限。
  4. Rolling deploy: 一台一台更新,服務不中斷 — 單機做不到 zero-downtime deploy。

代價是複雜度:你需要 load balancing、service discovery、distributed cache、data partitioning — 這些正是後面章節(Scalability Patterns、Caching Strategies、Databases & Storage)的主題。

Latency vs Throughput

Two different questions about performance:

  • Latency = how long one request takes(單一 request 從發出到收到回應的時間,單位 ms)。使用者「感覺到」的就是 latency。
  • Throughput = how many requests the system completes per unit time(單位時間完成的工作量,例如 QPS、requests/sec、MB/s)。系統「扛得住」的量就是 throughput。

直覺:latency 是一台車過高速公路要多久;throughput 是這條高速公路每小時能通過幾台車。拓寬車道(加機器)能提高 throughput,但不會讓單一台車開得更快;想降 latency 要縮短路徑(cache、CDN、減少 round trips)。

LatencyThroughput
Unitms per requestrequests per second
User impact頁面轉圈圈多久高峰期會不會被擋在門外
Improve byCaching, CDN, fewer network hops, faster algorithmsMore servers, batching, async processing, queues
Tradeoff exampleBatching 提高 throughput 但增加單一 request 的等待為了低 latency 放棄 batching → 每 request 開銷變大

Percentiles: Why Averages Lie

Latency is never a single number — it is a distribution. Report percentiles:

PercentileMeaningTypical Use
p50 (median)50% of requests are faster than this「一般使用者」的體驗
p9595% of requests are faster than this常見的 SLO 目標
p9999% of requests are faster than thisTail latency — 重度使用者最常撞到
p99.91 in 1000 requests is slower大型系統的 tail SLO

平均值會說謊的原因:latency 分布是右偏(right-skewed)的 — 大部分 request 很快,少數 request 因為 GC pause、cache miss、slow disk、lock contention 而非常慢。假設 99 個 request 花 10ms、1 個花 2000ms:平均是 29.9ms(看起來不錯),但 p99 是 2000ms — 每 100 個使用者就有 1 個等了 2 秒。

Tail Latency Amplification

微服務架構會放大 tail latency:如果一個頁面要平行呼叫 10 個 backend service,每個 service 的 p99 是 100ms,那使用者撞到「至少一個 service 超過 100ms」的機率是 1 - 0.99^10 ≈ 9.6%。也就是說,對使用者而言這其實接近 p90 的體驗。這就是為什麼大公司對 p99 甚至 p99.9 錙銖必較 — Amazon 的著名數據是每多 100ms latency 損失 1% 銷售額。

面試時的標準句型:「I would set an SLO on p95 and p99, not the mean, because latency distributions are right-skewed and the tail dominates user-perceived quality.」

Availability & Reliability

Availability = the fraction of time the system is operational and serving requests. Reliability = the probability the system performs correctly for a given duration(不出錯地連續運作)。一個每天閃斷 1 秒的系統 availability 很高但 reliability 差。

SLA / SLO / SLI

TermWhat It IsExample
SLI (Indicator)實際量測到的指標Measured p99 latency = 180ms; measured uptime = 99.95%
SLO (Objective)內部訂的目標值p99 latency under 200ms; availability at least 99.9%
SLA (Agreement)和客戶簽的合約,違反有賠償99.9% uptime or 10% refund

記法:SLI 是「量到什麼」、SLO 是「想達到什麼」、SLA 是「答應客戶什麼(沒做到要賠)」。SLA 通常比 SLO 寬鬆,留 buffer。SLO 和 SLI 之間的差距就是 error budget — 還可以「揮霍」的失敗額度,用來決定要衝 feature 還是先還穩定性的債。

The Nines Table

Availability is usually quoted in "nines":

AvailabilityNinesDowntime per YearDowntime per MonthDowntime per Day
99%2 nines3.65 days7.3 hours14.4 minutes
99.9%3 nines8.76 hours43.8 minutes1.44 minutes
99.99%4 nines52.6 minutes4.38 minutes8.64 seconds
99.999%5 nines5.26 minutes26.3 seconds0.864 seconds

直覺換算:一年約 525,600 分鐘;99.9% 的失效預算是 0.1% ≈ 526 分鐘 ≈ 8.76 小時。每多一個 9,允許的 downtime 除以 10 — 而達成成本大約乘以 10(更多 redundancy、更快的 failover、更嚴的變更管理)。5 個 9 意味著全年只能掛 5 分鐘,連「人工重開機」都來不及,一切 failover 必須全自動。

Redundancy and Failover

Eliminate single points of failure (SPOF) at every layer:

LayerRedundancy Strategy
Web/App serversMultiple stateless instances behind a load balancer
Load balancerActive-passive pair(heartbeat + virtual IP takeover)
DatabasePrimary-replica replication; promote replica on primary failure
Data centerMulti-AZ / multi-region deployment with geo-DNS routing
DataReplication factor 3; backups + point-in-time recovery

Failover patterns:

  • Active-passive(hot standby):備援機平時待命同步資料,主機掛掉時接手。切換有秒級空窗,但實作簡單。
  • Active-active:多台同時服務、互為備援,容量利用率高,但要處理資料同步與衝突。
  • Serial availability 的數學:串聯的元件會拉低整體 availability — 兩個 99.9% 的服務串在一起是 0.999 × 0.999 ≈ 99.8%。並聯(redundancy)則拉高:兩台各 99% 的機器同時掛掉的機率是 0.01 × 0.01 = 0.0001 → 整體 99.99%。
Aserial=A1×A2,Aparallel=1(1A1)(1A2)A_{\text{serial}} = A_1 \times A_2, \qquad A_{\text{parallel}} = 1 - (1 - A_1)(1 - A_2)

這條公式是面試金句:依賴鏈越長,可用性越低;備援越多,可用性越高 — 所以要縮短 critical path、為關鍵元件加 redundancy、為非關鍵依賴加 graceful degradation(降級而不是整個掛掉)。

CAP Theorem & Consistency Models

CAP theorem: in the presence of a network Partition, a distributed system must choose between Consistency (every read sees the latest write) and Availability (every request gets a response).

關鍵理解:P 不是選項,是現實 — 網路一定會斷(switch 壞掉、跨機房光纖被挖斷)。所以真正的選擇題只有一題:分區發生時,你要拒絕服務保資料一致(CP),還是繼續服務容忍舊資料(AP)?

ChoiceBehavior During PartitionReal SystemsTypical Use
CP少數派節點拒絕讀寫,保證不回傳 stale dataZooKeeper, etcd, HBase, MongoDB(預設配置)金流、庫存、分散式鎖、leader election
AP所有節點繼續服務,分區恢復後再收斂Cassandra, DynamoDB, CouchDB, DNSSocial feed、購物車、瀏覽紀錄、metrics
CA只存在於「沒有分區」的單機世界單節點 RDBMS(PostgreSQL 單機)不算真正的分散式選擇

Strong vs Eventual Consistency

ModelGuaranteeCostExample
Strong consistencyRead always returns the latest committed write寫入要等多數節點確認 → 高 latency、分區時犧牲 availabilitySpanner, etcd; bank balance
Eventual consistencyReplicas converge if writes stop; reads may be stale低 latency、高 availability;應用層要容忍暫時不一致DynamoDB, Cassandra; like count
Read-your-writes使用者一定看得到自己剛寫的資料介於中間(session 黏著或 read from primary)發文後自己要立刻看到
Causal consistency有因果關係的操作大家看到的順序一致中等留言一定出現在原貼文之後

直覺:strong consistency 像全公司共用一份即時同步的 Google Doc;eventual consistency 像大家各自改本地檔案、晚上再合併 — 白天看到的版本可能不同,但最終會一樣。

Quorum 的經典公式(N replicas, W write acks, R read acks):

W+R>N    strong consistency(讀寫集合必有交集)W + R > N \implies \text{strong consistency(讀寫集合必有交集)}

例如 N=3, W=2, R=2 → 任何 read 至少會碰到一個有最新寫入的 replica。調 W=1, R=1 就變成 eventual consistency,換來更低的 latency。

PACELC:CAP 的進階版

面試想拿高分可以補一句 PACELC:if Partition, choose A or C; Else (正常運作時), choose Latency or Consistency。意思是即使沒有分區,強一致性也要付出 latency 代價(等多數節點 ack)。DynamoDB 是 PA/EL(都選可用性與速度),Spanner 是 PC/EC(都選一致性)。

Back-of-the-Envelope Estimation

面試官問「設計 Twitter」時,第一步永遠是把模糊需求變成數字。目的不是算得準,是展現量級感合理假設能力

Powers of Two and Data Volume

PowerApproximate ValueBytes Name
2^10~1 thousand1 KB
2^20~1 million1 MB
2^30~1 billion1 GB
2^40~1 trillion1 TB
2^50~1 quadrillion1 PB

常用速算:一天 86,400 秒(心算用 100K);一年約 3,150 萬秒(心算用 30M);1 million requests/day ≈ 12 QPS;1 billion requests/day ≈ 12K QPS。

QPS, Storage, Bandwidth

Standard estimation formulas:

Average QPS=DAU×requests per user per day86,400\text{Average QPS} = \frac{\text{DAU} \times \text{requests per user per day}}{86{,}400} Peak QPS25×Average QPS\text{Peak QPS} \approx 2\text{--}5 \times \text{Average QPS} Storage per year=writes per day×object size×365×replication factor\text{Storage per year} = \text{writes per day} \times \text{object size} \times 365 \times \text{replication factor}

範例(做給面試官看的心算過程):一個服務有 100M DAU,每人每天 10 個 requests:

  • Requests per day = 100M × 10 = 1B → Average QPS = 1B ÷ 86,400 ≈ 12K QPS
  • Peak QPS ≈ 2.5 × 12K = 30K QPS
  • 若 10% 的 requests 是寫入、每筆 1 KB → 每天寫入 100M × 1 KB = 100 GB/day → 一年 36.5 TB,乘 replication factor 3 ≈ 110 TB/year
  • Bandwidth(read 為主):12K QPS × 10 KB response ≈ 120 MB/s average

估算的面試技巧

(1)先講假設再算:「假設 DAU 100M、每人 10 requests」— 假設錯沒關係,不講假設才扣分。(2)全部取整數方便心算:86,400 當 100K、一年當 30M 秒。(3)算完要 sanity check 並連回設計:「30K peak QPS,單台 app server 撐 1K QPS,所以需要約 30 台 + load balancer」。數字要推動決策,不是算完就丟。

Latency Numbers Every Engineer Should Know

OperationLatencyIntuition
L1 cache reference0.5 ns基準單位
Main memory reference100 nsL1 的 200 倍
Compress 1 KB (fast codec)3 µs壓縮很便宜,能省網路就壓
Send 1 KB over 1 Gbps network10 µs
SSD random read100 µsMemory 的 1,000 倍
Read 1 MB sequentially from memory250 µs
Round trip within same datacenter500 µs一次 RPC 的地板
Read 1 MB sequentially from SSD1 msMemory 的 4 倍
HDD disk seek10 ms隨機讀 HDD 是災難
Read 1 MB sequentially from HDD20 msSSD 的 20 倍
Round trip cross-continental (CA to EU)150 ms光速的物理限制

從這張表推出的設計原則(面試常考的「為什麼」):

  1. Memory is fast, disk is slow → cache 熱資料在記憶體(Redis/Memcached)。
  2. 避免 disk seek → 資料庫用 sequential write(WAL、LSM-tree)而不是 random write。
  3. 跨資料中心 round trip 是 ms 級 → 跨區同步複寫很貴;用 CDN 把內容推近使用者。
  4. 壓縮便宜、網路貴 → 傳輸前先壓縮。
  5. 一次 request 內的 RPC 次數決定 latency 下限 → 減少 round trips(batch API、parallel fan-out)。

Top 20 System Design Concepts

面試前的總複習清單 — 每個概念一句話,以及本站深入介紹的章節:

#ConceptOne-LinerCovered In
1Load Balancing把流量分散到多台 server,達成 scalability 與 availabilityScalability Patterns
2Caching把熱資料放在更快的儲存層,用空間換時間Caching Strategies
3CDN把靜態內容複製到全球 edge nodes,縮短物理距離Caching Strategies
4Database Sharding按 key 把資料水平切到多台 DB,突破單機容量Databases & Storage
5Replication資料多副本,提供讀擴展與故障容忍Databases & Storage
6Database Indexing用額外結構(B+ tree)加速查詢,代價是寫入變慢Databases & Storage
7CAP Theorem分區發生時,consistency 與 availability 二選一This page
8Eventual Consistency副本暫時不一致、最終收斂,換取低延遲高可用This page + Databases & Storage
9Consistent Hashing節點增減時只搬移最少量的 keysThis page
10Message Queue用非同步佇列解耦 producer 與 consumer、削峰填谷Message Queues & Streaming
11Rate Limiting限制請求頻率,保護系統不被打爆This page + API Design
12API Gateway統一入口:routing、auth、rate limit、監控API Design
13Idempotency同一操作執行多次結果不變 — retry 安全的前提API Design + Message Queues & Streaming
14Microservices按業務功能拆分獨立部署的服務Architecture Patterns
15Service Discovery動態環境中自動找到服務位址Architecture Patterns
16WebSockets雙向長連線,支援即時推播Networking & Protocols
17Authentication & Authorization驗證你是誰、決定你能做什麼Authentication & Security
18Fault Tolerance & FailoverRedundancy + 自動切換,消除單點故障This page + Scalability Patterns
19Monitoring & ObservabilityMetrics、logs、traces — 看得見才能修得快Containers, Kubernetes & CI/CD
20Data Partitioning把資料與流量按維度切開,隔離故障與熱點Databases & Storage

Key Algorithms for System Design Interviews

這些演算法是 system design 題的「積木」— 面試官不會叫你證明,但會期待你講得出直覺、複雜度與使用場景。

Consistent Hashing

Problem: 用 hash(key) mod N 把資料分到 N 台 server 時,N 一變(加減機器)幾乎所有 key 都要搬家 → cache 全滅、DB 大遷移。

Solution: 把 hash space 想成一個環(0 到 2^32 - 1)。Server 和 key 都 hash 到環上,key 由順時針方向遇到的第一台 server 負責。加一台 server 只影響它逆時針方向那一段的 keys — 平均只需搬移 K/N 的資料。

Virtual nodes: 每台實體 server 在環上放 100-200 個虛擬節點。目的:(1)讓負載更平均(單點 hash 可能讓某台接手超大區段);(2)容量不同的機器可以放不同數量的 virtual nodes 實現 weighted 分配;(3)一台掛掉時,它的負載平均分給所有存活機器,而不是全砸給順時針的下一台。

Propertyhash mod NConsistent Hashing
Keys remapped when adding a nodeAlmost allAbout K/N
Load balancePerfect (uniform)Good with virtual nodes
Used bySimple fixed clustersDynamoDB, Cassandra, CDN routing, distributed caches

Bloom Filter

A space-efficient probabilistic data structure answering "is this element possibly in the set?"

  • 一個 m bits 的陣列 + k 個 hash functions。加入元素:把 k 個 hash 位置設為 1。查詢:k 個位置全是 1 → 「可能存在」;任一是 0 → 「絕對不存在」。
  • No false negatives, possible false positives:說「不在」一定對;說「在」可能誤判(其他元素恰好填滿了那些 bits)。
  • 不能刪除(清 0 會誤傷別的元素)— 需要刪除用 counting bloom filter 或 cuckoo filter。
  • False positive rate approximately equals(n 個元素、m bits、k 個 hash):
p(1ekn/m)kp \approx \left(1 - e^{-kn/m}\right)^k

經典用途:cache 前擋掉「絕對不存在」的查詢避免 cache penetration、爬蟲判斷 URL 是否看過、Cassandra/HBase 判斷 SSTable 是否含某 key(省掉 disk read)、瀏覽器檢查惡意網址。

Rate Limiting Algorithms

AlgorithmHow It WorksProsConsBest For
Token Bucket桶子以固定速率補 token,request 消耗 token;桶滿則丟棄多的 token允許 burst(桶子容量內)、記憶體省、兩個參數直覺Burst 大小要調參API rate limiting 的預設選擇(AWS、Stripe)
Leaky BucketRequest 進 queue,以固定速率流出處理出流量絕對平滑穩定突發流量會排隊或被丟,舊 request 佔住 queue需要穩定處理速率的場景(寫入 DB、影音轉檔)
Fixed Window Counter每個時間窗(如每分鐘)一個計數器實作最簡單窗口邊界問題:兩個窗口交界處可通過 2 倍流量粗略限流、內部工具
Sliding Window Log記錄每個 request 的 timestamp,數過去 window 內的數量完全精確每個 request 都要存 timestamp → 記憶體重低流量但要求精確(付費 API 配額)
Sliding Window Counter當前窗計數 + 前一窗按重疊比例加權精確度與記憶體的好平衡假設前一窗流量均勻分布(近似)大規模生產環境(Cloudflare 用此法)

Token bucket 和 leaky bucket 的直覺差異:token bucket 管「你平均不能超速,但允許短暫衝刺」;leaky bucket 管「不管你怎麼來,我輸出永遠等速」。

Spatial Indexing: Quadtree vs Geohash

用於「找出我附近的餐廳/司機」這類 proximity 查詢 — 直接對千萬個點算距離是 O(n),需要空間索引。

GeohashQuadtree
Idea遞迴把地圖切四格,經緯度交錯編碼成 base32 字串;prefix 越長格子越小樹狀結構:一格內的點超過門檻(如 100 個)就切成四個子節點
Nearby query共同 prefix 越長通常越近 → 可用字串 prefix 查詢、存進一般 DB/Redis從 leaf 往上找鄰近節點
Grid size固定層級(precision 6 約 0.6km 格)自適應:市中心切得細、沙漠切得粗
Edge case邊界問題:相鄰兩點可能落在不同 prefix(要查 8 個鄰居格)樹的更新(司機移動)比較麻煩
Used byRedis GEO commands, Elasticsearch geo queriesYext, 部分地圖服務的內存索引

HyperLogLog

Problem: 精確計算「今天有幾個 unique visitors」需要一個巨大的 set — 1 億個 user id 可能要幾 GB。

Intuition: 把每個元素 hash 成均勻隨機的 bit string。觀察到「開頭連續 k 個 0」的 hash,大約代表看過 2^k 個不同元素(就像連續丟出 10 次正面硬幣,大概暗示你丟了上千次)。HyperLogLog 把元素分到多個 buckets、各自記錄最長前導零、再取調和平均數 — 用約 12 KB 的記憶體估出上億級的 cardinality,誤差約 0.81%(Redis 的 PFCOUNT 實作)。

ApproachMemory for 100M uniquesError
Exact hash setSeveral GB0
HyperLogLog12 KB~0.81%

用途:unique search queries、DAU 估算、廣告 unique impressions — 任何「大量、可容忍小誤差」的去重計數。

演算法選型的面試陷阱

常見扣分點:(1)說 bloom filter「可能有 false negative」— 方向記反了,bloom filter 說「不在」絕對可信。(2)用 fixed window 卻沒提邊界 burst 問題。(3)說 consistent hashing「完美平均分配」— 沒有 virtual nodes 時分配可能很不均。(4)用 HyperLogLog 算金額或帳務 — 它是近似演算法,只能用在可容忍誤差的統計。

Real-World Use Cases

Case 1: 推薦系統 API 的容量估算

你在電商公司負責推薦系統,PM 說「首頁要加個性化推薦模組」。開工前你要回答:需要幾台 serving 機器?feature store 要多大?

套用本頁概念:先做 back-of-the-envelope,再讓數字驅動架構決策。

# Assumptions (state them out loud in an interview)
dau = 20_000_000                  # 20M daily active users
views_per_user = 6                # homepage visits per user per day
requests_per_day = dau * views_per_user            # 120M requests/day

avg_qps = requests_per_day / 86_400                # ~1,400 QPS average
peak_qps = avg_qps * 3                             # ~4,200 QPS at peak

# Storage: user embeddings in the online feature store
embedding_bytes = 128 * 4         # 128-dim float32 vector = 512 B
total_users = 100_000_000         # all registered users
storage = total_users * embedding_bytes           # ~51 GB -> fits in a Redis cluster

# Latency budget: 200 ms end-to-end
# feature fetch ~20 ms + candidate generation ~50 ms
# + ranking ~50 ms + network/serialization ~30 ms -> ~50 ms buffer

結論會直接影響設計:4,200 peak QPS × 單台 model server 500 QPS → 約 9 台加上 redundancy 開 12 台;51 GB embeddings 放得進 memory → 用 Redis 而不是掃 DB;latency budget 200ms 內要塞 4 個步驟 → 每步都要有 p99 預算,而不是只管平均。

Interview follow-ups

  • 如果 DAU 成長 10 倍,哪個元件先爆?(feature store 的 QPS 和 model serving 的機器數 — storage 只是線性長)
  • 為什麼用 p99 訂每個步驟的 latency budget?(串聯步驟的 tail 會疊加,平均值會低估使用者實際體驗)
  • Peak 用 3 倍夠嗎?(電商有大促 — 需要看歷史峰值,大促可能是 10 倍,見 Case 3)

Case 2: 詐欺偵測系統的 Availability 設計

你的詐欺偵測模型攔在交易鏈路上:每筆刷卡都要先過你的 API 才能放行。風控主管問:「你的服務掛掉,全公司交易都停 — 你要怎麼保證不掛?」

套用本頁概念:這是 availability、redundancy、graceful degradation 的綜合題。

  • 訂 SLO:交易鏈路要求 99.99%(每年只能停 52.6 分鐘)→ 人工介入來不及,failover 必須全自動。
  • 消除 SPOF:model server 至少 3 個 instances 跨 AZ 部署(並聯公式:單台 99.9%,三台同掛的機率趨近 0);load balancer 用 active-passive pair;feature store(Redis)開 replica。
  • Graceful degradation(關鍵設計):ML model 超時或全掛時,降級到 rule-based fallback(例如金額低於閾值 + 熟悉裝置 → 直接放行),而不是擋掉所有交易。寧可短時間多放過一點詐欺,也不能停止全公司收單。
  • CAP 的應用:詐欺判斷讀的 user 歷史特徵可以接受秒級 stale(AP、eventual consistency,換取低延遲高可用);但「這筆交易已被判定並記錄」的決策日誌必須強一致(CP),否則重複扣款爭議無法追溯。
  • Timeout budget:交易整體 SLA 500ms,分給詐欺偵測 100ms — 超時即觸發 fallback,絕不讓下游等。

Interview follow-ups

  • Fallback 規則放行的詐欺損失 vs 停止收單的營收損失怎麼權衡?(量化:停機一分鐘的 GMV vs 降級一分鐘的預期詐欺額)
  • 怎麼知道系統正在降級?(監控 fallback 觸發率的 SLI + 告警 — 降級是設計好的行為,但必須看得見)
  • 為什麼不做同步跨 region 的強一致複寫?(跨區 round trip 約 150ms,會吃光 100ms 的 latency budget — latency numbers 直接否決這個方案)

Case 3: 電商大促的 Rate Limiting

雙 11 開賣瞬間,流量從平常的 4K QPS 衝到 80K QPS,其中一半是搶購腳本。你要保護下單服務不被打爆,同時不誤傷真實使用者。

套用本頁概念:rate limiting 演算法選型 + 分層限流 + idempotency。

  • 演算法選擇:API gateway 層用 token bucket(允許真人操作的合理 burst,例如刷新兩次頁面),參數如每 user 桶容量 10、補充速率 2 tokens/sec。下單寫入層用 leaky bucket 概念的 queue — 下單請求進 message queue,以 DB 能承受的固定速率消化(削峰填谷)。
  • 分層限流:全域限流(保護整體容量)→ per-user 限流(防單一腳本)→ per-IP 限流(防分散式腳本)。counter 存 Redis(單機 in-memory counter 在 horizontal scaling 下會失效 — 每台機器各數各的)。
  • 避免 fixed window 陷阱:促銷開始的整點正是 window 邊界 — fixed window 會在 59 秒和 61 秒各放一整個配額,瞬間 2 倍流量。用 sliding window counter。
  • Idempotency:被限流的 client 會 retry;下單 API 必須帶 idempotency key,確保 retry 不會產生重複訂單。
  • 回應設計:被限流回 HTTP 429 + Retry-After header,讓正常 client 退避,而不是無腦立刻重試造成 retry storm。

Interview follow-ups

  • Rate limiter 本身掛掉怎麼辦?(fail-open 放行保可用 vs fail-closed 擋下保護後端 — 大促期間通常 fail-open 加上下游自我保護)
  • 限流 counter 放 Redis,Redis 成為新瓶頸怎麼辦?(local cache + 週期同步的近似限流,犧牲精確度換 scalability)
  • 怎麼區分搶購腳本和真人?(rate limiting 只是第一層 — 再疊加 device fingerprint、行為特徵、CAPTCHA 挑戰)

Hands-on: Core Algorithms in Python

Consistent Hashing Ring with Virtual Nodes

import hashlib
from bisect import bisect_right

class ConsistentHashRing:
    """Hash ring with virtual nodes for even key distribution."""

    def __init__(self, replicas=150):
        self.replicas = replicas          # virtual nodes per physical node
        self.ring = {}                    # hash value -> physical node name
        self.sorted_hashes = []           # sorted hash values for binary search

    def _hash(self, key):
        # md5 gives a uniform 128-bit hash; take the integer value
        return int(hashlib.md5(key.encode()).hexdigest(), 16)

    def add_node(self, node):
        # place `replicas` virtual nodes on the ring for this physical node
        for i in range(self.replicas):
            h = self._hash(node + "#" + str(i))
            self.ring[h] = node
            self.sorted_hashes.append(h)
        self.sorted_hashes.sort()

    def remove_node(self, node):
        # only keys owned by this node's virtual nodes are remapped (~K/N)
        for i in range(self.replicas):
            h = self._hash(node + "#" + str(i))
            del self.ring[h]
            self.sorted_hashes.remove(h)

    def get_node(self, key):
        # walk clockwise: first virtual node at or after hash(key)
        h = self._hash(key)
        idx = bisect_right(self.sorted_hashes, h)
        if idx == len(self.sorted_hashes):
            idx = 0                       # wrap around the ring
        return self.ring[self.sorted_hashes[idx]]

# Usage: adding a 4th node remaps only ~25% of keys, not ~100%
ring = ConsistentHashRing()
for node in ["cache-a", "cache-b", "cache-c"]:
    ring.add_node(node)

owner = ring.get_node("user:12345")       # deterministic owner for this key
ring.add_node("cache-d")                  # most keys keep their old owner

Token Bucket Rate Limiter

import time

class TokenBucket:
    """Allows bursts up to `capacity`; sustains `rate` requests/sec on average."""

    def __init__(self, capacity, rate):
        self.capacity = capacity          # max tokens (burst size)
        self.rate = rate                  # tokens added per second
        self.tokens = capacity            # start with a full bucket
        self.last_refill = time.monotonic()

    def _refill(self):
        # lazy refill: add tokens proportional to elapsed time, cap at capacity
        now = time.monotonic()
        elapsed = now - self.last_refill
        self.tokens = min(self.capacity, self.tokens + elapsed * self.rate)
        self.last_refill = now

    def allow(self, tokens_needed=1):
        # returns True if the request may proceed, False if rate-limited
        self._refill()
        if self.tokens >= tokens_needed:
            self.tokens -= tokens_needed
            return True
        return False                      # caller should return HTTP 429

# Per-user limiters: burst of 10 requests, sustained 2 requests/sec
limiters = {}

def is_allowed(user_id):
    if user_id not in limiters:
        limiters[user_id] = TokenBucket(capacity=10, rate=2)
    return limiters[user_id].allow()

Interview Signals

What interviewers listen for:

  • 你會先問 requirements 和做估算(QPS、latency budget、availability target),而不是直接畫架構圖
  • 你用 p95/p99 討論 latency,並能解釋為什麼平均值會誤導
  • 你能背出 nines 對應的停機時間,並理解「每多一個 9 成本乘以 10」的含義
  • 你講 CAP 時聚焦在「分區發生時的取捨」,並能舉出 CP(etcd、ZooKeeper)和 AP(Cassandra、DynamoDB)的真實例子
  • 你講演算法時給的是直覺與取捨(bloom filter 無 false negative、token bucket 允許 burst),而不是死背定義

Practice

Flashcards

Flashcards (1/10)

Vertical scaling 和 horizontal scaling 的差異?為什麼大規模系統選 horizontal?

Vertical = 換更大的機器(簡單但有硬體上限、單點故障、成本超線性)。Horizontal = 加更多機器配 load balancer(近乎無上限、fault tolerant、成本線性),但需要 stateless service、load balancing、data partitioning。大規模下 horizontal 勝出:沒有天花板 + 壞一台不影響服務 + 可以 rolling deploy。

Click card to flip

Quiz

Question 1/10

你的 web service 要從單台擴展到多台機器,第一件必須解決的事是什麼?

Mark as Complete

3/5 — Okay