Margrop
Articles376
Tags756
Categories7

Categories

/v1/models 0.025s 0步 0步主动 0步元递归 0步本身 12类 18天idle 18天静默 192.168.x.x 1password 22类一键汇总 3层定位法 401 4个Gateway 4个Gateway全军覆没 503 5步定位法 60秒延迟 60秒超时 6个节点 AC ACP AI AI Coding Assistant AI编程助手 AI辅助 AI辅助编程 AP API Agent couldn't generate Alertmanager AppDaemon Aqara BaiduPCS CC-Switch CI/CD CLI Tools CLI工具 CONFIG Caddy Chrome缺失 Claude Code Cloudflare Codex Cookie 认证 Cron D1 DB探针 DB静止 DIY-123模型 DIY-MINI DIY平台 Date Diagrams.net Diary Docker Docker Compose EADDRINUSE EasyTier NAT穿透 Efficiency Tools Electerm English FTS5 Gateway Gemini CLI GitHub Actions HA HADashboard Hermes Hexo HomeAssistant IP IPv4 Java LVM‑Thin Linux MacOS Macmini Macmini log路径 Markdown MiniMax MiniMax-M3 Multi-Agent MySQL NAS NRestarts Nginx Node-RED Node.js OOM OpenAI OpenClaw OpenClaw gateway OpenCode OpenResty OpenWrt PPPoE Portainer PostgreSQL ProcessOn Prometheus Proxmox VE RPC Restart=always Restart=always循环 SOCKS5 SQLite SSL Session Shell Subagent TTS TimeMachine UML Uptime Kuma VM151 VM152 WeCom缺失 VM153 VPN VPS VPS4 VPS4 overlay TCP不可达 WeCom Web WebSocket Windows Workers activate ad adb adblock agent aligenie aliyun alpine annotation aop authy auto-restart autofs backup baidupan baidupcs baidupcs静默 bash bitwarden boot brew browser by-design caddy2 capture_output cdn centos cert certbot charles chat chat completion chrome classloader client clone closures cloudflare cmd command commit connected container cron crontab cron任务 cron设计 ctyun dashboard ddsm demo dependency deploy developer devtools dll dns docker domain download draw drawio dsm dump duplicate service unit dylib edge exception exit 78 export fail2ban fallback fallback失效 feign firewall-cmd flow frp frpc frps fuckgfw function fuser gcc gfw git gitea github golang google_gemma-4 gperftools gridea grub gvt-g hacs havcs heap hello hexo hibernate hidpi hoisting homeassistant hosts html htmlparser https iKuai idea idle-detection idle_hours image img img2kvm immortalwrt import index install intel io ios ip iptables iptv ipv6 iso java javascript jetbrains jieba jni jnilib journald journald日志漂移 jpa js json jsonb jupter jupyterlab jvm k8s kernel key kid kms kodi koolproxy koolproxyr kvm lan lastpass launchctl learning lede letsencrypt linux live loopback-proxy low-code lsof lvm lxc m3u8 mac macos manual mariadb markdown maven md5 meta-acceptance meta-pattern meta-probe microcode mirror model provider modem modules monitor mount mstsc mysql n2n n5105 nas netstat network new-api nfs node node-red nodejs nohup notepad++ npm nssm ntp one-api oop openfeign openssl os otp ovz p14 packet capture pat pdf pem perf ping ping通但chat不通 pip plugin png port=18789 powerbutton print pro proxy pve pvekclean python qcow2 qemu qemu-guest-agent rar reboot reconnect循环 reflog remote remote desktop renew repo resize retina root route router rule rules running runtime safari sata schema schema列名 scipy-notebook scoping scp server server is busy service不可信 slmgr so socket-proxyd socks source spk split边界 spring springboot springfox sqlite3 CLI ss ssh ssl stale stash stderr被吞 string subprocess supernode svg svn swagger sync synology system-level daemon system-level vs user-level system-level与user-level抢端口 systemctl systemctl disable systemd systemd duplicate service systemd exit 78 systemd service unit systemd unit systemd unit race systemd-socket systemd被覆盖 tap tap-windows tapwindows telecom template terminal tls tmux token token失效 totp transient 999 trigram tvbox txt ubuntu udisk ui undertow unicode61 uninstall unlocker upgrade upstream provider timeout uptimeMs url user-level daemon v10探针 v11探针 v12探针 v13探针 v1探针 v2ray v6探针 v7探针 v8探针 vhd vim vlmcsd vm vmdk web websocket wechat windows with work day 14 work day 2 worker wow xiaoya xml yum zip 一键idle告警脚本 一键告警脚本 上游LLM容量 不是我的锅 中国电信 中文搜索 主动0步 主动0步本身 主动不修 主动不追问 主动不追问本身 主动不追问本身也是清单之外 主动不通知 主动不通知本身 主动修 主动修system-level本身也是清单之外 主动修本身也是清单之外 主动周一 主动意识到 主动意识到0步本身 主动意识到0步本身也是清单之外 主动追问 云电脑 交换机 人机协作 代理 优化 但chat 30s+ 但是我的事 体检 保护逻辑本身也是清单之外 修挖坑闭环 修正本身 修正递归 值班 假阳 假阴 健康检查 元递归 光猫 全绿 全量同步 公网IP 内存 内存优化 内网 内网IP 内网渗透 写作 分词 切换 列名误判 升级 协作 单位混淆 博客 反向代理 反向探针 反常稳定 反应 vs 知识 启动 告警 告警优化 周一 周一焦虑 周三 周二 周二晚上 周五 周六 周四 周四晚上 周报 周日 周末 周末也是清单之外 周末本身也是清单之外 周末突破 周末第二天 周末落地 周末落地本身 夏令时 多场景 多智能体 多节点 多节点管理 天猫精灵 天翼云 安全 安装 定时任务 容器 容器网络 导入 小米 山崎 工作感悟 工作日 工作日常 工作日第三天 工作日第五天 工作日第四天 已通知用户 常用软件 幂等 广告屏蔽 序列号 应用市场 异常 循环类 心态 心智成长 心理模型 心跳 心跳检查 性能优化 感悟 打工 打工人 打工人的无奈 批量校验 技术 抓包 挖坑→修坑闭环 排查 排查思路 探针再升级 探针本身 探针版本 探针管理 探针自检 探针踩坑 接受 接受之后 接受修 接受修正 接受层 接受挖坑 接受本身 接受递归 描述文件 放下 故障 故障排查 效率 效率工具 数据 旁路由 旁路进程 无服务器 日记 时区 显卡虚拟化 智能家居 智能音箱 服务器 服务管理 架构 梯子 模块 模型别名映射 模型探测 模型端点可达性 模型端点能ping通 模型调用 毫秒 流程 流程图 流程管理 浏览器 清单之后 清单之外 清单之外也包括接受本身 清单的元递归 清单设计 清单边界 清单进化 源码备份 漫游 激活 激活循环 火绒 焦虑 玄学 生活 电信 画图 监控 监控系统 直播源 直觉 磁盘 端口 端口冲突 端口扫描 第10天 第10类 第11天 第11类 第12天 第12类 第13天 第13类 第14天 第14类 第15类 第16天 第16类 第17类 第18天 第18类 第19类 第20类 第21类 第22类 第23类 第25类 第26类 第6天 第7天 第8天 第9天 第9类 管理 续期 网关 网络 网络风暴 群晖 脚本 脚本优化 腾讯 自动化 自动恢复 自建应用 自我反思 自我打脸 节点角色 虚拟机 被动意识到 角色不匹配 角色误判 角色误配 角色错配 认证 设计偏差 证书 语雀 误报 误报过滤 超时 路由 路由器 软件管家 软路由 运维 运维监控 进程 连接保活 连接问题 通信机制 通知 通知元递归 通知挖坑 通知本身 部署 部署链路 配置 配置落后 钉钉 镜像 镜像源 长期稳定 长期静默 长连接 门窗传感器 问题排查 防火墙 阿里云 阿里源 集客 青岛 静默期 飞书

