跳转到内容

SQLite 存储与迁移系统

SQLite 存储与迁移系统:Claude-Mem 的具体实现

学习目标

本章聚焦 Claude-Mem 如何使用 SQLite 作为核心存储,以及 34 步增量迁移系统的运作方式。

项目实践

核心表结构

Claude-Mem 的 SQLite 数据库包含以下核心表:

迁移系统

src/services/sqlite/migrations/ 包含从 v1 到 v34 的增量迁移:

  • 迁移执行器runner.ts 检查 schema_versions 表,按顺序执行未应用的迁移
  • 幂等性:每步迁移使用 CREATE TABLE IF NOT EXISTSALTER TABLE 确保可重复执行
  • 快照文件schema.sql 记录迁移后的最终数据库形状,新数据库直接使用该形状
  • 降级策略:不支持回滚迁移,只能向前

关键索引设计

数据库为高频查询路径创建了专门的索引:

索引查询场景
observationsidx_observations_project按项目查询所有观察
observationsidx_observations_created按时间倒序获取最新观察
observationsidx_observations_content_hash去重检查(memory_session_id + content_hash)
sdk_sessionsidx_sdk_sessions_project按项目查询会话
pending_messagesidx_pending_messages_status获取待处理消息队列

去重机制

observations 表的 UNIQUE(memory_session_id, content_hash) 约束替代了内存中的去重窗口:

  • content_hash 基于观察的内容计算(非随机 ID)
  • 写入时使用 INSERT ... ON CONFLICT DO NOTHING 天然吸收重复
  • 避免 Hook 重复触发导致的重复记录

PendingMessageStore

pending_messages 表作为持久化的工作队列,替代了内存中的 pendingTools Map

  • 状态流转pendingprocessing
  • 唯一约束UNIQUE(content_session_id, tool_use_id) 保留摄取配对
  • 崩溃恢复:Worker 重启后可从 pending 状态继续处理

问题与规避

原生模块兼容性

问题:Bun 的 bun:sqlite 模块在不同平台可能因原生依赖编译失败。

规避ContextBuilder 中的 initializeDatabase() 捕获 ERR_DLOPEN_FAILED 错误,清理 .install-version 标记文件,提示用户重启 Claude Code 自动修复。

大表性能

问题:随着使用时间增长,observations 表可能达到数万行。

规避

  • 索引覆盖高频查询路径
  • FTS5 虚拟表提供全文搜索,避免全表扫描
  • projectcreated_at_epoch 的复合查询可通过组合索引优化

设计取舍

为什么用 SQLite 而非其他数据库?

维度SQLitePostgreSQL文件存储
部署复杂度零(单文件)需要安装和配置
查询能力SQL + FTS5完整 SQL需要自建索引
并发写入单写者多写者需要文件锁
适用场景单机单用户多用户服务端极小规模

Claude-Mem 选择 SQLite 的核心原因是零部署成本:用户不需要安装额外的数据库服务。单用户场景下的并发写入问题不存在(只有一个 Worker 进程写入)。

参考来源