47.2. 逻辑解码概念 #
47.2.1. 逻辑解码 #
逻辑解码是将数据库表上的所有持久更改提取为一种连贯、易于理解的格式的过程,这种格式无需详细了解数据库内部状态就能解释。
在 PostgreSQL 中,逻辑解码通过解码预写式日志的内容来实现。预写式日志描述的是存储层面的更改,而逻辑解码会将其转换成应用程序特定的形式,例如元组流或 SQL 语句流。
逻辑解码在两种情况下可用:
如果满足任一条件,则运行中的 WAL 级别等同于
logical,可通过 effective_wal_level 参数进行监视。
当 wal_level 设为 replica 时,在创建第一个逻辑复制槽时会自动激活逻辑解码。这个激活过程涉及多个步骤,并且需要各个进程之间的同步,因此可保证系统范围内的一致性。反之,如果
wal_level 设为 replica,并且最后一个逻辑复制槽被删除或失效,则逻辑解码会自动停用。请注意,这种停用可能需要一些时间,因为它是由检查点进程异步完成的。
小心
当 wal_level 设为 replica 时,删除或使最后一个逻辑槽失效会在主库上禁用逻辑解码,并导致备库上的槽失效。
47.2.2. 复制槽 #
在逻辑复制的上下文中,一个槽表示一个更改流,客户端可以按照这些更改在源服务器上发生的顺序来重放它们。每个槽都从单个数据库流式传输一系列更改。
注意
PostgreSQL 也可以使用流复制槽来维护备库(见第 26.2.5 节),但这通常使用物理复制,而不是逻辑复制。
复制槽在整个 PostgreSQL 集簇的所有数据库中都有唯一标识符。槽独立于使用它们的连接而持久存在,并且具备崩溃安全性。
逻辑槽在正常运行时会让每个更改只发送一次。每个槽的当前位置只会在检查点时持久化,因此在发生崩溃时,该槽可能会回退到较早的 LSN,这会导致服务器重启后最近的更改再次被发送。逻辑解码客户端有责任避免多次处理同一消息带来的不良影响。客户端可能希望记录解码时见到的最后一个 LSN,并跳过任何重复数据;或者(在使用复制协议时)请求从该 LSN 开始解码,而不是让服务器自行决定起始点。复制进度跟踪功能正是为此设计的,参见复制源。
对于同一个数据库,可以存在多个彼此独立的槽。每个槽都有自己的状态,允许不同的消费者从数据库更改流中的不同位置接收更改。对于大多数应用,每个消费者都需要一个单独的槽。
逻辑复制槽并不了解接收端的状态。甚至可以让多个不同的接收端在不同时间使用同一个槽;它们只会从前一个接收端停止消费更改的位置之后继续获得更改。但在任意给定时刻,只允许一个接收端从某个槽中消费更改。
逻辑复制槽也可以在热备上创建。为了防止 VACUUM 从系统目录中移除所需的行,应当在备库上设置 hot_standby_feedback。即便如此,如果任何所需行仍被移除,槽也会失效。强烈建议在主库和备库之间使用物理复制槽。否则,hot_standby_feedback 虽然也能工作,但只在连接存活期间有效(例如节点重启就会打断它)。随后主库可能会删除备库上逻辑解码所需的系统目录行(因为它并不知道备库上的
catalog_xmin)。如果主库上的 effective_wal_level
降低到小于 logical,备库上现有的逻辑槽也会失效。这会在备库检测到 WAL 流中的该变化后立刻发生。这意味着,对于落后的 WAL 发送进程(如果有),直到主库上 wal_level 参数变更为止的一些 WAL 记录将不会被解码。
创建逻辑槽需要有关当前所有正在运行事务的信息。在主库上,这些信息可以直接获得;但在备库上,这些信息必须从主库获取。因此,创建槽可能需要等待主库上发生某些活动。如果主库空闲,在备库上创建逻辑槽可能会花费可察觉的时间。通过在主库上调用 pg_log_standby_snapshot 函数可以加快这一过程。
小心
复制槽可跨崩溃持久存在,而且不了解其消费者的状态。即使没有连接使用它们,它们也会阻止所需资源被移除。这会消耗存储,因为只要复制槽仍然需要,VACUUM 就不能移除所需的 WAL 和系统目录中的相关行。在极端情况下,这可能会导致数据库关闭,以防止事务 ID 回卷(见第 24.1.5 节)。因此,如果槽已经不再需要,就应当将其删除。
47.2.3. 复制槽同步 #
主库上的逻辑复制槽可以通过
failover 参数同步到热备,该参数用于
pg_create_logical_replication_slot;或者在创建槽时执行带有
failover 选项的
CREATE SUBSCRIPTION,也可以把主库上的逻辑复制槽同步到热备。另外,还必须在备库上启用
sync_replication_slots。在备库上启用
sync_replication_slots 后,故障切换槽就可以由
slotsync 工作进程定期同步。要让同步生效,主库和备库之间必须使用物理复制槽(也就是说,备库上应当配置
primary_slot_name),并且备库上必须启用
hot_standby_feedback。还必须指定有效的 dbname,并将其放在
primary_conninfo
中。强烈建议在主库的
synchronized_standby_slots
列表中写入上述物理复制槽的名称,以防止订阅者消费更改的速度快于热备的接收进度。即使配置正确,由于需要等待
synchronized_standby_slots
中列出的槽,向逻辑订阅者发送更改时仍然会有一定延迟。使用
synchronized_standby_slots 时,主库在与
synchronized_standby_slots 中指定的物理复制槽相关联的备库确认已接收到截至主库最新刷盘位置的 WAL 之前,不会完全关闭。
注意
虽然启用
sync_replication_slots 允许对故障切换槽进行自动、周期性的同步,但也可以在备库上使用
pg_sync_replication_slots 函数手工同步。不过,与自动同步不同,它不会执行增量更新,而是会循环重试,直到在函数调用开始时主库上已存在的所有故障切换槽都同步完成。函数开始后新建的槽不会被同步。相比之下,通过 sync_replication_slots 实现的自动同步会持续更新槽,从而实现无缝故障切换并支持高可用。因此,这是推荐的槽同步方法。
当按建议完成配置,并且初始同步已经通过自动方式或通过
pg_sync_replication_slots 手工执行后,备库只有在满足以下条件时才能将同步后的槽持久化:主库上的逻辑复制槽必须保留那些在备库上仍可用的 WAL 和系统目录行。这样可以保证数据完整性,并允许在提升后顺畅继续逻辑复制。如果备库上所需的 WAL 或系统目录行已经被清除,则该槽不会被持久化,以避免数据丢失。此时,可能会出现如下日志消息:
LOG: could not synchronize replication slot "failover_slot" DETAIL: Synchronization could lead to data loss, because the remote slot needs WAL at LSN 0/03003F28 and catalog xmin 754, but the standby has LSN 0/03003F28 and catalog xmin 756.
如果某个消费者正在积极使用该逻辑复制槽,则无需人工干预;槽会自动前进,并在下一个周期恢复同步。不过,如果没有配置消费者,则建议在主库上使用
pg_logical_slot_get_changes 或
pg_logical_slot_get_binary_changes 手工推进该槽,以便同步继续进行。
故障切换后能否继续逻辑复制,取决于故障发生时备库上的同步槽在
pg_replication_slots.synced
列中的值。只有在故障切换前其值已经在备库上变为 true 的持久槽,才能在故障切换后继续用于逻辑复制。临时同步槽不能用于逻辑解码,因此这些槽的逻辑复制无法恢复。例如,如果某个同步槽由于订阅被禁用而无法在备库上变为持久槽,那么即使之后启用了订阅,故障切换后也无法恢复该订阅。
要在故障切换后从已同步的逻辑槽恢复逻辑复制,必须修改订阅的 'conninfo',使其指向新的主库。这可以使用
ALTER SUBSCRIPTION ... CONNECTION
完成。建议先禁用订阅,再提升备库,并在修改连接字符串之后重新启用订阅。
小心
在提升过程中,旧主库有可能重新上线;如果订阅未被禁用,那么即使完成提升,逻辑订阅者仍可能继续从旧主库接收数据,直到连接字符串被修改为止。这可能导致数据不一致问题,使逻辑订阅者无法继续从新的主库复制。
47.2.4. 输出插件 #
输出插件把预写式日志内部表示的数据转换成复制槽消费者所需的格式。
47.2.5. 导出快照 #
当使用流复制接口创建新的复制槽时(见 CREATE_REPLICATION_SLOT),会导出一个快照(见第 9.29.5 节),它准确反映了数据库的这样一种状态:从该状态之后开始,所有更改都会被纳入更改流。这可以通过使用
SET TRANSACTION
SNAPSHOT 读取创建槽那一刻的数据库状态,据此创建一个新副本。随后,该事务就可以用来转储该时刻的数据库状态,之后再利用该槽的内容进行更新,而不会丢失任何更改。
不需要导出快照的应用程序可以使用
SNAPSHOT 'nothing' 选项来禁止导出。