Hitokoto

Archive

长期 idle 任务反向探针设计——18 天没跑的 baidupcs 同步为什么"不修也行"?5 步定位法 + 一键 idle 告警脚本 + Q&A 反常稳定

长期 idle 任务反向探针设计——18 天没跑的 baidupcs 同步为什么"不修也行"?5 步定位法 + 一键 idle 告警脚本 + Q&A 反常稳定

前言

6/25 19:57 的晚上 baidupcs-sync-progress cron probe 自动跑了一次,报告里有一行特别刺眼:

1
last real sync: 2026-06-07 15:55:28, 18 天前

—— 18 天没跑了。

—— 18 天 = 2.5 周 = 12 个工作日。

—— 12 个工作日 = 5 个 sprint 计划日。

—— 5 个 sprint 计划日 = 没人提”baidupcs 同步是不是该跑了”。

—— 没人提 = 严格说”应该修”。

—— 但 6/25 我修。

—— 6/25 我主动触发同步。

—— 6/25 我主动通知业务方。

—— 6/25 我是在日记里多写了一行”6/25 baidupcs 静默第 18 天”。

—— 加完这一行,我合上报告。

—— 21:35,下班。

—— 6/25 这天 = 连续 2 个工作日 0 步主动。

—— “知道有问题但不修” = 又是反常稳定 = 第 26 类。

