docs: 同步 8/28 迁移故障排查记录(FFmpeg 缺失 + 磁盘写满死循环 + DiskGuard)

README.md 新增 Disk-Guard 模块表条目、修正一处遗漏的机器规格描述
(2C12G -> 4C23G)、当前进度补充这次完整排查记录;PROGRESS.md 服务运行
状态表更新 + 新增详细的故障排查时间线记录。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
ericwyuan
2026-08-28 12:10:14 +08:00
parent ef56ae5662
commit c9354ed1a1
2 changed files with 18 additions and 5 deletions

View File

@@ -1,17 +1,28 @@
# 项目进度追踪 # 项目进度追踪
> 最后更新: 2026-08-24 > 最后更新: 2026-08-28
## 服务运行状态 ## 服务运行状态
| 服务 | 节点 | 地址 | 状态 | 验证结果 | | 服务 | 节点 | 地址 | 状态 | 验证结果 |
|------|------|------|------|---------| |------|------|------|------|---------|
| FAM-Core | NAS | 0.0.0.0:8000 | ✅ 运行中 | health=ok, gunicorn 单 workerOracle-Sync + MotionNotifier 轮询 SS 事件推送(游标 DB 续用/失败重试);已部署事件时间轴删除功能 | | FAM-Core | NAS | 0.0.0.0:8000 | ✅ 运行中 | health=ok, gunicorn 单 workerOracle-Sync + MotionNotifier 轮询 SS 事件推送(游标 DB 续用/失败重试);已部署事件时间轴删除功能`/api/status`/`/api/ss/status` 500 bug 已修复 |
| FAM-Edge | Oracle | 0.0.0.0:5000 | ✅ 运行中 | systemd 守护fam-edge.service素材→运动片段分割→只分析片段问答改为转发 AI-Gateway队列消费正常已部署 `/api/oracle/video/delete` | | FAM-Edge | Oracle(新机 129.146.26.249 | 0.0.0.0:5000 | ✅ 运行中 | systemd 守护fam-edge.service素材→运动片段分割→只分析片段问答改为转发 AI-Gateway队列消费正常已部署 `/api/oracle/video/delete`FFmpeg 已补装;新增 DiskGuard 磁盘守护 |
| **AI-Gateway** | Oracle | 0.0.0.0:5100 | ✅ 运行中 | systemd 守护ai-gateway.service2026-08-23 新增;独立仓库/独立部署;`/health` 正常端到端问答实测成功provider=nvidia | | **AI-Gateway** | Oracle | 0.0.0.0:5100 | ✅ 运行中 | systemd 守护ai-gateway.service2026-08-23 新增;独立仓库/独立部署;`/health` 正常端到端问答实测成功provider=nvidia |
| MariaDB | NAS | 127.0.0.1:3306 | ✅ 运行中 | 10.11.11, 6 张表, utf8mb4 | | MariaDB | NAS | 127.0.0.1:3306 | ✅ 运行中 | 10.11.11, 6 张表, utf8mb4 |
| Ollama | Oracle | 127.0.0.1:11434 | ✅ 运行中 | qwen2.5:7b仅智能问答兜底不参与视觉/融合);**被 AI-Gateway 调用,不再被 FAM-Edge 直接调用** | | Ollama | Oracle | 127.0.0.1:11434 | ✅ 运行中 | qwen2.5:7b仅智能问答兜底不参与视觉/融合);**被 AI-Gateway 调用,不再被 FAM-Edge 直接调用** |
## 2026-08-28 Oracle 迁移故障排查FFmpeg 缺失 + 磁盘写满死循环 + DiskGuard
- **触发**:用户反馈事件时间轴历史图片丢失、"谷歌同步是不是有问题"。逐层排查,发现的是三个叠在一起的独立问题,不是一个:
1. **8/25 迁移新机器时漏装 FFmpeg**`ffprobe: command not found`。导致 `videos.duration_sec` 全部读成 0、运动片段分割 `_segment_motion_clips` 全部失败(`ffmpeg` 也没装190 个素材全部"分割 0 段"`events` 表完全是空的,`motion_clips/` 目录空的——事件时间轴从 8/25 起就没有任何新内容,图片自然也生成不出来。**已修复**`apt-get install ffmpeg`,恢复正常。
2. **8/25 之前的历史图片无法找回**:迁移时只搬了 `data/oracle.db`(数据库/文字记录),没有搬 `motion_clips/`磁盘上的运动片段视频文件缩略图的生成源旧机器129.146.203.203)已经释放销毁。历史事件的文字描述还在(早就同步进了 NAS 镜像),但配图永久丢失,无法找回。
3. **Oracle↔Google Drive 同步"只下载不删除"**:排查一度走了弯路(先怀疑 NAS CloudSync 停摆,后发现新文件其实一直在下载,是判断证据不足导致的误判)。最终精确对比 Google Drive 远端141 文件,最早 8/25vs Oracle 本地264 文件,最早 8/15实锤148 个文件是"远端已删、本地未删"的孤儿文件。根因是磁盘被写满到 100%`no space left on device`),触发 rclone 内置安全机制——同步过程中遇到 IO 错误就整体拒绝执行删除,形成"越满删不掉,删不掉越满"的死循环。**已修复**:手动删除 148 个孤儿文件,释放 41G磁盘 100%→59%;重新跑 `rclone_sync.sh` 验证 `exit=0` 无错误,下载+删除恢复正常。
- **新增 DiskGuard 磁盘守护**commit `ef56ae5`,永久性预防措施):`fam-edge/oracle_db.py` 新增 `get_oldest_purgeable_material()`(只挑最旧的、已完成分割阶段的整段素材,绝不碰运动片段和处理中的素材);`disk_guard.py` 新增后台线程5 分钟检查一次,剩余空间 <10GB 触发清理,删到 15GB 水位为止;`/api/oracle/activity` 新增 `disk` 字段(实时剩余空间 + 最近清理动作。9 个新测试fam-edge 全量 139/139 通过。
- **用户决策**8/25-28 期间因 FFmpeg 缺失而"分割 0 段"的 190 个素材/736 个运动事件,明确选择不重新处理,翻页接受这段时间的空白。
- **顺带修复**commit `09708bd``/api/status``/api/ss/status` 500——上次"移除 SS Webhook"重构(`cf3d145`)删了 `MotionNotifier``camera_name_to_id` 等属性但 `status()` 忘记同步更新,导致这两个端点自那次重构起就一直是坏的。
- **文档同步**commit `3ec79de`README 里 5 处 + `vite.config.js` 1 处仍描述"`/api/ss/webhook` 保留为可选"的过时内容,实际已被彻底删除,逐处订正。
## 2026-08-24 事件时间轴支持删除视频会话 ## 2026-08-24 事件时间轴支持删除视频会话
- **决策**:用户要求事件时间轴能删除某个视频会话;调研确认项目里没有任何 delete 类端点先例,且增量同步(`get_sync_delta`)只做 upsert 感知不到 Oracle 端的物理删除NAS 镜像必须显式清理,不能靠 `trigger_now()` 拉增量。用户明确要求连同磁盘上的视频文件一起删除,明确选择不做"防止误删事件被重新分割"的额外保护机制(风险极低:素材一旦标记 done 就不会被生产者重新捡起) - **决策**:用户要求事件时间轴能删除某个视频会话;调研确认项目里没有任何 delete 类端点先例,且增量同步(`get_sync_delta`)只做 upsert 感知不到 Oracle 端的物理删除NAS 镜像必须显式清理,不能靠 `trigger_now()` 拉增量。用户明确要求连同磁盘上的视频文件一起删除,明确选择不做"防止误删事件被重新分割"的额外保护机制(风险极低:素材一旦标记 done 就不会被生产者重新捡起)

View File

@@ -193,6 +193,7 @@ Orchestrator 视觉阶段按 `fallback` 模式顺序降级Gemini → NVIDIA N
| Video-Queue | `video_queue.py` | 生产-消费队列:生产者 30s 轮询 rclone 同步落地目录登记整段素材入队(含重启恢复);消费者(`max_concurrent` 个线程)取队列调 Video-Processor素材分割出的运动片段自动入队模型超时 = 原配置 ×`timeout_multiplier`;失败重试上限 `max_retries` | | Video-Queue | `video_queue.py` | 生产-消费队列:生产者 30s 轮询 rclone 同步落地目录登记整段素材入队(含重启恢复);消费者(`max_concurrent` 个线程)取队列调 Video-Processor素材分割出的运动片段自动入队模型超时 = 原配置 ×`timeout_multiplier`;失败重试上限 `max_retries` |
| Video-Processor | `video_processor.py` | **素材→分割/片段→分析**双分支:素材按 `ss_motion_events` 已结束运动事件 ffmpeg 分割运动片段(`-c:v copy -c:a aac` 保留音频,`motion_event_id` 幂等),片段只送云端 VLM 分析;按 `vision_order` 调适配器;首个成功即落库 Oracle `videos`+`events`+`people`;全失败标 `failed` | | Video-Processor | `video_processor.py` | **素材→分割/片段→分析**双分支:素材按 `ss_motion_events` 已结束运动事件 ffmpeg 分割运动片段(`-c:v copy -c:a aac` 保留音频,`motion_event_id` 幂等),片段只送云端 VLM 分析;按 `vision_order` 调适配器;首个成功即落库 Oracle `videos`+`events`+`people`;全失败标 `failed` |
| Person-Service | `person_service.py` | 汇总全量人物 → LLM 合并为规范名 → `set_canonical`;生成 `known_members_context` 回灌片段提示manual 命名优先不被覆盖 | | Person-Service | `person_service.py` | 汇总全量人物 → LLM 合并为规范名 → `set_canonical`;生成 `known_members_context` 回灌片段提示manual 命名优先不被覆盖 |
| Disk-Guard | `disk_guard.py` | **2026-08-28 新增**5 分钟检查一次磁盘剩余空间,低于 `min_free_gb`(默认 10GB就清理最旧的、已完成分割阶段的整段素材删到 `target_free_gb`(默认 15GB水位为止只碰原始素材`gdrive_videos`),绝不碰运动片段(`motion_clips`,事件时间轴/人物头像依赖它)或还在处理中的素材 |
| OracleDB | `oracle_db.py` | SQLitevideos`motion_event_id`/`camera_id` 列)/ events / people / sync_cursor / ss_motion_events`get_sync_delta(since)` 增量导出;`record_motion_events` / `get_motion_events_in_range`(已结束运动事件窗口查询,分割用)/ `has_motion_in_range_local` / 心跳 | | OracleDB | `oracle_db.py` | SQLitevideos`motion_event_id`/`camera_id` 列)/ events / people / sync_cursor / ss_motion_events`get_sync_delta(since)` 增量导出;`record_motion_events` / `get_motion_events_in_range`(已结束运动事件窗口查询,分割用)/ `has_motion_in_range_local` / 心跳 |
| Model-Adapters | `model_adapters/` | `BaseModelAdapter.analyze_video(video_path, known_members_context, event_start_time)`GeminiFiles API/ NVIDIA整视频 `video_url`)——**只有视觉分析用2026-08-23 起不再含 Ollama/文字模型**,问答模型完全移交 AI-Gateway | | Model-Adapters | `model_adapters/` | `BaseModelAdapter.analyze_video(video_path, known_members_context, event_start_time)`GeminiFiles API/ NVIDIA整视频 `video_url`)——**只有视觉分析用2026-08-23 起不再含 Ollama/文字模型**,问答模型完全移交 AI-Gateway |
| QA-Proxy | `qa.py` | **2026-08-23 重写为 HTTP 转发客户端**(原来自己遍历适配器 `chat()` 做 NVIDIA→Gemini→Ollama 三级降级的逻辑已整个搬到 AI-Gateway调 AI-Gateway `/v1/chat/completions`,把 OpenAI 兼容响应翻译回原有 `run_qa`/`run_qa_stream` 契约,`api_gateway.py` 和 FAM-Core 调用方零改动 | | QA-Proxy | `qa.py` | **2026-08-23 重写为 HTTP 转发客户端**(原来自己遍历适配器 `chat()` 做 NVIDIA→Gemini→Ollama 三级降级的逻辑已整个搬到 AI-Gateway调 AI-Gateway `/v1/chat/completions`,把 OpenAI 兼容响应翻译回原有 `run_qa`/`run_qa_stream` 契约,`api_gateway.py` 和 FAM-Core 调用方零改动 |
@@ -758,7 +759,7 @@ cd fam-ui && npm run build # 产物 fam-ui/dist/
|------|-----| |------|-----|
| 公网 IP | 129.146.26.249 | | 公网 IP | 129.146.26.249 |
| SSH 用户 | ubuntu密钥 `~/.ssh/oracle_new` | | SSH 用户 | ubuntu密钥 `~/.ssh/oracle_new` |
| 系统 | aarch64 (Ampere A1 2C12G), Ubuntu 20.04 LTS | | 系统 | aarch64 (Ampere A1 4C23G), Ubuntu 20.04 LTS |
| Tailscale | 已安装未启用(备用;新机公网直连为主) | | Tailscale | 已安装未启用(备用;新机公网直连为主) |
| 登录命令 | `ssh -i ~/.ssh/oracle_new ubuntu@129.146.26.249` | | 登录命令 | `ssh -i ~/.ssh/oracle_new ubuntu@129.146.26.249` |
@@ -829,8 +830,9 @@ export NVIDIA_API_KEY="nvapi-9cFAdO5xdbwPuxS8KGRTnlVimn1gJzbbbzWNhPwHa_Yl3pTe-Pf
## 12. 当前进度与 v1.1 计划 ## 12. 当前进度与 v1.1 计划
### 已完成(截至 2026-08-24 ### 已完成(截至 2026-08-28
- **Oracle 迁移故障排查 + DiskGuard 磁盘守护**2026-08-288/25 迁移新机器时漏装了 FFmpeg导致运动片段分割和帧图抽取全部失效`ffprobe: command not found`);同时 `gdrive_videos` 持续下载新素材没有配套清理,磁盘被写满到 100%,触发 rclone 的安全机制IO 错误时拒绝执行删除),形成"越满越删不掉"的死循环,表现为"只下载不删除"。已装回 FFmpeg、手动清理了 148 个远端已不存在的孤儿文件(释放 41G恢复同步新增 `DiskGuard` 后台服务作为永久兜底,剩余空间 <10GB 自动清理最旧的已完成素材。8/25 之前旧机器时代的历史事件帧图因源文件未随迁移保留、旧机器已销毁无法找回8/25-28 期间的 190 个素材/736 个运动事件按用户决定不重新处理
- **事件时间轴支持删除视频会话**2026-08-24三端联动沿用"NAS 转发写请求到 Oracle"的既有模式(新增 `POST /api/oracle/video/delete` + `DELETE /api/ui/videos/<id>`);删 events+videos 行 + 磁盘文件,不清理 `ss_motion_events` 源事件;`db_layer.delete_sync_video()` 是项目里第一个"NAS 直接写自己镜像表"的函数(增量同步机制感知不到删除,不能靠 `trigger_now()` 拉增量清理) - **事件时间轴支持删除视频会话**2026-08-24三端联动沿用"NAS 转发写请求到 Oracle"的既有模式(新增 `POST /api/oracle/video/delete` + `DELETE /api/ui/videos/<id>`);删 events+videos 行 + 磁盘文件,不清理 `ss_motion_events` 源事件;`db_layer.delete_sync_video()` 是项目里第一个"NAS 直接写自己镜像表"的函数(增量同步机制感知不到删除,不能靠 `trigger_now()` 拉增量清理)
- **问答链路抽离为独立 ai-gateway 服务**2026-08-23FAM-Edge 原本自己维护的问答模型降级链NVIDIA→Gemini→Ollama含 key 轮换/熔断)整个搬到独立仓库/独立部署单元 `ai-gateway`OpenAI 兼容协议 `/v1/chat/completions`Bearer token 鉴权,对外 `:5100`FAM-Edge `qa.py` 重写为转发客户端,`/api/edge/chat/ask(/stream)` 对 FAM-Core 的契约不变;`OllamaAdapter` 从 FAM-Edge 删除 - **问答链路抽离为独立 ai-gateway 服务**2026-08-23FAM-Edge 原本自己维护的问答模型降级链NVIDIA→Gemini→Ollama含 key 轮换/熔断)整个搬到独立仓库/独立部署单元 `ai-gateway`OpenAI 兼容协议 `/v1/chat/completions`Bearer token 鉴权,对外 `:5100`FAM-Edge `qa.py` 重写为转发客户端,`/api/edge/chat/ask(/stream)` 对 FAM-Core 的契约不变;`OllamaAdapter` 从 FAM-Edge 删除
- **运动事件驱动**v32026-08-22不再分析整段视频。NAS MotionNotifier 轮询 SS 事件列表60s游标续用/失败重试/心跳)推送 `ss_motion_events`Oracle 整段素材按运动事件 ffmpeg 分割运动片段(`-c:v copy -c:a aac`,只分割已结束事件,`motion_event_id` 幂等),**只分析运动片段**;前端契约不变 - **运动事件驱动**v32026-08-22不再分析整段视频。NAS MotionNotifier 轮询 SS 事件列表60s游标续用/失败重试/心跳)推送 `ss_motion_events`Oracle 整段素材按运动事件 ffmpeg 分割运动片段(`-c:v copy -c:a aac`,只分割已结束事件,`motion_event_id` 幂等),**只分析运动片段**;前端契约不变