尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

PostgreSQL主从复制不开归档能同步吗?原理与风险解析

PostgreSQL主从复制不开归档能同步吗?原理与风险解析 这个月第三次在技术交流群里看到类似的问题PG的主从复制主库不开归档备库能同步吗问的人多回答的人也不少但大部分答案只说了一个“能”字没讲清楚背后的边界和隐患。今天就把这个问题拆开揉碎从底层原理到实操步骤再到我真实遇到的故障现场一次性讲明白。先说结论能。只要你的wal_level不是minimal、max_wal_senders够用主库不开归档流复制照常工作。但“能同步”只是门槛真正要讨论的是不开归档时哪些场景会翻车以及翻车之后怎么收拾。这篇文章适合正在搭PG主从的DBA、被业务方要求“不能开归档”但又担心高可用的运维同学以及想真正理解PG复制机制的后端开发。1. 先给结论不开归档主从复制照常工作但“能同步”和“安全的同步”是两码事1.1 这个问题的标准答案PostgreSQL的物理流复制不依赖归档。主库把WAL日志通过walsender进程实时发送给备库的walreceiver进程备库拿到WAL后重放完成同步。整个过程中archive_mode处于什么状态根本不影响walsender的行为。换句话说归档是给WAL做“冷备份”流复制是给备库做“热传输”两者是平行关系不是依赖关系。很多DBA被Oracle的惯性思维带偏了——Oracle的非归档模式下redo log循环覆盖Data Guard同步很容易断所以到了PG这里看到“归档”两个字就下意识认为不开归档就做不了主从。实际上PG不是这个逻辑。1.2 这个疑虑到底从哪来我观察下来产生这个疑虑的原因主要有三个第一绝大多数PG主从的搭建教程都是“归档流复制”一把梭默认把archive_mode on、archive_command cp ...一起写上。照着教程配完自然形成“要主从必须开归档”的刻板印象。第二PG官方文档把archive_mode放在“WAL归档”小节紧挨着流复制相关的参数。读文档时很容易产生前后关联的错觉。第三生产环境下主库确实常常开着归档因为归档是PITR时间点恢复的基础。大家习惯了生产环境的配置模板很难想象没有归档的PG主从是什么样子。但只要你实际动手测一次就会知道不开归档时主库的WAL照常生成备库照常接收pg_stat_replication视图里照常能看到streaming状态。1.3 归档和复制谁也不是谁的必需品对比项归档开启归档关闭流复制正常工作是是备库追历史WAL的通道流复制 restore_command从归档补仅流复制PITR时间点恢复支持不支持备库长时间断连后追平更从容能从归档补段强烈依赖WAL保留量主库额外I/O开销多一个archiver进程写归档几乎没有误删数据后恢复手段有基本没有所以“不开归档能不能同步”这个问题的答案很简单但“不开归档敢不敢上生产”才是需要认真权衡的事情。2. WAL从主库到备库的完整链路归档其实站在“侧路”2.1 wal_level才是流复制的前置开关PG里决定WAL记录内容多少的参数叫wal_level它有三个取值minimal只保证崩溃恢复不包含足够的信息供流复制或归档使用。replica包含支持流复制和归档的完整信息是主从复制的最低要求PG 9.6以前叫hot_standby。logical在replica的基础上额外包含逻辑解码需要的信息。所以在主库上流复制的开启条件其实是两件事把wal_level设置成replica或logical然后再设置max_wal_senders给walsender连接留出位置。这两个参数和archive_mode搭不搭边。就算archive_mode offWAL该写还是写walsender该读还是读。我在给客户做数据库巡检时遇到过一位工程师把wal_level设成了默认的minimal然后折腾一天备库怎么都起不来。查日志发现主库根本没把WAL信息发出去报错信息写着“WAL level not sufficient for hot standby”。这不是归档的问题是wal_level的问题。2.2 一条事务记录的主备旅程用一个具体例子说明。主库上执行INSERT INTO orders VALUES (1001, 2024-01-15, 999.00);这条语句经历的大致路径是这样的PG把事务产生的WAL记录先写入内存中的WAL缓冲区。事务提交时WAL记录刷到主库的pg_wal目录下的WAL文件里。主库的walsender进程检测到新的WAL数据通过网络把这段记录发送给备库的walreceiver进程。备库的walreceiver收到后把WAL写入备库自己的pg_wal目录。备库的startup进程不断读取这些WAL并重放把orders表的数据页更新到可见状态。如果配置了同步复制第3步之后主库还会等待备库确认落盘才返回事务提交成功。但无论同步还是异步整个过程都和archive_mode没有关系。2.3 备库追日志的两条腿与restore_command严格来说备库恢复WAL有两条路径。第一条是流复制通道备库通过primary_conninfo连上主库实时或追赶式地接收WAL。第二条是归档通道备库在restore_command里配置了从归档目录拉取WAL文件的话当发现某个WAL段无法从主库获取时会尝试从归档目录读取。这意味着开了归档时备库相当于多了一条“补充粮道”——即使主库上的旧WAL被回收了备库还能从归档仓库里领到缺的段然后继续追上主库。不开归档时断供就是断供主库没有备库需要的WAL备库就只能卡住最终需要重建。这也是这篇文章最核心的认知归档不是流复制的前提但它是备库的“历史粮仓”。2.4 常见误解不归档就是没有WAL还有一个很普遍的误解是“不开归档主库就不生成WAL了”。错WAL是PG的崩溃恢复基础任何数据库操作都会产生WAL跟开不开归档完全无关。归档只是把写满切换下来的WAL文件额外复制一份到别的地方相当于给日志做了一个离线副本。就算你永远不归档主库的pg_wal目录里照样堆满WAL文件只是不断地被checkpoint循环复用。3. 不开归档的三个高风险时刻WAL被回收、断供追不上、冲突卡死3.1 checkpoint悄无声息回收WAL的那只手主库的pg_wal目录不是无限增长的。checkpoint之后旧的WAL文件会被回收复用或直接删除。判断哪些WAL可以回收PG主要看这几个条件这个WAL文件对应的数据页修改是否已经刷盘checkpoint推进到的位置。是否设置了wal_keep_size如果设置了主库会额外保留指定大小的WAL。是否有复制槽复制槽会强制保留备库尚未消费的WAL。是否开启归档归档成功确认过的WAL才允许被回收。假设你的主库WAL产生速度是50MB/minwal_keep_size只配了默认的1GB那大约只能覆盖20分钟的WAL。如果备库因为网络故障断开30分钟回来之后发现需要的WAL段早就被checkpoint回收了那备库就废了只能重建。3.2 网络恢复后发现备库已断供我处理过一起客户现场的问题。客户跑数据迁移主库在几个小时内产生了近200GB的WAL备库在迁移开始没多久就和主库断开了连接。网络恢复时备库试图继续接收WAL但主库早就把备库缺的那些段循环覆盖掉了。备库日志里出现了一行触目惊心的报错FATAL: requested WAL segment 0000000100000000000000A3 has already been removed这种“断供”和主从配置没有任何关系纯粹是备库落后时间超过了WAL保留周期。不开归档时遇到这种情况基本没有救只能重建备库。开了归档备库还能通过restore_command从归档目录把缺的段补回来然后无缝切回流复制。3.3 查询冲突把备库拖进滞后深渊备库重放WAL时如果备库上有一个长时间运行的查询正在读取某些行而主库的VACUUM恰好清理了这些行备库的重放就会产生冲突。默认情况下PG在max_standby_streaming_delay超时后会主动取消备库上冲突的查询。但取消也需要时间在这段时间里备库的重放是停滞的。如果这种情况持续发生备库的延迟会不断积累。在不开归档的环境下延迟一旦超过WAL保留周期就直接断供。开了归档之后即使延迟变大备库也能从归档补WAL顶多是恢复时间更长不会彻底断掉。缓解查询冲突的手段是开启hot_standby_feedback让备库把正在运行的事务信息反馈给主库主库的VACUUM会尽量避开这些行。但这个参数也有副作用它可能让主库的表膨胀需要根据实际情况权衡。3.4 复制槽不是银弹很多人会问那我不开归档但是给备库配置复制槽replication slot不就能防止WAL被回收了吗确实复制槽会强制主库保留备库尚未消费的WAL这是不开归档场景下最有效的保护手段之一。但复制槽有它自己的风险如果备库长时间离线主库的pg_wal目录会一直增长直到把磁盘撑爆。我曾经见过一个客户备库停机维护了大半个月主库上复制槽一直存在结果主库磁盘直接满掉数据库进入只读状态。这就是“备库没挂主库先挂了”。所以复制槽是一种“宁愿本地堆积也不让备库断粮”的策略。使用它必须搭配磁盘空间监控一旦复制槽不活跃持续太久要及时告警。4. 实操从零搭建一套不开归档的主从环境4.1 环境约定与初始化下面这套流程我基于PostgreSQL 15验证过CentOS 7环境。规划如下角色IP地址数据目录主库192.168.1.10/var/lib/pgsql/15/data备库192.168.1.20/var/lib/pgsql/15/data两个节点都装好PG 15初始化完成后开始配置。4.2 主库参数该写的写不该写的别写编辑主库$PGDATA/postgresql.confwal_level replica max_wal_senders 10 wal_keep_size 1GB listen_addresses 0.0.0.0注意这里故意没有写archive_mode和archive_command保持默认的off。修改之后重启主库pg_ctl restart -D /var/lib/pgsql/15/data然后创建用于复制的数据库账号CREATE ROLE replica LOGIN REPLICATION PASSWORD replica123;编辑pg_hba.conf允许备库以复制协议连接host replication replica 192.168.1.20/32 scram-sha-256执行pg_ctl reload让配置生效。这一步里最容易踩的坑是把REPLICATION权限漏了。普通登录权限无法做流复制备库连接时会被直接拒绝报错类似FATAL: connection requires a valid replication connection4.3 pg_basebackup拉取基础备份在备库上执行pg_basebackup -h 192.168.1.10 -U replica -D /var/lib/pgsql/15/data -X stream -P -R各参数含义-h 192.168.1.10主库地址。-U replica使用刚才创建的复制账号。-D备库数据目录。-X stream在备份过程中同时以流方式获取WAL确保备份集一致。-P显示进度。-R自动生成standby.signal文件并把primary_conninfo写入postgresql.conf。如果备库数据目录不是空的pg_basebackup会拒绝执行所以要先清空目录。备份完成后确认备库数据目录的属主是postgres用户chown -R postgres:postgres /var/lib/pgsql/15/data4.4 备库启动与同步验证启动备库pg_ctl start -D /var/lib/pgsql/15/data查看备库日志正常会看到database system is ready to accept read only connections在主库上执行SELECT client_addr, state, sync_state, sent_lsn, replay_lsn FROM pg_stat_replication;输出里state是streamingsync_state是async没有配置同步复制时默认异步。再到备库执行SELECT pg_is_in_recovery();返回t说明备库处于恢复模式。最后做一次完整验证。在主库创建测试表并插入数据CREATE TABLE test_sync(id serial primary key, ts timestamptz default now()); INSERT INTO test_sync(ts) VALUES (now()), (now());到备库查询SELECT * FROM test_sync;能查到这两条数据说明流复制已经跑通。整个过程中主库从未开启过归档。5. 什么环境下敢不开归档我的判断标准与兜底配置5.1 测试环境不开归档是常态开发测试环境的核心诉求是“快速、灵活、不占额外资源”。数据丢了可以重导备库断了可以直接重建归档反而显得多余。我自己的测试集群一直不开归档主从复制跑了一两年也没出过问题。原因很简单备库从没长时间断连即使断了重建只需要几分钟。5.2 生产环境能跑但必须有Plan B生产环境如果坚持不开归档至少要满足这几点配置复制槽primary_slot_name并且监控复制槽的活跃状态。wal_keep_size给一个和业务写入量匹配的值不能拍脑袋填1GB。主库pg_wal所在磁盘预留足够空间峰值WAL生成速度乘上备库最长可容忍中断时间再翻倍。备库重建流程要演练过确保故障时能在规定时间内恢复。必须有主库WAL增长、复制延迟、复制槽不活跃的监控告警。说实话即使全部满足我依然建议生产开归档。因为归档是成本最低的后悔药误删数据、表损坏、版本升级出错都需要归档加备份做PITR恢复。不开归档的话高可用是有了“可恢复性”几乎为零。5.3 我推荐的安全配置组合生产环境我的底线配置长这样wal_level replica max_wal_senders 10 wal_keep_size 1GB archive_mode on archive_command cp %p /pg_archive/%f chmod 644 /pg_archive/%f # 备库上额外配置 restore_command cp /pg_archive/%f %p primary_slot_name standby01这套组合下备库断开几个小时也能先从归档补WAL再切回流复制基本不需要人工介入。归档目录建议放到独立磁盘或者单独的存储避免和主库数据目录争抢I/O。5.4 开归档到底会带来多大性能损失这是客户问得最多的问题。实际经验告诉我归档对主库的额外开销主要是archiver进程定时复制WAL文件的I/O。如果归档目录和主库数据目录在同一块磁盘上写入量大的场景下会有一定争抢如果归档目录在独立存储上影响通常在5%以下。真正要担心的不是归档本身而是这个细节archive_command如果写得很慢比如把WAL传到又慢又远的NAS上会导致WAL文件迟迟不被确认归档主库的pg_wal目录堆积增长。所以归档目标存储的性能和archive_command的效率往往比开不开归档本身更重要。6. 一次真实排错备库显示streaming实际已经落后几小时6.1 故障现象与第一反应客户那边报了一个很诡异的故障备库在pg_stat_replication里state一直是streaming但主库插入数据后备库怎么都查不到延迟从几百MB一路涨到几个GB。第一反应是网络问题但ping主备之间完全通延迟也不高。6.2 一步步揪出“WAL已回收”的过程第一步看备库日志。PG的日志默认在数据目录的log文件夹下文件名为postgresql-*.log。翻到最新几行看到一条核心报错FATAL: requested WAL segment 0000000100000000000000A3 has already been removed这意味着备库想找主库要一个WAL段但主库已经没有这个文件了。第二步上主库看pg_wal目录现状ls -lt $PGDATA/pg_wal | head -20发现主库的WAL文件已经到了0000000100000000000000A5而备库需要的A3已经被回收。说明备库追不上主库的时间已经超过了WAL保留周期。第三步检查保留参数SHOW wal_keep_size; SHOW archive_mode; SHOW primary_slot_name;看到wal_keep_size是默认的1GBarchive_mode是offprimary_slot_name为空。问题定位清晰了主库WAL循环覆盖太快备库断流时间超过保留周期又没有归档兜底导致彻底断供。第四步尝试临时处理把wal_keep_size从1GB调大到5GB执行pg_ctl reload。这个参数在PG 13以后是SIGHUP级别可以直接reload生效。但备库缺的段是历史段调大只能防止未来再断救不回已经丢的WAL。这种情况下只能重建备库。6.3 重建备库与快速恢复路径重建备库的流程其实和初次搭建一样只不过多了几个注意事项停备库pg_ctl stop -D /var/lib/pgsql/15/data。备份原备库数据目录如果还需要排查的话然后清空rm -rf /var/lib/pgsql/15/data/*。重新执行pg_basebackuppg_basebackup -h 192.168.1.10 -U replica -D /var/lib/pgsql/15/data -X stream -P -R启动备库pg_ctl start -D /var/lib/pgsql/15/data回到主库确认复制恢复SELECT client_addr, state, sent_lsn, replay_lsn, pg_wal_lsn_diff(pg_current_wal_lsn(), replay_lsn) AS lag_bytes FROM pg_stat_replication;重建完成后备库延迟归零数据能查到了。整个过程大约10分钟其中大部分时间是pg_basebackup全量拷贝数据的时间。这里有人会问为什么不用pg_rewindpg_rewind的适用场景是备库与主库时间线不一致时快速把备库拉回主库时间线典型场景是主备切换后原主库要作为新备库重新加入。而像这种备库缺失中间一段WAL的情况备库自身状态已经无法安全重放老老实实全量重建最稳妥。6.4 复盘为什么我对归档越来越保守这个故障的根因其实不是“没开归档”而是“备库断流后没有被及时发现”。如果监控到位当延迟超过10分钟就告警在WAL保留周期内处理不开归档也能救回来。但现实总是残酷的半夜网络抖动、监控没配覆盖全、值班同事睡过去了。等到第二天早上发现WAL早就循环覆盖了。所以从那之后我对生产环境的态度就变得非常明确归档不是可选项是保命项。你可以不用它做PITR但你不能在备库断供时发现自己连条后路都没有。最后分享一个我自己坚持的习惯每次调整主从架构都顺手做一张参数速查表记录wal_level、wal_keep_size、archive_mode、复制槽状态这些关键参数。每半年做一次“从基础备份归档恢复到一个临时实例”的演练哪怕只用一台闲置机器关键时候真能救命。复制这条链路看起来简单但只有亲眼看着它从零恢复过一次你才敢在故障发生时说一句不慌有后手。
返回列表