物件儲存
把檔案當成一個個帶鍵的物件存放:容量幾乎無限、便宜又耐久,但不能只改檔案的一部分,也不適合拿來當資料庫查詢。
看哪一段
網路
5.0 GB
每段
每傳 1 GB 斷線機率
79 段
- 2
一次成功重傳過(數字是次數)超過重試上限,未完成
成功/所需段數
79 / 79
重傳的段
1
分段:共送出
5.0 GB
單次 PUT:共送出
31 GB(放棄)
5.0 GB 需要 79 段、每段 64 MB。全部分段成功;其中 1 段重試,只重傳那些段。總共送了 5.0 GB。整個檔案一次 PUT 的話,試了 20 次都沒成功,送了 31 GB:每失敗一次就從頭來。
亮起來的是這一步執行的程式碼
const SIGNING_SECRET = "storage-secret", MAX_TRIES = 20; type Transfer = (mb: number) => { ok: boolean; sent: number }; // Send a big file in parts; a failure costs one part, not the whole file.function uploadMultipart(sizeMb: number, partMb: number, transfer: Transfer) { const parts: { attempts: number; mbSent: number; ok: boolean }[] = []; const totalParts = Math.ceil(sizeMb / partMb); for (let offset = 0; offset < sizeMb; offset += partMb) { const size = Math.min(partMb, sizeMb - offset); const part = { attempts: 0, mbSent: 0, ok: false }; for (let tries = 0; tries < MAX_TRIES; tries++) { const result = transfer(size); part.attempts++; part.mbSent += result.sent; if (result.ok) { part.ok = true; break; } } parts.push(part); if (!part.ok) return { parts, ok: false, failedPart: parts.length - 1, totalParts }; } return { parts, ok: true, failedPart: null, totalParts }; // only success may complete} function fnv1a(text: string): number { let h = 0x811c9dc5; for (const byte of new TextEncoder().encode(text)) h = Math.imul(h ^ byte, 0x01000193); return h >>> 0;} // Real services use HMAC-SHA256; the idea is identical.function signature(key: string, expires: number): string { return fnv1a(`${key}|${expires}|${SIGNING_SECRET}`).toString(16);} // The app server hands this out; the client uploads straight to storage.function presign(key: string, expires: number): string { return `/${key}?expires=${expires}&sig=${signature(key, expires)}`;} // Storage checks the URL without asking the app server anything.function verify(key: string, expires: number, sig: string, now: number): string { if (now > expires) return "expired"; if (sig !== signature(key, expires)) return "bad-signature"; return "ok";} // One parity part lets any single lost part be rebuilt.function xorParity(parts: number[][]): number[] { const parity = new Array(parts[0].length).fill(0); for (const part of parts) { for (let i = 0; i < part.length; i++) parity[i] ^= part[i]; } return parity;} function recoverPart(parts: (number[] | null)[], parity: number[]): number[] { const rebuilt = [...parity]; for (const part of parts) { if (part === null) continue; for (let i = 0; i < part.length; i++) rebuilt[i] ^= part[i]; } return rebuilt;}模型假設與範圍
- 這是可重現的教學模型;延遲、容量、故障率與工作負載是設定或樣本,不能直接當作正式系統的效能承諾。
- 物件/中繼資料分離並展示分片、冗餘與簽章概念;範例協定不能代替正式授權、加密、durability 或儲存產品。
什麼時候用
- 存大量、不常修改的檔案:照片、影片、備份、日誌、機器學習的資料集。容量幾乎無限,按用量付費。
- 讓使用者直接上傳或下載大檔案,又不想讓應用伺服器經手:用預簽網址,伺服器只負責簽名。
- 搭配 CDN 當原站:物件儲存負責耐久,CDN 負責離使用者近。
和其他主題的關係
- 延伸閱讀
- CDN
出現在這些架構裡
時間與空間複雜度(Big O)
| 操作 | 平均 | 最差 |
|---|---|---|
| 用鍵 PUT/GET 一個物件 找到物件是查一次中繼資料;時間主要花在傳資料 | O(1) + O(size) | O(1) + O(size) |
| 分段上傳 每段一個請求,最多重試 k 次 | O(size / part) | O(size / part · k) |
| 列出某個前綴底下的物件 鍵排好序,回傳 m 筆;物件儲存沒有真正的資料夾 | O(log n + m) | O(log n + m) |
| 驗證預簽網址 重算一次簽名,不用查資料庫 | O(1) | O(1) |
空間:O(r · size),r 是放大倍數:三副本是 3,RS(6, 3) 是 1.5
Big O 實測:n 變大時步數怎麼長
數的是:分段上傳的成本(n 是檔案 MB 數;64 MB 一段,每 GB 39% 機率斷線)
| Big O | n = 1,000 | n = 10,000 | n = 100,000 | 成長倍數:實測(理論) | |
|---|---|---|---|---|---|
| 送出的 MB | O(n) | 1,000 | 10,211 | 101,407 | ×101 (×100) |
| 上傳請求數 | O(n) | 16 | 163 | 1,607 | ×100 (×100) |
分段之後,送出的量和請求數都只跟檔案大小成正比,重傳只多出一點點。一次 PUT 就不是這樣:成功的機率隨檔案大小指數下降,大到某個程度就永遠傳不完,所以不放進這張表。
和其他做法比
| 分段:送出/請求數 | 單次 PUT:送出/嘗試 | |
|---|---|---|
| 100 MB | 100 MB / 2 | 100 MB / 1 |
| 1.0 GB | 1.0 GB / 16 | 2.0 GB / 2 |
| 5.0 GB | 5.0 GB / 80 | 31 GB / 20(放棄) |
| 10 GB | 10 GB / 163 | 31 GB / 20(放棄) |
每傳 1 GB 有 39% 的機率斷線,分段每段 64 MB,每段或每次 PUT 最多試 20 次。小檔案兩種差不多;檔案一大,整個重來的成本就爆炸,到某個大小以上一次 PUT 幾乎不可能成功。
| 佔用空間 | 可以壞幾顆 | 物件遺失機率 | |
|---|---|---|---|
| 只存 1 份 | 1.00× | 0 | 1 / 200 |
| 3 份副本 | 3.00× | 2 | 1 / 8,000,000 |
| XOR 同位(4+1) | 1.25× | 1 | 1 / 4,040 |
| 糾刪碼 RS(6, 3) | 1.50× | 3 | 1 / 12,955,370 |
| 糾刪碼 RS(10, 4) | 1.40× | 4 | 1 / 165,957,979 |
每顆碟在修好之前有 0.5% 的機率也壞掉,片段放在不同的碟上。糾刪碼用比三副本少一半的空間、容忍更多顆碟壞掉;代價是讀寫時要計算,修復時要從好幾顆碟讀資料。
真實世界裡的它
- Amazon S3、Google Cloud Storage、Azure Blob Storage、Cloudflare R2;自架有 MinIO、Ceph。
- S3 標榜 11 個 9 的耐久性(99.999999999%),靠的就是跨機房的副本和糾刪碼。
- 資料湖(data lake)把 Parquet 檔放在物件儲存裡,再用 Spark、Trino 直接查。
取捨與陷阱
- 不能只改檔案的一部分:改一個位元組也要重寫整個物件。需要隨機寫入或查詢的資料應該放資料庫,物件儲存只放鍵和大檔。
- 列出大量物件很慢、也要錢:用前綴設計好鍵,不要把「資料夾」當成資料庫的目錄在用。
- 預簽網址要設短的有效期,而且簽名要涵蓋路徑和方法;不然網址外流,誰都能拿來讀寫。