1834 字
9 分钟

Dokploy 自带 PostgreSQL 备份局限与扩展

2026-06-03
教程
Dokploy
/
PostgreSQL
/
pgBackRest
/
R2
/
数据库备份

一开始我用的是 Dokploy 自带的数据库备份。它很方便,点几下就能做定时备份,也能保留最近几份。但是当数据库逐渐变大之后,我发现了一些问题。

这不是说 Dokploy 自带备份没用。它作为小库的兜底、临时导出、人工恢复很方便。但如果数据库已经到几十 GB,那靠 dump 就并不是一个好选择。

最终我的方案#

PostgreSQL
-> archive_mode + WAL archive
-> pgBackRest
-> S3-compatible object storage, here using Cloudflare R2

备份策略大概是:

每周一次 full backup
每天一次 differential backup
持续归档 WAL
每天做 pgBackRest check
保留多个 full backup

问题是怎么发现的#

最开始的信号很简单:

我的监控一直发现pg数据库一直处于高压状态,我寻思我的流量应该不至于能让数据库这么高负载,于是赶紧查看,由于之前也没怎么管过,一直用的dokploy原生的备份。看了眼并没有数据库本身的异常,我发现有个服务一直在压缩数据库,进而联想到了备份,最终终于找到了原因。

image-20260603182225272

当前的数据库 dump 已经接近 10GB,生产 PostgreSQL 物理数据目录接近 50GB。

Dokploy 自带 dump 备份的问题#

Dokploy 的自带数据库备份优点是简单:配置计划任务、保留份数。

但它作为生产 PostgreSQL 的主备份,有几个明显短板。

1. 它主要是逻辑备份#

逻辑备份通常是 pg_dump 一类思路。它适合:

  • 小数据库;
  • 迁移表结构和数据;
  • 临时导出;
  • 做一份额外保险。

但库大了以后,逻辑备份的问题会越来越明显:

  • dump 过程耗时变长;
  • restore 更慢;
  • 恢复时需要重建索引;
  • 不适合频繁恢复演练;
  • 不天然支持按时间点恢复。

如果业务写入比较频繁,单纯一天一次 dump 的恢复点也比较粗。比如凌晨 0 点备份,晚上 23 点出事故,那这中间的数据就很难恢复。你像如果我的库规模比较大,50G,我之前设置的每隔1H一次,保留最新的24份,导致每一个小时里40min都处于pg_dump状态,此时压力非常大,几乎只有20min正常。

2. 没有连续 WAL 归档#

PostgreSQL 真正可靠的生产备份,一般不能只看全量备份,还要看 WAL。

WAL 可以理解成 PostgreSQL 的变更日志。只要你有一个基础备份,再配合之后的 WAL,就有机会把数据库恢复到某个时间点。

只做 dump 的话,通常就只能恢复到上一次备份的时间点

而做了 WAL 归档后,恢复目标就可以变成:

某个 full/diff backup + 后续 WAL

这就是 RPO 的差别。

3. 恢复不可控#

备份没验证过,基本等于没备份。

Dokploy 自带 dump 对小库还好。库到了几十 GB 后,恢复时长、索引重建、停机窗口都会变成问题。

解决方案:pgBackRest + 对象存储#

我选择 pgBackRest。

https://pgbackrest.org/

原因很简单:数据库备份不是适合发明轮子的地方,这应该是认可度比较高的方案。

能提供:

  • full backup;
  • differential backup;
  • incremental backup;
  • WAL archive push;
  • S3-compatible 存储;
  • 加密;
  • retention 策略;
  • check
  • info
  • restore。

这正好覆盖我需要的核心能力。

部署思路#

我的 PostgreSQL 是 Dokploy / Docker Swarm 管理的服务,所以整体思路是:

  1. 做一个带 pgBackRest 的 PostgreSQL 镜像;
  2. 保留原来的 PostgreSQL data volume;
  3. 用 Docker secret 挂载 pgBackRest 配置;
  4. 开启 PostgreSQL WAL archive;
  5. 用 systemd timer 调度 full/diff/check;
  6. 把备份写到 R2;
  7. 做独立恢复验证。

pgBackRest 配置示例#

下面是结构示例,敏感信息全部用占位符。

[global]
repo1-type=s3
repo1-s3-endpoint=<s3-compatible-endpoint>
repo1-s3-region=<region>
repo1-s3-bucket=<bucket>
repo1-s3-key=<access-key-id>
repo1-s3-key-secret=<secret-access-key>
repo1-path=/postgres-backup
repo1-cipher-type=aes-256-cbc
repo1-cipher-pass=<long-random-passphrase>
repo1-retention-full=4
repo1-retention-full-type=count
repo1-retention-diff=14
process-max=2
start-fast=y
log-level-console=info
log-level-file=info
[main]
pg1-path=/var/lib/postgresql/data

PostgreSQL 开启 WAL archive#

archive_mode = on
archive_command = 'pgbackrest --config=/run/secrets/pgbackrest.conf --stanza=main archive-push %p'
archive_timeout = 300s
wal_compression = on

archive_timeout = 300s 的意思是即使 WAL 没写满,也最多 5 分钟切一次,降低极端情况下最后一段 WAL 长时间不归档的风险。 不是越小越好。太小会制造更多 WAL 文件和请求。要结合写入量、对象存储请求成本、RPO 要求来定。

