39.3 存储分级
一句话
存储分级的本质是按访问频率给数据定价:冷层单价能低 10–20 倍,但取回费、最短存储期、请求费会把“便宜”吃回去——分层的收益首先来自删数据和压缩,其次才是换层,而且换层必须拿 RTO 和代码复杂度去换。
四层:介质、延迟、单价
| 层 | 介质 | 访问延迟 | 单价量级(相对热层) | 取回 | 最短存储期 |
|---|---|---|---|---|---|
| 热 | SSD / NVMe、标准对象存储 | 毫秒 | 1× | 免费 | 无 |
| 温 | 大容量 HDD / 低频对象层 | 毫秒~几十毫秒 | 约 0.5× | 低价 | 30 天 |
| 冷 | 归档型对象存储 | 秒级(即时取回) | 约 0.15× | 中价 | 90 天 |
| 归档/深度归档 | 磁带库 / 深度归档层 | 分钟~小时(需解冻) | 约 0.04× | 高价 | 180 天 |
(单价是“示例量级”,用于演示成本结构;不同厂商、不同区域差异很大,选型时必须按当期报价重算。)
成本不止“每 GB 每月”
| 成本项 | 触发条件 | 谁最容易忽略 |
|---|---|---|
| 容量费 | 按 GB·月 | 所有人都看这个 |
| 请求费 | 每次 PUT/GET/LIST | 小文件、频繁列举 |
| 取回费 | 从温/冷/归档层读数据 | 出故障时才想起 |
| 最短存储期 | 提前删除要补差价 | 数据生命周期短 |
| 跨区/出网流量 | 跨地域复制、下载 | 灾备与多活 |
| 解冻时间 | 归档层取回要等 | 直接打穿 RTO |
算一笔账:100 TB 分层后省 64%
设定:100 TB 数据,热 10%、温 20%、冷 40%、归档 30%;每月热层 5000 万次 GET、温层 500 万次、冷层 20 万次、归档 1000 次;每月从冷层取回 2 TB、从归档取回 0.5 TB。
| 层 | 容量 | 存储费 | 请求费 | 取回费 | 小计 |
|---|---|---|---|---|---|
| 热 | 10,240 GB | $235.52 | $20.00 | 0 | $255.52 |
| 温 | 20,480 GB | $256.00 | $5.00 | $20.48 | $281.48 |
| 冷 | 40,960 GB | $163.84 | $2.00 | $61.44 | $227.28 |
| 归档 | 30,720 GB | $30.72 | $0.05 | $61.44 | $92.21 |
全部放热层:$2377.28/月
分层后: $856.49/月 → 省 64%
为什么不是省 70%+?因为存储费省了 71%($2355 → $686),但请求费与取回费又加回 $170。分层的净收益永远小于“单价差 × 冷数据占比”。
三个必须记住的数字
- 归档层取回 1 TB 要 $122.88,相当于存 120 个月(10 年)。冷层取回 1 TB 要 $30.72,相当于存 7.5 个月。取回一次 ≈ 存很久——这就是“冷数据不要反复读”的量化解释。
- 全部放归档 + 每月回读 30 TB:$3788.80/月,比全部放热层还贵 1.6 倍。冷层不是“更便宜的存储”,是“为极少访问的数据设计的存储”;访问模式一变,成本结构立刻反过来。
- 10 TB 归档只存 30 天就删,罚金 $51.20(补缴剩余 150 天)。最短存储期让“临时放一下”变得昂贵。
🔑 分层之前先问一句:这份数据真的还会被读吗? 如果答案是“不会”,正确动作是删除,不是换到更冷的层。删除的成本是 0,冷层还要付存储费、请求费、以及未来某次误读的取回费。
数据温度怎么判
| 信号 | 怎么用 | 坑 |
|---|---|---|
| 访问频率(次/月) | 热 > 100、温 1–100、冷 < 1 | 周期性访问(每月对账)会被误判为冷 |
| 最近访问时间 | 90 天未访问转冷,180 天转归档 | 长尾访问突然回来,取回费暴涨 |
| 可重建性 | 派生数据(索引、报表、缩略图)可直接删/重算 | 重算成本可能高于存储 |
| 合规要求 | 审计日志、交易凭证必须留存 | 留存 ≠ 要能秒级读,可放冷层 |
| 大小与对象数 | 小文件请求费高 | 见下 |
小文件是隐形杀手:4 KB 一个对象、1 GB 就是 26 万个对象。在热层每月完整读一遍,请求费约 26 万 ÷ 1000 × $0.0004 ≈ $0.105/GB,而存储费只有 $0.023/GB·月——请求费是存储费的 4.5 倍。小文件多的系统,先合并/打包,再谈分层。
生命周期规则:什么时候转、什么时候删
第 0 天 写入热层
第 30 天 无访问 → 转温层(单价降一半)
第 90 天 无访问 → 转冷层(取回要计费)
第 180 天 无访问 → 转归档(取回要解冻,小时级)
第 365 天 按合规要求删除或永久归档
三条工程约束:
- 规则要可回滚:转层是有代价的(取回费 + 解冻时间),上线前用小比例前缀验证,别一次性把整桶转过去。
- 版本控制与删除标记:开了版本控制的桶,删除只是加一个标记,旧版本仍占容量——“删了但没省”是账单异常的常见原因。
- 对象数也要治理:LIST 操作按请求计费,海量小文件的列举/清点本身就能产生可观费用。
分层与灾备:别让冷层拖垮 RTO
| 问题 | 后果 |
|---|---|
| 备份放归档层,恢复时先解冻 | RTO 从小时级变成“小时 + 解冻时间” |
| 灾备演练没算解冻 | 真实恢复时间超预估 3–5 倍 |
| 冷层数据没有校验 | 恢复时才发现损坏,且取回费已花 |
原则:恢复路径上的数据必须在“能按时取回”的层。备份的最近 7 天放温层/冷层(秒级可取),更早的放归档;演练时按真实取回路径计时,而不是只测“本地拷贝”。
可运行的成本模型
package main
import "fmt"
// 单价是「示例量级」,用于演示成本结构,不是任何厂商的报价。
type tier struct {
name string
gbPrice float64 // 美元 / GB / 月
getPer1k float64 // 美元 / 千次 GET
retrieval float64 // 美元 / GB 取回(含解冻)
minDays int // 最短存储期,提前删除要补差价
}
type usage struct {
tier
gb float64
gets float64
retrieveG float64
}
func main() {
tiers := map[string]tier{
"hot": {"热", 0.0230, 0.0004, 0.000, 0},
"warm": {"温", 0.0125, 0.0010, 0.010, 30},
"cold": {"冷", 0.0040, 0.0100, 0.030, 90},
"archive": {"归档", 0.0010, 0.0500, 0.120, 180},
}
totalGB := 102400.0 // 100 TB
plan := []usage{
{tiers["hot"], totalGB * 0.10, 50e6, 0},
{tiers["warm"], totalGB * 0.20, 5e6, 2048},
{tiers["cold"], totalGB * 0.40, 200e3, 2048},
{tiers["archive"], totalGB * 0.30, 1e3, 512},
}
fmt.Printf("%-6s %9s %10s %10s %10s %10s\n", "层", "容量GB", "存储费", "请求费", "取回费", "小计")
var tiered float64
for _, u := range plan {
storage := u.gb * u.gbPrice
req := u.gets / 1000 * u.getPer1k
ret := u.retrieveG * u.retrieval
sum := storage + req + ret
tiered += sum
fmt.Printf("%-6s %9.0f %10.2f %10.2f %10.2f %10.2f\n", u.name, u.gb, storage, req, ret, sum)
}
allHot := totalGB*tiers["hot"].gbPrice + 55.2e6/1000*tiers["hot"].getPer1k
fmt.Printf("\n全部放热层:$%.2f/月;分层后:$%.2f/月;节省 %.0f%%\n",
allHot, tiered, (1-tiered/allHot)*100)
// 误把所有数据扔进归档,但每月要回读 30 TB
allArchiveStorage := totalGB * tiers["archive"].gbPrice
rehydrate := 30720.0 * tiers["archive"].retrieval
fmt.Printf("全部归档 + 每月回读 30TB:$%.2f(存储 %.2f + 取回 %.2f),比全热贵 %.1f 倍\n",
allArchiveStorage+rehydrate, allArchiveStorage, rehydrate,
(allArchiveStorage+rehydrate)/allHot)
// 取回一次 1TB 的钱,够在归档层存多久
for _, key := range []string{"cold", "archive"} {
t := tiers[key]
one := 1024 * t.retrieval
year := 1024 * t.gbPrice * 12
fmt.Printf("在%s层取回 1TB 要 $%.2f,相当于存 %.1f 个月($%.2f/年)\n",
t.name, one, one/(1024*t.gbPrice), year)
}
// 最短存储期罚金:10TB 归档存 30 天就删
early := 10240.0 * tiers["archive"].gbPrice * float64(tiers["archive"].minDays-30) / 30
fmt.Printf("10TB 归档只存 30 天就删:补缴剩余 150 天,罚金 $%.2f\n", early)
}
输出:
层 容量GB 存储费 请求费 取回费 小计
热 10240 235.52 20.00 0.00 255.52
温 20480 256.00 5.00 20.48 281.48
冷 40960 163.84 2.00 61.44 227.28
归档 30720 30.72 0.05 61.44 92.21
全部放热层:$2377.28/月;分层后:$856.49/月;节省 64%
全部归档 + 每月回读 30TB:$3788.80(存储 102.40 + 取回 3686.40),比全热贵 1.6 倍
在冷层取回 1TB 要 $30.72,相当于存 7.5 个月($49.15/年)
在归档层取回 1TB 要 $122.88,相当于存 120.0 个月($12.29/年)
10TB 归档只存 30 天就删:补缴剩余 150 天,罚金 $51.20
参数怎么给
| 数据 | 层 | 转层依据 |
|---|---|---|
| 用户正在访问的图片/视频 | 热 + CDN | 命中率、回源率(见 9.2 回源策略与回源率,待补) |
| 30 天未访问的业务数据 | 温 | 访问时间 |
| 日志、监控原始数据 | 冷 | 只用于排障与审计,秒级取回 |
| 备份的最近 7 天 | 温/冷 | 必须能在 RTO 内取回 |
| 备份的 7 天以前 | 归档 | 合规留存,接受小时级解冻 |
| 可重算的派生数据 | 删 | 重算成本 < 存储成本 |
| 交易凭证/审计日志 | 冷或归档(不可变) | 合规,需 WORM |
反模式
| 反模式 | 后果 |
|---|---|
| 只比较“每 GB 单价” | 忽略取回费与请求费,账单反而上涨 |
| 把频繁访问的数据放归档层 | 取回费 + 解冻延迟,比全热贵 |
| 分层前不删数据 | 冷层仍在为“没人读的数据”付费 |
| 生命周期规则一次性全量生效 | 大批对象同时转层/删除,请求费尖峰 |
| 忽略版本控制与删除标记 | 删了不省容量 |
| 小文件直接分层 | 请求费是存储费的数倍 |
| 备份放归档层但没算解冻时间 | RTO 被解冻时间打穿 |
| 没有按团队/前缀归因 | 谁都在存,没人对账单负责 |
| 冷层不做完整性校验 | 恢复时才发现数据损坏 |
自检
Q1 冷层单价只有热层的约 1/6,为什么“全部放归档”反而比全热贵 1.6 倍?
▸ 答案:因为成本不只容量。归档层存储 $102/月,但每月回读 30 TB 的取回费是 30,720 GB × $0.12 = $3,686,合计 $3,789,超过全热的 $2,377。归档层的设计前提是“几乎不读”,一旦访问模式变成“每月读 30%”,取回费就压倒容量费。判断准则:取回量 × 单价 与 容量 × 单价差 比较,取回费大就说明这份数据不該放冷层。
Q2 100 TB 里 70% 是冷数据,为什么总成本只省 64%,而不是 70% 以上?
▸ 答案:冷数据只是“存储单价低”,但冷层仍有请求费与取回费,而且热层的请求费本身很贵。这笔账里存储费从 $2,355 降到 $686(省 71%),但请求费 + 取回费合计 $170 又加了回来,总成本 $2,377 → $856(省 64%)。冷数据占比越高、访问越少,节省比例越接近单价差;只要还有回读,就会低于占比。
Q3 归档层取回 1 TB 要 $122.88,而存 1 TB 一年只要 $12.29。这个比例说明什么?对架构有什么约束?
▸ 答案:说明“取回一次等于存十年”——归档层的经济性建立在极低访问频率上。架构约束:① 不要把归档层放进在线请求路径;② 灾备恢复路径上的数据不能只存在于归档层(否则 RTO + 解冻时间);③ 任何“定时全量回读归档数据”的任务(对账、审计、重算)都要先估算取回费,必要时改为抽样或把热数据单独留存。
Q4 分层的隐性成本有哪些?为什么说“多一套代码路径”比“多付一点存储费”更贵?
▸ 答案:隐性成本包括:① 两套读写路径(热层同步读、冷层异步取回),错误处理完全不同;② 取回延迟(秒~小时)改变接口语义,前端要处理“数据准备中”;③ 解冻状态机与重试;④ 分层规则与业务生命周期耦合,改规则要回归测试;⑤ 监控与告警要区分层。这些是持续的工程成本,而存储费是线性的、可预测的。所以只有“冷数据占比高 + 访问模式稳定”时才值得分层,否则多付存储费更便宜。
Q5 为什么说“先压缩/去重/删除,再分层”?给出一个量化的判断方式。
▸ 答案:压缩是对所有层同时生效的乘法:压缩 3 倍,等于每一层的单价都降到 1/3,且不增加取回费与代码路径;删除是直接归零。分层只是把同一份数据换到更便宜的层,收益受单价差限制。量化判断:先算“压缩后仍需存储的 GB × 各层单价”,再算“不压缩但分层”的成本,前者通常更低;只有当数据已经压缩过、且访问频率确实低,分层的边际收益才显现。
跨篇相关:成本模型与单位经济见 39.1 成本模型与单位经济;容量规划见 39.2 容量规划(待补);块/文件/对象存储的语义差异见 10.1 块存储 / 文件存储 / 对象存储;对象存储的生命周期与一致性见 10.2 对象存储语义;备份数据的留存与分层见 36.2 备份策略。