Skip to content

bug(storage): 索引库高频 SQLITE_BUSY,需统一写入契约并区分主库锁丢失 #2953

Description

@jolestar

概要

2026-09-13 对 Ubuntu 部署的只读分析发现:当前高频 database is locked 主要来自共享索引库 memory.v2.sqlite3,其多连接写入缺少覆盖全部入口的按库串行化;与此同时,runtime.sqlite 主库共享锁缺失仍有独立异常线索。两者属于需要一起梳理的 SQLite 锁生命周期问题,但尚未证明是同一个缺陷。

本 issue 聚焦索引库写入契约、事务边界及可观测性;#2948 跟踪后台轮次被全量重建阻塞,#2951 跟踪主库 sidecar divergence 与 GUI 重试放大。不要用索引竞争替代对主库锁丢失的独立归因。

环境与已确认的证据

  • 运行二进制:0.39.0 (e7bb6139),通过运行进程的 exe 核实,已包含 8bbd9d84 的 O_PATH 修复。不能继续将本次主库 sidecar 删除直接归因于已修复的普通 File::open/close 路径。
  • 2026-09-13 05:50–06:10 UTC(北京时间 13:50–14:10),3614 条 journal 记录中:
    • 245 次 memory index enqueue failed after canonical storage write。
    • 1 次 memory index outbox apply failed; retaining rows for ordered retry。
    • 246 条 Error code 5 是上述错误的多行明细,不是额外 246 次故障。
    • 未见 runtime db begin immediate transaction still locked 或 runtime db transaction still locked。
  • /proc/locks 中索引 SHM offset 120 的写锁属于 Holon 进程;该进程有多个索引主库 FD。同进程不同连接仍可能竞争。
  • 06:14:01 / 06:14:04 UTC 快照均见索引写锁,索引 WAL 从 17027992 增至 17052712 bytes;这不能证明同一事务持续持锁,也不能证明永久死锁。
  • 三次快照未列出 runtime.sqlite 主库的 POSIX 共享锁,但存在其 SHM offset 128 的读锁,所采文件 nlink=1。尚无具体释放者的系统调用证据。

当前锁机制

路径 契约
维护锁 RuntimeDbLock 独立 lock_path 上的 flock,Drop 解锁;不能替代 SQLite 事务锁
主库写入 按规范化数据库路径共享 ticket queue → Mutex<Connection> → BEGIN IMMEDIATE → commit/rollback → 释放 mutex/ticket
主库普通读取 另外开连接,不走写队列
主库竞争等待 busy_timeout=30 秒;部分 BUSY/LOCKED 路径无限退避重试,等待时仍占 writer mutex/ticket
索引库写入 多个 MemoryIndex 连接,busy_timeout=5 秒;各 AppStorage 的 mutex 不构成覆盖所有连接的按库写队列

代码风险与待验证假设

  1. rebuild() 在首次 DELETE 后持有写事务,再执行 collect_documents() 的源读取与全量重建,扩大持锁窗口。它是高优先级竞争候选,但本轮未捕获持锁事务调用栈,不能认定采样中的锁必属 rebuild。
  2. consume_pending_sources / consume_runtime_outbox 使用 deferred transaction,apply_pending_source_tx 先读 source_state 再写,存在快照升级失败风险。日志只有主错误码 5,未证实扩展码 SQLITE_BUSY_SNAPSHOT(517)。
  3. enqueue 失败后丢弃缓存连接、标 dirty 并通知后台;self-heal 可请求 rebuild,存在“竞争 → dirty → 更重重建 → 更多竞争”的潜在放大链,尚未证明该循环持续发生。
  4. enqueue 告警发生在 canonical storage write 之后;不能据此认定原业务数据写入失败。outbox apply 失败会保留未消费行。

源码定位(部署提交):

  • src/runtime_db/{connection,write_queue,mod}.rs
  • src/memory/index.rs:rebuild、consume_pending_sources、consume_runtime_outbox、apply_pending_source_tx
  • src/storage/index_outbox.rs、src/storage/mod.rs
  • src/host.rs:后台 self-heal / rebuild 调度

建议修复范围

  • 为索引库定义按库共享的写入入口,覆盖 enqueue、消费、rebuild、查询触发的刷新,并保留跨进程竞争处理。
  • 将慢源读取/计算移出写事务;若使用分批或 staging,必须同时保证原子可见性与游标一致性。
  • 对读后写事务明确采用 IMMEDIATE 或整事务回滚重试;定义总等待/取消边界,而非仅增加 busy_timeout。
  • 日志区分数据库角色、操作类型、扩展错误码,记录队列等待和事务持锁耗时。
  • 主库共享锁缺失另行做有界系统调用取证,不再次连接生产库试探删除。

验收标准

调查边界

本轮只读取源码、journal 和 /proc 元数据;未连接生产 SQLite,未重启或修改服务,未改代码。没有完成锁释放者的系统调用归因,也没有运行修复测试。

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    agent-healthAgent health check issues

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions