TT Lab
博客 开始
学习 学习路径 课程

PostgreSQL 复制与提升

测量落后的备库,释放被占住的 WAL

在 TT Lab 中继续学习

目标

区分并测量备用服务器“挂着”与“跟着”。用字节测量接收与应用的差距,实际制造备用服务器查询 与应用的冲突、反馈的代价、被遗弃的 slot 拖住的 WAL,然后修复连接完好、数据却过时的三台备用服务器。

为什么重要

复制监控通常只看连接状态。所以备用服务器落后几个小时也还是绿灯,出报表的人反而先 察觉。反过来,一台被删除的备用服务器留下的一个 slot,也可能在几周内拖住 WAL、填满主服务器的磁盘。两者 只要用位置(LSN)测量“停在了哪里”,就一目了然。而且,减少延迟的每个旋钮都会把代价转移到别处 ——取消查询、备用服务器落后、或主服务器的清理被推迟——亲身经历过,选择设置时就不会迷路。

环境

这个 Pod 以 postgres 账号运行(不用 su 或 sudo,直接输入即可)。主服务器运行在 5432 上。

psql                       주 서버(5432, labdb)에 바로 붙습니다
psql -h 127.0.0.1 -p 5433  이 실습에서 세울 대기 서버

连接备用服务器时务必加上 -h 127.0.0.1(Unix socket 目录已被移走)。本实验的文件放在 /root/db/lag/ 下。

步骤

  1. 用 slot lag1 在 5433 上搭建备用服务器 lag1
  2. 只停止应用,用字节测量积压量 → /root/db/lag/02-paused.txt
  3. 恢复并确认已追上 → /root/db/lag/03-caughtup.txt
  4. 备用服务器查询与应用的冲突 → /root/db/lag/04-conflict.txt
  5. 打开反馈,主服务器的清理就会被推迟 → /root/db/lag/05-feedback.txt
  6. 没人使用的 slot 拖住 WAL → /root/db/lag/06-ghost.txt
  7. 设置上限,切断被遗弃的 slot → /root/db/lag/07-slots.txt
  8. 事件:连接完好,三台报表用备用服务器的数据却已过时

参考

用 slot 接入备用服务器 lag1

在主服务器(5432)上创建物理 slot lag1,把 base backup 做到 /var/lib/postgresql/lag1,并作为备用服务器在 5433 上启动(日志 /var/log/labhub/lag1.log)。备用服务器必须通过 slot lag1 跟随主服务器。

方向:和第一个实验的做法完全一样——先创建 slot,用这个 slot 制作副本,再把副本作为备用服务器启动。 工具:pg_create_physical_replication_slot、pg_basebackup 的 -X stream、-S、-R,postgresql.auto.conf 中的 port,pg_ctl start,备用服务器的 pg_stat_wal_receiver。 具体:创建 slot 时第二个参数传 true,从创建的那一刻起就会拖住 WAL(以免备用服务器首次接入前所需的 WAL 被删除)。账号是 lab(本实验服务器允许回环复制)。连接备用服务器时要加上 -h 127.0.0.1 -p 5433。

只停止应用,用字节测量积压量

只停止备用服务器(5433)的应用(replay)。然后在主服务器上创建表 ticks 并插入 1000 行以上。确认备用服务器上还看不到 ticks,再把备用服务器已接收的位置、已应用的位置以及两者的差,以 receive_lsn: <lsn>、replay_lsn: <lsn>、gap_bytes: <bytes> 三行写入 /root/db/lag/02-paused.txt。让备用服务器保持暂停(下一步再恢复)。

方向:在备用服务器内部,接收和应用是分开运行的。只停止应用,接收仍在继续,两者之间就会拉开差距。 工具:pg_wal_replay_pause()、pg_get_wal_replay_pause_state()、pg_last_wal_receive_lsn()、pg_last_wal_replay_lsn()、pg_wal_lsn_diff()。 具体:两个位置要在同一个 select 中一次取出——分开测量的话,接收位置会在中间移动,差值就对不上。暂停是在备用服务器上调用的函数,在主服务器上调用会报错。

恢复应用并确认已追上

恢复备用服务器的应用,并等到已接收位置与已应用位置相同(追上为止)。然后在 /root/db/lag/03-caughtup.txt 中写下 replay_lsn: <lsn>(追上之后的应用位置)和 ticks_rows: <rows>(在备用服务器上统计的 ticks 行数)两行。

方向:暂停期间接收的 WAL 都在磁盘上,所以恢复后不会重新接收,只会直接应用。 工具:pg_wal_replay_resume(),比较 pg_last_wal_receive_lsn() 与 pg_last_wal_replay_lsn(),count(*)。 具体:刚恢复时可能还在应用中——看到两个位置相同之后再写。评分器会在两侧重新统计 ticks。

备用服务器查询与应用冲突而被取消

把备用服务器的 max_standby_streaming_delay 改为 5s(不重启)。在主服务器上创建 10 万行的表 big,在备用服务器上用可重复读(repeatable read)事务统计 big,并让一个停留约 30 秒的查询保持运行,同时在主服务器上删除 big 的一半并执行 VACUUM。把备用服务器查询得到的输出(包括错误)保存到 /root/db/lag/04-conflict.txt。

