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 合规要求“删除用户数据”,怎么做才算删干净?

▸ 答案:要覆盖所有副本:主库、从库、备份、数仓、搜索索引、缓存、日志、对象存储。做法是:① 用血缘/数据地图列出该用户数据的所有落点;② 主库删除 + 各派生层按主键删除/重建;③ 备份按保留期自然过期(或加密擦除);④ 记录删除凭证(时间、范围、执行人)。只删主表是最常见的合规漏洞。

关联