ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

PostgreSQL复制槽引发WAL膨胀?教你安全删除复制槽释放磁盘空间

PostgreSQL复制槽引发WAL膨胀?教你安全删除复制槽释放磁盘空间 上周凌晨两点生产库磁盘使用率冲到98%监控电话直接打到手机上。登录服务器一看pg_wal目录下堆满了日志文件再查复制槽果然有个逻辑复制槽的confirmed_flush_lsn已经停在三天前。这类问题几乎每个做数据库运维的人都躲不过海量数据库的日志清理、复制槽删除、磁盘空间释放三者永远绑在一起。这篇文章就把我这几年处理日志膨胀、删除复制槽的完整流程和经验总结一遍适合正在维护PostgreSQL、遇到WAL堆积或磁盘告警的同学直接参考。全文以实际生产处置为蓝本原理、排查、操作、避坑一次讲透。1. 日志膨胀背后复制槽是如何“锁死”WAL的1.1 WAL日志清理的基本逻辑先弄清楚数据库日志为什么能清理、什么时候不能清理。PostgreSQL里的WALWrite-Ahead Log可以理解成数据库的“记账本”每次数据修改先写日志、再写数据文件崩溃恢复全靠它。正常情况下WAL日志是有生存周期的checkpoint之后已经被持久化到数据文件里的旧日志就可以被回收复用这个动作叫WAL段回收。回收得越勤快磁盘占用越小。但如果有人“按住”了某个日志位置数据库就不敢动它日志只能一直堆着。为什么不敢动因为一旦动了需要从这个位置开始恢复的下游就断了粮。海量数据库的场景下这个“下游”往往是物理备库、逻辑订阅、或者CDC工具。它们靠读取WAL来同步数据如果日志被清了而下游还没消费完数据就永久丢失。所以数据库宁可让磁盘爆掉也要保住下游需要的日志。这是安全机制不是bug但的确会让磁盘告警。理解了这个底层逻辑再看复制槽就顺了。复制槽Replication Slot正是PostgreSQL用来“按住”日志位置的机制。它告诉数据库“有一个下游还需要从某个LSN位置开始读日志没我的允许不许清理。”只要复制槽存在数据库就会保留从槽记录位置开始的全部WAL不管这个下游是不是还活着。1.2 复制槽的“锁定”机制复制槽分两种物理复制槽和逻辑复制槽。物理复制槽记录的是备库已经接收到的WAL位置restart_lsn备库同步跟不上时主库就为它保留日志。逻辑复制槽记录的是逻辑解码位置confirmed_flush_lsn下游是逻辑订阅、Debezium这类工具时使用。不管是哪一种它们的共同特征就是槽在日志就得留。打个比方复制槽就像一个“订了报纸但一直不来取”的订户。送报员数据库明知道报纸越堆越多还是必须一份不少地留着因为订户说了“我哪天来取”。只要订户不主动退订送报员就只能一直堆下去。等到磁盘满了数据库直接罢工比送报员家里堆满报纸更严重整个业务都停了。海量数据库里这个问题的杀伤力是成倍放大的。普通库一天产生几十GB日志就算槽卡住一两天也能忍但海量库一天可能产生数百GB甚至上TB的WAL复制槽卡住半天就能把整个数据盘撑爆。我见过最夸张的一次一个日增500GB日志的库一个被遗忘的逻辑复制槽卡了19个小时磁盘直接清零可用空间所有写入全部阻塞业务方半夜打电话质问“数据库是不是挂了”。排查下来根本不是数据库性能问题就是一个复制槽没删。2. 排查定位怎么快速揪出“吃空间”的复制槽2.1 第一件事确认磁盘空间去哪了不管监控告警怎么提示我上手第一件事永远是先确认空间具体被什么吃了而不是急着一通乱删。海量库的磁盘空间大头无非几类数据文件、WAL日志、归档日志、临时文件、慢查询产生的临时排序。每类问题处理方式完全不同定位错了方向轻则白忙活重则删错东西。直接看目录大小是最快的。WAL日志目录在PG里叫pg_wal旧版本叫pg_xlog先看它占了多少du -sh $PGDATA/pg_wal如果发现这个目录已经有几十GB甚至几百GB基本就是日志堆积。再结合数据库内的视图做量化确认SELECT pg_size_pretty(sum(size)) AS total_wal_size FROM pg_ls_waldir();这一步只是为了确认“日志确实异常堆积”。正常情况下WAL目录大小应该和checkpoint周期、wal_keep_size、max_wal_size等参数匹配大小通常只有几GB。一旦达到几十GB基本可以断定有东西“按住”了日志。接下来就要找是谁按住的。2.2 精准定位复制槽与日志膨胀的因果关系定位复制槽其实很简单查视图就行SELECT slot_name, slot_type, active, pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)) AS retained_wal_size FROM pg_replication_slots;重点看两列active和retained_wal_size。active表示这个槽当前是否有下游连接retained_wal_size表示数据库因为这个槽而额外保留的WAL体积。如果一个槽active为false但retained_wal_size还在不断变大那它就是磁盘杀手基本可以宣判了。逻辑复制槽还可以进一步看它落后了多少SELECT slot_name, pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), confirmed_flush_lsn)) AS lag_bytes FROM pg_replication_slots WHERE slot_type logical;这里有个容易踩的坑active为true不代表没有问题。很多逻辑复制槽虽然有连接但下游消费极慢消费速度跟不上WAL产生速度confirmed_flush_lsn迟迟不前进日志一样会暴涨。所以不要只看active要看restart_lsn或confirmed_flush_lsn是不是在持续增长。2.3 删除前的三重确认定位到可疑复制槽之后别急着执行删除。复制槽删掉容易但要恢复可就难了。删错了意味着下游备库或订阅端永远丢失同步起点只能重新做全量初始化。所以删除前我固定做三个确认。第一确认这个槽对应的下游是否真的不再需要。物理复制槽要确认备库已经不需要重新同步或者备库已经销毁逻辑复制槽要确认订阅端任务已经彻底停止、且不会再恢复。第二确认整个复制拓扑里没有其他依赖。有时候一个槽看起来没用但某个报表平台或灾备系统还在周期性拉取。第三也是最容易被忽略的确认下游不会在删除后再次尝试连接。如果应用配置里还留着这个槽的名字下游重连时会直接报错甚至反复触发告警。我的习惯做法是先在下游侧停掉任务或断开连接观察主库上该槽的active变为false并稳定一段时间再回头删除。三步确认做完才进入实操环节。3. 实操全流程安全删除复制槽并释放空间3.1 删除复制槽的标准动作确认完毕删除动作本身只是一条SQL的事SELECT pg_drop_replication_slot(slot_name);但要注意这条命令有一个硬前提槽当前必须不是active状态。如果槽正在被下游使用直接执行会报错提示replication slot slot_name is active for PID xxxx。这时候先停掉下游连接再回来删。物理复制槽相对简单备库断开、销毁后直接删就行。逻辑复制槽要小心一点如果下游是PostgreSQL的逻辑订阅正确的顺序是先在下游执行删除订阅-- 在下游库执行 DROP SUBSCRIPTION subscription_name;然后再回主库删复制槽。如果反过来或者直接在主库强制删下游订阅会变成“断线重连但槽已不存在”的状态报错刷屏处理起来反而多一道手续。有一种情况比较特殊槽确实不再需要但下游任务已经彻底失联连下去停任务的机会都没有槽一直activefalse但日志一直涨。这种时候可以直接在主库删除因为activefalse本身就说明下游已经断开了。但如果下游进程还活着只是连接断了又反复重连就要稍微等待或用pg_terminate_backend处理掉对应连接进程。实际操作里我建议绝大多数情况都先尝试在下游停任务让删除操作万无一失。3.2 归档日志联动清理PostgreSQL与Oracle的对比删除复制槽只是第一步。很多海量库除了在线WAL日志还开了归档日志。复制槽删除后WAL目录会慢慢回落但归档目录里的历史日志不会自动消失。归档清理的规则和在线WAL完全不一样它不受复制槽影响而是靠备份保留策略。PostgreSQL里清理归档日志通常配合archive_cleanup_command或pg_archivecleanup工具pg_archivecleanup /path/to/archive /000000010000000000000123关键是找到所有归档里最新的、已经完成备份的日志编号然后清理它之前的所有文件。前提是你确认没有备库还需要从更早的归档恢复。Oracle场景下的归档清理逻辑类似但命令不同用的是RMAN。热搜词里提到的“oracle 清理归档日志”也是这个需求的典型场景。Oracle里最常用的清理方式rman target / DELETE ARCHIVELOG ALL COMPLETED BEFORE SYSDATE-7;这个命令删掉7天前的所有归档日志。更强的姿势是配合备份策略先备份再删除不需要保留的归档保证任何时间点恢复能力不丢。下面这个表格可以直接存下来对照数据库在线日志目录归档清理命令适用场景PostgreSQLpg_walpg_archivecleanup手动清理归档需指定截止日志Oracleredo logRMAN: DELETE ARCHIVELOG按时间或SCN批量清理归档MySQLbinlogPURGE BINARY LOGS BEFORE清理指定时间之前的binlog很多新手只盯着复制槽删删完一看磁盘还是满的才想起来归档目录没管。海量库运维里在线日志和归档日志必须“两手抓”一次处置都清理掉才能把磁盘真正救回来。3.3 清理后的验证与监控恢复删完复制槽、清理完归档不等于事情结束了。必须验证空间真的释放了并且监控要重新“武装”起来防止下次再犯。删除复制槽后WAL日志不会立刻消失要等下一个checkpoint周期触发WAL回收或者手动触发一次checkpoint加速释放CHECKPOINT;执行完后再次检查WAL目录大小du -sh $PGDATA/pg_wal正常情况下几十GB甚至上百GB的日志会在几分钟内被回收。如果等了一会儿还没降下来检查一下max_wal_size和checkpoint_timeout参数它们会影响日志回收的节奏。归档目录用同样的思路确认删除是否生效。然后要做的就是把监控补上。我强烈建议给这三个指标都配上告警复制槽保留的WAL体积、pg_wal目录总大小、复制槽数量。任何一项异常第一时间就能发现。你可以在监控系统里加一条类似这样的SQL做定期巡检SELECT slot_name, pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)) AS retained_size FROM pg_replication_slots WHERE pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn) 10 * 1024 * 1024 * 1024;把超过10GB的槽直接拉出来报警基本能赶在磁盘爆掉之前处理掉问题。这几条我是踩过坑之后才补上的早配上能少熬好几个通宵。4. 实战案例复盘与避坑手册4.1 我踩过的坑删不掉的槽和误删后的灾难这里面我把这几年真实遇到过的问题摊开讲讲。先说“删不掉的槽”。有一次我确认某个逻辑复制槽完全没用了执行删除却一直报active。查了半天发现是一个已经死掉的CDC进程残留了连接在数据库侧显示为active。处理办法是定位到对应的backend进程并终止SELECT pid, state, query FROM pg_stat_activity WHERE query LIKE %slot_name%;然后对查到的pid执行SELECT pg_terminate_backend(pid);杀掉残留进程后槽就变成false了删除成功。这个坑很常见因为很多连接池或中间件会保持连接不释放表面看有下游实际下游早就死了。再说“误删后的灾难”这个更疼。一次物理复制槽删除因为沟通不到位主库的槽删了但备库还在持续落后并依赖这个槽做增量同步。删除后备库直接报requested WAL segment has already been removed整个备库无法继续同步最后只能重新做全量基础备份恢复折腾了几个小时。这件事之后我定了一条铁律物理复制槽的删除必须关联备库状态确认备库状态不明绝不删。哪怕多花十分钟核对也值得。4.2 给日志膨胀上“保险”三个实用级别的预防手段处理了这么多次日志膨胀问题我总结出的预防手段分成三个档次。第一档是“保险丝”给复制槽保留的WAL体积设置告警。一旦某个槽保留的日志超过阈值比如10GB或20GB立刻告警趁早干预。这个最简单但最有效能防住绝大多数磁盘告警。第二档是“限流阀”对海量数据库的WAL产生速率做了解和评估。算出这个库一天产生多少WAL然后反推“复制槽最多可以容忍卡多久”。比如日增200GB、磁盘总空间1TB那复制槽最多卡不到两天就必须处理。有了这个数你才能设定合理的告警阈值和应急预案。第三档是“应急预案”下游出现故障时要迅速判断是“等下游恢复”还是“放弃下游”。如果下游是灾备一般要尽量恢复如果下游是临时的数据抽取任务直接删槽重建反而更快。这个决策要想清楚不要等磁盘满了再做那时候数据库可能已经连不上了。4.3 一次磁盘告警的完整处置时间线用一次真实生产事件复盘收尾更能说明问题。某海量库实例数据盘总量800GB日常WAL目录30GB左右。周一早上6点监控告警磁盘使用率85%。到了早上8点磁盘使用率已经91%WAL目录涨到110GB。排查步骤和耗时如下8:05 登录服务器先确认pg_wal目录大小发现110GB确认日志堆积8:12 查询pg_replication_slots发现一个逻辑复制槽slot_dts的confirmed_flush_lsn停在两天前retained_wal_size显示约78GB。而下游DTS任务因为周末维护被停掉了没人通知数据库团队8:20 确认下游任务已无需恢复且业务方确认不再使用该同步链路8:25 在主库执行SELECT pg_drop_replication_slot(slot_dts);删除成功8:30 主动触发CHECKPOINT;WAL目录开始回收8:45 磁盘使用率从91%回落到35%WAL目录恢复至30GB左右9:00 补充监控告警复制槽保留日志超过10GB即告警。整个过程不到一小时真正操作只有两条SQL但前面定位和确认花了大半时间。磁盘从91%降到35%那一刻心情舒畅。这就是复制槽删除在实际运维中的价值定位精准、操作简单、效果立竿见影。我个人在实际操作中的体会是海量数据库日志清理技术难点从来不在“会不会删”上而在“能不能快速定位该删谁”。复制槽删除前宁可多花时间确认上下文也不要手快。另外每次处理完日志膨胀问题记得顺手做一件小事把这次事件的根因、处置过程、耗时记录到运维文档里。下次同类问题出现时翻出来照着走一遍真的能救命。最后再分享一个小技巧日常巡检时不要只看复制槽数量要换算成“日志体积”来感知风险。一个槽可能无害但当它开始堆积时每多等一小时就多一分磁盘被打满的风险。
返回列表