快取與儲存練習題 (Practice - Caching & Storage)
答案採 >
Related Concepts
- 07-Caching-Storage/01-Caching
- 07-Caching-Storage/02-Blob-Storage
- 07-Caching-Storage/03-Distributed-Cache
- 07-Caching-Storage/04-Redis
- 10-Design-Patterns/04-Handling-Large-Blobs
- 02-Distributed-Systems/04-Consistent-Hashing
- 02-Distributed-Systems/05-Numbers-to-Know
- 05-Database-Advanced/03-Replication
| 關鍵字/場景 | 對應答案 |
|---|---|
| 預設快取模式 | Cache-Aside |
| 預設淘汰策略 | LRU + TTL |
| 熱門 key 過期,DB 被打垮 | Cache Stampede → Request Coalescing |
| 名人個人資料快取被打爆 | Hot Key → 複製 + in-process + rate limit |
| 靜態圖片放哪 | CDN |
| feature flag / 設定值 | In-Process Cache |
| 寫入後讀到舊值 | 寫入時 invalidate / 短 TTL / 接受最終一致 |
| 大型二進位資料放哪 | Blob Storage(S3),DB 只存 metadata |
| 防 S3 公開曝露 | 預設封鎖公開 + 私有 Bucket + Presigned URL |
| 控制儲存成本 | 儲存分層 + 生命週期策略(自動降級) |
| 防誤刪 / 回滾 | S3 版本控制(Versioning) |
| cache 升級成 cluster | 變成獨立分散式系統(sharding/replication/failover) |
| cluster key 分片 | Consistent Hashing(避免 % N 全重映射) |
| 整個 cache cluster 掛了 | 回源風暴 → replication + 限流回源 + 降級 |
| stampede 在 cluster 放大 | TTL jitter(源頭打散)+ single-flight(回源去重) |
| 跨 region cache | 每區一套、不跨區複製;failover 容忍 cold cache |
| Redis 為什麼快 | in-memory + single-threaded(命令原子無鎖) |
| Redis 耐久性 | RDB/AOF 皆非 DB 級;當可重建加速層,DB 為 source of truth |
| 排行榜 / 滑動視窗限流 | Sorted Set(log N) |
| 分散式鎖 | SET NX EX + Lua 釋放;嚴格用 Redlock + fencing |
| Pub/Sub vs Streams | Pub/Sub at-most-once 不持久;Streams 可重播 |
Caching(Q1–Q12)
Question 1 - 為什麼用快取 [recall]
從 PostgreSQL 讀一個用戶資料約需 50ms,從 Redis 讀只需 1ms。這個差距背後的根本原因是什麼?
資料庫把資料存在磁碟上,每次查詢都要付磁碟存取的代價(seek、IO)。
Redis 把資料放記憶體裡,離 CPU 近得多,繞過了磁碟瓶頸。
量級差距約 50×,這就是為什麼讀取密集系統 cache 是預設答案。
Question 2 - Latency 排序 [recall]
將以下快取位置依延遲由低到高排序:External Cache (Redis)、CDN、In-Process Cache。
In-Process Cache < External Cache (Redis) < CDN
- In-Process:零網路開銷,直接讀行程記憶體(奈秒級)
- Redis(同區):< 1ms,但仍有網路 hop
- CDN:20–40ms(已比直連源站 250–300ms 快很多)
Question 3 - Cache-Aside vs Write-Through [analysis]
Cache-Aside 和 Write-Through 的核心差異是什麼?為什麼面試預設選 Cache-Aside?
- Cache-Aside:Application 自己管理快取。讀取時先查 cache,miss 就查 DB 並寫入 cache。Cache 和 DB 分開管理。
- Write-Through:Application 寫入 cache → cache 同步寫入 DB。兩者都完成才算寫入成功。
預設 Cache-Aside 的原因:
- 不依賴特殊的快取基礎設施(Redis 原生不支援 Write-Through)
- 程式碼明確,容易追蹤錯誤
- Write-Through 仍有 dual-write 問題(cache 成功 / DB 失敗,反之亦然)
Question 4 - Write-Behind 風險 [recall]
Write-Behind(Write-Back)的主要風險是什麼?適合什麼場景?
風險:cache 在把資料 flush 到 DB 之前崩潰 → 資料永久遺失。
適合:高寫入吞吐量、可接受最終一致性的場景。
- 分析 / metrics pipeline
- 計數器(按讚數、播放數的非精確統計)
Question 5 - Cache Stampede [recall]
什麼是 Cache Stampede(快取雪崩)?最有效的解決方案是什麼?
Cache Stampede:熱門快取項目過期時,大量請求同時 miss,直接打到 DB,可能把 DB 打垮。
最有效解:Request Coalescing(請求合併 / Single Flight) —— 只讓一個請求去重建快取,其他請求等待那個結果。
其他解:cache warming(過期前主動刷新)—— 只在 TTL 過期模式有效。
Question 6 - Eviction Policy 錯誤敘述 [recall]
以下關於 eviction policy 的敘述,何者錯誤?
(A) LRU 移除最久沒被存取的資料
(B) LFU 移除存取次數最少的資料
(C) FIFO 是生產環境最常用的策略
(D) TTL 通常搭配 LRU 或 LFU 一起使用
答案:(C)
FIFO 很少在生產環境使用,因為它完全忽略使用模式,可能移除正在被頻繁存取的項目。
LRU 才是最常用的預設策略(90% 工作負載適合)。
Question 7 - Hot Key 問題 [application]
如果你的系統有一個極端熱門的快取 key(例如名人的個人資料),可能造成什麼問題?列舉三個解決方案。
問題:Hot Key —— 單一 key 收到大量流量,可能讓某個 Redis 節點過載成為瓶頸。整體 hit rate 高也救不了。
解法:
- 複製熱 key:把同樣的值存在多個快取節點上,分散讀取負載
⚠️ 各副本 TTL 錯開,否則同時過期會觸發 Stampede - 加行程內備援快取:把極端熱門的值存在 application 行程內,避免一直打 Redis
- Rate Limiting:對特定 key 的異常流量踩煞車
Question 8 - 五步驟面試話術 [recall]
面試中介紹快取策略的五個步驟是什麼?
- 確認瓶頸:指出快取要解決的具體問題(DB CPU 高、查詢慢、計算貴)
- 決定快取什麼:選讀取頻繁、不常變動、取得成本高的資料
- 選擇快取架構:預設 Cache-Aside;靜態資源加 CDN;極端熱 key 加 in-process
- 設定淘汰策略:預設 LRU + TTL
- 說明缺點:失效策略、故障降級(circuit breaker)、Stampede 應對
Question 9 - 何時用 CDN [application]
系統設計面試中,引入 CDN 最穩妥的理由是什麼?什麼時候不該用 CDN 當第一個答案?
最穩妥:系統需要大規模傳遞靜態媒體資源(.png、.jpg、.svg、.mp4)。
CDN 邊緣節點 20–40ms vs 跨洲源站 250–300ms,差距明顯。
不該當第一個答案:純 API / 動態內容為主的系統。雖然現代 CDN 也能快取 API 回應,但這會被面試官追問一堆問題(cache key 設計、invalidation、edge logic)。先講 Redis 外部快取,CDN 視題目再加。
Question 10 - 一致性策略選擇 [application]
你正在設計一個社群媒體 App 的用戶 profile cache。用戶可以隨時更新自己的暱稱和大頭照。請選擇並說明一個 cache invalidation 策略,包含取捨。
建議:寫入時失效(Cache-Aside + delete)+ 適度 TTL
更新 profile:
1. UPDATE users SET ... WHERE id = ?
2. cache.delete("user:{id}:profile")
讀取 profile:
1. 查 cache,hit 就回
2. miss → 查 DB → set cache (TTL 10 分鐘)
取捨:
- ✅ 用戶自己馬上看到變更(cache.delete 之後下次讀填新值)
- ⚠️ Dual-write race:DB 更新成功但 cache.delete 失敗 → 別人看到舊值最多 10 分鐘(TTL 自然刷新)
- 💡 對社群 profile,短暫不一致可接受,不需要分散式 transaction
Question 11 - Cache + DB 一致性根本問題 [analysis]
「Cache 和 DB 兩個儲存系統永遠不會 100% 一致」——這個說法對嗎?為什麼?
基本正確。
Cache 和 DB 是兩個獨立系統,沒有跨系統的分散式 transaction。任何寫入路徑都有一個窗口可能不一致:
- 先寫 DB 再 delete cache:兩步之間有人讀到舊 cache
- 先 delete cache 再寫 DB:兩步之間有 cache miss → 寫入舊值到 cache
- 同步 write-through:cache 成功 / DB 失敗 → 兩邊不一致
沒有完美解,只能根據業務選策略:
- 用 TTL 把不一致窗口限制在可接受時間內
- 用 CDC(Change Data Capture) 監聽 DB 變更非同步更新 cache
- 對強一致需求的資料乾脆不快取
Question 12 - Redis 掛了 [application]
你的系統重度依賴 Redis 快取,DB 在尖峰時被快取擋掉 80% 流量。如果 Redis 掛了,會發生什麼?怎麼設計才能撐住?
沒處理會發生:所有讀取突然打到 DB(流量 5×),DB CPU 100%,連鎖故障 → cascading failure。
設計:
- 降級直連 DB + Circuit Breaker:偵測 Redis 不可用,請求改走 DB;但 circuit breaker 限制流量,避免暴流壓垮 DB
- In-Process Cache 當最後防線:極熱 key 同時放行程內,Redis 掛了也能擋一部分
- Redis 本身做高可用:sentinel / cluster + replica + automatic failover(這是預防,不是降級)
- Graceful degradation:非核心功能(推薦、related items)失敗時返回降級結果,核心讀寫優先
💡 面試話術:「Cache 是性能優化,不是業務必需。Cache 故障時系統應該變慢但不該崩潰。」
Blob Storage(Q13–Q20)
Question 13 - File / Block / Object 差異 [recall]
File Storage、Block Storage、Object Storage 三者最關鍵的差異是什麼?物件儲存放棄了什麼能力?
- File Storage(NFS/EFS):樹狀目錄,用路徑找,可就地改幾行
- Block Storage(EBS):切成固定 block,低層位址存取,低延遲隨機讀寫(DB/VM 底層用)
- Object Storage(S3):扁平 key 空間,以 key 取整個物件,不可變(只能上傳新版替換)
物件儲存放棄了就地修改和低延遲隨機存取,換來極高耐久性 + 近乎無限擴展 + 低成本。users/123/avatar.jpg 只是 key 命名慣例,底層沒有真正目錄。
Question 14 - 耐久性 vs 可用性 [recall]
S3 Standard 的耐久性是 11 個 9,可用性是 99.99%。這兩個數字分別代表什麼?為什麼不一樣?
- 耐久性(Durability, 11 個 9):資料不會消失。存 1,000 萬個物件,一年預期損失 < 1 個。靠跨 3 個 AZ 冗餘達成,一個 AZ 全毀也不丟資料。
- 可用性(Availability, 99.99%):你當下能不能存取到資料。
兩者不同:AWS 偶爾局部服務中斷讓你暫時讀不到(可用性受影響),但你的資料不會消失(耐久性不受影響)。
Question 15 - 儲存分層取捨 [application]
面試官問:「這些用戶上傳的歷史資料怎麼處理才能控制成本?」你會怎麼回答?要注意什麼陷阱?
用儲存分層 + 生命週期策略自動降級:
最近 30 天 → S3 Standard(毫秒存取)
30–90 天 → S3 Infrequent Access(成本約少一半)
90 天以上 → Glacier(約 Standard 的 1/10)
透過 生命週期規則自動執行,不需任何應用層干預。
⚠️ 陷阱:核心取捨是「儲存越便宜,取回越貴」。Glacier Deep Archive 儲存極省,但如果突然大量取回,取回費可能遠超省下的儲存成本。冷儲存只適合「真的很少取回」的資料。
Question 16 - 防止公開曝露 [application]
S3 資料外洩事件幾乎都來自配置錯誤的公開 Bucket。設計中要說清楚哪幾道防線?
四道防線:
- 預設封鎖所有公開存取:Bucket 層級和帳號層級都設「Block Public Access」,作為預設值
- 私有 Bucket + Presigned URL:用有時限的臨時 URL 授予個別物件存取,而不是整桶公開
- S3 Access Analyzer:持續掃描 Bucket 設定,標記意外公開的 Bucket
- 最小權限 IAM:應用程式只需上傳特定前綴,就不要給它讀整個 Bucket 的權限
Question 17 - 防誤刪與回滾 [recall]
面試官問「怎麼確保重要資料不會被誤刪?怎麼支援回滾?」最乾淨的 S3 答案是什麼?代價是什麼?
啟用版本控制(Versioning)。
同一 key 上傳新物件時,舊版本不被覆蓋而是保留並分配新 Version ID:
- 防誤刪:DELETE 只加一個「刪除標記」,舊版本仍存在,可還原
- 覆寫保護:就算程式 bug 覆蓋了重要物件,舊版也還在
- 回滾:快速切回上一版,不需重新上傳
代價:所有歷史版本都收費 → 通常搭生命週期規則自動刪除超過 N 天或只留最近 N 個版本。
Question 18 - 全球存取設計 [analysis]
全球各地用戶都要存取同一份資料。讀多寫少和需要跨 Region 讀寫,兩種情況的設計有何不同?
取決於讀寫模式:
- 讀多寫少(靜態/媒體):CDN 是最直接解。S3 當 origin,CloudFront 在全球邊緣節點快取,用戶從最近節點取。不需要跨 Region 複製 S3,CDN 幫你做快取。
- 需跨 Region 讀寫:用 CRR(跨區複製)。各 Region 從自己的副本讀(低延遲),寫入發生在主 Region。
- ⚠️ CRR 是非同步(幾秒~幾分鐘),雙向複製時同一 key 衝突預設「最後寫入者勝」,要確認符合業務邏輯
- 合規不能離境:反向需求 —— 用 Bucket Policy 拒絕跨 Region 複製、鎖定 Region
Question 19 - Blob vs DB 怎麼選 [analysis]
「Blob Storage 和資料庫都能存資料」——你用什麼粗略標準決定一份資料放哪?
粗略標準:超過幾 MB 且不需要 SQL 查詢 → Blob Storage。
| 用資料庫 | 用 Blob Storage |
|---|---|
需多欄位查詢(WHERE ...) |
只按 key 取整個物件 |
| 需 ACID 事務 | 寫入後很少修改(不可變沒問題) |
| 結構化、有 schema、資料小(KB) | 大型二進位、需高耐久 + 低成本 |
標準模式:DB 存 metadata(key、大小、狀態、所有者),Blob Storage 存實際內容。二進位資料進 DB 會讓查詢效能和備份時間都崩掉。
Question 20 - 在 Blob Storage 上做搜尋 [application]
用戶把大量 PDF 和圖片存在 S3,現在要支援「搜尋文件內容」和「以圖搜圖」。怎麼做?
原則:S3 是儲存的地方,不是搜尋的地方。 物件儲存不支援按內容搜尋。需要搜尋的屬性都要在適合搜尋的系統裡建索引:
- metadata 查詢(標題、標籤、上傳者)→ 存關聯式 DB,SQL 查出 S3 key 再取物件
- 全文搜尋(文件內容)→ 上傳後用 Lambda 提取文字(PDF 解析 / OCR)→ 索引到 Elasticsearch
- 以圖搜圖 → ML 模型把圖片轉 embedding → 存向量資料庫(Pinecone / pgvector),query 圖也轉 vector 找最近鄰
S3 存實際 bytes,搜尋系統存「可搜尋的表示」。
| 問題 | 解法 |
|---|---|
| 用戶上傳的圖片/影片放哪 | Blob Storage(S3),DB 只存 metadata |
| File/Block/Object 差異 | Object = 扁平 + 不可變 + 以 key 取整個物件 |
| 耐久性 vs 可用性 | 11 個 9(不丟,跨 3 AZ)vs 99.99%(能否存取) |
| 控制儲存成本 | 儲存分層 + 生命週期策略(越便宜取回越貴) |
| 防 S3 公開曝露 | 預設封鎖公開 + 私有 + Presigned URL + Access Analyzer |
| 防誤刪 / 回滾 | 版本控制(搭生命週期清理舊版) |
| 全球讀多寫少 | CDN(不必跨 Region 複製 S3) |
| 跨 Region 讀寫 | CRR(非同步,最後寫入者勝) |
| 在 Blob 上搜尋 | metadata→DB / 全文→ES / 以圖搜圖→向量庫 |
Part 1:Distributed Cache
Q21(Recall):從單機 cache 升級成 distributed cache cluster 後,問題的重心如何轉移?
單機 cache 重心在記憶體管理與讀寫流程(eviction/LRU、TTL、cache-aside vs write-through、stampede)。
升級成 cluster 後,重心換成它自己變成一個獨立的分散式系統:key 怎麼分配、節點掛了怎麼 failover、網路延遲、流量偏斜(skew)。難點不在快,而在 sharding / replication / failover / 一致性。
Q22(Recall):為什麼 distributed cache 不能用 hash(key) % N 分片?正確做法是什麼?
% N 一旦節點數 N 改變(擴縮容),幾乎所有 key 重新映射 → 大量 cache miss → 瞬間 cold start,資料庫被打到平常數倍流量。
用 Consistent Hashing(hash ring):節點變動只影響約 1/N 的 key,其餘位置不變。代價是實作較複雜,需 virtual node 才能讓分布夠均勻。
Q23(Recall):Consistent Hashing 都做了,為什麼還是會有 hot key?三種緩解方法是什麼?
Consistent Hashing 只保證 key 均勻分布到節點,不保證 流量均勻。一個明星帳號的 key 只佔一個 slot,卻可能吸走 20%+ 流量,全壓在它所在那台機器 → 成為 bottleneck。分布均勻 ≠ 流量均勻。
三招:① key 多副本隨機讀 ② local in-process cache ③ 拆分邏輯 key(:0/:1/:2 分散到多節點)。
Q24(Application):整個 cache cluster 掛掉時會發生什麼?面試要講出哪幾層防護?
所有請求直接回源 DB。原本 90% 由 cache 處理 → DB 瞬間承受約 10 倍流量 → 雪崩。
三層防護:① 每個 shard 配 replica + failover,讓單 shard 故障不全掛;② client 端 timeout + retry 上限 + circuit breaker 避免重試風暴;③ cluster 級故障時限流回源 + 降級模式(回傳預設值/舊資料),主動犧牲體驗保護 DB。
Q25(Analysis):Cache stampede 的 TTL jitter 和 single-flight 都防 stampede,差在哪?為什麼生產系統常兩個都用?
角度不同。Jitter 從「源頭」下手:TTL 加隨機抖動,打散 key 的過期時間,避免它們同時失效。Single-flight 從「回源」下手:一個 key miss 時只放一個請求查 DB,其餘並發請求等它的結果共享。
前者降低同時過期機率,後者保證即使同時 miss 也只打 DB 一次 —— 互補,所以生產系統通常兩個都用。
Part 2:Redis
Q26(Recall):Redis 為什麼快、又為什麼「好推理」?單執行緒的代價是什麼?
快:全在記憶體(in-memory),微秒級延遲、單實例 ~O(100k) 寫入/秒。好推理:命令執行單執行緒,邏輯上一次只跑一條命令,所以 INCR、SADD 天生原子、無鎖、無 race。
代價:一條慢命令卡全場 —— O(N) 大命令(KEYS *、對大集合 SMEMBERS、大 SORT)會阻塞所有請求。生產環境用 SCAN 取代 KEYS。
Q27(Recall):Redis 的 RDB 和 AOF 兩種持久化機制各是什麼?為什麼說 Redis 耐久性是「明確取捨」?
- RDB(快照):定期 fork 出整個資料集寫成二進位快照。恢復快、檔案小;兩次快照間當機會掉資料。
- AOF(Append-Only File):把每條寫命令追加到日誌。掉資料少;檔案大、恢復較慢。
即使開 AOF,預設 appendfsync everysec 仍可能掉最多 1 秒寫入。所以把 Redis 當可重建的加速層,source of truth 放持久化 DB;要「速度 + 真耐久」用 AWS MemoryDB。
Q28(Recall):Redis Cluster 怎麼路由 key?跨鍵操作為什麼受限、怎麼解?
Cluster 用 CRC16(key) % 16384 算出 hash slot,每個 slot 歸屬某節點。Client 本地維護 slot → node 對照表直連正確節點;連錯時節點回 MOVED,client 更新映射後重連。
跨鍵操作(如 MGET k1 k2)要求所有 key 在同一 slot,因為跨節點交易代價高、會破壞性能目標。解法:用 hash tag {user1}:profile、{user1}:cart 強制同 slot。
Q29(Application):搶票/秒殺要「同一操作不被多實例同時執行」,用 Redis 怎麼做分散式鎖?有哪兩個經典陷阱?
SET lock:resource <id> NX EX 30(NX=不存在才設、EX=30s 自動過期);釋放時用 Lua 比對 id 再 DEL,避免刪到別人的鎖。
兩個陷阱:① 沒設 TTL → 持鎖者當機,鎖永不釋放(死鎖);② 設了 TTL 但業務超時 → 鎖過期被別人拿走,兩 client 同時持鎖。嚴格場景用 Redlock + fencing token(單調序號讓下游拒絕舊持鎖者)。
Q30(Analysis):Redis Pub/Sub 和 Streams 都能做「訊息傳遞」,何時用哪個?關鍵差異是什麼?
Pub/Sub 是 at-most-once、不持久化:subscriber 離線時訊息永久錯過,重連不補發。適合允許偶爾遺失的即時推播(聊天、在線狀態、通知)。
Streams 是 append-only 日誌 + consumer group:支援 at-least-once,consumer 掛掉其 PEL 可被 XCLAIM 接手,可重播。適合需可靠投遞的輕量工作佇列。需重量級持久化/巨大吞吐仍選 Kafka。
| 問題 | 解法 |
|---|---|
| 單機 → distributed 重心轉移 | 從記憶體管理 → 變成獨立的分散式系統 |
% N 為何不行 |
節點變動幾乎全 key 重映射 → 大量 miss / cold start |
| Consistent Hashing 解什麼 | 只解平均分布,不解流量偏斜(hot key) |
| Hot key 三招 | 多副本隨機讀 / 本地快取 / 拆邏輯 key |
| cluster 全掛 | replica+failover / client circuit breaker / 限流回源+降級 |
| Stampede 兩招 | TTL jitter(源頭打散)+ single-flight(回源去重) |
| Partial node failure | 變慢非 crash → timeout/retry 風暴 → circuit breaker |
| Cache replication 一致性 | eventual consistency 即可,cache 非 source of truth |
| Multi-region cache | 每區一套、不跨區複製;failover 容忍 cold cache |
| Redis 為何快 | in-memory + single-threaded(命令原子無鎖) |
| Redis 慢命令 | KEYS */大 SORT 卡全場 → 用 SCAN |
| Redis 耐久性 | RDB 快照 / AOF 日誌;皆非 DB 級;真耐久用 MemoryDB |
| Cluster 路由 / 限制 | 16384 slot、client 映射、MOVED;預設僅單鍵,跨鍵用 hash tag |
| 排行榜 / 滑動視窗限流 | Sorted Set(log N) |
| 分散式鎖 | SET NX EX + Lua 釋放;嚴格用 Redlock + fencing token |
| Pub/Sub vs Streams | Pub/Sub at-most-once 不持久;Streams 可重播 + consumer group |