<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>DB PATCH · 中文 · 数据库运维</title><description>记录 PostgreSQL、数据库运维、后端工程与 AI 实践的个人技术空间。</description><link>https://db-patch.com/tags/operations/</link><language>zh-CN</language><atom:link href="https://db-patch.com/tags/operations/rss.xml" rel="self" type="application/rss+xml"/><item><title>pg_wal 突然涨到 1TB，我查了这几个地方</title><link>https://db-patch.com/posts/pg-wal-1tb/</link><guid isPermaLink="false">dbpatch:zh:pg-wal-1tb</guid><description>一次 PostgreSQL 10 生产故障复盘：Flink CDC 的失活逻辑复制槽长期不推进，导致 WAL 无法回收、pg_wal 膨胀至 1TB 并写满磁盘。</description><pubDate>Tue, 08 Sep 2026 16:00:00 GMT</pubDate><content:encoded>&lt;p&gt;PostgreSQL 10 · 一主一备 · Flink CDC · 生产故障复盘&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://db-patch.com/images/pg-wal-1tb-cover.webp&quot; alt=&quot;蓝天下的 PostgreSQL 大象与 WAL 日志文件，DB PATCH 文章封面&quot; /&gt;&lt;/p&gt;
&lt;p&gt;这是一个平静的周五。白天刚做完检查，数据库、主备、磁盘都正常。我已经在等周六了：睡到自然醒，不看告警，最好连电脑都不开。&lt;/p&gt;
&lt;p&gt;半夜电话响了。&lt;/p&gt;
&lt;p&gt;“现场挂了，业务连不上数据库。”&lt;/p&gt;
&lt;p&gt;明明刚检查过，怎么会突然挂？&lt;/p&gt;
&lt;h2&gt;先看磁盘&lt;/h2&gt;
&lt;p&gt;登录机器，数据库进程已经退出。&lt;code&gt;df&lt;/code&gt; 一看，&lt;strong&gt;数据库分区 100%&lt;/strong&gt;。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;df -h
du -sh &quot;$PGDATA&quot;/* | sort -h
# 输出
1.0T    /data/pgsql/10/data/pg_wal
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;pg_wal&lt;/code&gt; 涨到了 1TB。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;脑子里先过一遍：归档失败？备库断了？复制槽不推进？&lt;code&gt;wal_keep_segments&lt;/code&gt; 被改过？还是最近有大批量写入？&lt;/p&gt;
&lt;p&gt;现场是 PG10，一主一备，核心业务不能长时间停。先停业务写入，也停掉刚上线的 Flink 任务；清理确认无用的应用日志和系统日志，腾出空间后启动数据库。&lt;/p&gt;
&lt;p&gt;WAL 日志是数据修改前先写下的日志，崩溃恢复和主备复制都要用。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;这里清的是普通日志，不是 &lt;code&gt;pg_wal&lt;/code&gt;。手工删 WAL，很可能把“磁盘满”变成“数据库再也起不来”。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;最近上线了什么&lt;/h2&gt;
&lt;p&gt;库起来后，我先问现场：“最近改了什么？”&lt;/p&gt;
&lt;p&gt;答复是刚上线了一套 Flink 业务。继续追问对数据库做改动了吗，答复为了做 CDC，业务让驻场人员把 &lt;code&gt;wal_level&lt;/code&gt; 从 &lt;code&gt;replica&lt;/code&gt; 改成了 &lt;code&gt;logical&lt;/code&gt;，还建了逻辑复制槽。&lt;/p&gt;
&lt;p&gt;改成 &lt;code&gt;logical&lt;/code&gt; 本身没错，Flink 要做逻辑解码就需要它。问题是核心库被重启改了参数、加了持久化复制槽，DBA 到出事才知道，更别说监控和退出方案了。&lt;/p&gt;
&lt;h2&gt;我查了这几个地方&lt;/h2&gt;
&lt;p&gt;先确认实际配置。不要只看 &lt;code&gt;postgresql.conf&lt;/code&gt;，查 &lt;code&gt;pg_settings&lt;/code&gt; 才知道当前值从哪里来的：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;SELECT name, setting, unit, source, pending_restart
FROM pg_settings
WHERE name IN (&apos;wal_level&apos;, &apos;archive_mode&apos;, &apos;archive_command&apos;,
               &apos;max_wal_size&apos;, &apos;min_wal_size&apos;,
               &apos;wal_keep_segments&apos;, &apos;max_replication_slots&apos;)
ORDER BY name;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后看归档和主备的情况：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;SELECT archived_count, last_archived_time,
       failed_count, last_failed_wal, last_failed_time
FROM pg_stat_archiver;

SELECT application_name, client_addr, state, sync_state,
       sent_lsn, write_lsn, flush_lsn, replay_lsn
FROM pg_stat_replication;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;归档没有卡住，主备延迟也解释不了这 1TB。最后查复制槽：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;SELECT slot_name, slot_type, database, active, active_pid,
       restart_lsn, confirmed_flush_lsn,
       pg_size_pretty(
         pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)::bigint
       ) AS retained_wal
FROM pg_replication_slots
ORDER BY pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)
         DESC NULLS LAST;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Flink 的逻辑复制槽 &lt;code&gt;active = false&lt;/code&gt;，&lt;strong&gt;&lt;code&gt;restart_lsn&lt;/code&gt; 很久没动，保留量接近 1TB&lt;/strong&gt;。找到了。&lt;/p&gt;
&lt;p&gt;复制槽就是下游留在数据库里的读取位置。下游不推进，PostgreSQL 就不敢回收它可能还要用的 WAL。PG10 又没有复制槽保留上限，于是 Flink 不再消费，核心业务还在写，&lt;code&gt;pg_wal&lt;/code&gt; 就一直涨到磁盘满。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;wal_level = logical&lt;/code&gt; 会增加 WAL 量，但这次真正拖住回收的是复制槽。这个锅不能只甩给 PostgreSQL。&lt;/p&gt;
&lt;h2&gt;别看到槽就直接删&lt;/h2&gt;
&lt;p&gt;删除槽会丢掉原来的消费位置。先找业务确认：任务还要不要？能不能恢复消费？是否接受重新做全量？确认槽已经废弃或可以重建后，才能执行 &lt;code&gt;SELECT pg_drop_replication_slot(&apos;flink_slot_name&apos;);&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;根因解除并留出足够磁盘空间后，等待检查点回收旧 WAL；确有需要再由 DBA 执行 &lt;code&gt;CHECKPOINT;&lt;/code&gt;。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;不要为了省几分钟直接删 &lt;code&gt;pg_wal&lt;/code&gt; 文件。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;下次先查这四项&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;归档失败，或者归档速度赶不上 WAL 生成速度；&lt;/li&gt;
&lt;li&gt;物理或逻辑复制槽落后，任务下线后槽却没删；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;wal_keep_segments&lt;/code&gt; 配得过大；&lt;/li&gt;
&lt;li&gt;批量导入、更新、删除或频繁检查点，让 WAL 突然增多。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;长事务会影响 VACUUM，但不会仅仅因为没提交就让旧 WAL 永远不能回收。这个问题很容易和表膨胀混在一起。&lt;/p&gt;
&lt;p&gt;PG10 最大的麻烦是复制槽可以无限保留 WAL，&lt;code&gt;max_wal_size&lt;/code&gt; 管不住。&lt;strong&gt;PG13 才加入 &lt;code&gt;max_slot_wal_keep_size&lt;/code&gt;&lt;/strong&gt;。它限制的是槽最多保留多少 WAL，不是槽本身的大小；默认值 &lt;code&gt;-1&lt;/code&gt; 仍表示不限制。&lt;/p&gt;
&lt;p&gt;我的做法很简单：监控每个槽保留了多少 WAL、位置是否推进，同时监控归档和磁盘；任务下线必须处理槽；PG10 靠外部告警兜底，并尽快升级。对核心库，确认任务已经废弃并准备好重建方案后，我宁愿主动处置不正常的复制槽，也不愿让失联的消费者拖死主库。&lt;/p&gt;
&lt;p&gt;那晚最有用的问题不是哪条 SQL，而是：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;“最近上线了什么？”&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;参考资料&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.postgresql.org/docs/10/runtime-config-wal.html&quot;&gt;PostgreSQL 10：WAL 配置&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.postgresql.org/docs/10/view-pg-replication-slots.html&quot;&gt;PostgreSQL 10：pg_replication_slots&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.postgresql.org/docs/13/runtime-config-replication.html#GUC-MAX-SLOT-WAL-KEEP-SIZE&quot;&gt;PostgreSQL 13：max_slot_wal_keep_size&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded><category>PostgreSQL</category><category>数据库运维</category></item></channel></rss>