Coding的痕迹

一位互联网奔跑者的网上日记

0%

记一次 apt autoremove 引发的“血案”

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

背景

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

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

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

恢复

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

初步的想法:

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

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    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 中。

1
2
3
4
5
6
7
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 安装时,不出意料地出现了:

1
2
3
4
5
6
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 看一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
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

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

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

1
2
3
4
5
6
7
8
9
/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博客