ai-memory 单写者 SQLite Actor:并发写入是如何做到零冲突的
ai-memory 单写者 SQLite Actor并发写入是如何做到零冲突的【免费下载链接】ai-memorySolution for long term memory for agent coding CLIs and to facilitate handoff between different agent vendors项目地址: https://gitcode.com/GitHub_Trending/ai/ai-memoryai-memory是一个为 AI 编程 AgentClaude Code、Codex、Gemini CLI 等提供跨会话长期记忆的开源项目它把 Agent 的工作过程沉淀为 Markdown wiki 页面并配一个 SQLite 索引支持全文检索。由于它通常作为共享服务运行多个 Agent 会同时向同一个 SQLite 数据库写入——这篇文章带你看看它的单写者 Actor 架构如何做到并发写入零冲突、零锁竞争。1. 先看问题SQLite 并发写为什么会打架SQLite 有一个天然限制同一时刻只允许一个写事务。如果应用里很多线程各开各的连接、各自随时INSERT就会出现经典的database is locked错误——请求排队、超时、甚至数据写丢。ai-memory 的写入压力并不小每个 Agent 的每次工具调用、每次会话开始/结束都会通过生命周期 Hook 上报一条观察记录多个 Agent 并行工作时就是高频并发写。如果按每个请求一个连接的常规思路写数据库很快就会被锁冲突淹没。2. 核心设计所有写入排队通过同一个闸门ai-memory 的解法非常直接——全进程只有一个写连接而且它只属于一个专用操作系统线程。这个线程叫ai-memory-writer它独占了写Connection其他任何代码都不直接碰数据库而是把要做的事封装成一个WriteCmd命令通过消息队列发过去。这个机制的核心代码在 writer.rs 中构件作用关键参数mpsc命令通道所有写命令的唯一入口容量 1024有界oneshot回复通道每个命令自带一个一次性回执一对一worker_loop主循环单线程依次取出命令、执行 SQL、回传结果见 worker_loop可以把它想象成银行柜台窗口只有一个但队伍规则清晰——想写数据的代码不执行 SQL只负责取号把命令放进队列send写线程按到达顺序一条条处理处理完把结果塞回oneshot回执调用方await拿到结果后继续调用方拿到的句柄WriterHandle是廉价的Arc克隆体克隆一万份也只指向同一个写线程。这样一来两个写入同时发生在结构上就不可能发生——零冲突不是靠锁竞争抢出来的而是靠只有一个写者这个构造性保证by construction得到的。代码注释里写得很直白该模式消除了 cognee 项目曾踩过的database is locked失败模式#2717这条设计被列为项目的硬性架构不变量记录在 docs/ARCHITECTURE.md 的Cross-cutting invariants第 2 条。而且所有多表操作都是单条命令、单个事务比如结束会话并创建交接EndSessionWithHandoff在同一个事务里完成会话收尾、插入 handoff 记录、登记观察数量任何一步失败整体回滚恢复逻辑永远不会看到半截状态。3. 读路径怎么不被写线程拖住WAL 只读连接池单写者只解决了写 vs 写的冲突读 vs 写怎么办答案是WALWrite-Ahead Logging模式。数据库在打开时就设置了三个 pragmaStore::openjournal_mode WAL写入先进日志、不阻塞读者synchronous NORMALWAL 下的标准安全/性能平衡点busy_timeout 5000极端情况下等待锁最多 5 秒而不是立刻报错。在 WAL 模式下任意数量的并发读者可以和唯一写者同时工作。读请求走的是另一个独立设施——ReaderPoolreader.rs它按软上限默认 4 个连接READER_POOL_SOFT_CAP复用只读连接专门服务 Web UI、MCP 检索和全文搜索。下图是 ai-memory 的 Web 项目列表页这些浏览和搜索请求全部通过只读池完成完全不经过写线程也就是说写走单窗口读走自助区两条路径物理隔离互不排队。4. 写入洪峰来了怎么办三层背压设计把所有写请求集中到一条队列最怕的是队列失控。ai-memory 为此设计了三层泄洪机制有界队列第一层命令通道容量固定为 1024。写线程处理不过来时新请求在send().await处自然阻塞——这叫背压backpressure。发送方被温柔地按住内存占用有上界而不是无限堆积请求把服务撑爆。Hook 路径快速失败第二层Agent 侧的 Hook 脚本硬超时 ≤200ms服务端在队列饱和时直接返回 HTTP 429 而不是排队。Agent 的热路径永远不会被数据库拖住宁丢后补有幂等键可重放不阻塞。取消可见化第三层如果调用方主动取消了等待超时或丢弃await写线程回传结果时发现接收端已关闭会通过 send_or_warn 打一条 warn 日志——运维能看到背压/取消噪音而不是静默丢失。5. 这套设计扛得住多大并发实测压测感觉能扛不算数项目里内置了一个专门的压测来回答单写者到底什么时候成为瓶颈——stress_writer_throughput.rs它模拟最热的写入路径每次工具调用插入一条观察记录让 1、8、32、128 个并发写者持续发压测量观察记录/秒的吞吐并换算成能支撑多少并发 Agent的预算。这个测试被标记为按需运行的测量项不纳入常规 CI 门槛设计意图很清楚用一个真实数字而不是拍脑袋的估计来回答单写者架构的容量问题。对普通用户来说结论可以概括为日常几十个 Agent 并发的量级下单写者通道远未成为瓶颈真正的上限由磁盘写入速度决定而不是通道排队。6. 关键文件速查表想动手读源码的话按这个顺序看最省力想了解去看单写者 Actor 与全部写命令crates/ai-memory-store/src/writer.rs数据库打开、WAL 配置、读写设施装配crates/ai-memory-store/src/lib.rs只读连接池与检索查询crates/ai-memory-store/src/reader.rs架构不变量与设计动机docs/ARCHITECTURE.md并发写入吞吐压测crates/ai-memory-store/tests/suite/stress_writer_throughput.rs数据目录布局wiki / db / rawdocs/ARCHITECTURE.md 的 Storage architecture 一节结语ai-memory 的并发写入方案值得借鉴与其在多线程之间反复抢数据库锁不如承认单写者是 SQLite 的原生约束把它变成架构的第一原则——一条有界队列、一个专用线程、一套回执再加 WAL 把读流量彻底分流。冲突没有被解决而是被设计掉了。这种用结构消除问题、而不是用重试掩盖问题的思路正是它能高枕无忧地支撑多 Agent 共享记忆的原因。【免费下载链接】ai-memorySolution for long term memory for agent coding CLIs and to facilitate handoff between different agent vendors项目地址: https://gitcode.com/GitHub_Trending/ai/ai-memory创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考