postgresql
的备份与恢复
postgresql
的备份恢复方式主要分为三种sql
转储,文件系统级别备份,连续归档和时间点恢复,每种备份恢复方式都有其优缺点,实现方式都不太一样。
测试数据库搭建
新建compose.yaml
文件
采用postgres15
版本
services:
postgres:
image: postgres:15-bullseye
container_name: demo-postgres
restart: always
volumes:
- demo-postgres-data:/var/lib/postgresql/data
ports:
- 127.0.0.1:5432:5432
environment:
- POSTGRES_DB=demo
- POSTGRES_USER=demo
- POSTGRES_PASSWORD=demo-pass
- TZ=Asia/Shanghai
volumes:
demo-postgres-data: {}
启动数据库
$ docker compose up -d
创建数据库表
CREATE TABLE public.users (
id serial NOT NULL,
"name" varchar(45) NOT NULL,
age int4 NULL,
"locked" bool NOT NULL DEFAULT false,
created_at timestamp NOT NULL,
CONSTRAINT users_pkey PRIMARY KEY (id)
);
测试插入单条数据
INSERT INTO users (name, age, created_at) VALUES ('Jim', 18, NOW());
测试批量写入数据
通过WHILE i < 5 LOOP
的判断语句决定执行次数
插入数据的name
字段通过user
前缀拼接一个随机数字写入
age
字段也是一个随机整数
DO $$
DECLARE
i INTEGER := 1;
BEGIN
WHILE i < 5 LOOP
INSERT INTO users (name, age, created_at) VALUES ('user'||round(random()*i*i), round(random()*i), NOW());
i = i + 1;
END LOOP;
END $$;
最后执行查询语句
select * from users;
id | name | age | locked | created_at |
---|---|---|---|---|
1 | Jim | 18 | false | 2022-11-18 15:37:22 |
2 | user0 | 0 | false | 2022-11-18 15:37:33 |
3 | user0 | 0 | false | 2022-11-18 15:37:33 |
4 | user1 | 1 | false | 2022-11-18 15:37:33 |
5 | user13 | 0 | false | 2022-11-18 15:37:33 |
sql
转储实现
概念逻辑
- 创建一个由
sql
命令组成的文件,当把这个文件回馈给服务器时,服务器将利用其中的sql
命令重建与转储时状态一样的数据库 - 采用
pgdump
或者pg_dumpall
工具,可以远程执行,需要有读取数据表的权限 - 转储表现了
pg_dump
开始运行时刻的数据库快照,在pg_dump
运行过程中发生的更新将不会被转储 pg_dump
工作的时候并不阻塞其他的对数据库的操作,但是会阻塞那些需要排它锁的操作,比如大部分形式的ALTER TABLE
pg_dump
实现的sql
转储方式,备份和恢复比较简单直接,不大容易遇到什么复杂的问题,由于是整张表或者整个数据库的备份,所以整体资源消耗比较大- 属于热备份,在数据库服务持续运行的情况下备份与恢复
- 创建一个由
备份存储
在宿主机上面pg_dump
可以采用sudo apt install postgresql-client
安装,但是pg_dump
如果和docker
里面的pg
版本对不上的话,执行导出命令会报错如下
pg_dump: error: server version: 15.1 (Debian 15.1-1.pgdg110+1); pg_dump version: 14.5 (Ubuntu 14.5-0ubuntu0.22.04.1)
pg_dump: error: aborting because of server version mismatch
pg_dump
是和postgres
容器一起封装的,所以可以使用容器内部的工具执行导出命令
$ docker exec -it demo-postgres pg_dump -h 127.0.0.1 -U demo -d demo -p 5432 -f demo.sql
参数解释
-h 127.0.0.1
指定使用本地数据库-U demo
指定使用账号demo
进行操作-p 5432
指定使用端口5432
-d demo
指定使用数据库demo
-f demo.sql
指定输出到文件demo.sql
上面的命令会在容器的根路径下创建一个demo.sql
文件
如果只想转储某几张表的数据,比如users_1
和users_2
,则需要指定-t
参数,可以多次指定,命令可以改为如下
$ docker exec -it demo-postgres pg_dump -h 127.0.0.1 -U demo -d demo -p 5432 -f demo.sql -t users_1 -t users_2
现在需要把该文件从容器内部复制出来
$ docker cp demo-postgres:/demo.sql .
导出的demo.sql
包含了数据库demo
里面的表users
的sql
创建语句以及该表关联的SEQUENCE
(users_id_seq
)的创建语句,users
表数据的写入语句
数据恢复
现在执行sql
命令清空数据表users
truncate users ;
执行数据恢复命令,该命令和导出命令是非常类似的,各个参数都一样,只是执行的命令从pg_dump
转变为psql
$ docker exec -it demo-postgres psql -h 127.0.0.1 -U demo -d demo -p 5432 -f demo.sql
命令执行的输出有些报错,比如类似下面的,主要是由于导出命令包含了数据表创建语句,Sequence
创建语句,主键创建语句,由于在执行的时候没有检测是否存在对应的对象,整个sql
文件在执行的时候没有被包含在一个事务当中,所以创建数据表的语句是执行失败的,数据导入是成功的
psql:demo.sql:33: ERROR: relation "users" already exists
ALTER TABLE
psql:demo.sql:48: ERROR: relation "users_id_seq" already exists
ALTER TABLE
ALTER SEQUENCE
ALTER TABLE
COPY 5
setval
--------
5
(1 row)
psql:demo.sql:92: ERROR: multiple primary keys for table "users" are not allowed
为了避免数据恢复时候的某些语句报错,可以在导出数据的时候增加一个参数-a
或者--data-only
,这样在生成的sql
语句就不包含了数据表相关的创建语句
$ docker exec -it demo-postgres pg_dump -h 127.0.0.1 -U demo -d demo -p 5432 -f demo.sql -a
文件系统级别备份实现
概念逻辑
- 冷备份,为了得到一个可用的备份,数据库服务器必须被关闭,在恢复数据之前你也需要关闭服务器
- 文件系统备份值适合于完整地备份或恢复整个数据库集簇
- 一个文件系统备份通常会比一个
sql
转储体积更大 - 官网文档原话说
这种方法不实用,或者说至少比pg_dump方法差
(however, which make this method impractical, or at least inferior to the pg_dump method
) - 实际操作过程当中感觉虽然操作不复杂,但是感觉容易出问题,如果在执行备份或者恢复过程当中忘记关闭数据库的话,一不小心可能就是文件损坏或者数据丢失了,尽量不要使用这个方案
备份存储
由于目前测试的数据库是采用docker
容器启动的,但是存储位置被挂载出来了
先关闭docker
,执行命令如下
$ docker compose down
由于我们compose.yaml
文件的所在文件夹的名称是pg-backup
,同时compose.yaml
文件当中包含如下数据,docker
容器中的postgres
默认输出存储位置是/var/lib/postgresql/data
,如果是直接安装在宿主机上面,数据存储位置可能是/usr/local/pgsql/data
volumes:
- demo-postgres-data:/var/lib/postgresql/data
所以数据存储在宿主机位置
/var/lib/docker/volumes/pg-backup_demo-postgres-data/_data
执行备份命令
$ sudo tar -cf backup.tar /var/lib/docker/volumes/pg-backup_demo-postgres-data/_data
如果执行报错的话,比如报错如下
tar: 从成员名中删除开头的“/”
需要cd
到根目录下面
$ cd /
$ tar -cf backup.tar var/lib/docker/volumes/pg-backup_demo-postgres-data/_data
数据恢复
如果backup.tar
文件是保存在根目录下面(/
)的,在停止服务器的情况下,在根目录下执行(需要慎重)
$ tar -xf backup.tar
连续归档和时间点恢复实现
连续归档备份与恢复的操作实现中,官方文档给出的实际操作中比较偏向于底层实现逻辑,比较繁琐,所以在连续归档的实现上适合引入第三方的备份回复项目用于实现该目的。
推荐两个开源项目,pgbackrest
(基于C
语言开发)和wal-g
(基于golang
开发),二者的实现逻辑是很类似的,在使用过程当中,pgbackrest
的文档写的更详细,从基本的工具使用上,发现wal-g
操作上报错信息比较难以定位问题,wal-g
也没有给出一个比较完整的操作说明,使得很多操作需要依靠自行搜索第三方说明文档(第三方文档的质量也一言难尽)或者去直接猜测,后面可能这个情况会有好转,毕竟wal-g
在github
上面的star
数量还是蛮多的
所以下面主要使用pgbackrest
,minio
对象存储作为备份数据的仓库
概念逻辑
- 热备份,可以把一个文件系统级别的备份和
WAL
文件的备份结合起来,当需要恢复时,我们先恢复文件系统备份,然后从备份的WAL
文件中重放来把系统带到一个当前状态 - 数据集簇目录的
pg_wal/
子目录下都保持有一个预写式日志WAL
,为了保证崩溃后的安全,确保没有提交的更改丢失的机制,事务按顺序写入WAL
,当这些写入刷新到磁盘时,事务被视为已提交之后,后台进程将更改写入主数据库集群文件(也称为堆),如果发生崩溃,WAL
将被重放以使数据库保持一致,WAL
在概念上是无限的,但实际上被分解成称为段的单个16MB
(默认大小)文件,WAL
段遵循命名约定0000000100000A1E000000FE
,其中前 8 位十六进制数字表示时间线,接下来的 16 位数字是逻辑序列号 (LSN
) - 这种方法只能支持整个数据库集簇的恢复
备份类型
完整备份(
Full Backup
)pgBackRest
将数据库集群的全部内容复制到备份中,数据库集群的第一次备份始终是完整备份,pgBackRest
始终能够直接恢复完整备份,完整备份不依赖于完整备份之外的任何文件以保持一致性差异备份(
Differential Backup
)pgBackRest
仅复制自上次完整备份以来发生更改的那些数据库集群文件,pgBackRest
通过复制所选差异备份中的所有文件和先前完整备份中适当的未更改文件来恢复差异备份,差异备份的优点是它比完整备份需要更少的磁盘空间,但是差异备份和完整备份必须都有效才能恢复差异备份增量备份(
Incremental Backup
)pgBackRest
仅复制自上次备份(可以是另一个增量备份、差异备份或完整备份)以来发生更改的那些数据库集群文件,由于增量备份仅包括自上次备份以来更改的文件,因此它们通常比完整备份或差异备份小得多,与差异备份一样,增量备份依赖于其他有效的备份来恢复增量备份,由于增量备份仅包括自上次备份以来的那些文件,因此所有之前的增量备份回到之前的差异、之前的差异备份和之前的完整备份都必须有效才能执行增量备份的恢复,如果不存在差异备份,则所有先前的增量备份都将返回到先前的完整备份,该完整备份必须存在,并且完整备份本身必须有效才能恢复增量备份
备份与恢复逻辑流程
- 创建
pgbackrest
的配置文件 - 创建
postgres.conf
自定义配置文件 - 创建
Dockerfile
重新打包镜像 - 创建
compose.yaml
文件编排容器 - 增加
minio
对象存储作为数据备份仓库,使用mkcert
创建自签名证书使得minio
使用https
(pgbackrest
远程备份仓库只使用https
传输数据,没找到配置可以改成http
),pgbackrest
支持Azure
,GCS
,Amazon S3
存储,minio
对象存储兼容S3
协议,所以采用该对象存储 - 执行
stanza
创建命令,检查备份状态 - 测试验证恢复数据
pgbackrest
配置
新建文件pgbackrest.conf
[demo]
pg1-path=/var/lib/postgresql/data
pg1-user=demo
[global]
repo1-cipher-pass=zWaf6XtpjIVZC5444yXB+cgFDFl7MxGlgkZSaoPvTGirhPygu4jOKOXf9LO4vjfO
repo1-cipher-type=aes-256-cbc
repo1-path=/test-back
repo1-retention-full=2
repo1-s3-bucket=file-bucket
repo1-s3-endpoint=minio:9000
repo1-s3-key=miniouser
repo1-s3-key-secret=miniopass
repo1-s3-uri-style=path
repo1-s3-region=us-east-1
repo1-storage-verify-tls=n
repo1-type=s3
start-fast=y
[global:archive-push]
compress-level=3
[demo]
: 配置stanza
的名称,stanza
(节)是数据库集群的配置,它定义了它的位置、备份方式、归档选项等,大多数数据库服务器只有一个postgres
数据库集群,因此只有一个节,而备份服务器将有一个需要备份的每个数据库集群的stanza
pg1-path
: 指定postgres
的数据存储位置pg1-user
: 指定postgres
备用数据所使用的用户名称repo1-cipher-pass
和repo1-cipher-type
:表示使用备份仓库加密,确保备份数据的安全,加密参数repo1-cipher-pass
的数据是使用命令openssl rand -base64 48
随机生成的,repo1-cipher-type
指定了加密类型repo1-path
: 表示备份的存储位置,如果使用远程对象存储备份的话,该参数用于在存储桶中的位置,如果不配置,则文件会存储在bucket
下面,如果配置为test-back
,则文件会存储在bucket
的test-back
文件夹下面repo1-retention-full
: 表示最多保留两个全量备份,超过的这个数量的旧的全量备份会被清理repo1-s3-bucket
:声明s3
的存储桶的名称,该存储桶需要在minio
服务启动之后访问控制页面手动创建repo1-s3-endpoint
: 指定s3
服务的访问地址,由于是使用docker compose
方式部署,所以在postgres
容器内部可以使用minio:9000
访问对象存储repo1-s3-key
和repo1-s3-key-secret
: 表示对象存储的账号密码repo1-s3-uri-style
: 可选项是host
和path
host
: 表示拼接bucket
和endpoint
的参数,把域名修改为{bucket}.{endpoint}
,虽然不知道当时设计的时候是什么原因,但是这个原因导致pgbackrest
命令执行的时候无法访问到对象存储,感觉太坑了path
: 表示使用endpoint
参数和bucket
的信息到url
中
repo1-s3-region
:配置s3
区域的信息repo1-storage-verify-tls
: 由于minio
服务的https
使用自签名证书,所以证书校验是不安全的,所以该参数需要设置为n
repo1-type
: 指定使用s3
存储,可选项是azure
-Azure Blob Storage Service
cifs
-Like posix, but disables links and directory fsyncs
gcs
-Google Cloud Storage
posix
-Posix-compliant file systems
s3
-AWS Simple Storage Service
start-fast=y
: 默认情况下,pgBackRest
将在开始备份之前等待postgres
服务下一个定期安排的检查点,检查点完成和备份开始之前可能需要相当长的时间,所以最好设置start-fast=y,这样会触发postgres
强制执行检查点,但由于备份频率不高,因此额外的检查点不会对性能产生明显影响,如果数据负载一直很高,需要更加需求使用这个命令compress-level=3
: 可以设置较低的压缩级别来加速archive-push
命令的归档
创建自签名证书
采用mkcert
开源项目创建证书,BSD-3
协议,golang
编写,github
目前star
有38.5k+
,项目地址https://github.com/FiloSottile/mkcert
安装依赖
$ sudo apt install libnss3-tools
安装
$ sudo apt install mkcert
创建一个文件夹certs
,在这个文件夹当中执行命令
指定私钥为private.key
, 指定证书文件为public.crt
,
$ mkcert -key-file private.key -cert-file public.crt localhost
镜像制作与部署
软件存在于 apt.postgresql.org
,所以可以直接apt
安装
$ sudo apt install pgbackrest
由于我们的postgresql
服务镜像没有包含该软件,所以需要重新构建一个镜像
新增.dockerignore
.idea/
.git/
certs/
新增一个Dockerfile
文件
需要先下载CA
确保可以使用国内源,最后使用国内源下载加快速度
FROM postgres:15-bullseye
RUN apt update \
&& apt install ca-certificates -y \
&& sed -i "s#http://deb.debian.org#https://mirrors.ustc.edu.cn#g" /etc/apt/sources.list \
&& apt update \
&& apt install pgbackrest -y \
&& rm -rf /var/lib/apt/lists/ /var/cache/apt/
修改compose.yaml
文件如下
services:
postgres:
# image: postgres:15-bullseye
image: demo-postgres
command: postgres -c archive_mode=on -c archive_command='pgbackrest --stanza=demo archive-push %p' -c archive_timeout=30
container_name: demo-postgres
restart: always
volumes:
- demo-postgres-data:/var/lib/postgresql/data
- ./pgbackrest.conf:/etc/pgbackrest/pgbackrest.conf
ports:
- 127.0.0.1:5433:5432
environment:
- POSTGRES_DB=demo
- POSTGRES_USER=demo
- POSTGRES_PASSWORD=demo-pass
- TZ=Asia/Shanghai
minio:
image: minio/minio
command: server /data --console-address ":9002"
restart: always
container_name: demo-minio
environment:
- MINIO_ROOT_USER=miniouser
- MINIO_ROOT_PASSWORD=miniopass
volumes:
- demo-minio:/data
- ./certs:/root/.minio/certs
ports:
- 9002:9002
volumes:
demo-postgres-data: {}
demo-minio: {}
postgres
启动命令
wal_level = replica
:wal_level
的级别决定了多少信息会被写入wal
日志文件中,默认是replica
,该参数只能在服务器启动的时候配置minimal
: 会去掉除从崩溃或者立即关机中进行恢复所需的信息之外的所有记录,对于创建或重写永久关系的事务的其余部分,不会记录任何信息,不会包括足够的信息来从基础备份和WAL
日志中重建数据,要启用WAL
归档或者流复制,必须使用replica
或更高级别(logical
)replica
: 它会写入足够的数据以支持wal
归档和复制,包括在后备服务器上运行只读查询logical
: 与replica
相同的信息会被记录,外加上允许从WAL
抽取逻辑修改集所需的信息,配置该参数会导致wal
日志文件增长更快archive
或hot_standby
: 在9.6
之前的版本中可以使用,现在这些参数会被映射为replica
archive_mode
:可选项off
(默认),on
,always
- 当
wal_level
被设置为minimal
时,archive_mode
不能被启用 off
: 关闭归档on
: 配置的archive_command
命令将完成的WAL
段发送到归档存储always
:WAL
归档器在归档恢复或者后备模式下也会被启用,所有从归档恢复 的或者用流复制传来的文件将被(再次)归档
- 当
archive_command
- 本地
shell
命令被执行来归档一个完成的WAL
文件段,默认是空 - 如果
archive_mode
是off
,该命令无效 pgbackrest --stanza=demo archive-push %p
表示使用pgbackrest
的程序,只备份stanza
为demo
的数据,该参数对应pgbackrest.conf
文件当中的[demo]
参数信息,archive-push
是pgbackrest
的一个子命令,会把一个指定的wal
段文件发送到归档,%p
是PostgreSQL
指定要归档的WAL
段位置的方式,默认的当前位置是postgres
的数据存放路径(比如/var/lib/postgresql/data
),%p
参数的值类似pg_wal/00000001000000000000001F
,表明生成的一个wal
文件的位置是/var/lib/postgresql/data/pg_wal/00000001000000000000001F
- 本地
archive_timeout
- 由于归档命令只在已经完成
wal
段上调用,如果服务器上面的更新操作比较少,那么事务完成之后,这个操作被纪录到归档存储之间有一个很大的延迟,所以为了进行测试数据按照时间点恢复的功能,目前把该参数调整为一个较小的数字,实际生产环境上需要把该参数配置为60或者更大,单位是秒 - 这个参数是服务器来周期性地切换到一个新的
WAL
段文件,设置为大于零时,只要从上次段文件切换后过了参数所设置的时间量,并且已经有过任何数据库活动(包括一个单一检查点),服务器将切换到一个新的段文件 - 由于强制切换而提早关闭的被归档文件仍然与完整的归档文件长度相同,所以该参数的配置会导致归档存储的磁盘消耗变多
- 这个参数是很有必要的,比如现在数据库新增了几条数据,在经过了
archive_timeout
的时长之后,wal
段完成,之后进行pgbackrest
备份命令,归档备份被传输到minio
仓库,之后才可以运行按照时间点恢复(即恢复的时间点必须小于最新的被归档的wal
日志段)
- 由于归档命令只在已经完成
minio
部分
- 启动命令和文件挂载需要注意
- 自签名证书需要挂载到
minio
的对应位置,服务在启动的时候会自动使用https
environment
: 配置minio
的账号密码,目前旧的环境变量MINIO_ACCESS_KEY
和MINIO_SECRET_KEY
还是可以使用的,运行的时候会产生告警WARNING:MINIO_ACCESS_KEY and MINIO_SECRET_KEY are deprecated. Please use MINIO_ROOT_USER and MINIO_ROOT_PASSWORD
--console-address ":9002"
参数表示开启页面访问,由于挂载了证书,所以访问https://127.0.0.1:9002
可以访问minio
现在整个项目的配置和文件树如下
.
├── certs
│ ├── private.key
│ └── public.crt
├── .dockerignore
├── compose.yaml
├── Dockerfile
└── pgbackrest.conf
命令更新服务
$ docker build -t demo-postgres .
备份与恢复操作
现在去查看postgres
容器的日志发现报错如下,是因为还没有创建pgbackrest
的stanza
demo-postgres | 2022-11-24 16:10:56.029 CST [79] LOG: archive command failed with exit code 103
demo-postgres | 2022-11-24 16:10:56.029 CST [79] DETAIL: The failed archive command was: pgbackrest --stanza=demo archive-push pg_wal/000000010000000000000001
demo-postgres | ERROR: [103]: unable to find a valid repository:
demo-postgres | repo1: [FileMissingError] unable to load info file '/test-back/archive/demo/archive.info' or '/test-back/archive/demo/archive.info.copy':
demo-postgres | FileMissingError: unable to open missing file '/test-back/archive/demo/archive.info' for read
demo-postgres | FileMissingError: unable to open missing file '/test-back/archive/demo/archive.info.copy' for read
demo-postgres | HINT: archive.info cannot be opened but is required to push/get WAL segments.
demo-postgres | HINT: is archive_command configured correctly in postgresql.conf?
demo-postgres | HINT: has a stanza-create been performed?
demo-postgres | HINT: use --no-archive-check to disable archive checks during backup if you have an alternate archiving scheme.
创建stanza
之前,需要访问https://127.0.0.1:9002
去创建存储桶bucket
,名称与pgbackrest.conf
文件的repo1-s3-bucket
参数保持一致
执行创建stanza
命令, 该命令需要指定使用postgres
用户,pgbackrest --stanza=demo --log-level-console=info stanza-create
是实际执行的命令,指定创建的stanza
为demo
$ docker exec -it -u postgres demo-postgres pgbackrest --stanza=demo --log-level-console=info stanza-create
2022-11-24 16:12:34.689 P00 INFO: stanza-create command begin 2.41: --exec-id=96-dada82ab --log-level-console=info --pg1-path=/var/lib/postgresql/data --pg1-user=demo --repo1-cipher-pass= --repo1-cipher-type=aes-256-cbc --repo1-path=/test-back --repo1-s3-bucket=file-bucket --repo1-s3-endpoint=minio:9000 --repo1-s3-key= --repo1-s3-key-secret= --repo1-s3-region=us-east-1 --repo1-s3-uri-style=path --no-repo1-storage-verify-tls --repo1-type=s3 --stanza=demo
2022-11-24 16:12:35.293 P00 INFO: stanza-create for stanza 'demo' on repo1
2022-11-24 16:12:35.320 P00 INFO: stanza-create command end: completed successfully (631ms)
执行第一次备份,由于没有上一次全量备份,所以备份会自动转换为全量备份,备份命令是需要有一个定时任务定期去执行的,首次备份由于是全量备份,所以首次执行的时候耗时会比较长,后面的都是增量备份,只会备份有变动的数据,所以会比较快
$ docker exec -it -u postgres demo-postgres pgbackrest --stanza=demo --log-level-console=info backup
现在查看备份文件的信息
$ docker exec -it -u postgres demo-postgres pgbackrest --stanza=demo --log-level-console=info info
stanza: demo
status: ok
cipher: aes-256-cbc
db (current)
wal archive min/max (15): 000000010000000000000001/000000010000000000000003
full backup: 20221124-161252F
timestamp start/stop: 2022-11-24 16:12:52 / 2022-11-24 16:12:58
wal start/stop: 000000010000000000000003 / 000000010000000000000003
database size: 29.1MB, database backup size: 29.1MB
repo1: backup set size: 3.9MB, backup size: 3.9MB
现在去对象存储的页面发现已经有归档文件和备份文件
现在去数据库执行数据表创建命令,并且写入一些数据
目前数据表的输入如下
> select * from users u ;
1 user0 0 false 2022-11-24 16:14:18
2 user2 1 false 2022-11-24 16:14:18
3 user2 0 false 2022-11-24 16:14:18
4 user16 3 false 2022-11-24 16:14:18
5 user1 1 false 2022-11-24 16:14:18
6 user2 2 false 2022-11-24 16:14:18
7 user3 2 false 2022-11-24 16:14:18
8 user16 3 false 2022-11-24 16:14:18
等待30秒
之后,再次执行备份命令,等待时间和archive_timeout
参数有关,目的是确保上次数据表创建,写入的事务已经归档产生了新的完整wal
段,该段可以被归档备份
$ docker exec -it -u postgres demo-postgres pgbackrest --stanza=demo --log-level-console=info backup
现在再次往users
表写入一些数据,把这些数据当做脏数据
> select * from users u ;
1 user0 0 false 2022-11-24 16:14:18
2 user2 1 false 2022-11-24 16:14:18
3 user2 0 false 2022-11-24 16:14:18
4 user16 3 false 2022-11-24 16:14:18
5 user1 1 false 2022-11-24 16:14:18
6 user2 2 false 2022-11-24 16:14:18
7 user3 2 false 2022-11-24 16:14:18
8 user16 3 false 2022-11-24 16:14:18
9 user0 0 false 2022-11-24 16:19:35
10 user0 1 false 2022-11-24 16:19:35
11 user5 1 false 2022-11-24 16:19:35
12 user2 2 false 2022-11-24 16:19:35
现在开始执行数据按照时间点回滚,现在需要回滚到2022-11-24 16:14:20
,即脏数据产生之前的一个时间点
现在停止服务
$ docker compose down
调整compose.yaml
文件如下
// --snip--
image: demo-postgres
# command: postgres -c archive_mode=on -c archive_command='pgbackrest --stanza=demo archive-push %p' -c archive_timeout=30
command:
- /bin/sh
- -c
- |
rm -f /var/lib/postgresql/data/postmaster.pid &&
echo "delete ok" &&
pgbackrest --stanza=demo --delta --type=time "--target=2022-11-24 16:14:20+08" --target-action=promote restore
// --snip--
- 由于数据恢复的时候需要关闭数据库,
postgres
又是运行在容器内部的,所以需要调整compose.yaml
文件的postgres
服务的启动command
- 执行恢复的时候
pgbackrest
会检测数据库是否正在运行,有一个判断条件是检测一个pid
文件是否存在,所以在执行恢复之前执行删除/var/lib/postgresql/data/postmaster.pid
- 执行
pgbackrest
指定恢复类型是按照时间点恢复,时间点参数是上文中的2022-11-24 16:14:20+08
,需要带上时区信息,确保时间是绝对的 target-action
指定在达到恢复目标时服务器应该立刻采取的动作,默认动作是pause
pause
表示恢复将会被暂停promote
表示恢复处理将会结束并且服务器将开始接受连接shutdown
将在达到恢复目标之后停止服务器
修改完成之后,开始启动服务
$ docker compose up -d
查看日志如下,demo-postgres exited with code 0
表示数据恢复成功运行,容器正常退出
$ docker compose logs -f
Attaching to demo-minio, demo-postgres
demo-postgres | delete ok
// --snip--
demo-postgres exited with code 0
demo-postgres exited with code 0
现在停止服务
$ docker compose down
把compose.yaml
文件当中postgres
的启动命令修改为原来的正常命令
command: postgres -c archive_mode=on -c archive_command='pgbackrest --stanza=demo archive-push %p' -c archive_timeout=30
最后启动容器并且查看日志如下,输出中有最后一句last completed transaction was at log time last completed transaction was at log time 2022-11-24 16:14:18.533467+08
,表示恢复在2022-11-24 16:14:18.533467+08
的最后一个事务停止了,虽然我们指定的是数据库恢复到--target=2022-11-24 16:14:20+08
,但是在这个表示的是截止到我们指定时间点的以前所有事务都会被执行重放恢复
$ docker compose up -d
$ docker compose logs -f
// --snip--
demo-postgres | 2022-11-24 16:39:07.347 CST [28] LOG: database system was interrupted; last known up at 2022-11-24 16:12:52 CST
demo-postgres | 2022-11-24 16:39:07.411 CST [28] LOG: starting point-in-time recovery to 2022-11-24 16:14:20+08
demo-postgres | 2022-11-24 16:39:07.452 CST [28] LOG: restored log file "000000010000000000000003" from archive
demo-postgres | 2022-11-24 16:39:07.466 CST [28] LOG: redo starts at 0/3000028
demo-postgres | 2022-11-24 16:39:07.509 CST [28] LOG: restored log file "000000010000000000000004" from archive
demo-postgres | 2022-11-24 16:39:07.523 CST [28] LOG: consistent recovery state reached at 0/3000138
demo-postgres | 2022-11-24 16:39:07.523 CST [1] LOG: database system is ready to accept read-only connections
demo-postgres | 2022-11-24 16:39:07.562 CST [28] LOG: restored log file "000000010000000000000005" from archive
demo-postgres | 2022-11-24 16:39:07.613 CST [28] LOG: restored log file "000000010000000000000006" from archive
demo-postgres | 2022-11-24 16:39:07.744 CST [28] LOG: restored log file "000000010000000000000007" from archive
demo-postgres | 2022-11-24 16:39:07.963 CST [28] LOG: restored log file "000000010000000000000008" from archive
demo-postgres | 2022-11-24 16:39:08.000 CST [28] LOG: recovery stopping before commit of transaction 740, time 2022-11-24 16:19:35.987987+08
demo-postgres | 2022-11-24 16:39:08.000 CST [28] LOG: redo done at 0/7000618 system usage: CPU: user: 0.00 s, system: 0.01 s, elapsed: 0.53 s
demo-postgres | 2022-11-24 16:39:08.000 CST [28] LOG: last completed transaction was at log time 2022-11-24 16:14:18.533467+08
demo-postgres | 2022-11-24 16:39:08.069 CST [28] LOG: restored log file "000000010000000000000007" from archive
demo-postgres | 2022-11-24 16:39:08.099 CST [28] LOG: selected new timeline ID: 2
demo-postgres | 2022-11-24 16:39:08.143 CST [28] LOG: archive recovery complete
demo-postgres | 2022-11-24 16:39:08.144 CST [26] LOG: checkpoint starting: end-of-recovery immediate wait
demo-postgres | 2022-11-24 16:39:08.162 CST [26] LOG: checkpoint complete: wrote 50 buffers (0.3%); 0 WAL file(s) added, 0 removed, 4 recycled; write=0.004 s, sync=0.005 s, total=0.019 s; sync files=39, longest=0.002 s, average=0.001 s; distance=65537 kB, estimate=65537 kB
demo-postgres | 2022-11-24 16:39:08.169 CST [1] LOG: database system is ready to accept connections
现在去查看数据库,发现我们后面的增加的那些脏数据没有了
> select * from users u ;
1 user0 0 false 2022-11-24 16:14:18
2 user2 1 false 2022-11-24 16:14:18
3 user2 0 false 2022-11-24 16:14:18
4 user16 3 false 2022-11-24 16:14:18
5 user1 1 false 2022-11-24 16:14:18
6 user2 2 false 2022-11-24 16:14:18
7 user3 2 false 2022-11-24 16:14:18
8 user16 3 false 2022-11-24 16:14:18
如果不需要指定按照时间点恢复,则可以指定全量恢复,即备份了多少归档wal
,恢复到多少,compose.yaml
的postgres
服务的启动command
改为如下即可,主要变动是--type
的参数改成immediate
// --snip--
image: demo-postgres
# command: postgres -c archive_mode=on -c archive_command='pgbackrest --stanza=demo archive-push %p' -c archive_timeout=30
command:
- /bin/sh
- -c
- |
rm -f /var/lib/postgresql/data/postmaster.pid &&
echo "delete ok" &&
pgbackrest --stanza=demo --delta --type=immediate --target-action=promote restore
// --snip--
小结
总体来说,增量备份和按照时间点恢复数据的操作是比较繁琐的,也踩了很多的坑,希望有一天postgres
可以官方支持一个配置选项,可以自己增量备份与按照时间点恢复,目前发现比较靠谱的第三方都已经写成一个项目的备份恢复,大概只有pgbackrest
,wal-g
,barman
比较靠谱,后面有兴趣可以去尝试一下。