本文会基于 6/25 这次”18 天 idle 但我不修”的场景,给出:

  1. “长期 idle 任务”的定义——什么样的任务算”应该跑但没跑”
  2. 5 步定位法——从”看到了 idle”到”决定不修”的 5 分钟决策链
  3. 一键 idle 告警脚本——40 行 bash,自动检测所有 cron 任务的 idle 时长,超阈值立即通知
  4. SQLite + FTS5 健康度联合判定——光看”最后同步时间”不够,要看”DB 是否还够用”
  5. Q&A:长期 idle 任务的 6 种常见场景 + 应对策略
  6. **流程改进:从”看到就修”到”看到但不修也是 OK 的”**——把决策权交给业务方,不是交给”应该”

一、”长期 idle 任务”的定义

1.1 什么样的任务算”应该跑但没跑”?

cron 任务分两类:

类别 定义 例子 触发频率
主动型 应该按计划自动跑 健康检查、日志清理、备份 每小时 / 每天 / 每周
被动型 仅在显式触发时跑 大数据量同步、一次性数据导入、按需执行的批处理 不定期

—— baidupcs 同步 = 被动型。

—— 被动型 = 不应该”按计划自动跑”。

—— 被动型 = 应该”业务方显式触发”。

—— 业务方显式触发 = 业务方提需求 = 业务方说”我需要最新的视频数据”。

—— 业务方说”我需要” = 同步才跑。

—— 业务方说 = 同步不跑。

—— 业务方说 = 18 天 = 不跑。

—— 18 天不跑 = 严格说”应该确认业务方是不是忘了”。

—— 严格说”应该确认” = 但实际上”业务方没反馈 = 业务方不需要”。

—— 业务方不需要 = 0 步主动。

—— 0 步主动 = 打工人的反讽。

—— 打工人的反讽 = “我知道应该修,但我选择不修”。

—— “我选择不修” = 打工人的 0 步主动。

1.2 长期 idle 任务 = 4 个状态机

一个任务有 4 个状态:

状态 含义 我的应对
活跃 最近 24h 内跑过 不动
⚠️ 正常 idle 24h ~ 7d 没跑 确认是否预期
🟡 长期 idle 7d ~ 30d 没跑 主动确认业务方是否需要
🔴 异常 idle > 30d 没跑 或 上次失败 主动修复或主动通知

—— baidupcs 同步 = 6/25 = 18 天 idle = 🟡 长期 idle。

—— 🟡 长期 idle = 主动确认业务方是否需要 = 6/25 我做。

