FreshRSS 自动更新订阅源失效排查:AutoTTL 扩展失效竟是 Docker 官方埋下的坑
最近我的 FreshRSS 阅读器出了一个怪现象:用来实现智能刷新订阅源的 AutoTTL 扩展在这个月初突然“罢工”了。具体表现为,我手动点击刷新后,它能按调整后的 TTL 时间更一次,之后就彻底“躺平”。所有订阅源的「下次更新时间」都卡在 pending,关掉 AutoTTL 反而能恢复正常自动刷新。
这问题有点意思,像是某个环节的状态机卡住了。作为一个喜欢刨根问底的人,我花了点时间深入排查,最终发现问题的根源竟是一个看似不相关的数据库警告。记录一下这次排查的全过程,给遇到类似问题的博友一个排故参考。

文章目录
FreshRSS 自动更新问题描述
FreshRSS 部署情况
- 运行环境:FreshRSS 与 PostgreSQL 均部署在 Docker 容器中。
- 软件版本:FreshRSS:V 1.27.1;PostgreSQL:V 15.15;AutoTTL: V 0.5.9。
FreshRSS 诡异现象
- 在 FreshRSS 管理页面点击“手动更新”,所有订阅源能正常刷新。
- AutoTTL 插件会在设定的 TTL 时间到达后,成功执行一次自动更新,刷新全部订阅源(其实并不,只是当时我以为是全刷新了)
- 但在此之后,所有订阅源的“下次更新时间”全部显示为 pending,AutoTTL 的自动调度机制似乎完全停止工作。
- 关键线索:关闭 AutoTTL 扩展后,FreshRSS 基础的计划任务反而能正常定时刷新。

