Greenplum的差异化修复原理简介
简介
Greenplum PR15146[1] 增加了一种新的 mirror 节点修复方式 —— 差异化修复(Differential recovery),介于增量修复(Incremental recovery)和全量修复(Full recovery)之间,当数据量庞大且由于 WAL 缺失不能进行增量修复时,差异化修复提供了一种快速修复的能力。但通过测试,差异化修复的性能反倒不如全量修复,让我们来一探究竟吧 🧐
☆ GPDB 节点修复原理简介
GPDB 的 segment 节点修复以 python 脚本 gprecoverseg
为入口,通过读取 Coordinator 节点的元数据,来获取需要修复(seg.isSegmentDown())节点的三元组(RecoveryTriplet):
- • failed: 当前 down 的 segment,即要修复的节点
- • live: 当前 up 的节点,作为修复的 source
- • failover: 节点修复的 dest,如果该值为 None,则在 failed 节点原地修复
通过将如上三元组数组构造为一个 GpMirrorListToBuild
结构后调用该对象的 recover_mirrors
函数进行修复。
recover_mirrors
首先通过 build_recover_info
为每个需要修复的 segment 生成一个 RecoverInfo
对象,保存在一个 { hostname, RecoverInfo list } 的字典结构中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | def build_recovery_info(mirrors_to_build): """ This function is used to format recovery information to send to each segment host @param mirrors_to_build: list of mirrors that need recovery @return A dictionary with the following format: Key = <host name> Value = list of RecoveryInfos - one RecoveryInfo per segment on that host """ timestamp = datetime.datetime.today().strftime('%Y%m%d_%H%M%S') recovery_info_by_host = defaultdict(list) for to_recover in mirrors_to_build: source_segment = to_recover.getLiveSegment() target_segment = to_recover.getFailoverSegment() or to_recover.getFailedSegment() process_name = 'pg_basebackup' if to_recover.isFullSynchronization() else 'pg_rewind' progress_file = '{}/{}.{}.dbid{}.out'.format(gplog.get_logger_dir(), process_name, timestamp, target_segment.getSegmentDbId()) hostname = target_segment.getSegmentHostName() recovery_info_by_host[hostname].append(RecoveryInfo( target_segment.getSegmentDataDirectory(), target_segment.getSegmentPort(), target_segment.getSegmentDbId(), source_segment.getSegmentHostName(), source_segment.getSegmentPort(), to_recover.isFullSynchronization(), progress_file)) return recovery_info_by_host |
然后调用 _run_setup_recovery
和 _run_recovery
分别去要修复的节点执行 gpsegsetuprecovery.py
和 gpsegrecovery.py
。
gpsegsetuprecovery.py
在目标节点上根据宕机节点的修复类型(full/incremental)做不同的前置工作:
- • 若是全量修复,执行
ValidationForFullRecovery
检测目标端的目录是否满足需求(如果非 overwrite 需要保证目标目录为空) - • 若是增量修复,执行
SetupForIncrementalRecovery
去源端执行CHECKPOINT
命令(执行 CHECKPOINT 是为了保证如果 source 端刚被 promote,其 control file 能反映最新的 timeline information,详见 pg_rewind[2])并将目的端的 postmaster.pid 删除。
gpsegrecovery.py
在目标节点上根据修复类型执行 pg_basebackup
或 pg_rewind
,各命令使用的参数如下:
PgBaseBackup
参数 | 含义 |
---|---|
-c fast | Sets checkpoint mode to fast |
-D target_datadir | 目标目录 |
-h source_host | 源端目录 |
-p source_port | 源端 port |
--create-slot | 进行 backup 之间创建 --slot 指定的 replication slot |
--slot | 要创建的 replcation slot 名称 |
--wal-method stream | basebackup 忽略 WAL 文件的传输,而是由另外一个进程在 basebackup 的同时流式传输 WAL 文件 |
--no-verify-checksums | 忽略 checksums |
--write-recovery-conf | 生成 Recovery Config,PG 12 之前为 recovery.conf,之后为 postgresql.auto.conf |
--force-overwrite | gpdb 引入的一个选项,在做 pg_basebackup 之前不需要目的端目录为空,而是在执行的过程中将需要从源端拷贝的目录或文件删除 |
--target-gp-dbid | gpdb 引入的选项,为了适配 Greenplum 用户定义的 tablespaces |
-E ./db_dumps -E ./promote | 忽略的目录 |
--progress | 显示进度百分比 |
--verbose | 显示更多的日志信息 |
PgRewind
参数 | 含义 |
---|---|
--write-recovery-conf | 生成 Recovery Config,PG 12 之前为 recovery.conf,之后为 postgresql.auto.conf |
--slot=internal_wal_replication_slot | 写入 Recovery Config 的 replication slot |
--source-server=CONNSTR | 源端数据库 |
--target-pgdata=DIRECTORY | 目的端数据目录 |
--progress | 显示更多的日志信息 |
值得注意的是,无论增量修复还是全量修复后,静态的实例状态未必处于一致的状态,都需要启动实例进入 recovery mode 回放日志来达到一致的状态。主要原因有两个:
- 有些文件在拷贝过之后在源端有修改,这就使得数据目录不是一个完整的快照状态
- 读取的文件可能不是完整的数据页(由于 pg 的 PAGE_SIZE 和 OS 的 PAGE_SIZE 不同),需要源端开启 full_page_writes 选项保证正确性
关于 postgres 全量修复和增量修复更详细的解读可参考 pg_basebackup 代码浅析[3] 和 pg_rewind 代码浅析[4]。
☆ 差异化修复
gpdb 作为分析型数据库通常实例的数据量较大,在宕机之后如果能增量修复具有显著的性能优势,但有时由于 WAL 日志缺失会导致增量修复的失败,且如果 pg_rewind 中断了,通常不建议进行重试,只能使用更耗时的全量修复。在全量修复过程中如果网络抖动,极有可能在同步了 90% 数据的时候修复失败,只能重新执行全量修复,费力劳心。