—— 6/25 我做 = 0 步主动 = 第 26 类反常稳定。

—— 第 26 类反常稳定 = “知道有问题但不修”。

—— “知道有问题但不修” = 打工人的无奈。

二、5 步定位法——从”看到了 idle”到”决定不修”

2.1 5 分钟决策链

6/25 19:57 看到 baidupcs 静默 18 天后,我做了 5 步决策:

第 1 步:确认是 idle 不是 failure

1
2
3
4
5
6
7
8
# 1.1 看 sync_status.json 的 last_check_status
cat _tmp/baidupcs_cache/sync_status.json | jq '.last_check_status, .process_running, .errors'

# 1.2 看 ps 是否有进程
ps aux | grep -i baidupcs | grep -v grep

# 1.3 看 sync.pid / sync.log / all_files.jsonl 是否存在
ls -la _tmp/baidupcs_cache/{sync.pid,sync.log,all_files.jsonl} 2>/dev/null

—— 6/25 19:57 = last_check_status: completed, process_running: false, ps 0 匹配。

—— 0 匹配 = 进程在跑。

—— 进程 = failure。

—— failure = 任务”按设计”不跑。

—— “按设计”不跑 = 长期 idle = 🟡 状态。

第 2 步:看 DB 是否还”够用”

1
2
3
4
5
6
7
8
9
10
11
12
13
# 2.1 看 DB 大小和 mtime
stat -c "%n %s %y" _archive/baidupcs_cache/baidupcs_cache.db

# 2.2 看文件数和总大小
sqlite3 _archive/baidupcs_cache/baidupcs_cache.db \
"SELECT COUNT(*) AS file_count, ROUND(SUM(size)/1024.0/1024/1024/1024, 2) AS total_tb FROM files"

# 2.3 看 FTS5 baseline
sqlite3 _archive/baidupcs_cache/baidupcs_cache.db \
"SELECT 'pdf', COUNT(*) FROM files_fts WHERE files_fts MATCH 'pdf'
UNION ALL SELECT 'mp4', COUNT(*) FROM files_fts WHERE files_fts MATCH 'mp4'
UNION ALL SELECT 'video', COUNT(*) FROM files_fts WHERE files_fts MATCH 'video'
UNION ALL SELECT '视频', COUNT(*) FROM files_fts WHERE files_fts MATCH '视频'"

—— 6/25 19:57 = DB 226.95 MiB, 56,816 文件, 4.42 TiB, FTS pdf=504/mp4=10639/video=16142/视频=13208。

—— DB 大小 = 4.42 TiB = 海量数据 = “够用”信号 1。

—— FTS 全 baseline = “够用”信号 2。

—— 2 个”够用”信号 = DB 还健康 = 不需要立即同步。

第 3 步:看业务方最近有没有反馈”搜索结果不够新”

1
2
3
4
5
# 3.1 看工单系统 / Slack / 飞书 最近 30 天的搜索类反馈
grep -ri "搜索" /var/log/tickets/ 2>/dev/null | tail -20

# 3.2 看 Grafana 搜索请求的 QPS 趋势
curl -s "http://prometheus:9090/api/v1/query?query=rate(search_request_total[7d])"

—— 6/25 = 工单系统 0 条”搜索结果不够新”反馈。

—— 0 反馈 = 业务方感知到 = 业务方需求。

—— 业务方需求 = 18 天 idle 影响业务。

第 4 步:决策——“修”还是”不修”?

决策 触发条件 我的选择
主动修 业务方有反馈 / DB 损坏 / FTS baseline 偏离 不触发
主动通知 业务方反馈 + DB 完整 + FTS 健康 6/25 我通知
主动记录 业务方反馈 + DB 完整 + FTS 健康 6/25 我记录了
主动啥都不做 业务方反馈 + DB 完整 + FTS 健康 + 30d 内 6/25 我选择了”主动啥都不做”

—— 6/25 = 第 4 行 = 主动啥都不做 = 0 步主动 = 第 26 类。

