mobile wallpaper 1
mobile wallpaper 2
mobile wallpaper 3
mobile wallpaper 4
mobile wallpaper 5
mobile wallpaper 6
1671 字
5 分钟
用 Restic 备份本地却在疯狂吃磁盘?
2026-05-25 11:57:47
2026-07-17 15:00:00

昨天刚把五台机的 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 / ~67

Server 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 1sed 掉所有 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/db
total 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"
fi
find "$DB_DIR" -name 'qq-bot-*.sqlite' -mtime +3 -delete
find "$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/db
total 2.6G
-rw------- 1 root root 2.6G May 25 10:57 qq-bot.sqlite

/var/backups/db11GB → 2.6GB,本地 SSD 大约多出 7.8GB

Server 2 清了 status-monitor-*.sqlite,Server 4 清了 docker-postgres-all-*.sql。库没 Server 3 那么大,但三台的备份写法总算对齐了:本地只留最新一致性快照,版本靠 Restic。


对齐之后:五台机各自干什么#

env、脚本和本地冗余清完,整套体系的数据流大致如下:

flowchart TD subgraph S3_Cloud ["云端存储 (DigitalOcean Spaces)"] DO_Spaces["DO Spaces S3 仓库"] end subgraph Internal_Servers ["五台服务器"] S1["Server 1<br>qq-bot-core / qq-bot-api"] S2["Server 2<br>my-blog / status-monitor"] S3["Server 3<br>qq-bot"] S4["Server 4<br>api-hub / llm-api / cli-proxy"] S5["Server 5<br>阿里云博客国内节点"] end S1 <--> S3 S1 & S2 & S5 --> DO_Spaces S3 -- "2.6G SQLite快照 (去重增量)" --> DO_Spaces S4 -- "pg_dumpall (增量)" --> DO_Spaces TG_Bot["Telegram 报警机器人"] S1 & S3 & S4 --> TG_Bot S2 -- "直连/代理中继通知" --> TG_Bot S5 -- "SSH 隧道中继" --> S2

(另有一张 Graphviz 风格全景图,结构同上,可对照看:)

五节点 Restic 备份拓扑

各节点分工:

节点角色备份核心数据库通知
Server 1qq-bot-core / qq-bot-apinginx、证书、Compose、Docker volume 数据目录无库,跳过 dump 与库路径校验海外直连 Telegram HTML
Server 2my-blog / status-monitor博客目录、status-monitor compose 与数据Python 在线热备未压缩 status-monitor.sqlite,integrity 通过后再备海外直连 Telegram
Server 3qq-bot 核心(写入最重)配置 / 插件 / 数据(排除在线 WAL 原库)在线热备固定名 qq-bot.sqlite,靠 Restic 块去重扛增量海外直连 Telegram
Server 4api-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.lockflock -n 抢不到就退出,避免 backup 和 prune 对撞
  • 双 timer:daily 做备份 + 轻量 check --no-cache;weekly 做 forget --prunecheck --read-data-subset=1G
  • trap ERR:失败时把最近约 40 行日志塞进 Telegram,方便对着看

更细的架构说明见上一篇备份体系搭建文,这里不重复展开。


收尾#

这次其实就修了两处「旧习惯带进新工具」:

  • 拷贝 env 模板时没按节点删干净,无库机器硬检查数据库路径 → 假异常
  • 用惯了 tar ...-$(date +%F),上了 Restic 还在本地 cp 日期副本 → SSD 被自己备份脚本吃掉

个人项目搭运维时特别容易这样:工具已经是快照去重了,脑子还停在「多留几份压缩包才安心」。

本地只留最新一份一致性快照;历史、校验、保留策略交给加密去重仓库。边界划清,链路才轻,关键时刻也不至于被自己的「保险措施」拖后腿。

分享

如果这篇文章对你有帮助,欢迎分享给更多人!

用 Restic 备份本地却在疯狂吃磁盘?
https://github.com/Dawn6666666/MyBlog
作者
黎明
发布于
2026-05-25 11:57:47
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时

目录