最近帮一个朋友处理了一次生产环境的 PostgreSQL 恢复,要把数据库状态回退到某天凌晨的备份点。环境是两节点 PostgreSQL 集群,主从由 repmgr 管理。
主库恢复不算难,麻烦的是后半段:恢复后怎么把从库重新拉回正确状态。按处理顺序记一下过程和踩到的坑。
确认备份方式
动手之前先看备份是怎么做的。机器上的定时任务:
0 0 * * * /path/to/scripts/pg_dump.sh >> /path/to/scripts/pg_dump.log 2>&1脚本核心逻辑:
backup_dir='/path/to/backups'
/usr/local/pgsql/bin/pg_dumpall -U ${pgsql_user} -h ${pgsql_host} -p ${pgsql_port} -f ${backup_dir}/${curr_sql}
cd ${backup_dir}
tar -zcvf ${curr_file} ${curr_sql}
rm -f ${curr_sql}用的是 pg_dumpall,备份整个实例,输出是 SQL 文本,最后打包成 .tar.gz。
这意味着只能做逻辑恢复,恢复到的"时间点"就是备份导出时的状态,不是 PITR 那种精确到秒的恢复。
不能直接往现有实例里灌
pg_dumpall 的输出包含数据库、角色、权限、schema、表、数据、索引等整实例对象。如果直接往还在运行的实例上执行,会大量报 already exists 错误,最后变成半旧半新的混合状态。
所以恢复思路是:停库 – 备份旧数据目录 – 初始化空实例 – 再导入。
主库恢复
停库,备份旧数据目录
systemctl stop postgresql
mv /path/to/data /path/to/data.bak_YYYYMMDD旧数据目录不要直接删,改名留着。后面万一需要回看配置或旧数据,有这个目录在手里不慌。
初始化空实例
mkdir -p /path/to/data
chown -R postgres:postgres /path/to/data
chmod 700 /path/to/data
su - postgres -c "/usr/local/pgsql/bin/initdb -D /path/to/data"拷回配置文件
把旧目录的配置拷回新目录,保持端口、认证方式、监听地址等和原实例一致:
cp /path/to/data.bak/postgresql.conf /path/to/data/
cp /path/to/data.bak/pg_hba.conf /path/to/data/
chown postgres:postgres /path/to/data/postgresql.conf /path/to/data/pg_hba.conf只拷配置,不拷旧数据文件。
启动并恢复
systemctl start postgresql
/usr/local/pgsql/bin/psql -U postgres -c "select version();"确认空实例正常后,执行恢复:
/usr/local/pgsql/bin/psql -U postgres -f /path/to/backup.sql 2>&1 | tee /path/to/restore.log过程中会刷 CREATE ROLE、CREATE DATABASE、COPY、ALTER TABLE、CREATE INDEX 等输出,都是正常的。跑完后检查日志:
grep -n '^ERROR:' /path/to/restore.log这次没有 ERROR:,算顺利。
验证主库
恢复结束后检查三项:
数据库列表:
select datname from pg_database order by 1;角色列表:
psql -U postgres -c "\du"业务库连接:
psql -U postgres -d <业务库> -c "select current_database();"确认业务库、管理库、各类账号都在,主库恢复完成。
处理从库 – 主从关系断了
主库恢复完看 repmgr 集群状态,发现从库(节点 B)虽然在 repmgr 里注册的是 standby,但实际已经作为 primary 在跑了。
这就是脑裂了。主库是逻辑备份重建的全新实例,从库还保持着自己原来的运行状态,复制关系已经不存在了。
先停从库把风险控住:
systemctl stop postgresql然后走标准的从库重建流程:
# 备份旧目录
mv /path/to/data /path/to/data.bak_YYYYMMDD
# 创建新目录
mkdir -p /path/to/data
chown -R postgres:postgres /path/to/data
chmod 700 /path/to/data
# 从主库 clone
su - postgres -c "repmgr -h <primary_host> -U <repmgr_user> -d <repmgr_db> -f /path/to/repmgr.conf standby clone --force"底层就是 pg_basebackup 从主库拉一份基线。
踩坑:从库监听地址配错了
clone 完启动后,执行 repmgr standby register --force 失败。
排查发现不是复制链路问题,是 postgresql.conf 里的 listen_addresses 不对。之前拷配置的时候把主库的 listen_addresses 一起抄过来了,里面绑的 IP 是主库节点的地址:
listen_addresses = '127.0.0.1,<node_ip>'<node_ip> 不是从库自己的地址,导致 TCP 连接不通。本地 Unix Socket 能用所以 psql 直连没问题,但 repmgr 走 conninfo 连 TCP 端口就报错了。
改成从库自己的地址,重启:
systemctl restart postgresql类似 listen_addresses、conninfo、primary_conninfo 这些带节点属性的配置,两台机器之间不能无脑照抄。
重新注册 standby
监听地址修正后,注册 standby:
su - postgres -c "repmgr -f /path/to/repmgr.conf standby register --force"回主库检查集群状态:
su - postgres -c "repmgr -f /path/to/repmgr.conf cluster show"主节点 primary | running,从节点 standby | running。
再看主库复制状态:
select application_name, client_addr, state, sync_state
from pg_stat_replication;从库以 streaming 状态挂上来,复制链路恢复。
确认业务连接
数据库和复制恢复不等于业务好了,还要看应用有没有连回来:
select usename, datname, client_addr, application_name, state
from pg_stat_activity
where pid <> pg_backend_pid()
order by datname, usename;看到业务账号从应用侧地址连进来(哪怕状态是 idle),说明网络通、鉴权通、目标库正确,应用已经开始恢复连接。再配合业务侧做关键功能验证就行。
小结
回头看这次恢复,主库那边没什么意外,pg_dumpall 逻辑恢复的套路比较固定,记得对空实例做就行。真正花时间的是从库——脑裂、监听地址抄错、repmgr 重新注册,这几个问题串起来折腾了不少时间。
另外 listen_addresses、conninfo、primary_conninfo 这类带节点 IP 的配置,以后在主从之间拷配置的时候要逐项过一遍,不能偷懒。