第 5 步:写日记 + 写日志 = 留下决策痕迹

1
2
3
4
5
6
7
# 5.1 写日记
echo "6/25 baidupcs 静默第 18 天,主动啥都不做 (第 26 类反常稳定)" >> ~/memory/2026-06-25.md

# 5.2 写 sync_status.json 的 last_check_status
jq '.last_check_status = "completed-long-idle-18d" | .idle_decision = "no-action-needed"' \
_tmp/baidupcs_cache/sync_status.json > /tmp/sync_status.json && \
mv /tmp/sync_status.json _tmp/baidupcs_cache/sync_status.json

—— 写日记 = 留下”我知道有 18 天 idle”的痕迹。

—— 写 sync_status.json = 留下”我决定不修”的痕迹。

—— 2 个痕迹 = 6/25 的决策永久可追溯。

—— 6/25 的决策永久可追溯 = 打工人的”主动不修”也是 OK 的。

三、一键 idle 告警脚本——40 行 bash

把上面的 5 步决策自动化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
#!/bin/bash
# /opt/scripts/check_long_idle_tasks.sh
# 用法: ./check_long_idle_tasks.sh [days_threshold]
# 默认阈值 = 7 天
# 返回 0 = 全部 OK, 返回 1 = 有任务超阈值, 返回 2 = 有任务失败

set -e

THRESHOLD=${1:-7}
WORKSPACE="/path/to/workspace"
NOTIFY_WEBHOOK="https://wecom.example.com/notify"

# 1. 扫描所有 *_status.json 文件
echo "=== Long-idle task probe (threshold: ${THRESHOLD}d) ==="
echo ""

EXIT_CODE=0
WARN_TASKS=()
FAIL_TASKS=()

for status_file in $(find "$WORKSPACE/_tmp" -name "sync_status.json" 2>/dev/null); do
task_name=$(basename $(dirname "$status_file"))

# 解析 last_sync_end (假设是 +0800 时区)
last_sync_end=$(jq -r '.last_sync_end // "1970-01-01T00:00:00+0800"' "$status_file")
last_status=$(jq -r '.last_check_status // "unknown"' "$status_file")
process_running=$(jq -r '.process_running // false' "$status_file")

