PostgreSQL主从复制参数详解:从WAL发送到同步提交的调优实践 做数据库维护的基本都绕不开 PostgreSQL 主从复制。平时看着pg_stat_replication里 statestreaming 就很安心但真正让人头疼的是备库追不上、主库 WAL 被清、同步提交卡死、备库查询频繁被取消。这一堆问题本质上都是复制配置参数没吃透导致的。标题里的“PostgreSQL 复制配置参数”不是背几个默认值就完事的东西参数之间会互相影响牵一发而动全身。这篇文把我平时调这些参数的经验和踩坑记录整理出来适合正在维护生产库、准备搭建主从或者被同步延迟折磨的 DBA、运维和后端同学参考。看完你至少能回答三个问题这些参数各自卡在复制链路的哪个环节怎么调才能既保证数据安全又不牺牲性能备库出故障时哪些参数能救命、哪些参数会补刀1. 复制架构与参数体系总览1.1 数据从主库到备库到底经过几个站主从同步不是“主库写完了顺便发给备库”这么简单。一个事务在主库提交后要经历这么一条链路事务生成 WAL 日志主库把 WAL 刷到本地磁盘然后由 wal sender 进程通过网络把 WAL 发给备库备库的 wal receiver 进程接收后写入备库的 WAL 文件最后由 startup 进程把 WAL 回放到数据文件里。看到没有从提交到备库真正能看到数据中间隔了三层发送、接收、回放。每一层都有对应参数在起作用。发送层有max_wal_senders、wal_keep_size、wal_sender_timeout接收层有primary_conninfo、wal_receiver_timeout回放层有hot_standby、max_standby_streaming_delay、recovery_min_apply_delay。而synchronous_commit和synchronous_standby_names则决定主库要不要等备库、等备库到哪一步才算数。搞懂这条链路再看参数就很有方向感你调参数其实就是在调整某一站的策略。1.2 参数按作用可以分成四组我习惯把复制参数分成四组这样排查问题时能快速定位是卡在哪个环节。连接与身份参数max_wal_senders、primary_conninfo、pg_hba.conf里的 replication 条目。这类参数决定备库能不能连上主库、能连多少个。传输与保留参数wal_keep_size、wal_sender_timeout、wal_receiver_timeout、max_replication_slots。它们负责 WAL 在网络上的流动和主库侧保留策略。接收与回放参数hot_standby、max_standby_streaming_delay、recovery_min_apply_delay。控制备库接收 WAL 后何时回放、回放与查询如何共存。一致性参数synchronous_commit、synchronous_standby_names。决定主从之间是异步还是同步以及 RPO 和性能的取舍。分组的好处是当备库报“无法连接到主库”时先查第一组当备库一直显示恢复中但数据不更新时重点查第三组和 WAL 保留当主库提交变慢时盯第四组。下面各章节就按这个分组逐个拆。1.3 先来一张常用参数速查表参数名默认值作用是否需要重启max_wal_senders10允许同时运行的 WAL 发送进程数是wal_keep_size0主库额外保留的 WAL 总量新版是wal_keep_segments0按 16MB 段数保留 WAL老版本是max_replication_slots10复制槽数量上限是wal_sender_timeout60s主库发送进程等待备库响应的超时否wal_receiver_timeout60s备库接收进程等待主库数据的超时否primary_conninfo空备库连接主库的连接串重启hot_standbyon备库是否允许只读查询重启max_standby_streaming_delay30s流式回放与查询冲突时的最大等待否max_standby_archive_delay30s归档回放与查询冲突时的最大等待否recovery_min_apply_delay0WAL 至少延迟多久再回放否synchronous_commiton提交时等待备库的级别否synchronous_standby_names空标志哪些备库为同步备库否archive_modeoff是否开启 WAL 归档是archive_command空归档命令否这张表先存着后面的每个参数都会展开讲包括我实际调参时踩过的坑。2. 主库发送侧参数别让 WAL 在源头卡住2.1 max_wal_senders连接数上限不是越大越好max_wal_senders控制主库最多能同时运行多少个 wal sender 进程。每个物理备库、每次pg_basebackup、某些备份工具都会占用一个。默认值是 10如果只有一两个备库看起来够用但实际生产环境里很容易忽略一个场景你用备份工具从主库拉全量备份时它也会占用一个 wal sender如果同时有多个备库重连、或者有人跑了个pg_receivewal连接数就会打满。打满后的表现很典型新备库或备份工具连不上主库日志出现类似number of requested standby connections exceeds max_wal_senders的报错。这不算致命但对一个正在追赶的主从环境来说很烦。我的建议是物理备库数量乘以 2 到 3 倍再额外留几个给备份。比如你有两个备库设成max_wal_senders 10就挺舒服如果备库到了五六个建议直接 20 起步。注意这个参数是主库参数备库如果作为级联转发源也需要相应调大后面讲级联复制时还会提到。2.2 wal_keep_size 与复制槽备库落后时靠什么兜底这是主从复制里最容易出事故的参数之一。备库因为网络抖动、维护停机、磁盘满之类的原因断开后主库不会停在那里等它而会继续产生新的 WAL 并清理旧的 WAL。如果备库追不上重连时就会发现自己需要的旧 WAL 已经被删了日志里报requested WAL segment ... has already been removed。这时候唯一的办法就是重建备库成本很高。wal_keep_size就是用来在主库磁盘上额外保留一段 WAL 的。比如设为wal_keep_size 1GB即使备库没有通过复制槽上报位置主库也会尽量保留最近 1GB 的 WAL。老版本用的是wal_keep_segments每段默认 16MB所以wal_keep_segments 64约等于保留 1GB。这个参数很粗暴设太大浪费磁盘设太小兜不住长断线。真正更精确的兜底办法是复制槽后面第 5 章会专门讲。这里先给个结论如果你不想动不动重建备库至少要保证断线期间主库 WAL 不被清掉。要么用复制槽要么用足够大的wal_keep_size。我个人生产环境首选复制槽wal_keep_size通常不设因为无脑保留大段 WAL 对磁盘压力很大。2.3 wal_sender_timeout主库怎么发现备库“没气了”wal_sender_timeout默认 60 秒意思是主库的 wal sender 在 60 秒内没有从备库收到任何反馈包括 WAL 接收确认、心跳等就主动断开这个复制连接。反过来备库侧的wal_receiver_timeout也是类似逻辑。这两个超时参数平时不太引人注意但我在排查“备库莫名断开”时第一个就查它们。如果网络有瞬断或延迟60 秒的默认值通常够用但如果你的网络经常抖动可以把两个超时都调到 120s 甚至 300s避免备库因为一次小抖动就频繁重连。设置成 0 表示永不超时我不建议在生产环境关掉因为如果备库进程真卡死了主库这边一直占着一个连接不释放迟早会耗尽 wal sender。对了这个参数能在线ALTER SYSTEM后 reload 生效不需要重启数据库排障时可以临时放宽但别长期改到 0。2.4 发送侧参数的一次实际配置假设一个环境一个主库两个物理备库偶尔用pg_basebackup做全量备份。我的postgresql.conf里相关段落大概长这样max_wal_senders 10 wal_keep_size 0 max_replication_slots 10 wal_sender_timeout 60s wal_level replicawal_level必须设成replica或更高级别否则物理复制根本起不来。这也是新手最常踩的坑辛辛苦苦配好主从备库日志一直报could not receive data from WAL stream: FATAL: requested WAL segment has already been removed一查才发现wal_level还是默认的minimal。注意wal_level修改后需要重启主库。3. 备库接收与回放侧参数同步到哪儿了回放多快3.1 先让备库知道自己是谁standby.signal 与 primary_conninfo从 PostgreSQL 12 开始备库的定位方式变了数据目录下需要放一个名为standby.signal的空文件同时在postgresql.conf里配置primary_conninfo启动时数据库才会以备库模式启动。老版本用的recovery.conf已经被移除了。primary_conninfo是备库连接主库的连接串内容类似primary_conninfo host192.168.1.10 port5432 userrepl passwordxxxx application_namestandby1这里的application_name特别重要。它不是随便填的后面配置synchronous_standby_names时就是靠这个名字来识别哪个备库参与同步提交。名字别用特殊字符尽量简单可辨识比如standby1、standby_dc1这种。备库还需要在数据目录的pg_hba.conf里允许复制连接主库也要有对应的replication权限用户否则连接会被认证挡住。备库侧还有两个容易被忽略的点一是要设置足够大的max_connections二是要确保hot_standby on否则备库就算起来了也不能提供查询就失去了“主从分担读压力”的意义。3.2 hot_standby 与查询冲突max_standby_streaming_delay 怎么调hot_standby on让备库能接受只读查询但在备库回放 WAL 时如果回放操作和正在运行的查询冲突就会产生矛盾。最典型的场景主库删掉一张正在被备库查询的表或者主库执行VACUUM清理了备库某个长事务还在看的旧版本数据。数据库没法同时满足“继续让查询跑”和“立刻回放这条 WAL”只能二选一。max_standby_streaming_delay就是给这个冲突一个缓冲时间默认 30 秒。意思是备库遇到冲突时先等最多 30 秒希望查询尽早结束如果 30 秒到了查询还没结束备库就会强制取消那个查询然后继续回放。同理还有一个max_standby_archive_delay对应从归档恢复 WAL 时的场景。从实际使用看这个参数是“查得爽”和“追得紧”之间的平衡。如果备库上有大量报表查询、分析任务建议把这个值调大比如300s让查询有机会跑完别被回放频繁打断但如果备库主要就是做高可用热备我建议保持默认甚至调小优先保证数据和主库对齐。被取消的查询会在备库日志里留下类似canceling statement due to conflict with recovery的记录看到这条日志不用慌先想想你的延迟是不是设得太小。3.3 recovery_min_apply_delay给误操作留一条后悔路这是一个很多人知道但不常用的参数。recovery_min_apply_delay的意思是备库收到 WAL 后不要立刻回放先等一个指定的时间。比如设为recovery_min_apply_delay 30min备库就始终比主库慢半小时。这看起来和“尽量追平”的目标矛盾但它的用处是防误操作。如果主库上有人误删了一张大表半小时内你会发现并干预。普通备库早就把删除操作回放完了想恢复只能靠备份而延迟备库还没回放到那条 WAL可以直接把还没被删的数据捞出来或者再配合pg_dump导出损失几乎为零。使用这个参数有几点要特别注意延迟是从备库接收 WAL 的那一刻开始算不是从主库产生 WAL 开始算。如果备库本身断连了延迟意义会打折扣。延迟备库的 WAL 保留同样受主库清理策略影响。如果主库 WAL 只保留 10 分钟而你把延迟设了 30 分钟那么备库可能还没把前 20 分钟前的 WAL 回放完主库上的对应 WAL 已经被清理备库就断了。所以要给延迟备库配复制槽。不建议把所有备库都设成延迟一般一两个专门的“慢备库”就够了其余备库还是老老实实追平。3.4 备库怎么追得更快硬件与参数一起看回放性能受很多因素影响参数层面能调的其实不多。如果备库一直处于streaming状态但pg_stat_replication里的write_lag、flush_lag、replay_lag持续增大首先应该看备库的磁盘 I/O 是不是瓶颈。流复制回放模式基本是单线程顺序写备库磁盘越慢延迟越明显。这时可以看看备库是不是和主库共用同一块机械盘或者 I/O 被其他查询抢占。另外备库的max_connections、shared_buffers这些常规资源参数也别设得比主库小太多。很多人建备库时只把primary_conninfo配好其他参数直接复制主库的结果备库内存不足回放效率明显打折。我的习惯是备库的shared_buffers至少和主库一致max_connections也要够用因为备库还要扛读查询。4. 同步复制参数什么时候必须等备库4.1 synchronous_commit 的五个挡位默认的复制是异步的主库提交成功不管备库有没有收到 WAL。这对性能最友好但故障切换时可能丢已提交事务。synchronous_commit就是用来控制主库要等备库到哪一步。这个参数有五个值从低到高分别是off、local、remote_write、on、remote_apply。它们的差异可以这样理解off主库自己都不等 WAL 刷盘就返回提交成功性能最强崩溃时丢数据的窗口也最大适合可以容忍丢失的批量导入场景。local主库等自己的 WAL 刷到本地磁盘但不等备库。这是不开启同步复制时比较安全的默认选择。remote_write主库等备库把 WAL 写入操作系统但不等它fsync到磁盘。备库操作系统崩溃时仍可能丢。on等备库的 WAL 刷到磁盘。这是默认值也是大多数生产环境推荐的同步级别。remote_apply等备库不仅刷盘而且已经回放完这条 WAL。备库上已经能看到数据对应用最友好但回放延迟会直接放大主库的提交延迟性能代价最高。我实际项目中金融类业务会用到synchronous_commit on配合remote_apply的备库来确保 RPO0但一般业务没必要上remote_apply因为它的延迟放大很明显。4.2 synchronous_standby_names指定等谁光有synchronous_commit on还不够你必须告诉主库“等谁”。这就是synchronous_standby_names的职责。这个参数在旧版本里就是简单列备库名从 PostgreSQL 10 开始支持更灵活的FIRST和ANY语法。FIRST n (name1, name2, ...)表示按列表顺序取前 n 个可达的备库作为同步备库。比如synchronous_standby_names FIRST 1 (standby1, standby2)意思是优先级最高的是standby1只要它活着主库就等它如果standby1挂了自动等standby2。这种配置可以避免因为一个同步备库宕机整个主库写入全部卡死。ANY n (name1, name2, ...)则表达 quorum 语义只要有任意 n 个备库确认就算数。比如synchronous_standby_names ANY 1 (standby1, standby2, standby3)三个备库中任意一个确认即可适合跨机房容灾可以减少“某个机房整体不可用”带来的影响。但要注意ANY 配置下如果写到了一个慢速备库它就成了瓶颈因为主库等多个 ack 中最快到达的那个而不是固定的最优备库。4.3 同步复制不是越多越好很多人看到同步复制就想把两个备库都设成同步结果发现主库写入延迟翻了几倍满脑子问号。同步备库越多主库提交时需要等待的 ack 就越多网络越长延迟越高。所以生产环境我一般建议同步备库最多 1 到 2 个通常 1 个就够了。使用FIRST 1 (standby1, standby2)保证单点故障时自动切换而不是傻等一个死掉的备库。跨机房做容灾时用ANY 1 (dc1_standby, dc2_standby)避免单机房故障把写入拖死。另一个关键点是synchronous_standby_names一旦配置如果同步备库失联且列表里没有其他备库可替补主库的所有写事务都会一直阻塞直到同步备库恢复或者手动改配置。这是很多团队第一次上同步复制时被吓到的地方。所以一定要配合监控盯住pg_stat_replication里的sync_state一旦出现sync_state potential或为空就要人工介入。4.4 一个能落地的同步复制配置参考主库配置synchronous_commit on synchronous_standby_names FIRST 1 (standby1, standby2)备库1连接串里写上application_namestandby1备库2写application_namestandby2。这样默认情况下主库等 standby1standby1 出问题后自动转 standby2不会立刻阻塞。备库都保持hot_standby on读流量可以分摊到两台备库上。如果还想再安全一点把synchronous_commit临时改成remote_apply做演练也可以但生产环境默认别开。5. 复制槽、归档与级联组合起来才不容易翻车5.1 复制槽精确控住 WAL 保留但别让磁盘爆了复制槽的底层逻辑是主库为每个槽记录一个“备库期望的最小 LSN”只要槽存在主库就不会清理这个 LSN 之前的 WAL。这比wal_keep_size精确得多也是我推荐生产环境使用它的原因。创建物理复制槽可以这样操作SELECT * FROM pg_create_physical_replication_slot(standby1_slot);如果使用pg_basebackup创建备库还可以用--create-slot之类的方式一并创建。备库连上主库后主库的pg_replication_slots视图里会显示这个槽的restart_lsn和confirmed_flush_lsn。备库越落后restart_lsn就越旧主库 WAL 堆积越多。复制槽最大的坑是备库如果永久断线或者被删了而槽还留在主库上主库的 WAL 会一直保留磁盘最终被撑爆。所以使用复制槽必须配套监控和清理机制。清理时用SELECT pg_drop_replication_slot(standby1_slot);在备库不存在时主库要等一会儿才能确认安全清理但绝不能长期留着孤儿槽不管。我见过不止一次因为孤儿复制槽把主库磁盘写满的事故一定要引以为戒。5.2 归档与主从复制不是一回事这里要泼一盆冷水主从复制再强也不是备份。主库上误删表、坏块、逻辑错误都会照样复制到备库。所以archive_mode on和archive_command依然是生产环境的标配。archive_mode开启后主库每写完一个 WAL 段就会调用archive_command把 WAL 拷到归档目录或者对象存储里。归档和流复制可以同时工作它们共享 WAL 资源但用途不同流复制负责降低故障恢复时间归档负责提供任意时间点恢复PITR。备库同样可以开启归档。如果备库也从主库持续接收 WAL并且自己也在归档那么主库的归档压力就能分摊出去。很多高可用方案里备库归档是常态。当然备库的archive_command要能访问归档存储且归档目录要避免和主库写同一个文件导致冲突通常按节点名分目录。5.3 级联复制让备库也能当上游级联复制就是 A 主库 → B 备库 → C 备库 这种树状结构。B 既是备库又是 C 的上游“主库”。级联的好处是减少主库侧的 wal sender 压力尤其备库数量多、分布在多个机房时很实用。配置级联时要注意B 上也要设置max_wal_senders和必要的复制槽因为 C 连的是 B不是 A。C 的primary_conninfo指向 B 的地址application_name也是相对 B 而言的。A 上要保留的 WAL 位置实际上要覆盖到“C 经过 B 获取 WAL”的最慢分支也就是说 A 的复制槽或者 WAL 保留策略要能覆盖整棵复制树的末端。级联链路越长端到端延迟越难以预测所以级联层级一般别超过两层。6. 实测这些参数在故障场景下怎么表现6.1 场景一备库停机半小时主库没配复制槽我有一次模拟备库停机把备库停掉半小时主库继续跑业务。结果备库再启动时傻眼了连不上日志明确说所需的 WAL 已经被移除。因为主库默认 WAL 保留量不够备库需要的旧 WAL 早被清理了。最后只能重新pg_basebackup全量重建花了二十多分钟。教训就是如果无法保证备库断线时间小于 WAL 保留窗口就必须上复制槽。后来我在主库把每个备库对应的物理复制槽建好再模拟同样半小时断线备库重连后直接从槽记录的位置续传一分钟内就追平了。复制槽值得每个生产库用起来。6.2 场景二max_wal_senders 设太小备份工具连不上生产环境里同时有三个备库和一个备份任务在跑某个时刻又有新备库尝试加入结果日志报连接数打满。查pg_stat_activity能看到一堆wal sender进程每个都对应一个连接有些是pg_basebackup拉起的临时发进程。这种问题不用重启改max_wal_senders后重启主库就能解决但重启对生产有影响。更合理的做法是提前按“备库数 备份任务数 余量”算好不要把默认 10 当金科玉律。另外pg_basebackup可以通过--max-rate限制带宽但连接数占用是省不掉的。6.3 场景三同步备库宕机主库写入卡死这个场景我愿称之为“同步复制最吓人的一面”。某次测试环境里我把synchronous_standby_names配成单个备库名然后故意停掉备库主库上的INSERT全部 hang 住应用侧超时报警。因为主库在等一个永远不可能来的确认。好在配置的是FIRST 1 (standby1, standby2)之前只写了一个名字所以没有替补。后来改进为带两个备库名的FIRST模式再停主备库主库自动等第二个备库确认业务几乎无感。这个案例给我一个很深的印象同步复制可以接受但必须给同步备库留替补否则等于把可用性押在一台机器上。6.4 场景四备库查询频繁被取消回放进度原地踏步备库上有几个报表查询跑一次要好几分钟。主库的 DDL 或者VACUUM一来备库日志开始刷canceling statement due to conflict with recovery报表任务反复失败。查pg_stat_replication发现replay_lag也在增长。我把备库的max_standby_streaming_delay从默认 30s 调到300s报表查询基本能跑完取消次数大幅下降。代价是极端情况下备库回放最多可能延后 5 分钟对这个只读分析库来说完全能接受。如果你的备库是纯高可用节点不想让查询拖累回放那就保持小延迟甚至把读流量挪走。7. 参数调优清单与避坑建议7.1 我建议的生产基线不同业务容忍度不一样但多数情况下下面这套配置可以作为起点参数推荐值说明wal_levelreplica物理复制的最低要求max_wal_senders10~20按备库数和备份任务数留余量max_replication_slots10和备库数匹配不要小于备库数wal_keep_size0 或 1GB有复制槽可以不设建议至少 1GB 兜底hot_standbyon备库要能承担读查询max_standby_streaming_delay30s~300s按备库查询压力调整synchronous_commiton有同步备库时使用synchronous_standby_namesFIRST 1 (备库1, 备库2)必须有替补别只写一个wal_sender_timeout / wal_receiver_timeout60s网络差可调到 120sarchive_modeon生产必须开归档这套基线不是万能但能避开大多数常见坑。在此基础上再根据延迟、磁盘、查询负载微调。7.2 改参数时最容易忽略的几件事第一参数是否是重启生效。max_wal_senders、max_replication_slots、wal_keep_size、hot_standby都是重启生效而synchronous_commit、synchronous_standby_names、wal_sender_timeout这些是 reload 生效。用ALTER SYSTEM修改后别忘了区分否则改了不生效还以为是配置错了。第二主备参数不对称。max_connections在备库上如果比主库小备库回放时可能因为连接资源不足卡住。复制场景里备库的max_connections最好大于等于主库因为备库还要处理额外查询。第三pg_hba.conf的 replication 条目。很多人配好了所有参数备库还是连不上最后发现是主库pg_hba.conf里没有replication类型的记录。至少加一条host replication repl_user 192.168.1.0/24 md5然后用pg_hba的格式逐条放行不要为了省事写trust生产环境一定要加密认证。第四密码文件权限。备库连接主库时密码通常写在~/.pgpass里如果权限太宽PostgreSQL 会直接忽略这个文件。报错可能很隐晦检查.pgpass权限是否 600。7.3 排障速查表症状排查思路参数与操作备库连不上主库查认证、网络、连接数pg_hba.conf、max_wal_senders备库起不来查数据目录是否有 standby.signalprimary_conninfo 配置备库一直显示恢复中但 lag 增长查回放冲突、磁盘 I/Omax_standby_streaming_delay、备库硬件必要 WAL 已删除查主库 WAL 保留wal_keep_size、复制槽主库写事务卡住查同步备库状态synchronous_standby_names、备库监控备库查询被取消查冲突日志调大 max_standby_streaming_delay复制槽导致磁盘爆查 pg_replication_slots清理孤儿槽我个人在实际排查中最常用的一条 SQL 就是SELECT application_name, state, sync_state, write_lag, flush_lag, replay_lag FROM pg_stat_replication;延迟为 NULL 不代表没问题可能说明备库没有在真正流式接收。配合备库的pg_stat_wal_receiver一起看基本能定位 90% 的复制问题。最后再分享一个小技巧每次调整复制参数前先在测试环境把主备切换演练一遍尤其是同步复制和复制槽的组合。参数改得好不好不是看监控曲线多漂亮而是看拔网线、关备库、强制切换这些动作后集群还能不能按预期恢复。复制参数这东西配置一次不难难的是把每种故障场景都提前想明白。