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. 归档层取回 1 TB 要 $122.88,相当于存 120 个月(10 年)。冷层取回 1 TB 要 $30.72,相当于存 7.5 个月。取回一次 ≈ 存很久——这就是“冷数据不要反复读”的量化解释。
  2. 全部放归档 + 每月回读 30 TB:$3788.80/月,比全部放热层还贵 1.6 倍。冷层不是“更便宜的存储”,是“为极少访问的数据设计的存储”;访问模式一变,成本结构立刻反过来。
  3. 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 天  按合规要求删除或永久归档

三条工程约束:

  1. 规则要可回滚:转层是有代价的(取回费 + 解冻时间),上线前用小比例前缀验证,别一次性把整桶转过去。
  2. 版本控制与删除标记:开了版本控制的桶,删除只是加一个标记,旧版本仍占容量——“删了但没省”是账单异常的常见原因。
  3. 对象数也要治理: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 备份策略。