昨天刚把五台机的 Restic + DigitalOcean Spaces 备份体系跑通:首备、timer、Telegram 通知、阿里云 SSH 中继,感觉挺稳。
今天凌晨 3:58,Telegram 冷不防来一张黄牌:⚠️ 备份异常|<SERVER1_ALIAS>。
顺藤摸瓜又发现 Server 3 本地的备份临时目录在以每天约 2.7GB 的速度吞盘。
这篇文章记两件事:怎么消掉那条误报,以及怎么把「传统本地留多份日期备份」的习惯从 Restic 体系里踢出去——本机 SSD 一口气拿回将近 8GB。
幽灵警告:环境变量拷贝拷歪了
早上起来看通知,大概长这样:
⚠️ 备份异常|<SERVER1_ALIAS><SERVER1_INSTANCE>
数据库快照不存在:/var/backups/db/qq-bot.sqlite.gz📌 快照:<SNAPSHOT_ID>
摘要时间 05-25 03:58 CST耗时 1 分钟上传 32.5 MiB / 17.9 MiB总量 438.59 MiB变化 +57 / ~67Server 1(<SERVER1_INSTANCE>)上只跑 qq-bot-api 做协议转换:收 QQ 消息、转协议、交给 Server 3 的 qq-bot 核心做决策,再把回复拉回来发。
它是个无状态中继,根本没有 SQLite。
那它干嘛喊 数据库快照不存在:/var/backups/db/qq-bot.sqlite.gz?还把 Server 3 上 qq-bot 的库名喊出来了?
SSH 上去看 /etc/restic/env(权限 600):
sudo cat /etc/restic/env文件尾部写着:
export BACKUP_DB_PATH="/var/backups/db/qq-bot.sqlite.gz"export BACKUP_DB_LABEL="SQLite"昨天多机初始化时,Server 1 的 env 大概是从模板或 Server 3 拷过来的,数据库节点专有的变量没删干净。
脚本的 notify_success 很较真:只要定义了 BACKUP_DB_PATH,就会检查文件在不在;不在就报异常。Server 1 没有 qq-bot 主库,当然找不到那个 .gz,警报就响了一夜。
处理很干脆:
- Server 1:
sed掉所有BACKUP_DB_相关变量。之后不再做数据库快照存在性检查,通知回到🟢 状态:OK。 - Server 4:顺手审计时发现它每天都在导出 PostgreSQL 裸 SQL,env 里却漏配了
BACKUP_DB_PATH。成功通知里看不到库体积,导出挂了也不会丢库报警。补上:
export BACKUP_DB_PATH="/var/backups/db/docker-postgres-all.sql"export BACKUP_DB_LABEL="PostgreSQL"没有数据库的节点,env 里别留 BACKUP_DB_*;有库的节点则要和实际导出路径对齐——环境变量就是脚本的指挥棒,拷贝模板时多看一眼。
本地 11GB:日期后缀副本在囤盘
告警消了之后,我在 Server 3 上随手看了眼临时目录:
sudo ls -lh /var/backups/dbtotal 11G-rw------- 1 root root 2.6G May 23 21:36 qq-bot-2026-05-23.sqlite-rw------- 1 root root 2.6G May 24 18:36 qq-bot-2026-05-24.sqlite-rw------- 1 root root 2.6G May 25 10:58 qq-bot-2026-05-25.sqlite-rw------- 1 root root 2.6G May 25 10:57 qq-bot.sqlite临时目录塞了 11GB。 一个 2.6G 的库,本地躺了四份。
为什么会这样?昨天写的脚本里有一段很「传统」的逻辑:
# 传统逻辑:mv "$DB_DIR/qq-bot.sqlite.tmp" "$DB_DIR/qq-bot.sqlite"cp "$DB_DIR/qq-bot.sqlite" "$DB_DIR/qq-bot-${TODAY}.sqlite" # 复制出日期命名的本地归档
# 清理 3 天前的日期副本find "$DB_DIR" -name 'qq-bot-*.sqlite' -mtime +3 -delete思路很常见:怕介质出问题,或者想本地快速翻前几天的版本,就再拷一份带日期的文件,保留最近 3 天。
在 tar / 纯对象覆盖上传的世界里,这说得通。
但在 Restic / Borg 这种快照 + 去重工具面前,这纯属画蛇添足。
| 维度 | 传统备份(tar / 傻上传) | Restic(快照 / 去重) |
|---|---|---|
| 本地生成物 | 往往要带日期的独立包,否则后端旧文件会被盖掉 | 每天一个固定名最新快照即可(如 qq-bot.sqlite) |
| 历史版本 | 本地或云端留 file-YYYY-MM-DD 链条 | 每次备份一个 Snapshot,历史由仓库元数据管 |
| 磁盘占用 | 本地留几天就翻几倍(2.6G → 11G) | 本地通常只要一倍大小 |
| 找回某天 | 下对应压缩包再解压 | restic restore <snapshot_id> 或挂载快照 |
Restic 本身就是版本管理器。我在本地 cp 多份日期文件,既占 SSD,每天备份还要多扫几个 GB 冗余,CPU 和 I/O 白干一截。
历史该交给仓库,本地只留「当前这一份一致性快照」。
砍掉日期副本,磁盘瞬间轻松
想通之后改动很小:本地不再做日期后缀副本,历史 100% 交给 Restic。
有数据库的节点一起改:Server 3(qq-bot SQLite)、Server 2(status-monitor SQLite)、Server 4(api-hub PostgreSQL)。
以 Server 3 为例:
mv "$DB_DIR/qq-bot.sqlite.tmp" "$DB_DIR/qq-bot.sqlite"cp "$DB_DIR/qq-bot.sqlite" "$DB_DIR/qq-bot-${TODAY}.sqlite"if [ "${BACKUP_CREATE_COMPRESSED_DB:-0}" = "1" ]; then if command -v pigz >/dev/null 2>&1; then pigz -1 -p "$(nproc)" -c "$DB_DIR/qq-bot.sqlite" > "$DB_DIR/qq-bot.sqlite.gz"; else gzip -1 -c "$DB_DIR/qq-bot.sqlite" > "$DB_DIR/qq-bot.sqlite.gz"; fi gzip -t "$DB_DIR/qq-bot.sqlite.gz"fifind "$DB_DIR" -name 'qq-bot-*.sqlite' -mtime +3 -deletefind "$DB_DIR" -name 'qq-bot-*.sqlite.gz' -mtime +3 -delete脚本部署后再清历史日期文件:
sudo bash -c 'rm -f /var/backups/db/qq-bot-*.sqlite /var/backups/db/qq-bot-*.sqlite.gz'sudo ls -lh /var/backups/dbtotal 2.6G-rw------- 1 root root 2.6G May 25 10:57 qq-bot.sqlite/var/backups/db 从 11GB → 2.6GB,本地 SSD 大约多出 7.8GB。
Server 2 清了 status-monitor-*.sqlite,Server 4 清了 docker-postgres-all-*.sql。库没 Server 3 那么大,但三台的备份写法总算对齐了:本地只留最新一致性快照,版本靠 Restic。
对齐之后:五台机各自干什么
env、脚本和本地冗余清完,整套体系的数据流大致如下:
(另有一张 Graphviz 风格全景图,结构同上,可对照看:)
各节点分工:
| 节点 | 角色 | 备份核心 | 数据库 | 通知 |
|---|---|---|---|---|
| Server 1 | qq-bot-core / qq-bot-api | nginx、证书、Compose、Docker volume 数据目录 | 无库,跳过 dump 与库路径校验 | 海外直连 Telegram HTML |
| Server 2 | my-blog / status-monitor | 博客目录、status-monitor compose 与数据 | Python 在线热备未压缩 status-monitor.sqlite,integrity 通过后再备 | 海外直连 Telegram |
| Server 3 | qq-bot 核心(写入最重) | 配置 / 插件 / 数据(排除在线 WAL 原库) | 在线热备固定名 qq-bot.sqlite,靠 Restic 块去重扛增量 | 海外直连 Telegram |
| Server 4 | api-hub / llm-api / cli-proxy 等 | Compose、环境与挂载数据 | 容器外 docker compose exec 导出未压缩 docker-postgres-all.sql | 海外直连 Telegram |
| Server 5 | 阿里云加速 / 宝塔 | 宝塔 nginx、证书、静态产物 | 无库 | 国内不直连 TG,受限 SSH 私钥把消息管道推到 Server 2 代发 |
Server 3 是备份压力最大的一台:高频写入的 qq-bot 库 + 适配器和 Agent 数据交汇。改成固定名 qq-bot.sqlite 后,历史版本全在仓库里,本机再也不用为「多留几天」翻倍占盘。
五台脚本共用的底盘没变,只提一句:
flock:daily / weekly 共锁/run/restic-backup.lock,flock -n抢不到就退出,避免 backup 和 prune 对撞- 双 timer:daily 做备份 + 轻量
check --no-cache;weekly 做forget --prune和check --read-data-subset=1G trap ERR:失败时把最近约 40 行日志塞进 Telegram,方便对着看
更细的架构说明见上一篇备份体系搭建文,这里不重复展开。
收尾
这次其实就修了两处「旧习惯带进新工具」:
- 拷贝 env 模板时没按节点删干净,无库机器硬检查数据库路径 → 假异常
- 用惯了
tar ...-$(date +%F),上了 Restic 还在本地cp日期副本 → SSD 被自己备份脚本吃掉
个人项目搭运维时特别容易这样:工具已经是快照去重了,脑子还停在「多留几份压缩包才安心」。
本地只留最新一份一致性快照;历史、校验、保留策略交给加密去重仓库。边界划清,链路才轻,关键时刻也不至于被自己的「保险措施」拖后腿。
如果这篇文章对你有帮助,欢迎分享给更多人!
部分信息可能已经过时