FreshRSS 自动更新问题初步判断
问题的核心矛盾点很明确:
- 手动刷新有效:说明 FreshRSS 的核心更新脚本
actualize_script.php和网络连接本身没问题。 - AutoTTL 自动调度失效:说明负责定时触发更新的“闹钟”——也就是 Cron 服务,或者 AutoTTL 扩展自身出了问题。
- 关闭 AutoTTL 后正常:这几乎将矛头直接指向了 AutoTTL 扩展。我第一感觉是插件冲突或者插件本身 Bug 了。
FreshRSS 自动更新问题排查
最讨厌这种“时灵时不灵”的问题,因为手动刷新后,AutoTTL 扩展居然还能正常工作一次(其实并不是正常工作,只是当时我没发现而已。其实这次会在更新到一半时卡住,但因为会更新一部分订阅源所以我当时一直以为订源被全部更新了)
第一步:先确保自己是在用最新版的软件
首先重新拉取一次镜像,并检查AutoTTL 扩展的实际版本,确保他们都是最新版,以防这个 bug 其实早就被修复了,只是我没更新,或者是两者某一方更新后,另一方没更新导致的兼容性问题。
经过检查,确认目前,FreshRSS、PostgreSQL、AutoTTL都是他们各自的最新版本了。
第二步:看眼前端日志
看眼日志里都有点啥问题,是不是某个订阅源有问题,导致卡死在它上边了
虽然日志中有很多类似报错
cURL error 28: Operation timed out
HTTP 503 Service Unavailable!
HTML+XPath Web scraping failed for
Error fetching content: HTTP code 0: Could not resolve host:
但这基本都是订阅源本身的问题,比如触发了源的抓取频率限制,源站服务器卡了。并没有发现会引起订阅源无法更新的故障。于是这时我感觉肯定是扩展的锅,于是就跑去 github 给 AutoTTL 发了个 issues。
扩展作者mgnsk的回复提醒了我“How often does your cron run? A pending status means that the time for updating the feed has arrived but cron has not run yet.(cron 每隔多久运行一次?挂起状态意味着更新 feed 的时间已到,但 cron 尚未运行。)”
第三步:检查 Docker 内的 Cron 服务
FreshRSS 的自动更新依赖于容器内的 Cron 服务定时执行任务,既然自动更新卡住,那就先检查 cron 是不是正常工作。
- 这里为了行文方便,先假定一些配置
FreshRSS本体容器名:freshrss-app PostgreSQL数据库容器名:freshrss-db PostgreSQL数据库用户名:freshrss PostgreSQL数据库密码:freshrss - 进入容器:首先得进到容器内部看看。
docker exec -it freshrss-app /bin/bash - 检查 Cron 状态:看下是不是 cron 服务宕了
输入service cron status,结果显示cron is running.。嗯,系统级的 cron 在正常走,没问题。 -
查看定时任务:看看具体定时任务是什么
执行
crontab -l,看到了关键配置:*/21 * * * * . /var/www/FreshRSS/Docker/env.txt; su www-data -s /bin/sh -c 'php /var/www/FreshRSS/app/actualize_script.php' 2>> /proc/1/fd/2 > /tmp/FreshRSS.log
这个配置设计得很周到:先加载环境变量文件,然后切换到 www-data 用户执行更新脚本,还把日志重定向了。
- 手动执行定时任务:
先不带参数执行一下试试- 直接键入
php /var/www/FreshRSS/app/actualize_script.php:结果直接罢工了,好吧看来环境变量是必须的。 - 那就带上参数试试
. /var/www/FreshRSS/Docker/env.txt; su www-data -s /bin/sh -c 'php /var/www/FreshRSS/app/actualize_script.php'结果订阅源正确刷新了! 这说明Docker内,cron设置的更新命令本身和权限设置都是正确的,所以如果不使用 AutoTTL 时能正常更新是理所应当的。
- 直接键入
第四步 研究下 AutoTTL 是如何工作的
AutoTTL 的工作原理,其实就是
1. 先根据每个订阅源历史上的平均更新间隔,最短更新间隔,计算出每个不同的订阅源,最合适的刷新间隔。
2. 拦截系统的cron,让他不是刷新所有订阅源,而是改为触发 AutoTTL,由 AutoTTL 去判断本次 cron 应该去刷新哪些订阅源。
3. 就在这时,我注意到了一个事情:AutoTTL 会往数据库里写数据并计算排序他们 既然刚才手动执行系统级 Cron 任务能成功,为什么自动运行时 AutoTTL 就不行呢?差别就在于“手动”和“自动”之间的环境差异。我意识到,刚才的输出信息我还没仔细看。
第五步:回头再看一眼刚才被忽略的警告日志
再次手动执行 Cron 任务,但这次我紧紧盯着终端输出。果然,在一堆刷新成功的提示信息之间,发现了一条 WARNING:
WARNING: database "freshrss" has a collation version mismatch
DETAIL: The database was created using collation version 2.36, but the operating system provides version 2.41.
HINT: Rebuild all objects in this database that use the default collation and run ALTER DATABASE freshrss REFRESH COLLATION VERSION, or build PostgreSQL with the right library version.
这个警告来自于 PostgreSQL 数据库。大意是:数据库的排序规则版本和操作系统提供的版本不匹配。通常是因为系统底层库升级了,但数据库对象还用的是旧规则。
我想起来,月初时服务器宕机了一次,被我顺势维护了一番,当属将所有能更新的 docker 都手动更新了一次,而日常docker 的自动更新是由 Watchtower 做的,为了稳定性,我并不允许 Watchtower 去更新 docker 中的数据库版本,这次我看 PostgreSQL 只是一个小版本升级( 15.14 → 15.15 )更新日志中没改啥东西,就顺手也给升级了。
第六步:修复数据库排序规则
根据警告信息的提示,我们需要对 PostgreSQL 数据库进行操作。
- 连接至 PostgreSQL 数据库:
# 进入 PostgreSQL 的容器,使用 psql 客户端连接 docker exec -it freshrss-db psql -U freshrss -d freshrss - 重建数据库索引(重要):
在数据库连接中,执行以下 SQL 命令。执行以下命令,重建所有使用默认排序规则的数据库对象(主要是索引)以确保其与新版本的规则兼容。REINDEX DATABASE freshrss;这个过程可能会花费一些时间,取决于你数据库大小。
-
刷新数据库的排序规则版本:
重建完成后,重建完成后,执行 WARNING 提示中的命令,更新数据库的系统目录版本:ALTER DATABASE freshrss REFRESH COLLATION VERSION; - 在freshrss中手动刷新一次订阅源,耐心等待了下一个 Cron 周期…………好了 AutoTTL 正常工作了,订阅源能够按照 Adjusted TTL 定期自动更新,完成故障修复。
FreshRSS 自动更新,为什么因为“警告”就会导致故障?
我推测是这样的机制
- 系统级 Cron 按时启动,AutoTTL拦截 Cron。
- AutoTTL 开始工作,首先它会连接数据库,准备获取需要更新的订阅源列表。
- AutoTTL 连接数据库执行初始查询,排序订阅源列表,确定现在哪些订阅源需要更新。
- PostgreSQL 输出了这个排序规则不匹配的警告。这个警告信息可能被 AutoTTL 的错误处理机制捕获,导致脚本的执行流程被意外中断或挂起,但又没有抛出致命的错误,所以 FreshRSS 的日志中也不会有记录。
- 于是,AutoTTL “静默”失败了。对 AutoTTL 插件来说,它感知到的状态就是“上一次更新任务启动后没正确结束”,所以它不敢再调度新的任务,所有状态便卡在了
pending。 - 当我手动刷新时,绕过了 AutoTTL 的排序步骤, AutoTTL 只记录订阅源的最后刷新时刻,所以更新能成功。
总结与教训
- 不要忽视任何警告(Warning):尤其是数据库、系统底层的警告。它们可能不会立即导致服务崩溃,但会像“慢性病”一样,在特定条件下引发诡异的行为。
- 日志是救命的黄金:不要感觉如果能跑 WARNING 日志就不需要,而只记录 ERROR 日志。这次如果放过日志中的 WARNING ,我可能还在插件代码里兜圈子。
- 数据库升级需谨慎:在这之前我只锁死大版本,谁能想到这次小版本升级都能出事,Docker 官方实打实的给我上了一课。
- Docker跑数据库需指定精确的版本号:数据库的 docker 镜像一定要写死版本,绝对不要使用 latest 标签,务必使用精确的版本号,以确保部署的一致性。最好连 Debian 版本号也指定上,也就是 17.6-bookworm 这样的版本号。为什么?比如这次的小版本更新中实际 隐含着一次 Linux 操作系统大版本升级。 你以为自己只是从 PostgreSQL 15.14 升级到 PostgreSQL 15.15 只是数据库的一次小版本号升级,但实际上 Docker 官方提供的 PostgreSQL 镜像,这次把运行 PostgreSQL 的操作系统从 Debian 12 升级到了 13 。这就导致 C 函数库 (glibc) 版本出现了跃迁 —— glibc 版本从 Debian 12 的 2.36 升级到了 Debian 13 的 2.41,而在这两个 glibc 版本中,排序规则发生了变化,这也就是本次故障产生的核心原因所在。
希望这篇记录能帮到遇到类似问题的朋友。如果你的 FreshRSS 或者其他使用了 PostgreSQL 的Docker 也出现了什么灵异现象,不妨先去检查一下数据库日志,说不定会有惊喜(或者说惊吓)。
具体这次 Docker 官方在 PostgreSQL 升级时做了什么,可以参考这篇文章《原地报废:不要在生产环境用Docker跑PostgreSQL!》

