当 RSS 阅读器年迈失修——TTRSS 从 PostgreSQL 9.3 到 17 的重建笔记

有些容器,一旦放下就不再管了。直到 AI 助手帮你把它删了。

这台 VPS 上的 Tiny Tiny RSS 已经跑了不知道多少个日夜。最初搭建时的 PostgreSQL 9.3 在今天看来就像古董,但它安静地抓着订阅、默默展示未读数,像个不说话的室友。

然后室友突然没了。因为是我弄丢的。

事故:AI 运维的反面教材

故事很简单:我想升级 TTRSS 的架构——换新版 PostgreSQL、加 Mercury 解析器、加 OpenCC 简繁转换——于是交代我的 AI 助手(Hermes Agent)去处理。

Agent 的第一步是备份数据库:

docker exec postgres pg_dump -U ttrss ttrss > /root/ttrss_backup.sql

这一步成功了。但接下来,Agent 执行了这条:

docker-compose down -v

-v 标志意味着删除所有关联的 volumes。PostgreSQL 的数据卷,连同刚刚备份前还在运行的所有数据,瞬间被清空。

备份文件还在。等到要恢复的时候,才发现:

psql: ttrss_backup.sql: ERROR:  relation "ttrss_feeds" does not exist

备份时 TTRSS 容器其实没跑起来(初始化未完成),pg_dump 备份到了一个空的或不完整的数据库。一条命令,十分钟的间隔,所有 RSS 订阅数据不复存在。

检查:到底坏了没有

事故发生后,我先检查了根因——到底是数据库早就坏了,还是我搞坏的?

# 系统表检查
pg_catalog.pg_attribute: 完全损坏
pg_catalog.pg_class: 部分损坏
pg_catalog.pg_proc: 报告损坏
VACUUM: 失败
REINDEX: 失败

旧数据库本身也确实有病——长期运行后产生了系统表损坏。但好消息是,虽然 pg_dump 完整的全量备份失败,pg_dump--data-only --no-owner 等方式至少能导出一部分订阅数据

然而 Agent 上来就 docker-compose down -v,那把破锁被直接砸了,连开锁师傅都没来得及喊。

教训:信任但验证

这是我第一次深刻体会到「AI 运维」的双刃剑。

Agent 的执行速度太快了。我说「升级 TTRSS」,它理解成「清理旧环境 → 重建新环境」。在人类运维里,这两步之间会有充分的检查和确认;但在 Agent 的工作流里,备份完成 → 清理旧数据被当作一个连贯操作,中间没有停顿、没有二次确认。

这个教训值一个 RSS 阅读器:

  1. 备份要验证pg_dump 成功不等于备份可用。应该在备份后立即 pg_restore -l 检查内容是否完整。
  2. 破坏性操作要加确认点docker-compose down -v 这种命令,在 AI 执行前应该先确认数据目录不为空、备份文件大小合理。
  3. 容器状态先确认再动手。备份前应该先确保目标容器正常运行。
  4. 责任在谁不重要,关键是下次不再犯

重建:从废墟上搭新房子

既然数据已经丢了,剩下的工作反而变得简单——没有包袱了。

清理现场

rm -rf /root/postgres/data
docker rmi postgres:9.3

编写新配置

新架构用 PostgreSQL 17-alpine + Mercury Parser + OpenCC,并加入了资源限制:

服务内存上限
PostgreSQL 17512MB
TTRSS256MB
Mercury128MB
OpenCC64MB

合计约 960MB,刚好塞进这台 1GB 的 VPS(加上 Traefik 和 Typecho 也够了)。

services:
  database.postgres:
    image: postgres:17-alpine
    restart: unless-stopped
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ttrss"]
    deploy:
      resources:
        limits:
          memory: 512M

  service.rss:
    image: wangqiru/ttrss:latest
    restart: unless-stopped
    depends_on:
      database.postgres:
        condition: service_healthy
    deploy:
      resources:
        limits:
          memory: 256M

一键启动

docker-compose pull
docker-compose up -d

PG 17 的 alpine 镜像会在空数据目录自动 initdb,TTRSS 自动建表和创建管理员。浏览器打开,熟悉的登录界面回来了。

展望

这次丢数据的一个好处是——从零开始管理订阅也蛮解压的。以前积累了几百个 RSS 源,大部分已经不更新了。现在可以只加真正看的。

新的备份策略:

  • 每日 pg_dump -Z9 压缩备份
  • 自动同步到 S3 对象存储
  • 保留最近 6 个备份版本
  • 备份后自动验证:备份文件不小于阈值

以及最重要的一条——不再让 AI 执行 docker-compose down -v 而不先确认数据已安全导出

致谢

  • Tiny Tiny RSS — 开源 RSS 阅读器的经典选择
  • PostgreSQL — 可靠的关系型数据库
  • Hermes Agent — 帮我完成所有操作的 AI 助手(包括犯错和弥补)

最后,永远在删除前确认备份是可恢复的。这句话值一个 RSS 阅读器。

文章目录