設計 S3 式物件儲存 (Object Storage)

Hard★★★★Draft

FromSystem Design Interview

Concepts practiced🗄️ 物件儲存 (Object Storage)🧩 一致性雜湊 (Consistent Hashing)🧩 複製 (Replication)

Convention: read the requirements & constraints first, sketch your own design, then expand the reference to compare.

📚 From the Books

設計 S3 式物件儲存 (Object Storage)ProblemHardStorage & File

自己造一個 S3:用 RESTful API 存取的物件儲存,犧牲效能換取巨大規模、高耐久與低成本。難點在於把 metadata 與 data 分離,並用複寫或 erasure coding 撐起 6 個 9 的耐久性。

📋 Requirements

🧠 Core Ideas

TIP

核心是「metadata 與 data 分離 + data 儲存只認 UUID + 放置服務決定副本落點 + 複寫或 erasure coding 撐耐久性」。

設計哲學類似 UNIX 檔案系統:檔名記在 inode,資料放在磁碟另一處。這裡物件儲存把 inode 換成 metadata 儲存(可變資料),硬碟換成 data 儲存(不可變資料),metadata 用物件 ID 透過網路請求在 data 儲存中找到對應資料。分離讓兩個元件能各自獨立實作與最佳化。

高層元件:負載平衡器 → 無狀態 API 服務(協調 IAM、metadata、data 三者的呼叫)→ IAM(認證/授權)、metadata 儲存、data 儲存。上傳時先驗證 WRITE 權限,把 payload 存進 data 儲存拿回 UUID,再到 metadata 建 object_name → object_id → bucket_id 條目;下載則先用物件名對應到 UUID,再用 UUID 從 data 儲存取資料。

⚖️ Reference Design

Data 儲存:路由、放置、資料節點
  • 資料路由服務:無狀態,查放置服務取得最佳資料節點,並負責讀/寫資料節點。
  • 放置服務:決定物件的複寫群組(主節點 + 副本),維護含實體拓撲的虛擬叢集地圖以確保副本實體分離;靠心跳監控節點,逾寬限期(如 15 秒)標記下線。因為關鍵,建議用 Paxos/Raft 建 5 或 7 節點共識叢集。
  • 資料節點:存實際物件資料,透過複寫到多個節點確保耐久;上跑 daemon 持續回報心跳(管幾顆磁碟、各存多少)。
  • 給定 UUID 要能確定性地找到複寫群組、且增減群組後仍成立——一致性雜湊是常見實作。
持久化流程與一致性/延遲權衡
  • 流程:路由服務為物件產 UUID → 查放置服務拿主節點 → 送資料到主節點 → 主節點存本機並複寫到兩個次要節點 → 回 UUID 給 API 服務。
  • 主節點等所有次要節點寫完才回應 = 強一致,但延遲取決於最慢副本。
  • 也可只等「主 + 一個次要」(中一致中延遲)或「只等主」(弱一致低延遲)——後兩者是最終一致。
資料如何組織在節點上
  • 小物件各存一檔會浪費磁碟區塊(4 KB 對齊)並耗盡 inode。解法:把許多小物件合併附加到一個大檔,運作類似 WAL——寫滿數 GB 後標記唯讀、開新讀寫檔;對唯讀檔只服務讀取。
  • 讀寫檔的寫入須序列化,會限制吞吐;可為每個處理核心配專屬讀寫檔。
  • object_mapping 表(object_id、file_name、start_offset、object_size)定位物件。寫一次讀多次,讀效能較重要,故選 B+ tree 的關聯式庫優於 RocksDB;且對應資料在各節點隔離、不需共享,於是每節點放一個 SQLite。
耐久性:複寫 vs erasure coding
  • 複寫:資料複 3 份(年故障率 0.81% → 約 6 個 9),且副本跨故障域(機架/節點/AZ)以抗大規模停電、災害。儲存開銷 200%,讀寫快、無計算成本。
  • Erasure coding:資料切 chunk + 算 parity(如 8+4,容忍 4 節點下線),可達約 11 個 9,儲存開銷僅 50%;但寫要算 parity、讀要從多節點取,複雜度與延遲較高。
  • 結論:延遲敏感用複寫,最小化成本用 erasure coding;本設計主用複寫。
  • 正確性驗證:記憶體資料損毀常見,於程序邊界比對 checksum(如 MD5),每個物件與整檔尾端各附 checksum;讀取時比對,不符就從其他故障域復原。
Metadata 資料模型與擴展
  • 兩表 bucket、object;需支援:依名查 ID、依名增刪、列出共享 prefix 的物件。
  • bucket 表小(可讀副本分流);object 表巨大需分片。依 bucket_id 分片會熱點、依 object_id 分片查不了 URI,故用 (bucket_name, object_name) 的雜湊當分片鍵。
  • 列出物件:扁平結構靠 prefix 模擬目錄。分片後跨 shard 彙整 + 分頁很複雜;因列出效能非物件儲存優先項,可把列出資料反正規化到一張依 bucket ID 分片的專用表,隔離查詢、簡化實作。
版本控管、多部分上傳與垃圾回收
  • 版本控管(bucket 層級啟用):不覆寫舊記錄,插入相同 bucket_id/object_name 但新 object_id 與 object_version(TIMEUUID)的記錄;目前版本 = 最大 TIMEUUID。刪除則插入刪除標記成為目前版本,GET 回 404。
  • 多部分上傳:大物件切片獨立上傳,各片回 ETag(md5),全數完成後由 data 儲存依 part number 重組;斷線只需重傳該片。
  • 垃圾回收:延遲刪除、孤兒資料(半上傳/被棄的多部分)、損毀資料都成垃圾;靠壓縮定期清理——把有效物件複製到新檔、跳過刪除標記者,並更新 object_mapping(用交易保一致)。

🔑 Takeaways

✍️ My Notes

No notes yet — jot your takeaways or Q&A here.

📖 Further Reading