46.6 数据生命周期
一句话
数据不是“存下来就完了”:访问频率决定它该待在哪一层,合规要求决定它能待多久——分层存储 + 到期删除,是把存储成本从“线性增长”压成“缓慢增长”的唯一办法。
四个阶段
| 阶段 | 访问频率 | 存储 | 成本(量级) | 典型 |
|---|---|---|---|---|
| 热 | 每天多次 | SSD/数据库/搜索 | 高 | 最近 7 天订单、在线商品 |
| 温 | 每周几次 | 普通磁盘/对象存储标准层 | 中 | 最近 90 天订单、日志 |
| 冷 | 每月几次 | 低频/归档存储 | 低 | 历史订单、审计数据 |
| 归档/删除 | 几乎不访问 | 磁带/深度归档,或删除 | 极低 | 超过留存期的数据 |
🔑 分层的依据不是“数据多大”,而是**“访问频率 × 单次访问的容忍延迟”**:一天查一次但要求毫秒响应的,不能放归档层(取回要几分钟到几小时)。
代码:分层前后的账
package main
import "fmt"
const totalTB = 100.0 // 总数据量
type tier struct {
name string
share float64 // 数据占比
priceGB float64 // 每 GB·月
retrieve float64 // 每 GB 取回成本(仅冷/归档)
hitRate float64 // 每月有访问的比例
}
var tiers = []tier{
{"热(SSD/库)", 0.05, 0.10, 0, 1.0},
{"温(标准对象)", 0.15, 0.023, 0, 0.5},
{"冷(低频)", 0.40, 0.010, 0.01, 0.1},
{"归档", 0.40, 0.004, 0.03, 0.01},
}
func main() {
gbPerTB := 1024.0
fmt.Printf("总量 %.0f TB,按访问频率分层\n\n", totalTB)
fmt.Printf("%-16s %-10s %-14s %-14s %s\n", "层", "占比", "存储成本/月", "取回成本/月", "合计")
allHot := totalTB * gbPerTB * 0.10
stored, retrieved := 0.0, 0.0
for _, t := range tiers {
gb := totalTB * t.share * gbPerTB
store := gb * t.priceGB
get := gb * t.hitRate * t.retrieve
stored += store
retrieved += get
fmt.Printf("%-16s %-10s %-14.0f %-14.0f %.0f\n", t.name,
fmt.Sprintf("%.0f%%", t.share*100), store, get, store+get)
}
tiered := stored + retrieved
fmt.Printf("\n全热存储:%.0f/月\n分层存储:%.0f/月(省 %.0f%%)\n",
allHot, tiered, (1-tiered/allHot)*100)
fmt.Printf("注:取回成本只按“有访问的那部分数据”计,实际还要加请求费与流量费\n")
// 留存期:删除能省多少
fmt.Println("\n=== 留存期的影响(假设每年新增 40TB,热层价 0.10/GB·月)===")
for _, years := range []float64{1, 3, 5} {
gb := 40 * years * gbPerTB
fmt.Printf("保留 %v 年:热层 %.0f GB,月成本 %.0f\n", years, gb, gb*0.10)
}
}
本机实测(macOS,Apple M1,Go 1.27):
总量 100 TB,按访问频率分层
层 占比 存储成本/月 取回成本/月 合计
热(SSD/库) 5% 512 0 512
温(标准对象) 15% 353 0 353
冷(低频) 40% 410 41 451
归档 40% 164 12 176
全热存储:10240/月
分层存储:1492/月(省 85%)
注:取回成本只按“有访问的那部分数据”计,实际还要加请求费与流量费
=== 留存期的影响(假设每年新增 40TB,热层价 0.10/GB·月)===
保留 1 年:热层 40960 GB,月成本 4096
保留 3 年:热层 122880 GB,月成本 12288
保留 5 年:热层 204800 GB,月成本 20480
读数:
| 方案 | 月成本(相对单位) | 说明 |
|---|---|---|
| 全热 | 10,240 | 100 TB 全放最贵的一层 |
| 分层 | 1,492 | 冷/归档占 80% 数据,但只贡献 588(约 39%)成本 |
| 省下的比例 | 85% | 与访问分布强相关:本例冷/归档占 80% 且取回少,才省这么多 |
🔑 分层能省多少,取决于冷数据占比与取回频率:冷数据越多、越少取回,省得越多;如果冷数据每月都被取回,取回成本会把省下的存储费吃掉。
留存期:合规与成本的双约束
| 约束 | 来源 | 做法 |
|---|---|---|
| 最短保留 | 合规/审计(如交易记录 5 年) | 到期前不能删,且要可查 |
| 最长保留 | 个人信息保护(最小必要) | 到期必须删,且要能证明删了 |
| 删除方式 | 逐条 vs 分区 | 分区表按天/月 drop,比 delete 快几个数量级(44.17 同理) |
⚠️ 最常见的坑:“删除”只删了主表,备份/副本/数仓/日志里还有。合规要求的是“全链路删除”,要有一张“数据分布地图”(46.5 的血缘正好用得上)。
反模式
| 反模式 | 后果 |
|---|---|
| 所有数据放热层 | 存储成本随数据量线性涨,第二年预算翻倍 |
| 冷数据放归档但要求毫秒查询 | 取回要几分钟,SLA 直接违约 |
| 只删主表不删备份/数仓 | 合规审计不通过 |
| 逐行 delete 清理历史数据 | 锁表、产生大量 undo,比写入还慢 |
| 没有留存策略 | 数据无限增长,成本与风险都失控 |
| 分层后不校验访问模式 | 冷热判断错了,取回成本超过省下的存储费 |
自检
Q1 为什么“逐行 DELETE”清理历史数据很慢,而“按分区 DROP”很快?
▸ 答案:DELETE 是逐行标记删除:要写 undo/redo、维护索引、可能锁表,还要等 purge 线程真正回收空间;数据量一大就是“删比写还慢”。按分区 DROP 是元数据操作:直接把整个分区文件摘掉,O(1) 次文件操作,不产生行级日志。所以时序/日志类数据都按时间分区,过期即 drop。
Q2 分层存储省钱的边界在哪?
▸ 答案:边界是“取回成本 < 省下的存储费”。冷/归档层的取回有费用(按 GB 计)与延迟(分钟到小时)。如果某份“冷数据”其实每月都被大量取回,取回费会超过把它放热层;如果业务要求毫秒级查询,归档层根本不能用。所以分层前要先统计“访问频率分布”,而不是按数据年龄一刀切。
Q3 合规要求“删除用户数据”,怎么做才算删干净?
▸ 答案:要覆盖所有副本:主库、从库、备份、数仓、搜索索引、缓存、日志、对象存储。做法是:① 用血缘/数据地图列出该用户数据的所有落点;② 主库删除 + 各派生层按主键删除/重建;③ 备份按保留期自然过期(或加密擦除);④ 记录删除凭证(时间、范围、执行人)。只删主表是最常见的合规漏洞。