Skip to main content
损坏或异常的 ClickHouse Keeper 快照可能导致严重的系统不稳定,例如元数据不一致、表进入只读状态、资源耗尽或备份失败。本文涵盖:

Keeper 快照概述

什么是快照?

快照是 Keeper 在某一特定时间点内部数据的序列化状态 (例如有关集群、表协调路径和配置的元数据) 。快照对于重新同步集群中的 Keeper 节点、在发生故障时恢复元数据,以及依赖已知正常 Keeper 状态的启动或重启过程都至关重要。

在哪里可以找到快照?

快照以文件形式存储在 Keeper 节点的本地文件系统中。默认情况下,它们位于 /var/lib/clickhouse/coordination/snapshots/;如果你在 keeper_server.xml 文件中通过 snapshot_storage_path 指定了自定义路径,则会存储在那里。快照名称按递增编号命名 (例如 snapshot.23) ,编号越大表示快照越新。 对于多节点集群,每个 Keeper 节点都有各自的快照目录。
各节点上的快照保持一致性对于恢复至关重要。

Keeper 损坏快照的关键症状和表现

下表列出了一些 Keeper 快照损坏的常见症状和表现: 日志指示: 在诊断快照损坏之前,请检查 Keeper 日志 中是否存在特定错误模式:

从损坏的 Keeper 快照中恢复

在操作任何文件之前,务必始终:
  1. 停止所有 Keeper 节点,防止进一步损坏
  2. 将整个协调目录复制到安全位置,备份所有内容
  3. 验证集群的 quorum,确保至少有一个节点保存了完好的数据

  1. 从现有备份中恢复

如果出现以下情况,应按此流程操作:
  • Keeper 元数据或快照损坏,导致当前数据无法恢复。
  • 存在一个 Keeper 状态已知正常的备份。
按照以下步骤从现有备份中恢复:
  1. 找到最新的备份,并验证其元数据一致性。
  2. 关闭 ClickHouse 和 Keeper 服务。
  3. 用备份目录中的快照和日志替换损坏的快照和日志。
  4. 重启 Keeper 集群,并验证元数据同步是否正常。
定期备份如果备份已过期,你可能会丢失最近的元数据变更。因此,我们建议定期备份。

  1. 回滚到较早的快照

在以下情况下,应按此流程操作:
  • 最近的快照已损坏,但较早的快照仍可使用。
  • 增量日志完好,可用于一致性恢复。
按以下步骤回滚到较早的快照:
  1. 从 Keeper 目录中找到并选择一个有效的较早快照 (例如 snapshot.19) 。
  2. 删除较新的快照和日志。
  3. 重启 Keeper,使其重放日志以重建元数据状态。
元数据不同步风险如果快照和日志缺失或不完整,可能会导致元数据不同步。

  1. 使用 SYSTEM RESTORE REPLICA 恢复元数据

在以下情况下,应遵循此流程:
  • Keeper 元数据丢失或损坏,但表数据仍存在于磁盘上
  • 由于缺少 ZooKeeper/Keeper 元数据,表已切换为只读模式
  • 你需要根据本地可用的数据分区片段在 Keeper 中重建元数据
按照以下步骤恢复元数据:
  1. 验证表数据是否存在于本地的 clickHouse-server 数据路径中,该路径由 config 中的 <path> 配置项指定。 (默认为 /var/lib/clickhouse/data/)
  2. 对于每个受影响的表,执行:
  1. 进行数据库级恢复时 (如果使用 Replicated 数据库引擎) :
  1. 等待同步完成:
  1. 通过检查 system.replicas 中的 is_readonly = 0 并监控 system.detached_parts,验证恢复是否成功
工作原理SYSTEM RESTORE REPLICA 会先分离所有现有 parts,在 Keeper 中重建元数据 (就像这是一个新的空表一样) ,然后再重新附加所有 parts。这样可以避免通过网络重新下载数据。
前置条件只有在本地数据分区片段完好的情况下,此方法才有效。如果数据也已损坏,请改用策略 #5 (重建集群) 。

  1. 在 Keeper 中删除并重建副本元数据

在以下情况下,建议按此流程处理:
  • 错误仅发生在集群中的某一个副本上,且该副本在 Keeper 中的元数据已损坏或不一致
  • 你遇到类似 “Part XXXXX intersects previous part YYYYY” 的错误
  • 你需要在保留本地数据的同时,彻底重置某个副本在 Keeper 中的元数据
按照以下步骤删除并重建元数据:
  1. 在受影响的副本上,对该表执行 detach:
  1. 从 Keeper 中删除该副本的元数据 (可在任意副本上执行) :
要找到正确的 ZooKeeper 路径:
  1. 重新附加该表 (将以只读模式附加) :
  1. 恢复副本元数据:
  1. 与其他副本同步:
  1. 恢复后检查所有副本上的 system.detached_parts
在所有受影响的副本上执行如果损坏影响了多个副本,请在每个副本上依次重复这些步骤。
适用于整个数据库如果使用的是 Replicated 数据库,也可以改用 SYSTEM DROP REPLICA ... FROM DATABASE db_name。
替代方案:使用 force_restore_data 标志 如需在服务器启动时自动恢复所有复制表:
  1. 停止 ClickHouse server
  2. 创建恢复标志:
  1. 启动 ClickHouse server
  2. 服务器会自动删除该标志,并恢复所有复制表
  3. 监控日志,查看恢复进度
