稳如老狗:PG 19 停库不再死等 WAL Sender
PG 19 预览 - WAL Sender 关闭超时:复制环境下的优雅关闭
本文档解读 Fujii Masao 提交的 wal_sender_shutdown_timeout GUC,该参数限制 walsender 在关闭期间等待复制的最长时间,解决逻辑复制场景下关闭延迟的问题。
相关 Commit
a8f45dee9176 | |
21b018e7eab5 | |
57706799186a | |
2497dac55648 |
核心价值
可控关闭时间:设置最大等待时间,避免无限等待 解决痛点:逻辑复制 apply worker 阻塞时,walsender 不再无限等待 可配置:默认 -1(禁用),可以设为毫秒级超时
原理详解
1. GUC 定义
1.1 变量定义 (src/backend/replication/walsender.c line 146-148)
/* 最大等待时间(毫秒),-1 表示禁用 */
int wal_sender_shutdown_timeout = -1;
1.2 GUC 元数据 (src/backend/utils/misc/guc_parameters.dat line 3507-3515)
wal_sender_shutdown_timeout
GUC_NAME
wal_sender_shutdown_timeout
GUC_CONTEXT SUGGEST_CONTEXT
vartype INT
min -1
max INT_MAX / 1000
unit ms
boot_val -1
range_val -1, 2147483
group CONNECTIONS_AND_AUTH / REPLICATION
short_desc "Sets the maximum time to wait for replication to finish during shutdown."
1.3 外部声明 (src/include/replication/walsender.h line 36)
extern PGDLLIMPORT int wal_sender_shutdown_timeout;
2. 核心检查函数
2.1 WalSndCheckShutdownTimeout() (line 2964-2996)
staticvoid
WalSndCheckShutdownTimeout(void)
{
TimestampTz now = GetCurrentTimestamp();/* 如果没有请求关闭,直接返回 */
if (shutdown_request_timestamp == 0)
return;
/* 如果已超过超时时间 */
if (TimestampDifferenceExceeds(shutdown_request_timestamp,
now,
wal_sender_shutdown_timeout))
{
/* 执行强制关闭 */
WalSndDoneImmediate();
}
}
2.2 WalSndComputeSleeptime() (line 2876-2919)
计算 sleep 时间,当有关闭请求且设置了超时时:
staticlong
WalSndComputeSleeptime(TimestampTz now)
{
TimestampTz wakeup_time;
long sleeptime = 10000; /* 默认 10 秒 */if (wal_sender_timeout > 0 && last_reply_timestamp > 0)
{
/* 基于 wal_sender_timeout 计算唤醒时间 */
wakeup_time = TimestampTzPlusMilliseconds(last_reply_timestamp,
wal_sender_timeout);
/* 如果还没有发送 ping,在超时一半时唤醒 */
if (!waiting_for_ping_response)
wakeup_time = TimestampTzPlusMilliseconds(last_reply_timestamp,
wal_sender_timeout / 2);
/* 计算相对唤醒时间 */
sleeptime = TimestampDifferenceMilliseconds(now, wakeup_time);
}
if (shutdown_request_timestamp != 0 && wal_sender_shutdown_timeout > 0)
{
long shutdown_sleeptime;
wakeup_time = TimestampTzPlusMilliseconds(shutdown_request_timestamp,
wal_sender_shutdown_timeout);
shutdown_sleeptime = TimestampDifferenceMilliseconds(now, wakeup_time);
/* 选择最早的唤醒时间 */
if (shutdown_sleeptime < sleeptime)
sleeptime = shutdown_sleeptime;
}
return sleeptime;
}
2.3 WalSndDoneImmediate() (line 3710-3748) - 强制关闭实现
staticvoid
WalSndDoneImmediate(void)
{
WalSndState state = WalSnd->state;/* 只对活动状态执行强制关闭 */
if (state == WALSNDSTATE_CATCHUP ||
state == WALSNDSTATE_STREAMING ||
state == WALSNDSTATE_STOPPING)
{
/* 发送 CopyDone 命令 */
EndCommand("CopyDone", DestReceiver);
/* 尝试刷新输出,但不阻塞 */
pq_flush_if_writable();
/* 记录警告日志 */
ereport(WARNING,
(errmsg("terminating walsender due to replication shutdown timeout"),
errdetail("Walsender process might have been terminated before "
"all WAL data was replicated to the receiver.")));
/* 标记不再发送输出 */
whereToSendOutput = DestNone;
/* 立即退出进程 */
proc_exit(0);
}
}
3. 调用时机
WalSndCheckShutdownTimeout() 在以下位置被调用:
WalSndSendFunctions() | ||
WalSndReceive | ||
WalSndLastCycle() |
4. 关闭流程
1. Postmaster 发送 SIGUSR2 信号
└── got_SIGUSR2 = true (line 3885)2. WalSndCheckShutdownTimeout() 首次被调用
└── 记录 shutdown_request_timestamp
3. 后续每次调用检查是否超时
└── TimestampDifferenceExceeds() 比较时间差
4. 超时后调用 WalSndDoneImmediate()
└── 发送 CopyDone
└── 记录警告日志
└── 设置 whereToSendOutput = DestNone
└── proc_exit(0)
5. 传统行为 vs 新行为对比
传统行为(问题场景)
时间线:
T=0: 管理员执行 pg_ctl stop
T=0: Postmaster 向所有 walsender 发送 SIGUSR2
T=0: walsender 开始等待复制完成
T=1: apply worker 获取排他锁被阻塞
T=30: 管理员等待 30 秒...
T=60: 管理员等待 60 秒...
T=300: apply worker 仍未释放锁,关闭卡住
(如果设置了 statement_timeout,可能永远不会释放)
新行为(wal_sender_shutdown_timeout=30)
时间线:
T=0: 管理员执行 pg_ctl stop
T=0: Postmaster 向所有 walsender 发送 SIGUSR2
T=0: walsender 开始等待,最长 30 秒
T=1: apply worker 获取排他锁被阻塞
T=30: WalSndCheckShutdownTimeout() 检测到超时
T=30: WalSndDoneImmediate() 执行强制关闭
T=30: 记录警告日志,walsender 退出
T=31: Postmaster 继续关闭流程
使用实践
1. 配置 wal_sender_shutdown_timeout
在 postgresql.conf 中设置:
# 推荐值:30 秒(对于大多数场景足够)
wal_sender_shutdown_timeout = 30s# 保守值:60 秒
# wal_sender_shutdown_timeout = 60s
# 禁用(默认行为,无限等待)
# wal_sender_shutdown_timeout = -1
2. 查看当前配置
-- 查看 wal_sender_timeout 和 wal_sender_shutdown_timeout
SHOW wal_sender_shutdown_timeout;
SHOW wal_sender_timeout;-- 查看 walsender 状态
SELECT
pid,
state,
sent_lsn,
write_lsn,
flush_lsn,
replay_lsn,
sync_state
FROM pg_stat_replication;
3. 监控关闭过程
-- 查看正在进行的 walsender 进程
SELECT
pid,
usename,
application_name,
state,
query_start,
query
FROM pg_stat_activity
WHERE application_name LIKE'%walsender%';-- 查看 slot 状态
SELECT
slot_name,
plugin,
slot_type,
active,
restart_lsn
FROM pg_replication_slots;
4. 物理复制 vs 逻辑复制
物理复制(通常不需要设置)
# 物理复制通常是同步的,walsender 等待确认
# 关闭时很少出现长时间等待
wal_sender_shutdown_timeout = -1 # 默认
逻辑复制(推荐设置)
# 逻辑复制 apply worker 可能被长时间运行的语句阻塞
wal_sender_shutdown_timeout = 30s
5. 高可用场景下的设置
场景 1:主库切换
# 主库切换时,确保 walsender 不会无限等待
wal_sender_shutdown_timeout = 60s# 同时设置合理的 wal_sender_timeout
wal_sender_timeout = 60s
场景 2:级联复制
# 级联复制中,中间节点可能需要更长时间
wal_sender_shutdown_timeout = 120s
6. 日志分析
当发生超时关闭时,日志会记录:
WARNING: terminating walsender process due to replication shutdown timeout
DETAIL: Walsender process might have been terminated before all WAL data was replicated to the receiver.
处理建议:
检查 apply worker 阻塞原因 考虑增加超时时间 检查复制槽状态 评估数据一致性风险
7. 与其他参数的关系
wal_sender_timeout | ||
wal_sender_shutdown_timeout | ||
wal_keep_size | ||
max_replication_slots |
8. 故障排除
问题:关闭时复制数据丢失
原因:超时设置过短,WAL 还在等待复制时被强制关闭
解决:
增加 wal_sender_shutdown_timeout检查 apply worker 阻塞原因 确保 wal_keep_size足够大
-- 检查复制延迟
SELECT
slot_name,
pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)) AS retained_wal
FROM pg_replication_slots;
问题:walsender 一直不退出
原因:wal_sender_shutdown_timeout = -1(禁用)
解决:设置合理的超时值
wal_sender_shutdown_timeout = 60s
总结
wal_sender_shutdown_timeout 是复制环境下的重要改进,解决了逻辑复制场景下关闭延迟的痛点。
关键要点:
解决问题:apply worker 阻塞导致 walsender 无限等待 推荐值:30-60 秒对大多数场景足够 物理复制:通常不需要设置,保持默认 -1 逻辑复制:建议设置,避免关闭卡住 数据安全:超时后会记录警告,可能丢失未复制的 WAL 数据