ADR 002 —— 作为派生读取缓存的 SQLite
状态: 已接受
日期: 2026-04
更新(2026-06)
签名事件日志尚未实现 —— 今日摄取直接写入 SQLite(取舍中描述的“阶段 1”路径)。日志设计也已从 Hypercore 转向 Raiznet 原生的仅追加事件日志(ADR-004);SQLite 作为派生索引的原则保持不变。
背景
Raiznet 意图的真相来源是一份仅追加、经加密签名的事件日志。然而,直接从这样的日志提供快速 API 查询(时间范围读取、聚合、按 H3 单元过滤)是不切实际的:它是为顺序追加和复制设计的,而非随机访问的索引查询。
需要一个二级索引层。
决定
SQLite(经由 better-sqlite3)用作派生读取缓存。它不打算成为长期的真相来源。一旦事件日志存在,损坏或删除的 SQLite 数据库可通过从第一个事件重放日志来完全重建。
维护两个独立的数据库:
| 数据库 | 来源 | 访问 |
|---|---|---|
raiznet_public.db | 公共摄取(事件日志复制计划中) | 公共端点 |
raiznet_private.db | 仅本地摄取 | 仅本地端点 |
理由
- 查询性能:
REAL类型的固定列允许带索引的标准 SQL 聚合(AVG、MIN、MAX、GROUP BY)。查询时无 JSON 解析。 - 模式简洁:无 ORM —— 通过
better-sqlite3的同步 API 直接 SQL,带类型化结果。 - 重建保证(事件日志落地后):因为 SQLite 派生自日志,模式演进不意味着数据丢失。删除文件、重放,完成。
- 通过隔离实现安全:公共端点的 Fastify 实例仅持有到
raiznet_public.db的连接。公共端点上的查询无法返回私有数据,因为数据库连接对象根本不可用 —— 隔离在连接层,而非查询层。 better-sqlite3的同步 API:自然契合 Fastify 的异步路由处理器,无需单独的线程池或回调间接。
取舍
- 添加新传感器类型需要模式迁移(三个新列:
_plain、_cipher、_nonce)。这是为快速聚合查询接受的代价。 - 宽表设计(每条读数一行,所有传感器列在同一行)比窄的键值表使用更多磁盘空间,但无需 join 即可实现带索引的范围查询。
- 阶段 1 直接写入 SQLite。阶段 2 增加事件日志 → 索引器 → SQLite 的管线。API 层在两个阶段始终从 SQLite 读取。
后果
apps/server/src/storage/public-db.ts与private-db.ts拥有模式创建(CREATE TABLE IF NOT EXISTS)。- 阶段 1 无迁移框架 —— 表在首次启动时创建,模式稳定。
- 一旦事件日志复制激活,索引器(阶段 2)将成为
raiznet_public.db的唯一写入者。 raiznet_private.db始终由本地摄取路径直接写入 —— 从不复制。