fix(fam-edge): SQLite 改每线程一条连接,修好 DiskGuard 等一切并发写

OracleDB 原来在 __init__ 里建一条 check_same_thread=False 的连接给全进程共用,
而 VideoQueue / PersonService / DiskGuard 三个后台线程 + gunicorn 的请求线程都在
并发读写它。sqlite3 的连接对象本来就不是可并发共享的,事务状态互相踩踏,线上
累计刷出三类错误(同一个根因):

  database is locked                            71604 次
  cannot start a transaction within a transaction  378 次
  no more rows available                           92 次(堆栈落在 commit())

最严重的后果是磁盘守护形同虚设:DiskGuard 的清理被打断 2837 次,成功仅 128 次,
剩余空间在 2.7GB 和 16GB 之间来回荡——赶上 rclone 集中下载(实测 5 分钟写入
12.6GB)就掉进危险区。NAS 推来的运动事件被 500 打回也是它(失败批次不推进游标,
下一轮补推,所以没丢事件)。

改法:_conn 改成 @property,从 threading.local() 取当前线程的连接,没有就新建
(WAL + busy_timeout=10000)。做成 property 是因为外部调用方(api_gateway 的
activity 端点)也在直接用 db._conn.execute(...),这样全部现有调用点原样工作。
close() 相应改成收掉所有线程开过的连接。_write_lock 保留,复合写语义不变。

测试:新增 8 线程 × 25 轮并发读写、close 收连接两个用例;在旧代码上稳定复现
同族错误 cannot commit transaction - SQL statements in progress。

线上验证:重启后 DiskGuard 立刻跑通一轮(清 40 个文件,剩余回到 15.3GB),
三类 SQLite 错误在重启后的日志里均为 0。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
ericwyuan
2026-09-13 09:34:24 +08:00
parent a494d60361
commit 3b5f7db51d
4 changed files with 134 additions and 5 deletions

View File

@@ -724,6 +724,7 @@ print('NVIDIA NIM 连接成功:', response.choices[0].message.content)
- NAS scp 子系统被禁用,同样用 stdin 管道传文件
- **Oracle fam-edge 由 systemd `fam-edge.service` 守护Restart=always**:部署代码后必须 `sudo systemctl restart fam-edge`;手动 `setsid` 启动会和守护打架(端口 `Connection in use`
- NAS 远端 kill gunicorn 用 `ps aux | grep "[f]am-core/venv/bin/gunicorn"` 字符类技巧pkill/pgrep 会匹配 SSH 自身命令行导致断连)
- **sqlite3 连接不能跨线程共享**2026-09-13 修):`OracleDB` 原来在 `__init__` 里建一条 `check_same_thread=False` 的连接给全进程用VideoQueue / PersonService / DiskGuard 三个后台线程加 gunicorn 请求线程并发读写它,事务状态互相踩踏,线上累计刷出 `database is locked` 71604 次、`cannot start a transaction within a transaction` 378 次、`no more rows available` 92 次DiskGuard 的清理被打断 2837 次,磁盘守护形同虚设(剩余空间在 2.7G 和 16G 之间来回荡)。改成 `threading.local()` 每线程一条连接后WAL 下读不互斥、写由 SQLite 自己排队。**新增后台线程时不要再去共用某一条连接对象**
- fam-core 启动模块路径是 `src.fam_core.app:app`(不是 `fam_core.app:app``start_core.sh` 会 source 仓库根 `.env` 注入 `DSM_*/ORACLE_SYNC_TOKEN`
- Edge 单 worker 处理任务期间 `/health` 可能不响应,属正常
- **运动数据清理**:切换架构/重新提取时清 Oracle `videos/events/people` + `motion_clips/`(保留 `ss_motion_events` 与素材)与 NAS `sync_*` 镜像,重启两端自动重新分割分析