背景
在常见的PostgreSQL双节点高可用构架中,如果主库挂了且主备无延迟,高可用系统会提升老备库为新主库对外服务。而对于老主库,则可以有很多处理策略,例如:
删掉,重搭新备库。
降级为备库,继续服务。
很显然,相比来说第一种不是个很好的方案。当数据量比较大时,重搭备库的时间成本太高,系统的可用性降低。但是因为老的主库挂掉的原因多种多样,甚至有可能是高可用系统的误判,而老主库也有可能是在挂掉之后又重新作为主库启动起来,这个时候降级并重搭流复制关系的操作就有可能失败(新的备库比新主库数据更超前)。
为了解决这种情况,PostgreSQL 引入了pg_rewind工具。
功能介绍
初始化集群的时候打开 data checksums 或者 wal_log_hints=on,参数说明详见文档 full_page_writes=on,参数说明详见文档
具体实现
在目的集群中找到源集群和目的集群的分叉点之前的最近一次checkpoint 点。这样相当于找到了在两个副本数据产生不同前的最后一个一致性位点。目的集群在这个位点之后所有的表数据变化都记录在这个位点之后的WAL 日志中。 pg_rewind 会将目的集群这些变化的数据页从源集群复制过来。这里会有2种方式: 使用文件系统方式拷贝 使用 libpq 建立连接的方式拷贝 我们会在pg_rewind 的用法当中详细说明两者的区别。注意:这里拷贝的只是表数据文件,下文会具体将满足什么条件的才是表数据文件。 除了变化的数据块之外,拷贝其他所有的文件比如说pg_xact 文件和配置文件。 生成backup label 文件,指定恢复开始的WAL 位点为1步骤中获得的位点,目的集群启动时会从该点开始应用WAL 日志。 更新目的集群的pg_control文件。 使用initdb -S 把目的集群所有的修改刷到磁盘上去。
如何判断两个集群来源于同一份数据副本? 如何找到目的集群和源集群的分叉点? 如何找到目的集群的数据变化? 目的集群生成的label 文件的内容是什么?
唯一集群系统标识
gettimeofday(&tv, NULL);
sysidentifier = ((uint64) tv.tv_sec) << 32;
sysidentifier |= ((uint64) tv.tv_usec) << 12;
sysidentifier |= getpid() & 0xFFF;
复制
pg_control version number: 942
Catalog version number: 201409291
Database system identifier: 6537134048787931336
Database cluster state: in production
pg_control last modified: Sat 19 May 2018 06:36:03 PM CST
Latest checkpoint location: 0/3A321900
Prior checkpoint location: 0/3A3202D8
Latest checkpoint's REDO location: 0/3A3218C8
Latest checkpoint's REDO WAL file: 00000006000000000000003A
Latest checkpoint's TimeLineID: 6
Latest checkpoint's PrevTimeLineID: 6
Latest checkpoint's full_page_writes: on
Latest checkpoint's NextXID: 0/316602
Latest checkpoint's NextOID: 32769
Latest checkpoint's NextMultiXactId: 1
Latest checkpoint's NextMultiOffset: 0
Latest checkpoint's oldestXID: 1798
Latest checkpoint's oldestXID's DB: 1
Latest checkpoint's oldestActiveXID: 316602
Latest checkpoint's oldestMultiXid: 1
Latest checkpoint's oldestMulti's DB: 1
Time of latest checkpoint: Sat 19 May 2018 06:36:03 PM CST
Fake LSN counter for unlogged rels: 0/1
Minimum recovery ending location: 0/0
Min recovery ending loc's timeline: 0
Backup start location: 0/0
Backup end location: 0/0
End-of-backup record required: no
Current wal_level setting: hot_standby
Current wal_log_hints setting: off
Current max_connections setting: 2100
Current max_worker_processes setting: 8
Current max_prepared_xacts setting: 800
Current max_locks_per_xact setting: 64
Maximum data alignment: 8
Database block size: 8192
Blocks per segment of large relation: 131072
WAL block size: 8192
Bytes per WAL segment: 16777216
Maximum length of identifiers: 64
Maximum columns in an index: 32
Maximum size of a TOAST chunk: 1996
Size of a large-object chunk: 2048
Date/time type storage: 64-bit integers
Float4 argument passing: by value
Float8 argument passing: by value
Data page checksum version: 1
复制
pg_control version number 的一致性 Catalog version number 的一致性
目的集群和源集群的分叉点
1 0/3E40B00 no recovery target specified
2 0/72EA320 no recovery target specified
3 0/9321E28 no recovery target specified
4 0/1802D168 no recovery target specified
5 0/18063AF0 no recovery target specified
复制
parentTLI 代表之前的时间线,关于时间线的分析可以参考之前的月报 switchpoint 代表这个时间线和下个时间线分割的WAL 日志位置 reason 代表时间线切换的原因
找到第一个Timeline不同或者switchpoint 不同的位置,这说明在这之前两者的数据还是一致的 找到1 的上一行记录的Timeline和两者中最小的switchpoint,这个位置两个集群的数据一定是一致的
如何找到目的集群的数据变化
struct file_entry_t
{
char *path;
file_type_t type;
file_action_t action;
* for a regular file */
size_t oldsize;
size_t newsize;
bool isrelfile; * is it a relation data file? */
datapagemap_t pagemap;
* for a symlink */
char *link_target;
struct file_entry_t *next;
};
复制
FILE_TYPE_REGULAR 常规文件 FILE_TYPE_DIRECTORY 目录 FILE_TYPE_SYMLINK 软链接
FILE_ACTION_CREATE 创建目录或者软链接 FILE_ACTION_COPY 复制整个文件或者重写已存在的文件 FILE_ACTION_COPY_TAIL 从目的集群文件尾复制源集群的增量数据 FILE_ACTION_NONE 无操作 FILE_ACTION_TRUNCATE 删除目的集群的文件,使目的集群文件和源集群文件大小相等 FILE_ACTION_REMOVE 删除本地文件/目录/软链接
global/ 目录下的文件,即数据库共享的表文件目录下的文件 base/ / 目录下的文件,即默认tablespace的表文件目录下的文件 pg_tblspc/ /PG_9.4_201403261/目录下的文件,即其他tablespace 的表文件目录下的文件,其中PG_9.4_201403261 与版本相关 . ,文件名符合的格式
oldsize > newsize? action=FILE_ACTION_TRUNCATE oldsize < newsize? action=FILE_ACTION_COPY_TAIL 如果文件不存在,则action=FILE_ACTION_COPY 如果目录不存在,则action=FILE_ACTION_CREATE 如果文件多余,则action=FILE_ACTION_REMOVE
对所有的文件(目录)action 进行排序,主要是保证操作的正确性,比如先删除父目录,再删除子目录,先创建父目录,再创建子目录或者文件。 遍历所有文件(目录),循环执行3,4步骤。 根据文件的pagemap,将变化的page 从源集群拷贝到目的集群。 根据对应的action 进行相应的操作,具体操作见上文file_action_t action 的介绍。
生成的backup label内容
len = snprintf(buf, sizeof(buf),
"START WAL LOCATION: %X/%X (file %s)\n"
"CHECKPOINT LOCATION: %X/%X\n"
"BACKUP METHOD: pg_rewind\n"
"BACKUP FROM: standby\n"
"START TIME: %s\n",
* omit LABEL: line */
(uint32) (startpoint >> 32), (uint32) startpoint, xlogfilename,
(uint32) (checkpointloc >> 32), (uint32) checkpointloc,
strfbuf);
复制
具体用法
pg_rewind [option...] { -D | --target-pgdata } directory { --source-pgdata=directory | --source-server=connstr }
复制
PostgreSQL中文社区欢迎广大技术人员投稿
投稿邮箱:press@postgres.cn
文章转载自PostgreSQL中文社区,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。