面試陷阱題 (Exam Traps)
目的
收集系統設計面試中常見的陷阱、易錯點和容易混淆的概念。
Client-Server Architecture
Trap:聽到「低延遲通訊」就跳進 P2P
- WebRTC / P2P 極難做對,有複雜的 NAT traversal 問題
- 大多數「即時」需求(聊天、協作編輯、通知)用 Client-Server 就足夠
- 正確做法:只有明確涉及音視訊通話才考慮 WebRTC
- 主從架構
Trap:忽略 Client 類型對 API 設計的影響
- Thin Client(SSR)需要一次取大塊資料;Thick Client(SPA)需要細粒度 REST API
- 正確做法:確認 Client 類型,再決定 API 設計方向
- 主從架構
傳輸層協定
Trap:預設選 UDP 追求效能
- 低延遲不等於需要 UDP,大多數場景 TCP 才是正確選擇
- UDP 不保證送達和順序,應用層需要自己處理
- 正確做法:預設 TCP,只有同時滿足「低延遲至關重要 + 可容忍丟包 + 不需瀏覽器支援」才考慮 UDP
- 傳輸層協定 TCP vs UDP
Trap:忽略瀏覽器對 UDP 的限制
- 瀏覽器只能透過 WebRTC 使用 UDP,無法直接用原生 UDP
- 設計需要 UDP 但支援瀏覽器的方案時,必須提供替代路徑
- 傳輸層協定 TCP vs UDP
HTTP 與 API 設計
Trap:以為 HTTPS 代表請求內容可信
- HTTPS 加密傳輸通道,不保證 request body 是合法客戶端發出的
- 錯誤:從 body 讀 user_id 做查詢
- 正確:從驗證過的 session/token 取得用戶身份
- HTTP 與 HTTPS
Trap:用操作命名 REST API
POST /updateUser、GET /createOrder不是 RESTful- 正確:
PUT /users/{id}、POST /orders(資源 + HTTP 動詞) - API 範式比較
Trap:面試中過早跳到 gRPC
- 面試官想看你思考問題,不是優化協定
- 正確做法:先 REST,確認有效能瓶頸且是內部服務時才提 gRPC
- API 範式比較、RPC 與 gRPC
API 設計決策框架
Trap:把決策樹兩個提問的順序搞反
- 正確順序:先問 External/Internal,再問 Over/Under-fetching
- Internal 直接走 RPC,根本不需要問 fetching 問題
- 順序錯了會把內部服務推向 GraphQL,偏離業界慣例
- API 設計決策框架
Trap:對外 API 一開始就選 gRPC
- gRPC 對外缺點多:瀏覽器需 gRPC-Web 代理、debug 困難、不易讓第三方測試
- 正確做法:對外 API 預設 REST,只有內部服務間才用 gRPC
- API 設計決策框架、RPC 與 gRPC
REST
Trap:把冪等談成「回應內容相同」
- 冪等講的是 伺服器狀態,不是回應內容
- 例:DELETE 第一次回 204、第二次回 404,仍是冪等(伺服器狀態「已不存在」不變)
- REST 架構風格
Trap:以為 PATCH 一定冪等
- PATCH 依設計而定:
PATCH {age: 30}冪等;PATCH {age: age+1}非冪等 - 實務 一般不保證 → 需要冪等時用 Idempotency-Key header
- REST 架構風格
Trap:重試非冪等 API 沒用 Idempotency-Key
Trap:在 GET 帶 body
- CDN / Proxy / 瀏覽器多以 URL 為 cache key,不看 body
- 許多 HTTP client library 會丟棄 GET body
- 正確做法:用 Query Parameter;需要複雜 body 就改
POST /search(犧牲快取) - REST 架構風格
Trap:混淆 401 與 403
GraphQL
Trap:忽略 N+1 問題
- Field-based resolver 對巢狀查詢會跑 1 + N 次 DB query
- 正確做法:用 DataLoader batch 合成
IN (...)或 resolver 層 JOIN - GraphQL 查詢語言
Trap:以為 GraphQL 的 HTTP cache 和 REST 一樣
- GraphQL 所有查詢都 POST 到
/graphql→ 無法靠 URL 當 CDN cache key - 正確做法:
- Persisted queries:用 query hash 當 key,可走 GET + CDN
- 客戶端 normalized cache(Apollo / Relay)
- GraphQL 查詢語言
Trap:開放 client 任意深度巢狀查詢
- 惡意使用者可送
users → orders → users → orders ...拖垮伺服器 - 正確做法:Query depth / complexity limit + rate limiting + persisted queries(白名單)
- GraphQL 查詢語言、API 安全
RPC / gRPC
Trap:把 gRPC 往對外 API 推
- 瀏覽器原生不支援 gRPC(需要 gRPC-Web 代理)
- 第三方開發者 debug / 測試成本高
- 正確做法:對外 REST / GraphQL,對內才 gRPC
- RPC 與 gRPC
Trap:直接改 Protobuf 欄位的 tag number 或型別
- 會破壞向後相容,舊 client 解不開
- 正確做法:刪欄位用
reserved、新欄位用新 tag、型別變更另開新欄位 - RPC 與 gRPC
API Security
Trap:把 OAuth 2.0 當作 Authentication
- OAuth 2.0 做的是 delegated authorization(「代 X 存取某資源」)
- 要認證「就是 X 本人」應該用 OpenID Connect(OIDC)
- API 安全
Trap:把敏感資料放進 JWT payload
- JWT payload 只是 base64 編碼,不是加密 → 任何人都能解開
- 正確做法:payload 只放 user_id、roles、expire;敏感資料查 DB
- API 安全
Trap:JWT 沒處理撤銷
- JWT 是 stateless → 伺服器無法主動使它失效
- 正確做法:Access token 短期(幾分鐘)+ Refresh token + 必要時黑名單機制
- 警惕
alg: none漏洞 → 伺服器固定允許的 algorithm - API 安全
Trap:內部微服務用 API Key 代替 mTLS
- API Key 單向驗證、粒度粗、洩漏風險大
- 正確做法:內部服務用 mTLS(雙向憑證 + 自動輪替,通常靠 Service Mesh)
- API 安全
Trap:RBAC 適用所有情境
- 權限細粒度需求多時,RBAC 會 角色爆炸(Admin-v1、Admin-HR、Admin-HR-readonly...)
- 正確做法:主架構用 RBAC,細節用 ABAC(屬性)補;第三方授權用 Scope
- API 安全
即時通訊
Trap:沒說明原因就直接用 WebSocket
- WebSocket 需要持久有狀態連線,基礎設施成本高
- 客戶端到伺服器每個元件(防火牆、代理、LB)都需要支援
- 正確做法:先考慮 SSE(單向推送夠嗎?),雙向高頻才用 WebSocket
- 即時通訊協定
Trap:用 WebRTC 設計非音視訊系統
- WebRTC 極難做對,連最好的實作都有連線問題
- 正確做法:WebRTC 只用於音視訊通話/會議場景
- 即時通訊協定
負載平衡
Trap:WebSocket 使用 L7 負載平衡器
Trap:持久連線服務用 Round Robin
- SSE/WebSocket 用 Round Robin,新伺服器不會分到已建立的連線,舊伺服器逐漸積累所有連線
- 正確做法:持久連線服務用 Least Connections
- 負載平衡
故障處理
Trap:重試時沒有退避和抖動
- 立即重試 + 大量客戶端同步 = thundering herd,讓已掙扎的服務更不堪負荷
- 正確做法:指數退避 + 隨機抖動
- 魔法短語:「retry with exponential backoff and jitter」
- 故障處理模式
Trap:重試非冪等 API(如支付)
- 重試 POST /payments 可能導致重複扣款
- 正確做法:使用冪等鍵(idempotency key)確保同一請求只處理一次
- 故障處理模式
Trap:忽略級聯故障
- 下游服務崩潰時,持續重試只會讓問題更嚴重(thundering herd)
- 正確做法:使用熔斷器(Circuit Breaker)自動保護下游
- 故障處理模式
CAP Theorem
Trap:以為可以同時滿足 C、A、P 三者
- CAP 定理明確指出三者不可兼得,網路分區時只能二選一
- 正確做法:確認業務能否接受暫時不一致 → AP;不能 → CP
- CAP 定理
Trap:說「我們選 CA 系統」
- CA 系統假設網路永遠不斷線,在真實分散式環境中不可能存在
- 正確做法:P 是必選,只在 C 和 A 之間取捨;CA 說法會讓面試官扣分
- CAP 定理
Trap:預設選 CP(強一致性)
- 大多數系統能接受最終一致性,過度強調 CP 會犧牲可用性
- 正確做法:預設 AP;只有在「暫時不一致會造成嚴重後果」(銀行、庫存、訂票)時才選 CP
- CAP 定理
Scalability
Trap:一開始就設計水平擴展
- 水平擴展設計複雜(LB、Session 狀態、資料一致性),過早引入增加不必要的複雜度
- 正確做法:先垂直擴展(快速簡單),流量持續增加或需要高可用性時再水平擴展
- 可擴展性
Trap:水平擴展後忘記 Session 狀態問題
- 多台 Server 時,請求可能打到不同機器,Stateful Server 會導致 Session 遺失
- 正確做法:Server 設計為無狀態(Stateless),Session 存入集中式 Store(Redis)
- 可擴展性
Trap:只擴展 Server,忘記 DB 也是瓶頸
- Server 水平擴展後,DB 往往成為新瓶頸
- 正確做法:同時考慮 Read Replica、Caching(Redis)、DB Sharding
- 可擴展性
Consistent Hashing
Trap:用
hash(key) % N 做分散式分片
- N 變動(擴縮容)→ 幾乎所有 key 重新分配 → 快取全 miss → DB 瞬間被打爆(cache stampede)
- 9 個 key、N 從 3 → 4 時有 7 個要搬
- 正確做法:用 Consistent Hashing,搬遷量降到 O(K/N)
- 一致性雜湊
Trap:省略 Virtual Nodes
- 節點少時資料分佈不均,熱點嚴重
- 節點下線時所有流量集中壓到下一節點 → 負載翻倍
- 強機弱機都只拿一個區間 → 無法按硬體能力分配
- 正確做法:每實體節點配 100~200 個 vNode
- 一致性雜湊
索引基礎
Trap:所有欄位都加索引追求速度
- 寫入
INSERT/UPDATE/DELETE要維護所有索引 → 寫入效能崩跌 - 索引佔儲存空間(每個索引獨立結構)
- 查詢優化器要在眾多索引間挑選,分析成本增加
- 正確做法:只對高頻查詢欄位 + 選擇性高的欄位建索引
- 資料庫索引概論
B-Tree Index
Trap:以為 InnoDB 用 B-Tree
- InnoDB 實際用的是 B+Tree:只有葉節點存資料,葉節點間有 linked list
- 這個差異對範圍查詢效能影響很大(沿葉節點線性走 vs 反覆回上層)
- 正確做法:說 B+Tree,並解釋為何範圍查詢更快
- B-Tree 索引
Trap:用 B-Tree 索引加速
LIKE '%abc'LSM Tree
Trap:忽略 Compaction 對前台的影響
- Compaction 需大量讀寫 SSTable → 佔用磁碟 I/O 與 CPU
- 和前台查詢共用資源時會拖慢線上請求
- 正確做法:低峰時段排程 compaction、或用 rate limit 控制
- LSM Tree
Trap:對 read-heavy 場景直接選 LSM
Trap:大量 DELETE 後查詢變慢
- LSM 的 DELETE 不立即清理,而是寫 tombstone
- 大量 tombstone 累積 → 讀取要掃過所有 tombstone → 讀放大
- 正確做法:定期 compaction 清理 tombstone;或避免把 LSM 當 delete-heavy 表
- LSM Tree
Hash Index
Trap:用 Hash Index 做範圍查詢
WHERE age > 25無法利用 hash(hash 打亂 key 順序)→ 退化到全掃 bucket- 正確做法:範圍查詢一律用 B-Tree;Hash Index 只用在純等值查詢
- 雜湊索引
Trap:忽略 Hash DoS 攻擊
- 攻擊者刻意產生大量同 hash 的 key → 全擠在同一 bucket → 查詢退化 O(n)
- 正確做法:用 salted hash / 密碼學安全 hash;對外服務不直接暴露 hash 結構
- 雜湊索引
Geospatial Index
Trap:用兩個獨立的 1D B-Tree 做地理鄰近搜尋
- 只用緯度索引 → 橫跨全球的長條帶,經度索引派不上用場
- 兩個索引交集 → 形成矩形區域(比圓形大得多)→ 還要過濾距離
- 正確做法:用 Geohash / R-Tree / S2 / H3 等專門空間索引
- 地理空間索引
Trap:忽略 Geohash 邊界效應
- 相鄰但跨越 Geohash 格子的點可能有完全不同前綴(例:馬路兩側)
- 正確做法:查詢時同時檢查中心 cell + 8 個鄰居 cell
- 地理空間索引
Trap:Quadtree 用在生產環境
- Quadtree 需專門樹結構,不能用既有 B-Tree
- 固定切 4 等份不適應資料密度,密集區樹很深
- 正確做法:現代生產環境用 R-Tree(PostGIS、SQLite);Quadtree 留給遊戲碰撞檢測
- 地理空間索引
Inverted Index
Trap:用 Inverted Index 做結構化欄位查詢
WHERE age = 30這種精確欄位比對,B-Tree 更直接- Inverted Index 擅長「文字包含某 term」,不是精確欄位匹配
- 正確做法:結構化用 B-Tree,全文檢索用 Inverted Index
- 倒排索引
Trap:忽略 Phrase Query 需要 positions
- 只存 doc ID 的 Inverted Index 無法做
"apple pie"短語查詢 - 必須存 positions 才能判斷 term 是否相鄰
- 正確做法:生產系統(Elasticsearch/Lucene)預設就存 positions,但會增加索引體積
- 倒排索引
Database Transactions
Trap:把 ACID 的 C 和 CAP 的 C 混為一談
- ACID-C:商業邏輯正確性(餘額不能為負、constraint 必須成立)
- CAP-C:分散式副本之間的同步(所有節點同一時間看到相同資料)
- 完全不同概念,面試最常被混淆的點
- 資料庫交易
Trap:用 Serializable 防超賣
- 庫存超賣本質是 Lost Update(不是 Phantom Read)
- 正確做法:原子
UPDATE qty = qty - 1 WHERE qty > 0一條 SQL 即可 - 提升到 Serializable 大幅犧牲並行性,是過度設計
- 如果扣減前需要複雜判斷,再用
SELECT FOR UPDATE鎖該行就好 - 資料庫交易
Trap:微服務跨服務 transaction 用 2PC
- 2PC 在 coordinator 崩潰時節點會卡在「prepared 未 commit」,生產環境通常避免
- 正確做法:用 Saga —— 本地交易 + 補償操作 + 接受最終一致
- 現代微服務的主流選擇
- 資料庫交易
Trap:Pessimistic Locking 不考慮 Deadlock
SELECT FOR UPDATE在不同 transaction 以不同順序鎖資源時會 deadlock- 正確做法:所有 transaction 用一致鎖序(永遠先鎖 ID 小的)
- DB 的 deadlock detection 會自動 rollback 一方,application 層要處理重試
- 資料庫交易
Sharding
Trap:用低基數欄位當 Shard Key
is_premium(布林)最多只能分 2 個 shardcountry在 90% 用戶集中於美國時嚴重不均- 正確做法:選高基數 + 均勻分佈 + 契合查詢的欄位(通常
user_id、order_id) - 資料分片
Trap:用
created_at 分片
- 所有新寫入都打到最新 shard → 寫入熱點
- 舊 shard 幾乎只在讀歷史時才有流量
- 正確做法:用
user_id/order_id等隨時間均勻分佈的欄位 - 資料分片
Trap:過早 Sharding
Trap:頻繁跨 Shard 查詢沒當回事
- 跨 shard 查詢頻繁是「shard key 選錯」的訊號
- 正確做法:重新評估 shard key / 反正規化 / 快取 / 背景預計算
- 面試官期待你「最小化跨 shard 查詢」,不是「理所當然全 shard 聚合」
- 資料分片
Replication
Trap:用牆上時鐘解 Read-After-Write
- 時鐘有 clock skew,不同節點時間不一致
- 正確做法:用 LSN(PostgreSQL)/ binlog position(MySQL) 等 replication log 邏輯位置
- Client 帶上 LSN,replica 必須追上才能服務
- 資料複寫
Trap:Failover 沒處理 Split Brain
- 偵測 leader 失效不可靠(網路 partition / 暫時延遲)→ 兩節點都以為自己是 leader
- 兩邊都接受寫入 → 資料損毀
- 正確做法:Fencing Token(新 leader 帶遞增 token,舊 leader 寫入被儲存層拒絕)/ STONITH / Raft 共識
- 資料複寫
Trap:Sloppy Quorum 還以為有強一致
- Sloppy quorum 寫到「非家節點」,w/r 可能完全沒重疊
- 即使
w + r > n,也無法保證讀到最新值 - 正確認知:Sloppy quorum 是 AP 配置,犧牲一致性換可用性
- 嚴格一致需求要嚴格 quorum(連不到足夠節點就返回錯誤)→ CP
- 資料複寫
Trap:以為所有 follower 同步就最安全
- 全同步 replication 下,任何一台 follower 失效就讓整個系統寫入卡住
- 正確做法:半同步(Semi-synchronous) —— 一台 follower 同步、其他非同步
- 至少保證 2 份節點有最新資料,又不會被單一 follower 卡死
- 資料複寫
Caching
Trap:Cache Stampede 用 Cache Warming 解所有情況
- Cache warming(過期前主動刷新)只在 TTL 過期模式有效
- 如果是「寫入時 invalidate」模式,無法預測何時失效,warming 沒用
- 真正有效解:Request Coalescing(Single Flight) —— 只讓一個請求重建快取
- 快取機制
Trap:複製 Hot Key 但 TTL 相同
- 多個副本同時過期 → 所有副本同時 miss → 更大規模的 Stampede
- 正確做法:各副本 TTL 錯開(加隨機 jitter)
- 或用機率性提前過期(probabilistic early expiration)
- 快取機制
Trap:Cache 故障時 DB 被打垮
- Redis 掛了 → 所有讀取突然打 DB(流量 5×)→ cascading failure
- 正確做法:Circuit Breaker 限制 DB 流量 + In-Process Cache 當最後防線
- 心法:「Cache 是性能優化,不是業務必需。故障時系統應該變慢但不該崩潰」
- 快取機制
Trap:把 FIFO 當預設淘汰策略
- FIFO 完全忽略使用模式,可能踢掉正在被頻繁存取的項目
- 正確做法:LRU 是預設(90% 工作負載適合)
- 長期熱門資料用 LFU,新鮮度敏感配 TTL
- 快取機制
Numbers to Know
Trap:以為記憶體大小是現代瓶頸
- 跟幾年前完全相反 —— 單機 RAM 最高 2TB,通常先碰到 CPU
- 真正瓶頸通常是 ops/sec(每秒操作) 或 網路頻寬
- 正確認知:先算 ops/sec 和頻寬,再談記憶體
- 系統設計關鍵數字
Trap:800GB 資料就喊 Sharding
- 一個調校良好的單一 PostgreSQL 可以撐數百萬到上千萬用戶
- 觸發 sharding 的真實量級:50TiB / 10k TPS 寫 / uncached < 5ms / 跨區域
- 正確順序:index → cache → read replica → 升級硬體 → 最後才 sharding
- 系統設計關鍵數字
Trap:在面試中精準背數字
- 你不需要記得每個數字的精確值
- 要有「量級直覺」:Latency 順序 memory ≪ disk ≪ network
- 面試話術用「大概量級 + 推理」:「假設一台機器有幾十 GB memory…」
- 重點是避免 over-engineering,不是炫耀數字
- 系統設計關鍵數字
Scaling Reads(讀取擴展)
Trap:直接跳到分散式 caching,沒先用盡 index / replica
- 演進順序是 index → denormalize → replica → cache,很多人略過前面更簡單的步驟
- 現代 DB 在正確 index 下能承受遠超預期的負載
- 正確做法:先從你擁有的開始,把 DB 內部優化用盡再加基礎設施
- 讀取擴展
Trap:把「讀取擴展」和「降低延遲」混為一談
- 讀取擴展是關於減少 DB 負載(DB 撐不住時)
- 「DB 沒問題、只要更低延遲」是不同問題 → edge computing / service mesh,而非盲目加 cache
- 讀取擴展
Trap:寫入後刪 cache,忽略 race condition
Scaling Writes(寫入擴展)
Trap:選低基數 / 傾斜的 Partition Key
Trap:用 Queue 去 patch 連穩定負載都扛不住的 DB
- Queue 只在爆發是短暫時有用;持續寫入比 DB 快 → queue 無限成長、掩蓋底層問題
- 正確做法:先確認 DB 能消化穩定負載,queue 只拿來吸收短暫爆發
- 寫入擴展
Trap:把 hot key 拆分用在須原子的資料
- 拆 sub-key 只適用可聚合 metrics(讚 / 觀看 / 計數)
- 用戶 profile 等須原子的資料不能拆(幸好它們也不承受那種寫入壓力)
- 寫入擴展
Long-Running Tasks(長時間任務)
Trap:依 CPU 使用率而非 Queue 深度做 autoscaling
- 等 CPU 飆高時,queue 早就積了大量待處理 job
- 正確做法:關鍵 metric 是 queue 深度 —— 超過閾值就多開 worker
- 長時間執行任務
Trap:job 不冪等 → 重複工作 / 重複扣款
- 用戶點三次「產生報告」→ 三個一樣的 job,發三封信 / 刷三次卡
- 正確做法:Idempotency Key(user ID + action + timestamp),開工前查 key 是否已存在
- 長時間執行任務
Trap:沒有 DLQ,poison message 拖垮整個 worker 群
- 一直失敗的 job 無限重試浪費資源;讓 worker 崩潰的訊息會讓每台試處理的 worker 都死
- 正確做法:DLQ(失敗 3~5 次後隔離)+ 監控 DLQ(持續增加 = 有 bug)
- 長時間執行任務
Handling Large Blobs(大型檔案)
Trap:信任 client 通知「上傳完成」
- 製造 race condition、孤兒檔案、惡意標記、通知遺失
- 正確做法:Storage 事件(主)+ 對帳(安全網) —— 由儲存服務本身確認,client 移出信任鏈
- 大型檔案處理
Trap:Presigned URL 沒加檔案大小限制
- 有人可用本來給小圖片的 URL 上傳幾 TB → 儲存費用爆炸
- 正確做法:簽名時烘焙
content-length-range、content-type條件 - 大型檔案處理
Trap:小檔案也用直傳
- < 10MB 的 JSON / 表單用兩步驟直傳,增加延遲和複雜度卻無實質好處
- 正確做法:> 10MB 才用直傳;小檔案走一般 API 端點
- 大型檔案處理
Real-time Updates(即時更新)
Trap:過度設計,直接上 WebSocket
Trap:WebSocket 用 L7 負載平衡器
Trap:忘記處理重連與中斷期間遺漏的訊息
- WebSocket 不一定發斷線訊號(殭屍連線);重連後直接繼續會漏掉中斷期間的更新
- 正確做法:heartbeat 偵測殭屍連線 + 序列號重連補發(Redis Stream)
- 即時更新
Search System(搜尋系統)
Trap:用
LIKE '%keyword%' / DB 查詢做搜尋 Trap:用雙寫(dual write)同步搜尋索引
Trap:把即時一致的資料(庫存)放進搜尋索引
- CDC 有幾秒~幾十秒延遲,庫存不能接受
- 正確做法:搜尋索引拿候選結果,再去主 DB 做最終庫存驗證
- 搜尋系統
Trap:低估 Elasticsearch 的 Shard 數量
- Shard 數建立後無法修改(資料路由依賴它),少了只能 Reindex
- 正確做法:每 shard 10~50GB,預估資料量 ÷ 目標大小,寧可多設
- 搜尋系統
RAG
Trap:該用 RAG 卻去 fine-tune
- Fine-tune 成本高、需專業知識,且知識仍會過時、無法引用來源
- 正確做法:注入即時/專有知識用 RAG(划算、可引用、可獨立調整);fine-tune 用於改變行為/風格
- 檢索增強生成 RAG
Trap:純 semantic search 漏掉縮寫 / 產品名
- dense vector 對內部特定領域語言(縮寫、產品代號、團隊名)表現差
- 正確做法:Hybrid Search(dense semantic + sparse lexical)+ reranking
- 檢索增強生成 RAG
Trap:沒做 Ground Truth Evaluation 就上線
- 不知道 RAG 何時正常工作、改進是否真的有效
- 正確做法:先建一組 query + 預期答案的評估集並持續維護
- 檢索增強生成 RAG
Data Pipeline(資料管線)
Trap:上來就說「用 Kafka + Flink」不問延遲需求
- 把技術砸進去,而非從需求推導
- 正確做法:先問「資料要多快被看到」→ 幾秒串流 / 幾小時批次;再選架構、說容錯
- 資料管線設計
Trap:追求 Exactly-once
- 難完美實現且有效能代價
- 正確做法:at-least-once + 下游冪等寫入(upsert /
ON CONFLICT DO NOTHING)—— 更簡單可靠 - 資料管線設計
Trap:延遲需求 < 10 分鐘還用 Spark batch
- 一次 batch 完整生命週期約 5~8 分鐘,合理最小間隔 ~10 分鐘
- 正確做法:< 10 分鐘延遲需求必須轉串流(Flink / Spark Streaming)
- 資料管線設計
Trap:預設 Lambda Architecture 維護兩套程式碼
- 批次 + 串流兩套同邏輯,任何改動要同步兩處,維護負擔大
- 正確做法:現代傾向 Kappa(單套串流 + Kafka 重播);只有超大歷史分析才用 Lambda
- 資料管線設計
Database 選型
Trap:一開始就比較「SQL vs NoSQL」
- 面試官想看你「從需求推導選擇」,不是背比較表
- 正確做法:依序問三問 —— ① 資料什麼形狀 / 查詢模式?② OLTP 還是 OLAP?③ 規模與一致性取捨?
- 資料庫總覽與選型
Trap:「需要擴展」就棄用關聯式資料庫
- 單純「需要 scale」不是放棄 PostgreSQL 的理由 —— 調校良好的單一 PG 能撐數百萬到上千萬用戶
- 正確做法:先 index → cache → read replica → 升級硬體,真的撞牆才換 NoSQL
- 資料庫總覽與選型、PostgreSQL
PostgreSQL
Trap:只說「我用 PostgreSQL 因為它支援 ACID」
- 面試官想知道你具體怎麼利用 ACID 解決一致性問題,而不是貼標籤
- 正確做法:說清楚 —— 「用交易 + 對拍賣記錄的列層級鎖(SELECT … FOR UPDATE)」
- PostgreSQL
Trap:以為「包在交易裡」就能防並發競態
- 拍賣出價:預設 Read Committed 下,兩個交易都可能在對方 commit 前讀到同一個 maxBid → 接受了較低的出價
- 正確做法:Row-Level Locking(
FOR UPDATE) 鎖該行,或提升到 Serializable(需重試邏輯) - 知道何時鎖哪些行 → 列層級鎖;複雜到難推理 → Serializable
- PostgreSQL、資料庫交易
Trap:對每個欄位都加索引
- 每個索引讓寫入變慢(要同步更新 + WAL)、佔磁碟空間、且不一定被用到
- 正確做法:只對高頻查詢 + 高選擇性欄位建索引;善用涵蓋索引 / 部分索引
- PostgreSQL、資料庫索引概論
Trap:以為各家的 Repeatable Read 都一樣
- PostgreSQL 的 Repeatable Read 用交易開始的一致快照,連 phantom read 都防(比 SQL 標準強)
- 很多在別的 DB 要 Serializable 才能處理的情況,PG 用 Repeatable Read 就夠
- PostgreSQL
DynamoDB
Trap:用時間戳當 Sort Key
- 時間戳不保證唯一(同一毫秒可能多筆)→ 覆蓋 / 漏資料
- 正確做法:用單調遞增 ID(UUID v7 / Snowflake / ULID)—— 同時有時間順序與唯一性
- DynamoDB
Trap:以為 ProjectionExpression 像 SQL 欄位選取能省讀取成本
- 它只減少網路傳輸,底層仍讀整個 item、按 item 完整大小 計 RCU
- 正確做法:大型 item 要正規化拆分,避免每次讀回超過需要的內容
- DynamoDB
Trap:在 GSI 上期待強一致讀取
- GSI 只支援最終一致(非同步維護的獨立內部表);強一致讀只支援基底表與 LSI
- 正確做法:需要強一致的存取路徑別走 GSI
- DynamoDB
Trap:還在說「NoSQL 就是沒有交易」
- DynamoDB 早已支援 ACID 交易(
TransactWriteItems,跨多表最多 100 個 item,可序列化隔離) - LSI 另一個坑:只能在建表時定義,事後無法新增/移除
- DynamoDB
OLTP vs OLAP
Trap:把分析查詢丟在線上 OLTP 資料庫跑
- 分析掃全表會鎖住業務資料、與線上寫入互相拖累
- 正確做法:OLAP 系統(BigQuery/ClickHouse)+ CDC / ETL 隔離分析負載
- OLTP vs OLAP
Trap:預設上 CDC(Debezium + Kafka)
- CDC 把延遲壓到幾秒,但代價是更多基礎設施與故障點(consumer lag、schema 變更、exactly-once)
- 正確做法:先用 ETL 起步,明確確認即時分析需求後再引入 CDC —— 過早上 CDC 是過度工程
- OLTP vs OLAP、資料管線設計
Trap:以為 column-oriented 只是「存法不同」
- 對分析快是三重收益疊加:① 只讀需要的欄位(I/O 省 90%+)② 同型別高壓縮(字典編碼)③ 向量化 SIMD 執行
- OLTP vs OLAP
Elasticsearch
Trap:把 Elasticsearch 當唯一資料庫 / source of truth
- ES 首先是搜尋引擎,早期在一致性與持久性上問題不少
- 正確做法:權威資料放別處(Postgres/DynamoDB),ES 當搜尋層,用 CDC 同步
- Elasticsearch
Trap:對寫入/更新極頻繁的資料用 Elasticsearch
- Lucene segment 不可變:更新 = soft delete + 插新 doc,要等 merge 才清;高頻更新(按讚數、曝光數)很吃力
- 正確做法:ES 偏讀多寫少;高頻計數另尋方案或加 write buffer
- Elasticsearch
Trap:keyword 與 text 型別混用
text會被分詞(走 inverted index,全文搜尋);id/枚舉/精確值要用keyword(不分詞,像 hash,查詢排序更快)- Elasticsearch
Trap:用 from/size 做深分頁
- 超過上萬筆時極貴 —— 每次都要排序並取出前面所有 document
- 正確做法:search_after(用上一頁最後一筆 sort 值);要一致視角用 PIT + search_after 的 cursor
- Elasticsearch
Vector Database
Trap:把向量資料庫當 source of truth
- 它本質上是索引,擅長相似度搜尋,不擅長一般 DB 功能
- 正確做法:權威資料放主 DB,向量庫只存語義表示
- 向量資料庫
Trap:換 embedding 模型卻沒重建索引(Embedding Drift)
- 不同模型的 embedding 不相容,舊向量全部失效
- 正確做法:重新產生所有 embedding;API 要帶「哪個模型產生此向量」避免用錯
- 向量資料庫
Trap:預設就用專門向量資料庫
- 低估多維運一套新系統的複雜度
- 正確做法:先簡單 —— 已用 PostgreSQL 就上 pgvector;ES kNN 適合 hybrid。只有規模/需求超出(>100M、記憶體受限、寫入極頻繁)才上 Pinecone/Milvus
- 向量資料庫
Trap:用向量搜尋做精確 ID 查找
- 向量搜尋找的是「相似」不是「完全相同」
- 正確做法:用 ID 找特定文件就用一般資料庫;有些場景兩種都要
- 向量資料庫
Blob Storage
Trap:把大型二進位資料塞進資料庫
- 圖片/影片進 DB 會讓查詢效能和備份時間都崩掉
- 正確做法:DB 存 metadata(key、大小、狀態、所有者),Blob Storage 存實際內容
- 物件儲存
Trap:為省成本把熱資料直接放 Glacier
- 儲存越便宜,取回越貴;突然大量取回 Glacier 可能遠超省下的儲存費
- 正確做法:用生命週期策略按存取頻率自動分層(Standard → IA → Glacier)
- 物件儲存
Trap:把 Bucket 設為公開
- 配置錯誤的公開 Bucket 是 S3 資料外洩的頭號來源
- 正確做法:Block Public Access + 私有 Bucket + Presigned URL 給有時限的個別物件存取 + 最小權限 IAM
- 物件儲存
Trap:混淆耐久性與可用性
- 耐久性(durability)= 資料不會消失(S3 十一個 9);可用性(availability)= 當下能否存取(S3 Standard 99.99%)
- AWS 局部中斷時你可能暫時讀不到,但資料不會因此遺失
- 物件儲存
API Gateway
Trap:把 API Gateway 和 Load Balancer 混為一談
Trap:以為 API Gateway 是單點故障
- 正確做法:Gateway 多實例部署 + 前掛 Load Balancer;Gateway 無狀態,水平擴展自然
- 限流計數器要存共享 Redis,否則用戶可在多實例間繞過限制
- API 閘道
Trap:把認證授權全丟給 Gateway(或全丟給服務)
Trap:把業務邏輯塞爆 API Gateway
- 過度使用請求聚合會讓 Gateway「懂業務」,變成難測試維護的第二個 monolith
- 正確做法:Gateway 應是薄薄的、可預期的轉發層
- API 閘道
Load Balancer(基礎設施)
Trap:在每個服務前面都畫一個 Load Balancer
- 顯得多餘、讓設計圖混亂
- 正確做法:只在系統入口畫一個(當抽象層),或乾脆省略只說「這些服務是水平擴展的」
- 負載平衡器
Trap:WebSocket / 長連線用 L7 LB
Trap:把 Sticky Session 當長期最優架構
- 代價:流量分佈不均 + 節點掛掉 session 直接消失
- 正確做法:把 session 外移到 Redis / DB,讓應用層 stateless;Sticky Session 只是過渡方案
- 負載平衡器
Trap:健康檢查邏輯過於簡單
- 服務「行程還活著」但依賴的 DB 已掛 → 仍被判健康,請求打過去白打
- 正確做法:健康檢查涵蓋關鍵依賴;檢查頻率拿捏(太低切換慢、太高增負擔)
- 負載平衡器
Container
Trap:把狀態存在容器本地記憶體
- 容器是短暫的(ephemeral):請求打到不同容器找不到 session、容器重啟 session 全失
- 正確做法:狀態外部化 —— Session→Redis、持久資料→DB、檔案→S3、設定→ConfigMap
- 容器化與編排
Trap:宣稱「我們全部用容器」
- 強安全隔離(多租戶跑他人程式碼)、跑不同 OS、legacy 應用仍需 VM
- Lambda 底層用 Firecracker microVM、Fargate 也是 VM 級隔離
- 正確做法:90% 工作負載用容器,但說得出何時該用 VM 才是成熟判斷
- 容器化與編排
Trap:滾動更新時做不向後相容的 schema migration
- 滾動更新期間新舊版本並存,「加一個 NOT NULL 欄位」會讓舊版容器崩
- 正確做法:migration 向後相容、API 版本化、收到 SIGTERM 做 graceful shutdown(先拒新請求、等舊請求做完)
- 容器化與編排
Trap:混淆 Liveness 與 Readiness Probe
- Liveness:「還活著嗎?」失敗 → 重啟容器
- Readiness:「準備好收流量了嗎?」失敗 → 從 Service 後端移除(暖機中 / 連不到 DB 時不該收流量)
- 容器化與編排
Serverless
Trap:對延遲穩定 / 長任務 / 持續高流量的場景用 Serverless
- Cold Start 延遲不穩(P99 SLA < 100ms 受影響)、Lambda 15 分鐘硬上限、持續高流量按毫秒計費比容器貴
- 正確做法:這些用容器 / EC2 Worker;Serverless 留給事件驅動 + 流量峰谷明顯的場景
- 無伺服器運算
Trap:幾千個 Lambda 實例直連關聯式資料庫
Trap:所有場景都擔心 Cold Start
- 只有同步 API 請求才需認真處理;非同步(SQS 觸發、排程)多幾百毫秒無所謂
- 緩解:快語言(Node/Python)、小部署包、初始化放 handler 外(Warm Start 重用)、Provisioned Concurrency
- 無伺服器運算
Trap:忽略 Serverless 的 Vendor Lock-in
- 事件格式(API GW/S3 event)、DynamoDB API、EventBridge 規則都是 AWS 特定,跨雲幾乎要重寫整合
- 正確做法:handler 內加一層抽象隔離業務邏輯;面試中明說「接受這個取捨換取免運維 + 自動擴展」
- 無伺服器運算
分散式快取
Trap:consistent hashing 解決所有分布問題
- 它只解平均分布,不解流量偏斜(hot key);單一熱門 key 仍打爆單台,需多副本/本地快取/拆邏輯 key。
- 分散式快取
Trap:cache 掛掉沒關係能從 DB 重建
- 高流量下 cache 全掛會讓所有請求回源、DB 瞬間數倍流量雪崩;需 replication + failover + 限流降級。
- 分散式快取
Trap:用 hash(key) % N 做 cache 分片
- 節點數一變幾乎所有 key 重映射→大量 miss + cold start;用 consistent hashing 讓變動只影響約 1/N。
- 分散式快取
Trap:靠 invalidation 廣播保證一致
- 有節點漏收會永久 stale;主流 TTL 為主、invalidation 為輔,把正確性降級成短期新鮮度。
- 分散式快取
Redis
Trap:AOF 開了就等於 DB 級持久化
- AOF 預設 everysec 仍可能掉最多 1 秒,always 又拖慢吞吐;都不等於 DB commit 必落盤。當可重建加速層,真耐久用 AWS MemoryDB。
- Redis
Trap:把 Pub/Sub 當可靠訊息佇列
- Pub/Sub 是 at-most-once、不持久化,subscriber 離線會永久漏訊息。要可靠投遞/重播用 Redis Streams 或 Kafka。
- Redis
Trap:SET NX 做分散式鎖就萬無一失
- 沒 TTL 會死鎖、超時鎖被搶走、故障轉移鎖會丟。嚴格場景需 Redlock + fencing token,真要強一致用 Zookeeper/etcd。
- Redis
Trap:single-threaded 代表只能用一核
- 命令執行單執行緒(原子性來源),但網路 I/O(6.0+)、持久化 fork 用其他核。真正風險是一條慢命令(KEYS *)卡住全場。
- Redis
訊息佇列
Trap:認為 Queue 能解決系統過載
- Queue 不增加處理能力,只緩衝瞬時尖峰;平均到達率持續 > 處理率會無限堆積。真解法是 backpressure(回壓/拒絕/限流 producer)。
- 訊息佇列
Trap:宣稱 Queue 能 exactly-once
- 分散式幾乎做不到純正 exactly-once(ack 會丟)。主流是 at-least-once + consumer 冪等(message_id 去重)等效之。
- 訊息佇列
Trap:多 consumer 平行仍保證順序
- 多 consumer 平行拉取無法控制完成順序;只能在單一 partition 內保序(partition key 路由 + 該分區單一 consumer),代價是吞吐受限。
- 訊息佇列
Trap:強延遲(<500ms)同步場景塞 Queue
- Queue 天生非同步、要排隊/重試,幾乎一定破壞延遲 SLA;要即時用同步 RPC。
- 訊息佇列
Kafka
Trap:以為 Kafka 保證整個 topic 有序
- 順序只保證在單一 partition 內;全域有序只能用 1 partition(犧牲並行),實務是用對的 key 把需有序的一群導進同一 partition。
- Kafka
Trap:以為 Kafka 預設 exactly-once
- 預設 at-least-once(commit offset 前掛掉會重做);exactly-once 需 idempotent producer + transactional API,或下游冪等。
- Kafka
Trap:把大檔直接塞進 Kafka
- Anti-pattern:Kafka 存小訊息(<1MB)。大檔存 S3,Kafka 只放指向 S3 的小訊息。
- Kafka
Trap:以為 consumer 失敗會自動重試
- Kafka 沒有內建 consumer 重試(SQS 有);要自己做 retry topic,多次失敗再進 DLQ。
- Kafka
分散式鎖
Trap:SET NX EX 設個 TTL 就安全
- TTL 不保證互斥:GC pause/網路延遲讓鎖過期但持鎖節點仍寫入→兩人同時持鎖。正確性場景必加 fencing token(單調遞增,資源拒絕較舊 token)。
- 分散式鎖
Trap:釋放鎖直接 DEL lockKey
- 裸 DEL 會誤刪別人的鎖(A 過期、B 拿到後 A 來 DEL)。取鎖寫唯一 token,釋放用 Lua 原子比對 token 才刪。
- 分散式鎖
Trap:Redlock 最安全該用於正確性
- Redlock 依賴時鐘/timeout 假設,GC pause 下仍不保證正確且不提供 fencing;正確性鎖應用 ZooKeeper/etcd(zxid/revision 天然可 fence)。
- 分散式鎖
Trap:K8s replicas:1 是輕量分散式鎖
- 那只是營運層單實例限制,代價是放棄水平擴展與高可用(Pod 重啟中斷)。只適合簡單低可用場景。
- 分散式鎖
ZooKeeper
Trap:把 ZooKeeper 當資料庫存大量資料
- 它是協調服務:通知變化、存 metadata,單一 ZNode < 1MB、資料集要能放進記憶體,讀寫比約 10:1 最佳。
- ZooKeeper
Trap:以為 leader 選舉都是序號最小當 leader
- 方向相反:應用層用 sequential ZNode 序號最小者當 leader;ZooKeeper 內部 ZAB 選擁有最新 transaction 歷史者。
- ZooKeeper
Trap:所有候選都 watch 同一個節點
- 造成羊群效應(herd effect);正確做法是每台只 watch 序號比自己小一號的節點,O(n)→O(1)。
- ZooKeeper
Trap:強一致所以任一台讀都最新
- follower 用本地副本服務讀以求吞吐,可能讀到 stale;要最強一致需讀前呼叫 sync。
- ZooKeeper
內容傳遞網路
Trap:在所有流量前都畫一個 CDN
- CDN 只放靜態資源與高頻共享內容前;每人不同、不可快取的動態回應盲目擺 CDN 既不命中又多一跳。
- 內容傳遞網路
Trap:用很短 TTL 確保即時更新
- 短 TTL 摧毀 hit ratio 並打爆源站;正解是 immutable + hash busting,要立即下架才用 Purge API。
- 內容傳遞網路
Trap:把 CDN 當需要強一致的資料來源
- CDN 最終一致,更新後要等 TTL/purge/SWR 才刷新;只快取對所有人一樣且可容忍短暫陳舊的內容。
- 內容傳遞網路
Trap:動態內容無法用 CDN 就完全不放
- 純動態不能快取但仍能加速:就近 TLS 終結、骨幹路由、連線重用、Edge Compute 拼個人化。
- 內容傳遞網路
處理競爭
Trap:包在 transaction 裡(atomicity)就不會超賣
- Atomicity 只保證 transaction 內全成全敗,擋不住其他 transaction 同時讀到一樣資料;仍需 pessimistic lock/OCC/SERIALIZABLE 防 race。
- 處理競爭
Trap:預設 isolation level 能擋門票超賣
- PG 預設 READ COMMITTED、MySQL 預設 REPEATABLE READ 都允許兩人同時讀到剩 1 再各自更新;要 isolation 解必須調到 SERIALIZABLE(貴且要重試)。
- 處理競爭
Trap:OCC 用非單調值(平均評分)當版本號
- 會踩 ABA:A→B→A 時 CAS 以為沒變而漏更新;要用永遠單調遞增的欄位(如 review_count)當版本。
- 處理競爭
Trap:hot partition 靠 sharding/LB/read replica 解
- 需求集中單一資源時三者全失效;先質疑能否改變問題本身,真要強一致用 queue-based serialization 單 worker 序列化。
- 處理競爭
過載保護
Trap:Rate Limiting 和 Concurrency Limiting 是同一件事
- Rate 管速率(每秒幾個)、Concurrency 管同時處理中的數量。請求變慢時速率合格但並發數暴增打爆執行緒池,兩者互補要一起用。
- 過載保護
Trap:Backpressure 和 Load Shedding 是同一種
- Load shedding 直接丟棄;Backpressure 不丟,只讓上游放慢整條鏈路流速。Backpressure 更溫和但需上游配合背壓訊號。
- 過載保護
Trap:限流演算法隨便挑/預設 Fixed Window
- 預設 Token Bucket(允許突發、貼近真實)。Fixed Window 有邊界突發(跨視窗放行 2 倍);Leaky Bucket 強制平滑不允許突發。
- 過載保護
Trap:擴容縮容用同樣反應速度
- 擴容要快、縮容要慢,否則小流量回升又觸發擴容造成振盪;且 Reactive 有 1-3 分空窗需 load shedding/預測擴容/佇列撐住。
- 過載保護
可靠交付
Trap:對任何失敗都重試
- 重試只適用暫時性故障(5xx/網路抖動)。重試 4xx 永遠失敗;重試非冪等會重複下單/扣款;不加退避會驚群壓垮服務。
- 可靠交付
Trap:把 exactly-once 當佇列標準目標
- 真正 exactly-once 跨系統極難昂貴(需分散式交易)。主流是 at-least-once + 冪等 Consumer(去重表) = 等效效果。
- 可靠交付
Trap:熔斷器 Half-Open 探針成功後立刻全放
- 應逐步放量(ramp-up),否則突然全流量把剛恢復的下游又打垮。
- 可靠交付
Trap:對外請求不設超時/只說「看情況」
- 不設超時會耗盡執行緒/連線池造成級聯故障。設值要量測依賴 P99 × 2~3,並受整體鏈路時間預算約束。
- 可靠交付
可觀測性
Trap:用平均延遲報告效能
- 尾端延遲必須用 Histogram 取 P99;平均會被大量快請求拉低、掩蓋慢請求。
- 可觀測性
Trap:對原因告警(CPU>80% 就告警)
- 要對症狀告警(錯誤率>1%、P99>1s 這種用戶有感的);對原因告警造成告警疲勞。
- 可觀測性
Trap:把 SLO 設成 100%
- 100% 意味永遠不能改動;應從 99.9%(每月約 43 分 error budget)起步,用錯誤預算平衡可靠性與發布速度。
- 可觀測性
Trap:把 user_id/request_id 塞進 metric label
- 高基數讓時序爆炸、TSDB 炸掉;高基數放 Logs/Traces,Metrics label 只放低基數維度(method/status/region)。
- 可觀測性
案例研究(Case Studies)
Trap(QR Code 產生器):看到「低延遲」直覺選 301(瀏覽器快取、少一跳)
- 選 302:擁有者要能改/刪對應,必須每次經 server 取最新;延遲靠 cache/CDN 補
- QR Code 產生器
Trap(地震通知系統):alert 時用 raw lat/long 即時算每位使用者距離,或多邊形仍用 Geohash
- 把 geo 比對攤到寫入:使用者回報 cell_id、維護 cell→devices 反向索引,alert 只 polyfill→查 KV→去重;多邊形用 S2/H3
- 地震通知系統
Trap(Polymarket 預測市場):鏈上結算失敗時為了「強原子」rollback 已完成的 off-chain 撮合
- 撮合 append-only/deterministic,鏈上失敗只 retry/升級;回滾會破壞 ordering、使 replica 與下游狀態分歧
- Polymarket 預測市場
Trap(Amazon 價格追蹤):直接設計爬遍全 Amazon 的暴力 crawler,或低頻 cron + full table scan 做通知
- hybrid 爬取(extension 熱門 + crawler 補長尾)、通知改 event-driven(CDC/dual-write→queue→worker)、圖表打每日 pre-aggregation 表
- Amazon 價格追蹤
Trap(Robotaxi 叫車):只靠 Redis per-ride lock 保證單 AV 不被多筆指派,或把 matching state 放 worker 本機記憶體
- per-ride lock 只管單 ride;跨 ride 搶同一 AV 需 Ride DB partial unique index 原子兜底;matching state 集中放 Redis + CAS
- Robotaxi 叫車
Trap(Spotify 熱門排行):當一般 CRUD 題,用 worker 每小時直接掃 OLTP 做 aggregate+sort 算 Top K
- OLTP 為 point lookup 最佳化會 timeout 且與寫入搶資源;走 CDC 解耦→lakehouse→OLAP(column)+Spark 聚合,分析永不碰線上寫入
- Spotify 熱門排行
Trap(Messenger 即時通訊):直覺以 user_id 分片連線與推送,逐一 RPC 給每位接收者
- 以 chat_id 分片做 pub/sub:單一 owner 兼順序權威,fan-out 降為 M 個 subscribing gateway,gateway 可 stateless 擴展
- Messenger 即時通訊
Trap(Webhook 平台):為了讓客戶免去重而承諾 exactly-once 投遞
- at-least-once + payload 帶唯一 event_id 由客戶冪等去重;明說「平台保證不丟、客戶負責去重」
- Webhook 平台
Trap(Google Docs 協作編輯):不問併發上限就上 CRDT 炫技,或以為 client 只要被動套用 server 廣播的 op
- 單文件≤100 人 + 有中心服務→中心化 OT;且 client 已樂觀套本地 edit,必須對 server 廣播的 op 再做一次 OT transform 才收斂
- Google Docs 協作編輯
Trap(YouTube 影音平台):client 把影片上傳到我們 service 再轉存 S3,或塞 DB BLOB、觀看時直接下載整支
- service 只發 presigned URL,client 直傳 S3(multipart 續傳);bytes 放 S3、DB 存位置;觀看回 manifest,client 分段 ABR + CDN 卸載
- YouTube 影音平台
Trap(ChatGPT Tasks 排程):單 executor 同時查詢+執行,或宣稱 exactly-once 卻沒讓 job 冪等,又或 worker crash 沒 heartbeat 致 job 靜默消失
- watcher 查到期丟 queue、worker 群消費;明說 at-least-once 並要求 job 冪等;用 Visibility Timeout 當 heartbeat 逾時自動重投 + DLQ
- ChatGPT Tasks 排程
Trap(Airbnb 訂房平台):用悲觀鎖(SELECT FOR UPDATE)鎖 inventory 到付款回來,或讓正確性依賴 cron 把過期 reserved 翻回
- 短交易 + 帶 expiration 的 reservation:「可訂」= available OR 過期 reserved,以單筆原子 conditional write 搶位;cron 只做事後清理
- Airbnb 訂房平台
Trap(Q&A 客服 Agent):只靠 system prompt 寫「不要捏造」就想防幻覺,或當一般 chatbot 直接讓 LLM 回答
- RAG 先檢索真實資料再 grounded 生成;防幻覺要四層(prompt + hybrid search/rerank + grounding check + 低分拒答轉真人),retrieval 品質是根本
- Q&A 客服 Agent
Trap(LLM 推論 API):把 POST /sample 直接同步呼叫 batched_sample(一請求一推論),或選 SQS 當 Request Queue
- 請求先入 Request Queue,GPU worker dual-trigger 湊批;即時 API 用 sub-ms 且一次拉 1-100 筆的 Redis Streams,而非每次最多 10 條/5-20ms 的 SQS
- LLM 推論 API