方向:主服务器的 VACUUM 会删除“已经没人再看”的行,而备用服务器的查询却在主服务器不知道的地方看着这些行。要应用这条清理记录,备用服务器只能等查询结束,或者取消查询。 工具:在备用服务器上执行 alter system set 与 pg_reload_conf()、多次使用 psql 的 -c(延续事务)、后台运行(&)、备用服务器的 pg_stat_database_conflicts。 具体:备用服务器上的查询放到后台运行,并用 2>&1 把输出写入文件。几秒后在主服务器上执行 delete 和 vacuum,5 秒的上限一过查询就会被取消,冲突统计加一。评分器看的就是这个统计。

打开反馈,代价就转到主服务器

把备用服务器的 hot_standby_feedback 改为 on(不重启)。像 04 那样在备用服务器上保持一个读取 big 的长查询,同时在主服务器上再删除一些 big 的行,运行 VACUUM (VERBOSE) big,并把输出保存到 /root/db/lag/05-feedback.txt。

方向:反馈是备用服务器告诉主服务器“我的查询还在看这个时间点的行”的机制。查询保住了,但删不掉这些行的是主服务器。 工具:备用服务器上的 alter system set 与 pg_reload_conf()、VACUUM (VERBOSE) 的 tuples 行、主服务器 pg_replication_slots 中的 xmin。 具体:VERBOSE 的输出以 INFO 写到标准错误——要用 2>&1 接收。“N are dead but not yet removable”中的 N 大于 0,就说明反馈阻止了清理。

没人使用的 slot 拖住 WAL

在主服务器上创建一个没人会使用的物理 slot ghost(让它从创建那一刻起就拖住 WAL)。向表 bulk 大量写入,使 ghost 拖住的 WAL 累积到 100MB(104857600 字节)以上,然后执行 CHECKPOINT。在 /root/db/lag/06-ghost.txt 中写下 ghost_restart_lsn: <lsn>、current_lsn: <lsn>(主服务器当前位置)、retained_bytes: <bytes>(两个位置的差)三行。

方向:slot 的 restart_lsn 是“从这里开始不要删”的承诺。没有使用它的备用服务器,这个位置就永远不会移动。 工具:pg_create_physical_replication_slot(, true)、insert … select generate_series、CHECKPOINT、pg_replication_slots 的 restart_lsn 与 wal_status、pg_current_wal_lsn()、pg_wal_lsn_diff()、pg_ls_waldir()。 具体:几十万行 200 字节的行,WAL 就会超过 100MB。用 pg_ls_waldir() 的合计确认一下,即使执行检查点,pg_wal 目录的大小也不会减小。两个位置要一次取出。

设置上限,切断被遗弃的 slot

把主服务器的 max_slot_wal_keep_size 设为 64MB(不重启)。用 CHECKPOINT 确认 ghost 被切断(wal_status 为 lost),把当时 pg_replication_slots 的输出保存到 /root/db/lag/07-slots.txt,然后删除 ghost。存活的备用服务器的 slot lag1 必须照常跟上。

方向:上限是“一个 slot 最多能拖住多少 WAL”。切断超出的 slot 来保护主服务器的磁盘,被切断的备用服务器则必须重新搭建。 工具:alter system set 与 pg_reload_conf()、CHECKPOINT、pg_replication_slots 的 wal_status 与 safe_wal_size、主服务器日志 /var/log/postgresql.log、pg_drop_replication_slot。 具体:上限检查不是在修改设置的那一刻,而是在检查点时运行。切断时,主服务器日志中会留下一行含有该 slot 名称的记录。确认 lost 的输出要在删除之前保存。

事件——连接完好,三台报表用备用服务器的数据却已过时

运行 incident rp-stale-standby 就会发生事件(请先阅读工单)。症状:跟随主服务器 rp-src(127.0.0.1:5450)的三台报表用备用服务器 rpt1、rpt2、rpt3(5451、5452、5453)都以 streaming 状态连着,但查询时看不到刚写入主服务器的内容。请让三台都实时跟上主服务器,并纠正导致问题的设置,防止同样的事再次发生。备用服务器必须仍然是备用服务器,只有在修改必须重启才能改变的设置时才重启(每台备用服务器上都连着仪表板会话)。在 /root/incident/rp-stale-standby/report.txt 中为每台备用服务器写一行 rpt1: <code> 和一行 evidence-rpt1: <observed line>(rpt2、rpt3 格式相同)。每台备用服务器的代码是以下之一:apply-delay(故意推迟应用的设置)、conflict-wait(无限等待与备用服务器查询的冲突)、recovery-target(到达恢复目标点而停止)、replay-paused(有人手动停止了应用)。

方向:连接完好,说明接收在进行——问题在应用。先按“是否处于暂停状态”两两分开,再看每一对之内有什么不同。 工具:主服务器 pg_stat_replication 的 flush_lsn、replay_lsn 与 pg_wal_lsn_diff,备用服务器的 pg_get_wal_replay_pause_state(),用 show(或 pg_settings)查看应用与恢复相关设置,pg_stat_activity(startup 进程的 wait_event、长时间停留的查询的 backend_xmin),备用服务器日志(/var/log/labhub/rp-stale/.log)。 具体:处于暂停状态的两台中,有一台一旦 resume 就会结束恢复并晋级——把日志中的 HINT 让你做什么读到最后。replay_lag 这类时间数字在应用停住时显示的是过时的值。大多数应用设置 reload 就能改变,但 recovery_target_* 需要重启才会改变。