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>
24 KiB
24 KiB