数据库备份是实际运维中必须进行的操作,然而有时因为没空或者懒,便忽视了这项工作,有时甚至造成损失。

背景

今天早上对本博客的 nodejsnpm 进行升级时,apt 提示部分包可以使用 apt autoremove 命令删除。为了减少系统体积,便执行了。因为注意到执行前系统内存占用在 600M 左右,执行后内存占用在 240M 左右,便感觉到不对劲。于是首先便想到了同一台服务器上的数据库,执行:

root@Aliyun:~# systemctl status postgres
Unit postgres.service could not be found.

想着出事了,博客上运行的 PostgreSQL 不翼而飞。由于用户量不大,开始不慌不忙地进行恢复工作。

恢复

旧的 pg 版本是 12,正好之前一直想升级,就干脆装了个 13 版本的,安装完后同步一下数据就好了。

初步的想法:

  1. 首先把数据库再装上去,按照 在 Linux 上安装 PostgreSQL 的方法,新安装一个 PostgreSQL 数据库。

  2. 同时上传之前利用数据库导出功能,在关键时刻对数据库做的备份。并导入(部分细节模糊处理)

    root@Aliyun:/# su postgres
    postgres@Aliyun:/$ psql -p 1000
    psql (13.1 (Debian 13.1-1.pgdg90+1))
    Type "help" for help.
    
    postgres=# \c kite
    you are now connected to database "kite" as user "postgres".
    kite-# \i /var/backup/kite-20201215.sql
    
    (...)
    kite-#\q
    

数据恢复到 12 月 15 日的存档。由于近段时间没有过多修改,可以慢慢补上了。到此线上已经可以继续使用了。

找回数据

我们都知道使用 apt 卸载软件,并不会完全删除软件操作的数据,尤其是数据库管理系统这样的软件。查了查资料,发现数据库文件存储在 /var/lib/postgresql/12/main 中。

postgres@Aliyun:~/12/main$ ls
base          pg_notify     pg_subtrans  postgresql.auto.conf
global        pg_replslot   pg_tblspc    postmaster.opts
pg_commit_ts  pg_serial     pg_twophase  postmaster.pid
pg_dynshmem   pg_snapshots  PG_VERSION
pg_logical    pg_stat       pg_wal
pg_multixact  pg_stat_tmp   pg_xact

但是这些数据应该怎么读呢?一种方法是,将 postgresql-12 再安装上,并导出、再导入数据库;另一种方法是,使用 pg_upgrade 工具升级。因为没有尝试过后一种方法,先试试。

pg_upgrade

pg_upgrade 与其他 postgres 小工具在同一个目录下,而不作为 deb 包单独发博。所以我刚开始使用 apt 安装时,不出意料地出现了:

root@Aliyun:~# apt install pg_upgrade
Reading package lists... Done
Building dependency tree
Reading state information... Done
E: Unable to locate package pg_upgrade
root@Aliyun:~#

后来找了找,在 /usr/lib/postgresql/13/bin 下。 --help 看一下:

root@Aliyun:~# /usr/lib/postgresql/13/bin/pg_upgrade --help
pg_upgrade upgrades a PostgreSQL cluster to a different major version.

Usage:
  pg_upgrade [OPTION]...

Options:
  -b, --old-bindir=BINDIR       old cluster executable directory
  -B, --new-bindir=BINDIR       new cluster executable directory (default
                                same directory as pg_upgrade)
  -c, --check                   check clusters only, don't change any data
  -d, --old-datadir=DATADIR     old cluster data directory
  -D, --new-datadir=DATADIR     new cluster data directory
  -j, --jobs=NUM                number of simultaneous processes or threads to use
  -k, --link                    link instead of copying files to new cluster
  -o, --old-options=OPTIONS     old cluster options to pass to the server
  -O, --new-options=OPTIONS     new cluster options to pass to the server
  -p, --old-port=PORT           old cluster port number (default 50432)
  -P, --new-port=PORT           new cluster port number (default 50432)
  -r, --retain                  retain SQL and log files after success
  -s, --socketdir=DIR           socket directory to use (default current dir.)
  -U, --username=NAME           cluster superuser (default "root")
  -v, --verbose                 enable verbose internal logging
  -V, --version                 display version information, then exit
  --clone                       clone instead of copying files to new cluster
  -?, --help                    show this help, then exit

Before running pg_upgrade you must:
  create a new database cluster (using the new version of initdb)
  shutdown the postmaster servicing the old cluster
  shutdown the postmaster servicing the new cluster

When you run pg_upgrade, you must provide the following information:
  the data directory for the old cluster  (-d DATADIR)
  the data directory for the new cluster  (-D DATADIR)
  the "bin" directory for the old version (-b BINDIR)
  the "bin" directory for the new version (-B BINDIR)

For example:
  pg_upgrade -d oldCluster/data -D newCluster/data -b oldCluster/bin -B newCluster/bin
or
  $ export PGDATAOLD=oldCluster/data
  $ export PGDATANEW=newCluster/data
  $ export PGBINOLD=oldCluster/bin
  $ export PGBINNEW=newCluster/bin
  $ pg_upgrade

Report bugs to <pgsql-bugs@lists.postgresql.org>.
PostgreSQL home page: <https://www.postgresql.org/>

工具需要指定源和目标的可执行文件位置、数据目录位置、端口。由于该工具是数据库系统升级使用的,因而它会转换所有数据库,并且要求新的数据库(13版本)是空的。

但是我在刚才已经拷贝了 12 月 15 日的备份,因此新的数据库不是空的。我们需要 re-initdb

# 删除所有新数据库文件
rm -r /var/lib/postgresql/13/main/*
# 重建数据库文件
/usr/lib/postgresql/13/bin/initdb /var/lib/postgresql/13/main

即可重置新数据库。接着,进行迁移:

/usr/lib/postgresql/13/bin/pg_upgrade \
	--old-datadir=/var/lib/postgresql/12/main/ \
	--new-datadir=/var/lib/postgresql/13/main/  \
	--old-bindir=/usr/lib/postgresql/12/bin/  \
	--new-bindir=/usr/lib/postgresql/13/bin \
	-p1071 \
	-P20071 \
	-o"-c config_file=/etc/postgresql/12/main/postgresql.conf" \
	-O"-c config_file=/etc/postgresql/13/main/postgresql.conf"

即可完成迁移。

但是我不小心对旧数据库执行了 initdb,所以…永久丢失了部分数据……

 经过上述操作后,理论上就可以恢复 apt autoremove 造成的影响了。

反思

  1. 通常数据库的备份,是通过每日一个全量备份,加上 WAL 日志增量备份来进行的。这样可以随时恢复到任意状态。平时对它研究较少,没有做。
  2. 之前想做一个主从备份,但是 pg 的主从要求从机实时在线,否则无法写入,故作罢。考虑应该搭建一套主从的环境,以应对宕机的情况,毕竟这比数据丢失要好得多。
  3. apt autoremove 命令了解不足。刚才发现网上有不少文章都指出 autoremove 可能存在的问题,但是以前缺少被坑经验,没有防范。

参考资料

[1] gguxxing008, Postgres 数据表文件存储位置, CSDN论坛

[2] cxy486, PostgreSQL利用pg_upgrade升级版本, 博客园

[3] pg_upgrade, PostgreSQL 10 文档

[4] 不剪发的Tony老师, PostgreSQL 数据库跨版本升级常用方案, CSDN博客