ADR 002 — 派生読み取りキャッシュとしてのSQLite
ステータス: 受理済み
日付: 2026-04
更新(2026-06)
署名付きイベントログはまだ実装されていません — 今日、取り込みはSQLiteに直接書き込みます(トレードオフで述べた「フェーズ1」の経路)。ログの設計もHypercoreから、Raiznetネイティブの追記専用イベントログへと移りました(ADR-004); SQLiteを派生インデックスとする原則は変わりません。
文脈
Raiznetが意図するソースオブトゥルースは、追記専用で暗号学的に署名されたイベントログです。しかし、そのようなログから直接、高速なAPIクエリ(時間範囲の読み取り、集計、H3セルでのフィルタ)を提供するのは非現実的です。それは逐次追記と複製のために設計されており、ランダムアクセスのインデックスクエリのためではありません。
二次インデックス層が必要です。
決定
SQLite(better-sqlite3 経由)を派生読み取りキャッシュとして使います。長期的なソースオブトゥルースを意図していません。イベントログが存在すれば、破損または削除されたSQLiteデータベースは、最初のイベントからログを再生することで完全に再構築できます。
2つの別個のデータベースを維持します。
| データベース | 供給元 | アクセス |
|---|---|---|
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の非同期ルートハンドラに自然に収まり、別個のスレッドプールやコールバックの間接化を必要としません。
トレードオフ
- 新しいセンサー型の追加にはスキーマ移行(3つの新カラム:
_plain,_cipher,_nonce)が必要です。これは高速な集計クエリのために受け入れたコストです。 - ワイドテーブル設計(1読み取りにつき1行、すべてのセンサーカラムを同じ行に)は、狭いキー値テーブルより多くのディスク容量を使いますが、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は常にローカル取り込み経路で直接書き込まれます — 決して複製されません。