PostgreSQL特性—Replication Slot
流复制槽是 PostgreSQL 9.4 中的一项新功能,作为逻辑变更集提取功能的一部分。
什么是replication slots?
流复制槽是 PostgreSQL 9.4 中引入的新功能。它们是replica 状态的持久记录,即使在replica offline 和 disconnect 时都会保留在主服务器上。
默认情况下,它们不用于物理复制,因此只有在启用它们的情况下才会与它们打交道。这篇文章解释了它们的作用,为什么它们对物理复制有用,为什么它们对逻辑复制是必要的,replication slots的用法、内核实现,以及一些注意事项等等。
概述
为了在日志流逻辑复制、变更集提取和双向复制方面的工作能够顺利的完成,所以需要更好地控制 WAL 保留。
由于流复制正常工作的时候,主服务器不会为落后的备用服务器保留额外的 WAL。如果备用服务器落后太多,主服务器将删除备用服务器仍需要重放的 WAL 段,那么备用机无法恢复。就会收到如下错误:
ERROR: requested WAL segment 00000001000000010000002D has already been removed
为防止这种情况,需要配置连续归档并提供restore_command以授予副本访问归档的权限,或者愿意重新创建落后的备用数据库。
一个不太可靠的替代方案是将wal_keep_segments设置为“足够高”的值,以确保副本永远不会落后太多——但没有真正的方法来猜测什么是足够高的,而且设置的值越高,风险越大master 将可能用完pg_xlog中的空间。所以一般来说归档是首选。
master 可以在连接时为副本保留 WAL,但是一旦副本由于任何原因断开连接,master 就会忘记所有这一切。由于瞬时断开连接很常见,这意味着仅为连接的副本保留 WAL 没有多大意义。
到目前为止,我们已经解决了这个问题,因为当主服务器丢弃副本仍然需要的 WAL 时,可以使用回退到连续归档的选项。当与WAL-E 之类的脚本、共享网络文件系统或scp存档的共享主机结合使用时,它可以很好地完成工作。这对新手dba来说有点混乱,但效果还是很好的。
使用日志流逻辑复制,我们不能再依赖于连续归档,因为主节点必须读取 WAL 并解码,将结果发送到副本;它不能只发送原始 WAL 记录。副本不能只从主服务器读取原始 WAL 并提取它们自己需要的东西,因为只有主服务器有事务 ID 状态、对象的本地 oid 等的记录。
我们可以让主服务器配置restore_command 运行—但是当许多副本需要相同的 WAL 存档时,这可能会变得非常低效。我们需要缓存系统和其他复杂性。它也没有消除管理员管理存档 WAL 保留的需要。
与物理复制不同,逻辑复制不接受仅重新回放落后太多的副本的选项。副本主机上可能有其他数据库包含它们自己的原始数据。在双向复制的情况下,副本数据库还可能包含尚未复制到其他节点的新数据。因此,我们不能像物理复制那样只写下复制副本并从主服务器创建新的pg_basebackup或重新同步它。
插槽的引入旨在通过让主服务器提供副本状态的持久性来解决此问题,即使它们副本断开连接也是如此。虽然它是为逻辑复制而添加的,但这个概念在物理复制中也很有用,因为它让主服务器保留所有备用设备所需的WA,而不再保留更多。没有像wal_keep_segments那样的猜测。无需设置单独的 WAL 归档系统并管理其中的保留。只要pg_xlog中有足够的空间用于额外的 WAL,它就可以正常工作。如果复制副本断开连接,主服务器可以为其保留 WAL,直到它重新连接并重放 WAL。
这种方法的缺点是,它为无限制的 WAL 增长添加了另一种填充pg_xlog的方式——当副本无法足够快地接收或重放 WAL 或长时间保持离线时,主服务器将继续保留 WAL 直到pg_xlog填满并且它开始向客户报告错误。这可能已经发生在重负载下具有长检查点间隔的主服务器上,但是使用插槽触发要容易得多,因为无限期离线的副本将导致pg_xlog填满。
虽然监控和调整pg_xlog容量对于繁忙的 PostgreSQL 服务器来说已经是必要的,但对于使用插槽来管理 WAL 保留以进行复制的服务器来说更是如此。
作为9.4版本的一个主要功能,它不但可以用于逻辑复制,还可用于物理复制(或者叫Streaming Replication)。针对物理复制的Replication Slot称为Physical Replication Slot。
由于大家目前主要用的还只是PG自带的物理复制方式,我们就重点分析一下Physical Replication Slot。
使用Physical Replication Slot,可以达到两个效果:
A)可以确保从库(standby)需要的日志不被过早备份出去而导致从库失败,出现下面的error:
ERROR: requested WAL segment 00000001000000010000002D has already been removed
通过Replication Slot记录的从库状态,PG会保证从库还没有apply的日志,不会从主库的日志目录里面清除或archive掉。而且,replication slot的状态信息是持久化保存的,即便从库断掉或主库重启,这些信息仍然不会丢掉或失效。
B)当允许应用连接从库做只读查询时,Replication Slot可以与运行时参数hot_standby_feedback配合使用,使主库的vacuum操作不会过早的清掉从库查询需要的记录,而出现如下错误(错误的原因下面会详细解释):
ERROR: canceling statement due to conflict with recovery
下面看看Physical Replication Slot的用法和内核实现。
用法
下面是启用Replication Slot的步骤,很简单:
1)首先需要配置好Steaming Replication的主库从库。涉及的参数有
listen_addresses=’*’
hot_standby=on
wal_level=hot_standby
max_wal_senders=1
尤其注意配置max_replication_slots大于等于1。这些参数主从库应一致。
2)在主库创建replication slot:
postgres=# SELECT * FROM pg_create_physical_replication_slot('my_rep_slot_1');
slot_name | lsn
---------------+-----
my_rep_slot_1 |
(1 row)
postgres=#
此时replication slot还不处于active状态。
3)在从库配置如下,然后重启从库:
primary_slot_name = my_rep_slot_1
primary_conninfo = 'user=replxs password=replxs host=192.168.60.191 port=5432 sslmode=prefer sslcompression=1 krbsrvname=postgres'
4)观察主库replication slot的状态变化:
postgres=# select * from pg_replication_slots ;
slot_name | plugin | slot_type | datoid | database | temporary | active | active_pid | xmin | catalog_xmin | restart_lsn | confirmed_flush_lsn | wal_status | safe_wal_size | two_phase
---------------+--------+-----------+--------+----------+-----------+--------+------------+------+--------------+-------------+---------------------+------------+---------------+-----------
my_rep_slot_1 | | physical | | | f | t | 11640 | | | 0/40001F8 | | reserved | | f
(1 row)
postgres=#
5)与hot_standby_feedback配合使用。在将从库的postgresql.conf文件中的hot_standby_feedback选项设为on,重启从库即可。
内核实现
replication slot是由下面的patch加入内核中的:
author Robert Haas <rhaas@postgresql.org>
Sat, 1 Feb 2014 03:45:17 +0000 (22:45 -0500)
committer Robert Haas <rhaas@postgresql.org>
Sat, 1 Feb 2014 03:45:36 +0000 (22:45 -0500)
Replication slots are a crash-safe data structure which can be created
on either a master or a standby to prevent premature removal of
write-ahead log segments needed by a standby, as well as (with
hot_standby_feedback=on) pruning of tuples whose removal would cause
replication conflicts. Slots have some advantages over existing
techniques, as explained in the documentation.
这个patch改的文件不少,分析这些代码,我们重点关注下面的问题:
A)Replication Slot是如何在内核中创建的?
通过分析创建Replication Slot时调用的函数ReplicationSlotCreate,可以看出,**Replication Slot实质上是内存中的一些数据结构,加上持久化保存到pg_replslot/目录中的二进制状态文件。**在PG启动的时候,预先在共享内存中分配好这些数据结构所用内存(即一个大小为max_replication_slots的数组)。这些数据结构在用户创建Replication Slot时开始被使用。一个Replication Slot被创建并使用后,其数据结构和状态文件会被WAL(Write-Ahead-Log)的发送者(wal_sender)进程更新。
另外,如果单纯从Replication Slot的名字,我们很容易觉得Replication Slot会创建新的与从库的连接,进行日志发送。实际上,创建过程中并没有创建新的与从库的连接,Replication Slot还是使用了wal_sender原有连接(由于一个从库一个wal_sender连接,所以一个从库和主库之间也只有一个active的Replication Slot)。
B) Replication Slot的状态是如何被更新的?
很容易发现,Replication Slot的状态的更新有两种情况。
第一种是在ProcessStandbyHSFeedbackMessage这个函数被更新。这个函数是在处理wal_sender所收到的从库发回的feedback reply message时调用的。通过这个函数,我们可以看出,每个wal_sender进程的Replication Slot(就是用户创建的Replication Slot)保存在MyReplicationSlot这个全局变量中。在处理从库发回的reply时,reply中的xmin信息会被提取出来,存入slot的data.xmin和effective_xmin域中,并通过函数ProcArraySetReplicationSlotXmin,最终更新到系统全局的procArray->replication_slot_xmin结构中(以使其对所有进程可见),完成slot的更新。
这里要注意,如果我们有多个Replication Slot(分别对应各自从库),则在更新全局结构procArray->replication_slot_xmin时,会选取所有slot中最小的xmin值。
第二种是在ProcessStandbyReplyMessage中。这个函数处理从库发送的restart lsn信息(即从库apply的日志的编号),会直接将其更新到replication slot的restart lsn域中,并保存到磁盘,用于主库判断是否要保留日志不被archive。
C) Replication Slot如何和hot_standby_feedback配合,来避免从库的查询冲突的?
这里,从库的查询冲突指的是下面的情况:从库上有正在运行的查询,而且运行时间很长;这时主库上在做正常的vaccum,清除掉无用的记录版本。但主库的vaccum是不知道从库的查询存在的,所以在清除时,不考虑从库的正在运行的查询,只考虑主库里面的事务状态。其结果,vacuum可能会清除掉从库查询中涉及的,仍然在使用的记录版本。当这些vaccum操作,通过日志同步到从库,而恰好从库的查询仍然没有运行完,vaccum就要等待或cancel这个查询,以保证同步正常继续和查询不出现错误的结果。这样,每当用户在从库运行长查询,就容易出现我们上面提到到query conflict error。
如何避免这种冲突呢?目前最好的解决方案是使用hot_standby_feedback + Replication Slot。其原理简单说就是,从库将它的查询所依赖的记录版本的信息,以一个事务id来表示,并放在从库发回给主库wal_sender的reply中发给主库(见函数XLogWalRcvSendHSFeedback),并最终传导给主库vaccum,让其刀下留人,暂时不清除相关记录。
具体过程是,在从库,函数XLogWalRcvSendHSFeedback调用GetOldestXmin获得xmin,放入给主库的reply中。主库的wal_sender收到后,如果使用了Replication Slot,就把这个xmin放入slot的状态信息中,并更新此时系统所有slot的最小xmin。这个系统所有slot的最小xmin怎么在主库传导给vacuum的呢?以自动触发的vacuum操作为例,其中的逻辑的顺序如下:
GetSnapshotData(vacuum事务开始时,获取slot xmin,存入全局变量) ->vacuum_set_xid_limits(调用 GetOldestXmin,通过全局变量,获取系统xmin和slot xmin,取较小值)-> vacuum_lazy (使用xmin,判断哪些记录版本可以清除)
这样,利用Replication Slot这个渠道,就解决了从库查询冲突。
注意事项
最后,介绍一下使用Replication Slot的注意事项:
1)如果收不到从库的reply,Replication Slot的状态restart lsn会保持不变,造成主库会一直保留本地日志,可能导致日志磁盘满。所以应该实时监控日志磁盘使用情况,并设置较小的wal_sender_timeout,及早发现从库断掉的情况。
2)将hot_standby_feedback设为on时,注意如果从库长时间有慢查询发生,可能导致发回到主库的xmin变化较慢,主库的vaccum操作停滞,造成主库被频繁更新的表大小暴增。
3)默认情况下,物理复制不会使用槽。没有什么改变。您必须在流式传输之上使用 WAL 归档以确保可靠的复制。您可以使用recovery.conf中的primary-slotname参数启用复制槽进行物理复制。物理复制槽必须已经在 master 上用pg_create_physical_replication_slot(...)函数创建;
4)当使用槽来管理物理复制时,复制不再需要 WAL 归档(尽管您可能希望为 PITR 保留它)。您无需处理在可能在线或不在线的多个副本之间管理 WAL 保留的困难,以及离线时主服务器不知道的重放位置。您无需管理存档存储中的空间。相反,您需要留意pg_xlog中的空间。WAL 保留是自动为您管理的,但您必须确保有足够的可用空间。
5)如果副本因主机故障而退出服务或不可恢复地丢失,您必须手动从主服务器中删除插槽,因为在您这样做之前,主服务器不会删除任何 xlog。如果你有一个 master 快速生成 xlog 的系统,这可能需要大量的pg_xlog空间或相当快的操作。强烈推荐使用监控来监控系统。
6)无法跟上主服务器的副本必须修复或下线,以防止主服务器耗尽 xlog 空间。
7)使用插槽,您不再需要监视副本以确定它们的状态,因为pg_replication_slots视图中始终提供准确和最新的信息。所以你需要做的就是监控master的pg_xlog空间和所有replication slots的状态。
8)任何wal_keep_segments参数都将被视为 WAL 保留的最小值。
所以:使用插槽进行物理复制是一种权衡。您不再需要归档,但您必须更密切地监视系统状态以避免破坏主控。
参考文档
http://static.kancloud.cn/taobaomysql/monthly/67169
https://www.2ndquadrant.com/en/blog/postgresql-9-4-slots/




