背景
pg_rewind类似Oracle flashback,可以将一个数据库回退到一个以前的状态,例如用于:
1、PG物理流复制的从库,当激活后,可以开启读写,使用pg_rewind可以将从库回退为只读从库的角色。而不需要重建整个从库。
2、当异步主从发生角色切换后,主库的wal目录中可能还有没完全同步到从库的内容,因此老的主库无法直接切换为新主库的从库。使用pg_rewind可以修复老的主库,使之成为新主库的只读从库。而不需要重建整个从库。
如果没有pg_rewind,遇到以上情况,需要完全重建从库,如果库占用空间很大,重建非常耗时,也非常耗费上游数据库的资源(读)。
详见:
《PostgreSQL pg_rewind,时间线修复,脑裂修复 - 从库开启读写后,回退为只读从库。异步主从发生角色切换后,主库rewind为新主库的从库》
以上解决的是怎么回退的问题,还有一个问题没有解,在分歧点到当前状态下,这些被回退掉的WAL,其中包含了哪些逻辑变化,这些信息怎么补齐?
时间线分歧变化量补齐原理
1、开启wal_level=logical
1.1、确保有足够的slots
2、开启DDL定义功能,参考:
《PostgreSQL 逻辑订阅 - DDL 订阅 实现方法》
3、在主库,为每一个数据库(或需要做时间线补齐的数据库)创建一个logical SLOT
4、有更新、删除操作的表,必须有主键
5、间歇性移动slot的位置到pg_stat_replication.sent_lsn的位置
6、如果从库被激活,假设老主库上还有未发送到从库的WAL
7、从从库获取激活位置LSN
8、由于使用了SLOT,所以从库激活位点LSN之后的WAL一定存在于老主库WAL目录中。
9、将老主库的slot移动到激活位置LSN
10、从激活位置开始获取logical变化量
11、业务层根据业务逻辑对这些变化量进行处理,补齐时间线分歧
示例
环境使用:
《PostgreSQL pg_rewind,时间线修复,脑裂修复 - 从库开启读写后,回退为只读从库。异步主从发生角色切换后,主库rewind为新主库的从库》
主库
port 4001
从库
port 4000
1、开启wal_level=logical
psql -p 4000
postgres=# alter system set wal_level=logical;
ALTER SYSTEM
psql -p 4001
postgres=# alter system set wal_level=logical;
ALTER SYSTEM
1.1、确保有足够的slots
edb=# show max_replication_slots ;
max_replication_slots
-----------------------
16
(1 row)
重启数据库。
2、开启DDL定义功能,参考:
《PostgreSQL 逻辑订阅 - DDL 订阅 实现方法》
3、在主库,为每一个数据库(或需要做时间线补齐的数据库)创建一个logical SLOT
postgres=# select pg_create_logical_replication_slot('fix_tl','test_decoding');
pg_create_logical_replication_slot
------------------------------------
(fix_tl,B/73000140)
(1 row)
edb=# select pg_create_logical_replication_slot('fix_tl_edb','test_decoding');
pg_create_logical_replication_slot
------------------------------------
(fix_tl_edb,B/73000140)
(1 row)
4、有更新、删除操作的表,必须有主键
5、间歇性移动slot的位置到pg_stat_replication.sent_lsn的位置
连接到对应的库操作
postgres=# select pg_replication_slot_advance('fix_tl',sent_lsn) from pg_stat_replication ;
pg_replication_slot_advance
-----------------------------
(fix_tl,B/73000140)
(1 row)
edb=# select pg_replication_slot_advance('fix_tl_edb',sent_lsn) from pg_stat_replication ;
pg_replication_slot_advance
-----------------------------
(fix_tl,B/73000140)
(1 row)
6、如果从库被激活,假设老主库上还有未发送到从库的WAL
pg_ctl promote -D /data04/ppas11/pg_root4000
7、从从库获取激活位置LSN
cd /data04/ppas11/pg_root4000
cat pg_wal/00000003.history
1 8/48DE2318 no recovery target specified
2 D/FD5FFFB8 no recovery target specified
8、由于使用了SLOT,所以从库激活位点LSN之后的WAL一定存在于老主库WAL目录中。
9、将老主库的slot移动到激活位置LSN
psql -p 4001 postgres
postgres=# select pg_replication_slot_advance('fix_tl','D/FD5FFFB8');
psql -p 4001 edb
edb=# select pg_replication_slot_advance('fix_tl_edb','D/FD5FFFB8');
10、从激活位置开始获取logical变化量
edb=# select * from pg_logical_slot_get_changes('fix_tl_edb',NULL,10,'include-xids', '0');
lsn | xid | data
-----+-----+------
(0 rows)
由于EDB库没有变化,所以返回0条记录
postgres=# select * from pg_logical_slot_get_changes('fix_tl',NULL,10,'include-xids', '0');
lsn | xid | data
------------+----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
D/FD5FEC60 | 68900576 | BEGIN
D/FD5FEC60 | 68900576 | table public.pgbench_accounts: UPDATE: aid[integer]:44681547 bid[integer]:447 abalance[integer]:-4591 filler[character]:' '
D/FD5FF3A8 | 68900576 | table public.pgbench_tellers: UPDATE: tid[integer]:5091 bid[integer]:510 tbalance[integer]:-160944 filler[character]:null
D/FD5FF9A8 | 68900576 | table public.pgbench_branches: UPDATE: bid[integer]:740 bbalance[integer]:-261044 filler[character]:null
D/FD5FFEF8 | 68900576 | table public.pgbench_history: INSERT: tid[integer]:5091 bid[integer]:740 aid[integer]:44681547 delta[integer]:-4591 mtime[timestamp without time zone]:'29-JAN-19 09:48:14.39739' filler[character]:null
D/FD6001E8 | 68900576 | COMMIT
D/FD5FE790 | 68900574 | BEGIN
D/FD5FE790 | 68900574 | table public.pgbench_accounts: UPDATE: aid[integer]:60858810 bid[integer]:609 abalance[integer]:3473 filler[character]:' '
D/FD5FF1C8 | 68900574 | table public.pgbench_tellers: UPDATE: tid[integer]:8829 bid[integer]:883 tbalance[integer]:60244 filler[character]:null
D/FD5FF810 | 68900574 | table public.pgbench_branches: UPDATE: bid[integer]:33 bbalance[integer]:86295 filler[character]:null
D/FD5FFD80 | 68900574 | table public.pgbench_history: INSERT: tid[integer]:8829 bid[integer]:33 aid[integer]:60858810 delta[integer]:3473 mtime[timestamp without time zone]:'29-JAN-19 09:48:14.397383' filler[character]:null
D/FD600218 | 68900574 | COMMIT
(12 rows)
postgres=# select * from pg_logical_slot_get_changes('fix_tl',NULL,10,'include-xids', '0');
lsn | xid | data
------------+----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
D/FD5FEED0 | 68900578 | BEGIN
D/FD5FEED0 | 68900578 | table public.pgbench_accounts: UPDATE: aid[integer]:15334791 bid[integer]:154 abalance[integer]:-2741 filler[character]:' '
D/FD5FF518 | 68900578 | table public.pgbench_tellers: UPDATE: tid[integer]:2402 bid[integer]:241 tbalance[integer]:191936 filler[character]:null
D/FD5FFB88 | 68900578 | table public.pgbench_branches: UPDATE: bid[integer]:345 bbalance[integer]:-693783 filler[character]:null
D/FD5FFFB8 | 68900578 | table public.pgbench_history: INSERT: tid[integer]:2402 bid[integer]:345 aid[integer]:15334791 delta[integer]:-2741 mtime[timestamp without time zone]:'29-JAN-19 09:48:14.397396' filler[character]:null
D/FD600248 | 68900578 | COMMIT
D/FD5FF438 | 68900579 | BEGIN
D/FD5FF438 | 68900579 | table public.pgbench_accounts: UPDATE: aid[integer]:54259132 bid[integer]:543 abalance[integer]:3952 filler[character]:' '
D/FD5FFEA8 | 68900579 | table public.pgbench_tellers: UPDATE: tid[integer]:9591 bid[integer]:960 tbalance[integer]:-498586 filler[character]:null
D/FD600298 | 68900579 | table public.pgbench_branches: UPDATE: bid[integer]:147 bbalance[integer]:459542 filler[character]:null
D/FD600560 | 68900579 | table public.pgbench_history: INSERT: tid[integer]:9591 bid[integer]:147 aid[integer]:54259132 delta[integer]:3952 mtime[timestamp without time zone]:'29-JAN-19 09:48:14.397464' filler[character]:null
D/FD600938 | 68900579 | COMMIT
(12 rows)
... ...
直到没有记录返回,说明已获取到所有变化量
直到没有记录返回,说明已获取到所有变化量
10.1、查看SLOT状态,当前WAL位置信息
psql -p 4001
postgres=# select * from pg_get_replication_slots();
slot_name | plugin | slot_type | datoid | temporary | active | active_pid | xmin | catalog_xmin | restart_lsn | confirmed_flush_lsn
------------+---------------+-----------+--------+-----------+--------+------------+------+--------------+-------------+---------------------
fix_tl | test_decoding | logical | 15844 | f | f | | | 67005646 | D/D7959218 | D/FD600218
fix_tl_edb | test_decoding | logical | 15845 | f | f | | | 72528996 | E/71C92B00 | E/71C92B38
(2 rows)
当前WAL位置
postgres=# select pg_current_wal_lsn();
pg_current_wal_lsn
--------------------
E/71C92B38
(1 row)
11、业务层根据业务逻辑对这些变化量进行处理,补齐时间线分歧
小结
主库开启逻辑SLOT,并根据从库的接收LSN位置,使用pg_replication_slot_advance移动主库的slot位点到从库的接收LSN位置。
当从库激活,老主库还有未同步到从库的WAL时,可以通过逻辑decode的方法,获取到未同步的逻辑变化量。
业务层根据业务逻辑,补齐这些变化量到新的主库。
注意:
1、开启logical wal_level,会给数据库增加较多的WAL日志,请酌情开启。
2、开启SLOT后,由于数据库会保证没有被订阅的WAL保留在pg_wal目录中,那么如果SLOT没有及时移动,则可能导致主库的pg_wal目录暴增。
参考
https://www.postgresql.org/docs/11/test-decoding.html
https://www.postgresql.org/docs/11/functions-admin.html#FUNCTIONS-REPLICATION
《PostgreSQL 逻辑订阅 - DDL 订阅 实现方法》
《PostgreSQL pg_rewind,时间线修复,脑裂修复 - 从库开启读写后,回退为只读从库。异步主从发生角色切换后,主库rewind为新主库的从库》
PostgreSQL 许愿链接
您的愿望将传达给PG kernel hacker、数据库厂商等, 帮助提高数据库产品质量和功能, 说不定下一个PG版本就有您提出的功能点. 针对非常好的提议,奖励限量版PG文化衫、纪念品、贴纸、PG热门书籍等,奖品丰富,快来许愿。开不开森.