当多个表需要同时恢复时,这种方法很有用。

  1. 重建 Keeper 集群

在以下情况下,应按此流程操作:
  • 没有可用于恢复的有效快照、日志或备份。
  • 需要重建整个 Keeper 集群及其元数据。
按照以下步骤重建 Keeper 集群:
  1. 完全停止 ClickHouse 和 Keeper 集群。
  2. 清理快照和日志目录,以重置每个 Keeper 节点。
  3. 将一个 Keeper 节点初始化为 leader,然后逐步添加其他节点。
  4. 如果外部记录中有可用信息,请重新导入元数据。
耗时较长的流程此流程非常耗时,并且存在长时间中断的风险。需要重建全部数据。

  1. 在启动时从快照中移除孤立节点

孤立节点是指父节点在快照中缺失的节点。Keeper 会拒绝加载此类 快照,并在日志中记录 Failure to load from latest snapshot with index。 在以下情况下,应采用此方法:
  • 快照恰好出现了这种结构性损坏,并且
  • 没有可用的有效备份或较旧的快照 (只要策略 1 和策略 2 适用,应优先采用) 。
在 <keeper_server> 下同时设置以下两项,然后重启该节点:
需要使用内存节点存储只有内存节点存储实现了该清理功能。如果在 <coordination_settings> 下启用了 use_lsmt_storage, 则设置 remove_orphaned_nodes_on_startup 后 Keeper 会拒绝启动:请在执行恢复性重启时禁用 use_lsmt_storage,待成功启动后再重新启用。
随后,Keeper 会丢弃孤立子树并完成启动。删除情况会以 WARNING 级别在日志中输出为两行: 一行给出已删除节点的总数以及被删除孤立子树的根节点,另一行给出快照中缺失的、划定损坏区域边界的路径。 这些根节点之下的后代节点同样会被删除,但不会逐一列出。 在检查本地日志尾部之后、启动 Raft 之前,Keeper 会将修复后的树写入同一日志索引处的新快照文件。 已完成的修复优先于该索引处的较旧副本,因此中断的恢复过程可以继续进行,而不会选用已损坏的快照。 新的跟随者会接收修复后的快照,后续重启时也 无需再启用孤立节点删除。原始快照以及所有同索引副本都会以 orphaned_ 文件名前缀 保留;这些文件不会参与快照加载,也不受自动保留策略管理。请保留它们以便诊断,不再需要时再手动删除。 恢复性重启需要额外的磁盘空间,用于存放修复后的快照及原始快照的备份。 如果写入修复后的快照、同步本地目录变更或保留原始文件 失败,Keeper 会拒绝启动。后续启动时会完成所有被中断的归档操作。
仅修复本地状态此操作只会从当前节点的数据副本中删除节点。仍持有这些节点的对等节点将与其出现不一致, 而当 digest_enabled 设置为 false 时,此后将没有任何机制能检测到这种不一致。请仅将此操作用于单节点恢复, 或将其应用于集群中的所有节点。节点启动后,请将这两个设置恢复为默认值。
如果快照之后的某条本地日志条目引用了损坏区域 (即被删除的节点、其下的任何节点,或被删除子树的直接父节点) ,Keeper 会有意拒绝启动。 直接父节点之所以算作冲突,是因为其子节点集合已与写入该日志时所依据的树不再一致, 因此即便某条条目只涉及该父节点 (包括在其下创建或删除兄弟节点,这类操作会更新其已修复的子节点计数器) ,重放结果也可能不同。 仅检查父节点是否存在、版本或修改 ID 的条目不受影响,也不会阻碍恢复,例如 Check、CheckNotExists、既不比较 numChildren 也不比较 cversion 的 CheckStat、 仅重写父节点自身数据或 ACL 的 Set 或 SetACL, 以及客户端重连后重新注册的 data、child 和 exist watch;客户端重新注册的持久 watch 从不访问树,因此也绝不会阻碍恢复。 链路中更上层的节点不受影响:它们的数据、版本和子节点与健康对等节点上的完全一致, 因此仅涉及此类节点的条目重放结果相同,不会阻碍恢复。例外情况是会遍历其路径下整个子树的条目, 例如递归删除或递归列举:这类操作确实会读取到被删除的节点,因此无论位于哪一层级都会产生冲突, 除非 Keeper 在访问树之前就拒绝了该请求 (例如对 / 的递归删除) 。 日志尾部的会话关闭 (Close) 也是如此:如果该会话拥有某个已被删除的临时节点,或拥有直接位于此类父节点下且仍保留的临时节点, 关闭会话时就会删除其临时节点并更新其父节点的计数器。基于修复后的树重放任何此类条目, 都会在无声无息中产生不同的状态。遇到这种情况时,请改为从健康的对等节点恢复该节点: 停止该节点,清空其协调目录,让其从 leader 重新同步;或者采用策略 1 或策略 2。