4、快照机制详解:全量快照 vs 增量快照、快照创建与恢复流程

快照这东西,说白了就是给数据拍个「合影」。你想想看,向量数据每天都在变——插入、删除、更新,索引结构也在不断调整。如果没有快照,一旦系统崩溃,你连回到哪个时间点都不知道。

我个人习惯把快照比作游戏存档。玩 RPG 时,你会在打 BOSS 前存个档,对吧?快照就是那个存档。不同的是,向量数据库的存档分两种:全量快照和增量快照。今天我们就把它彻底讲透。

4.1 全量快照 vs 增量快照

先看一张对比图,帮你快速建立整体认知:

全量快照 vs 增量快照 对比 全量快照 • 复制所有向量数据 + 索引结构 • 占用空间大,创建时间长 • 恢复速度快,独立性强 • 适合:基线版本、定期归档 增量快照 • 只记录变更数据(WAL / 日志) • 占用空间小,创建速度快 • 恢复需依赖全量基线 • 适合:高频备份、实时保护 💡 核心差异: 全量快照是「完整副本」,增量快照是「变更记录」。两者通常配合使用,形成快照链。 典型快照链结构 全量 V1 增量 1 增量 2 增量 3 全量 V2

4.2 全量快照:什么时候用?

全量快照,就是把当前时刻的所有向量数据、索引结构、元数据,完整地复制一份。我在项目中遇到过这样的情况:某次线上事故,索引文件损坏,幸好前一天做了全量快照,直接恢复,5 分钟解决问题。

全量快照的典型场景:

  • 基线版本:每周或每月生成一次,作为恢复的锚点
  • 版本发布前:升级索引算法或调整参数前,拍一张全量
  • 数据迁移时:从一个集群搬到另一个集群,全量快照是最可靠的

全量快照的优缺点

维度说明
优点恢复快、独立性强、不依赖其他快照
缺点占用存储大(通常是数据量的 1.2~1.5 倍)、创建时间长
典型耗时100 万条 128 维向量,全量快照约 30~60 秒

4.3 增量快照:轻量级守护

增量快照只记录「从上一个快照以来,发生了什么变化」。说白了,就是记日志——哪些向量被插入了,哪些被删除了,哪些索引节点分裂了。

我记得有一次,系统每 5 分钟做一次增量快照,存储开销只有全量快照的 5%~10%。但要注意,增量快照不能单独恢复,你必须先恢复最近的全量基线,然后按顺序回放增量。

我的建议:全量快照 + 增量快照配合使用。比如每天凌晨做一次全量,白天每 15 分钟做一次增量。这样既控制了存储成本,又能做到分钟级恢复。

4.4 快照创建流程

不管全量还是增量,创建流程都遵循一个基本框架。我把它拆成 5 步:

  1. 冻结写入:暂停当前分片的写入操作,确保数据一致性
  2. 记录元数据:保存当前时间戳、版本号、索引状态
  3. 复制数据:全量则复制所有数据文件;增量则提取 WAL 中的变更记录
  4. 校验完整性:计算 checksum,确保数据没损坏
  5. 更新快照索引:将新快照注册到元数据服务中

下面是一个简化的快照创建代码示例(伪代码):

def create_snapshot(snapshot_type, segment_id):
    # 1. 冻结写入
    segment = get_segment(segment_id)
    segment.freeze_writes()
    
    # 2. 记录元数据
    meta = {
        "timestamp": now(),
        "version": segment.version,
        "type": snapshot_type,
        "base_snapshot": segment.last_full_snapshot_id
    }
    
    # 3. 复制数据
    if snapshot_type == "full":
        snapshot_path = copy_all_files(segment.data_dir)
    else:
        snapshot_path = extract_wal_changes(segment.wal, since=segment.last_snapshot_time)
    
    # 4. 校验完整性
    checksum = compute_checksum(snapshot_path)
    meta["checksum"] = checksum
    
    # 5. 注册快照
    snapshot_id = register_snapshot(meta, snapshot_path)
    segment.last_snapshot_time = meta["timestamp"]
    
    return snapshot_id

⚠️ 注意:冻结写入这一步很关键。如果冻结时间过长,会影响在线查询。我建议冻结时间控制在 100ms 以内,超过这个阈值就要考虑使用「写时复制(Copy-on-Write)」技术。

4.5 快照恢复流程

恢复流程是创建流程的逆操作,但多了几个关键判断。我曾经因为搞错增量快照的顺序,恢复出一个数据不一致的版本……嗯,从那以后我再也不敢跳过校验步骤了。

恢复流程如下:

  1. 定位基线:找到目标时间点之前最近的全量快照
  2. 加载全量:将全量快照的数据恢复到工作目录
  3. 回放增量:按时间顺序,依次应用增量快照中的变更
  4. 重建索引:对恢复后的数据重新构建向量索引(HNSW/IVF 等)
  5. 校验数据:对比 checksum,确认恢复结果正确
  6. 切换上线:将恢复后的分片切换为活跃状态

恢复时间估算(经验值)

数据规模全量恢复增量回放(每 1000 条变更)
10 万向量~5 秒~0.5 秒
100 万向量~30 秒~3 秒
1000 万向量~5 分钟~30 秒

4.6 避坑指南

做快照这么多年,踩过的坑不少。分享几个典型的:

  • 增量快照链过长:如果连续做了 100 个增量快照,恢复时就要回放 100 次,速度会越来越慢。我建议每 20~30 个增量后,强制做一次全量快照,打断链条。
  • 快照文件损坏:磁盘故障或写入中断可能导致快照不完整。每次创建完快照,一定要做 checksum 校验,并且把校验值存到另一个节点上。
  • 并发写入冲突:创建快照时如果还有写入操作,可能导致数据不一致。要么用「冻结写入」,要么用「MVCC(多版本并发控制)」——我个人更推荐后者,对在线业务影响更小。

一个小技巧:快照创建完成后,不要立即删除旧快照。保留最近 3 个全量快照 + 对应的增量链,这样即使某个快照损坏,你还有回退余地。

4.7 快照策略配置示例

最后,给一个实际项目中常用的快照策略配置,供你参考:

# snapshot_policy.yaml
full_snapshot:
  interval: "24h"          # 每天一次全量
  retention: 7             # 保留最近 7 个全量快照
  compress: true           # 启用压缩

incremental_snapshot:
  interval: "15m"          # 每 15 分钟一次增量
  retention: 96            # 保留最近 96 个增量(对应 24 小时)
  max_chain_length: 30     # 增量链最长 30 个,之后强制全量

recovery:
  preferred_base: "latest" # 优先使用最新的全量基线
  verify_checksum: true    # 恢复时强制校验
  rebuild_index: true      # 恢复后重建索引

这个配置我在生产环境用了大半年,存储开销控制在数据量的 1.5 倍以内,恢复时间基本在 5 分钟内。你可以根据自己的业务场景调整间隔和保留数量。