State & Concurrency

SessionDB 会话存储与 SQLite 高并发锁自愈

深度解析 hermes_state.py、网络文件系统 WAL 降级与 Jitter Retry 随机避堵算法
SessionDB 高并发锁优化
📊 图 4-1:SQLite 库的写冲突 Jitter Retry 与 schema 在轨自愈流转图

💾 1. SQLite state.db 核心表关系与外键设计

会话状态(如 Turn 历史消息、变量快照、Kanban 子任务生命周期)被统一持久化在 SQLite 中(通常命名为 state.db)。底层数据表拓扑关系如下 ER 图所示:

SQLite 数据库表关系 ER 图
📊 图 4-2:SQLite state.db 核心数据表外键关系 ER 图

核心数据表职责

  • sessions 表:主表。存储会话全局状态(如 session_id PK, status, metadata JSON)。
  • messages 表:保存对话树。其 session_id 作为 FK 级联关联主表。每一行存储 Turn 角色(role)、内容(content BLOB)和唯一序列号。
  • variables 表:存储用户在会话期间绑定的环境变量及快照,在后台任务与重试时提供上下文自愈。
  • compression_locks 表:高并发对话轨迹压缩互斥锁表,确保同一个 `session` 绝对不会有两个后台线程同时做压缩重构。

☁️ 2. 网络文件系统的 WAL 兼容降级 (apply_wal_with_fallback)

如果用户的工程被部署在 NFS、SMB、WSL 宿主机虚拟挂载盘 上,底层往往缺少对 SQLite WAL 模式所要求的共享内存映射文件(.shm)和 byte-range locks 字节范围锁协议的支持,这会抛出:

sqlite3.OperationalError: locking protocol (or: not authorized)
为了解决这一云挂载环境的致命痛点,在 hermes_state.py 中实现了降级机制:

# apply_wal_with_fallback 实现细节
def apply_wal_with_fallback(conn: sqlite3.Connection, db_label: str) -> str:
    try:
        conn.execute("PRAGMA journal_mode=WAL")
        return "wal"
    except sqlite3.OperationalError as exc:
        msg = str(exc).lower()
        if any(marker in msg for marker in ("locking protocol", "not authorized")):
            # 自动向 DELETE 模式回退降级,关闭共享内存映射
            conn.execute("PRAGMA journal_mode=DELETE")
            _log_wal_fallback_once(db_label, exc)
            return "delete"
        raise

⏳ 3. 应用层 Jitter Retry 与锁 convoy 避堵算法

普通的 SQLite 重试机制会引起“惊群效应(Convoy Pattern)”——多个阻塞的线程同时被唤醒抢占同一把锁,导致数据库持续死锁。Hermes 在 SessionDB 中利用应用层随机抖动(Jitter)算法打破了队列 convoy:

  1. BEGIN IMMEDIATE 显式抢占: 在执行任何写 SQL 前,连接立即宣告 BEGIN IMMEDIATE。这使得 SQLite 写锁冲突在事务一开始就被拦截,防止事务执行到一半时发生冲突回滚。
  2. 应用层 Jitter(随机退避): 一旦发生 locked 报错,代码自动释放当前的 Python thread lock 并开启随机休眠:
    # Jitter 避堵逻辑核心
    _WRITE_RETRY_MIN_S = 0.020   # 20ms
    _WRITE_RETRY_MAX_S = 0.150   # 150ms
    
    # 锁冲突时,释放 Python 锁并睡眠 20ms 到 150ms 之间随机时间,打破惊群排队
    jitter = random.uniform(_WRITE_RETRY_MIN_S, _WRITE_RETRY_MAX_S)
    time.sleep(jitter)

🔧 4. sqlite_master Schema 损坏的在轨直接手术

在极端意外情况下,元数据表 sqlite_master 会出现异常的主键冲突,普通开发人员往往无从下手。Hermes 在 SessionDB 内部封装了在轨修复手术:

  • 排他自愈锁 (`_repair_attempt_lock`):确保在整修期间,没有其他并发的后台线程可以读写该 DB 块。
  • 可写 Schema PRAGMA 写入
    # PRAGMA 强制修复 schema
    conn.execute("PRAGMA writable_schema=ON")
    # 1. 尝试 Strategy 1:执行去重手术,仅保存 rowid 最低的那条原始定义
    conn.execute("""
        DELETE FROM sqlite_master 
        WHERE rowid NOT IN (
            SELECT MIN(rowid) FROM sqlite_master GROUP BY type, name
        )
    """)
    # 2. 如果损坏依然存在,执行 Strategy 2:直接强制清理 FTS 虚表定义元数据
    conn.execute("DELETE FROM sqlite_master WHERE name LIKE 'messages_fts%'")
    conn.execute("PRAGMA writable_schema=OFF")
🛡️
也在关心 Agent 的治理和合规?

架构深度决定了治理能力。OOMeta Governance Agent 利用你在此学到的模式(工具反射、JSON 净化、并发控制),提供开箱即用的策略引擎、审计日志和合规报告。

查看 Governance Agent →