定时任务 systemd timer#

频率如下:

周日凌晨 full backup
周一到周六凌晨 differential backup
每天早上 pgBackRest check

用 systemd timer 的好处是:

  • 可以看 timer 状态;
  • 可以看 service 日志;
  • 可以配合 wrapper;
  • 比散落的 crontab 更容易排查。

wrapper 脚本里我会做几件事:

  • 动态找到当前 PostgreSQL container;
  • 通过 Docker secret 里的配置运行 pgBackRest;
  • flock 防止 full、diff、check 重叠;
  • 把日志写到固定文件;
  • 不在命令行里暴露密钥。

示意:

#!/usr/bin/env bash
set -euo pipefail
backup_type="${1:-diff}"
lock_file="/var/lock/postgres-pgbackrest.lock"
exec flock -n "$lock_file" bash -c '
cid=$(docker ps --filter label=com.docker.swarm.service.name=<postgres-service-name> -q | head -n1)
if [ -z "$cid" ]; then
echo "postgres container not found"
exit 1
fi
docker exec -u postgres "$cid" \
pgbackrest --config=/run/secrets/pgbackrest.conf \
--stanza=main \
backup --type='"$backup_type"'
'

保留策略怎么估算#

我当时的规模大概是:

PostgreSQL 物理库:49.6GB
pgBackRest full repo size:11.5GB

如果保留 4 份 full backup,那么只算 full:

11.5GB * 4 = 46GB

然后再加:

  • 每天 differential backup;
  • 持续 WAL archive;
  • 对象存储自身版本或生命周期策略;
  • 未来写入增长。

所以实际预估不能只看 full。一个比较保守的估算方式是:

低写入:60-80GB
中等写入:80-150GB
高写入:150GB+

真正准确的方式是等几天自动 diff 跑完,看 pgBackRest info 和对象存储增长。

Terminal window
pgbackrest --config=/run/secrets/pgbackrest.conf --stanza=main info

顺手做的 PostgreSQL 调优#

既然开启 WAL archive 和备份需要维护窗口,我也顺手调整了一些 PostgreSQL 参数。

示例:

shared_buffers = 8GB
effective_cache_size = 32GB
max_wal_size = 16GB
min_wal_size = 2GB
checkpoint_timeout = 15min
checkpoint_completion_target = 0.9
wal_compression = on
track_io_timing = on
log_checkpoints = on
idle_in_transaction_session_timeout = 15min

这些参数不是通用答案。它们要根据机器内存、业务写入、连接数、磁盘性能调整。(不要照抄,可以用ai帮调)

这里的目标是:

  • 降低频繁 checkpoint 带来的写放大;
  • 控制 WAL 体积;
  • 打开 IO timing,方便后续分析慢查询;
  • 记录 checkpoint;
  • 避免长时间 idle transaction 阻塞 vacuum。

验证#

这一步最重要。

恢复流程:

  1. 创建一个独立目录;
  2. 挂载同一个 pgBackRest 配置 secret;
  3. pgbackrest restore 到这个目录;
  4. 启动一个临时 PostgreSQL;
  5. 只监听容器内部地址或本机地址;
  6. 执行只读查询;
  7. 删除临时服务。

验证时可以查:

select pg_is_in_recovery();
select count(*) from pg_database;
show server_version_num;

我当时验证结果大概是:

restore command end: completed successfully
database system is ready to accept read-only connections
pg_is_in_recovery(): true
database count: 4
server_version_num: 180003

这一步完成以后,我才认为“这份备份可以恢复”。

日常检查#

pgBackRest 信息#

Terminal window
pgbackrest --config=/run/secrets/pgbackrest.conf --stanza=main info

看:

  • full/diff 是否按计划生成;
  • repo size;
  • WAL archive 范围;
  • 是否有 error。

WAL 归档健康#

select
archived_count,
failed_count,
last_archived_wal,
last_archived_time,
last_failed_wal,
last_failed_time
from pg_stat_archiver;

failed_count 不应该持续增加。

pg_wal 大小#

select pg_size_pretty(sum(size)) as pg_wal_size
from pg_ls_waldir();

如果 pg_wal 不断膨胀,要优先查 archive 是否失败、replication slot 是否卡住、checkpoint 是否异常。

定时器状态#

Terminal window
systemctl list-timers --all "postgres-pgbackrest*" --no-pager
systemctl status postgres-pgbackrest-diff.timer postgres-pgbackrest-full.timer postgres-pgbackrest-check.timer --no-pager

PostgreSQL 是否有 pending restart#

select name, setting, pending_restart
from pg_settings
where pending_restart = true
order by name;

最后怎么处理 Dokploy 自带备份#

我的建议不是立刻关掉 Dokploy dump。

在过渡期,Dokploy dump 仍然有价值。它可以作为逻辑层面的第二保险。

Dokploy 自带 PostgreSQL 备份局限与扩展
https://catcat.blog/2026/06/dokploy-postgres-backup-pgbackrest-r2.html
作者
猫猫博客
发布于
2026-06-03
许可协议
CC BY-NC-SA 4.0