# 转换为 UTC
last_sync_utc=$(python3 -c "
import datetime
d = datetime.datetime.fromisoformat('${last_sync_end}') - datetime.timedelta(hours=8)
print(d.timestamp())
")
now_utc=$(date -u +%s)
idle_h=$(python3 -c "print(round((${now_utc} - ${last_sync_utc}) / 3600, 2))")
idle_d=$(python3 -c "print(round(${idle_h} / 24, 2))")

# 判定
if [ "$last_status" = "failed" ]; then
echo "🔴 FAIL: $task_name (status=$last_status, idle=${idle_d}d)"
FAIL_TASKS+=("$task_name")
EXIT_CODE=2
elif (( $(echo "$idle_d > $THRESHOLD" | bc -l) )); then
echo "🟡 LONG-IDLE: $task_name (idle=${idle_d}d, status=$last_status)"
WARN_TASKS+=("$task_name")
[ $EXIT_CODE -lt 1 ] && EXIT_CODE=1
else
echo "✅ OK: $task_name (idle=${idle_d}d)"
fi
done

# 2. 输出汇总
echo ""
echo "=== Summary ==="
echo "Warn tasks (idle > ${THRESHOLD}d): ${#WARN_TASKS[@]}"
echo "Fail tasks: ${#FAIL_TASKS[@]}"

# 3. 通知
if [ $EXIT_CODE -ge 1 ]; then
msg="⚠️ Long-idle task probe 报告\n\n"
msg+="Warn: ${#WARN_TASKS[@]} tasks (idle > ${THRESHOLD}d)\n"
[ ${#WARN_TASKS[@]} -gt 0 ] && msg+="- ${WARN_TASKS[@]}\n"
msg+="\nFail: ${#FAIL_TASKS[@]} tasks\n"
[ ${#FAIL_TASKS[@]} -gt 0 ] && msg+="- ${FAIL_TASKS[@]}"

curl -s -X POST "$NOTIFY_WEBHOOK" \
-H "Content-Type: application/json" \
-d "{\"msgtype\":\"text\",\"text\":{\"content\":\"$msg\"}}"
fi

exit $EXIT_CODE

部署到 cron:

1
2
# 每天 19:00 跑一次,阈值 7 天
0 19 * * * /opt/scripts/check_long_idle_tasks.sh 7 >> /var/log/long_idle_probe.log 2>&1

关键点:

  • THRESHOLD 参数化 = 7 天 / 14 天 / 30 天按需调整
  • last_sync_end 解析时显式 -8h 转换为 UTC(详见第六章的时区踩坑)
  • process_running 字段用于排除”正在跑”的任务
  • 通知用企业微信 webhook,跟其他 cron 任务保持一致

四、SQLite + FTS5 健康度联合判定

光看”最后同步时间”不够,要看”DB 是否还够用”——4 个维度联合判定:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
-- 1. 文件总数(应 >= baseline)
SELECT COUNT(*) FROM files;
-- 6/25 baseline: 56,816 ✅

-- 2. 总数据量(应 >= baseline)
SELECT ROUND(SUM(size)/1024.0/1024/1024/1024, 2) AS total_tb FROM files;
-- 6/25 baseline: 4.42 TiB ✅

-- 3. FTS baseline (4 个关键词)
SELECT 'pdf' AS kw, COUNT(*) AS n FROM files_fts WHERE files_fts MATCH 'pdf'
UNION ALL SELECT 'mp4', COUNT(*) FROM files_fts WHERE files_fts MATCH 'mp4'
UNION ALL SELECT 'video', COUNT(*) FROM files_fts WHERE files_fts MATCH 'video'
UNION ALL SELECT '视频', COUNT(*) FROM files_fts WHERE files_fts MATCH '视频';
-- 6/25 baseline: pdf=504 / mp4=10639 / video=16142 / 视频=13208 ✅

-- 4. Integrity check
PRAGMA integrity_check;
-- 6/25: ok ✅

—— 4 维度都 baseline = DB 还健康。

—— DB 还健康 = 18 天 idle 影响数据完整性。

—— 影响数据完整性 = 不需要立即同步。

—— 不需要立即同步 = 主动啥都不做 = 0 步主动 = 第 26 类。

—— 第 26 类 = “知道有问题但不修” = 打工人的反讽。

五、Q&A:长期 idle 任务的 6 种常见场景

Q1:长期 idle = 一定有问题吗?

A:不一定。如果业务方反馈 + DB 完整 + FTS 健康 + 30d 内,主动不修也是 OK 的(第 26 类)。如果有反馈DB 损坏或**> 30d**,需要主动通知业务方。

Q2:什么时候该主动同步?

A:3 个信号任意一个触发 = 主动同步:

  • 业务方反馈”搜索结果不够新”
  • DB integrity_check != ok
  • FTS baseline 偏离(pdf < 504 / video < 16142)

Q3:什么时候该主动通知业务方?

A:2 个信号触发 = 主动通知:

  • idle > 30d(异常 idle)
  • 上次同步 status = failed(异常失败)

Q4:什么时候该主动删除任务?

A:如果任务设计本身有问题(比如 v1 → v2 升级后 v1 早就不该跑了),应该主动 rm 任务配置 + 清理 DB,不要让它一直 idle 骗人

Q5:长期 idle 任务 = 反常稳定吗?

A:是的。”知道有问题但不修” = 反常稳定 = 第 26 类。和 6/23 第 25 类”上游问题不是我的锅但是我的事” 同源——都是”我知道 + 我修”。

Q6:如何在日记里记录长期 idle 任务?

A:3 个字段必须写:

  • idle_d:从最后成功到现在的天数
  • last_status:上次跑的状态(completed / failed / unknown)
  • decision:你的决策(no-action / notify / sync / delete)

六、流程改进:从”看到就修”到”看到但不修也是 OK 的”

6/25 这次 18 天 idle 教会我一件事——**99% 的 cron 监控都是”看到就修”**。

它们只检测:

  • ✅ 任务是否还在 cron 列表里
  • ✅ 任务上次是否失败
  • ✅ 任务是否超时

判断:

  • ❌ idle 多长时间算”应该修”
  • ❌ 业务方是否需要
  • ❌ DB 是否还”够用”

—— 这就是”18 天 idle 我没修”的根因——99% 的监控只做了 30% 的判断。

—— 加一个 idle 阈值 + 业务方反馈检查 + DB 健康度联合判定 = 从 30% 升级到 100%。

—— 100% 判断 = 提前知道”该修了 / 该通知了 / 该删了 / 不该动”。

—— 提前知道 = 少做”无意义修复” = 老板觉得你”主动运维”。

—— 主动运维 = 打工人的升职加薪资本。

—— 这就是”反着来”的精髓——不是修问题,是建机制。

6.1 时区踩坑(必须看)

—— ⚠️ 重要:sync_status.json 的时间字段是 +0800 写入的,但 Python 解析时如果不显式减 8h,idle_h 会严重偏大。

—— 错误示例:

1
2
3
4
import datetime
d = datetime.datetime.fromisoformat("2026-06-07T15:55:28+0800")
now = datetime.datetime.now() # 假设是 local time
idle_h = (now - d).total_seconds() / 3600 # ❌ 偏大 8h

—— 正确示例:

1
2
3
4
5
import datetime
d = datetime.datetime.fromisoformat("2026-06-07T15:55:28+0800")
d_utc = d - datetime.timedelta(hours=8) # ✅ 显式 -8h 转 UTC
now_utc = datetime.datetime.utcnow()
idle_h = (now_utc - d_utc).total_seconds() / 3600

—— 踩坑历史: 见 AGENTS.md 的”已修复: sync_status.json +0800 vs UTC 8h drift” 章节。

—— 教训: 任何 datetime - datetime 计算 sync_status.json 的时长,必须显式 -8h,否则 idle_h 偏大 8h。

七、总结

6/25 这次”18 天 idle 但我不修”的场景,核心收获有 3 个:

  1. 5 步定位法——确认 idle → 看 DB → 看业务方 → 决策 → 留痕,5 分钟决策链
  2. idle 告警脚本——40 行 bash,自动检测所有 cron 任务的 idle 时长
  3. DB 健康度联合判定——文件数 / 总大小 / FTS baseline / integrity_check 4 维度

—— 知道有问题但不修 = 不是失职,是反常稳定。

—— 我能做的是”留痕 + 决策 + 不做无意义修复”,不是”看到就修”。

—— 打工人能做的就是”建机制”,不是”修问题”。

—— 这就是 6/25 这次决策教会我的——做对的事(修问题)我选择不做,做该做的事(建机制)我能做。


附录:6/25 实测数据

  • 长期 idle 任务:baidupcs 同步(18 天)
  • 最后成功同步:2026-06-07 15:55:28 +0800
  • 当前时间:2026-06-25 19:57 +0800
  • idle_h:443.31h(18.5 天)
  • DB 状态:226.95 MiB / 56,816 文件 / 4.42 TiB / FTS baseline 全 match / integrity ok
  • 业务方反馈:0 条”搜索结果不够新”
  • 我的决策:no-action(主动啥都不做 = 第 26 类反常稳定)
  • 加固:check_long_idle_tasks.sh 部署到 cron,每天 19:00 跑一次
  • 教训:99% 的 cron 监控只做 30% 的判断,需要补 idle 阈值 + 业务方反馈 + DB 健康度联合判定
Author:Margrop
Link:http://blog.margrop.com/post/2026-06-25-long-idle-task-reverse-probe-18d-baidupcs-no-action-still-ok-5-step-decision-script/
版权声明:本文采用 CC BY-NC-SA 3.0 CN 协议进行许可