
2026/08/22 1:15
Nix に SQLite を持ち込むための 3 つの方法
RSS: https://news.ycombinator.com/rss
要約▶
Japanese Translation:
核心的なメッセージは、外部データベースによる Nix パッケージの照会最適化が速度向上をもたらす一方で、現時点ではシステム安全性と安定性を確保するため標準的な JSON ファイルを維持することをチームが好むという点にあります。Nix の組み込み JSON 解析器はメモリ負荷のため単一クエリに対して低速ですが、SQLite やネイティブコードへの切り替えは重大なリスクを伴います。外部実行は不要なプロセスフォークを発生させますし、
importNative はセキュリティ境界を損なう危険な評価フラグを必要とします。WebAssembly はより安全でサンドボックス化された代替手段を提供しますが、現在では高い起動遅延に苦しみ、不安定な機能への依存があります。たった 12.8 MiB に 305,000 つ以上のパッケージバージョンを格納しているというインデックスサイズを踏まえれば、純粋なパフォーマンス向上よりも安定した環境を優先する一般ユーザーにとって、現在の JSON フォーマットを維持することが実用的な選択です。WebAssembly コードのキャッシングなどの将来の最適化が起動時間を短縮できるようになる可能性はありますが、大規模コンパイル済みアートの管理の複雑さが普及の妨げとなっています。最終的に、この決定は、高頻度での照会速度の必要性と、生産システムにおける不安全な評価を回避することが極めて重要であるという両者のバランスの上に成り立っています。本文
nixpkgs-multiverse の高速化に向けた技術的探求:JSON から SQLite へ
nixpkgs-multiverse の核心部分は Nix API や CLI を除いた状態において、**「インデックス」**にあります。これは、
(属性名, バージョン) の組み合わせをパッケージが配信されたリビジョンにマッピングする JSON ファイルです。
現状のデータ規模:
: 5.3 MiB (305,492 パッケージバージョン)versions.json
: 7.5 MiB (1,534 リビジョン)history.json- 合計: 31,904 の属性を網羅
JSON データの扱いと課題
Nix API はこれらの JSON ファイルを**遅延読み込み(lazily)**するはずですが、実際には
builtins.fromJSON を通じて解析され、**貪欲(eager)**に動作します。
index = builtins.fromJSON (builtins.readFile ./index/versions.json);
- 5.3 MB のファイルをすべて読み込みます。
- ヒープ上に 305,492 つの値をすべて材料化(materialise)します。
- 結果として、単一のパッケージを検索するコストが、すべてのパッケージを検索したコストとほぼ同じになります。
重要: ルックアップ操作自体のスキャンコストではなく、JSON 解析および巨大なファイルのダウンロードがボトルネックとなります。
解決策:データを効率的にエンコードする方法
Nixpkgs のフェッチ数を最小化するため、現在の JSON ファイルをデータベース化することを検討します。JSON に縛られない他のアプローチを検討しました。
1. builtins.exec
を用いた外部コマンド実行
builtins.execNix 標準機能で文字列リストを受け取り、プログラムを実行し、出力を解析する方法です(2017 年より存在)。
let versionsOf = attr: builtins.exec [ "${sqlite}/bin/sqlite3" "-noheader" "-separator" "" "./index.db" '' SELECT '{' || group_concat( '"' || version || '" = ' || COALESCE(CAST(rev AS TEXT), 'null') || ';', ' ') || '}' FROM versions WHERE attr = '${attr}'; '' ]; in versionsOf "hello"
- 特徴: SQLite が直接 Nix 属性セットを発行します。
- 課題: クエリごとに「フォーク + exec + プロセス起動 + 再解析」のオーバーヘッドが発生します(約 3.8ms/クエリ)。多くのクエリがある場合、
と比較すると非効率的になります。fromJSON
2. builtins.importNative
を用いたネイティブコード読み込み
builtins.importNative共有オブジェクトへのパスとシンボル名を受け取り、
dlopen で呼び出す方法です(2014 年より存在)。
// C++ フィールドの例 extern "C" void nix_sqlite_versions(EvalState & state, Value & v) { ... }
- 特徴: 評価機内で SQLite ハンドルをキャッシュし、データベース開閉やプロセス起動のコストを回避します。
- 性能: 全範囲で約 0.05s。
よりも大幅に高速です。fromJSON - 注意:
オプションのオンが必要です。allow-unsafe-native-code-during-evaluation
3. JSON から Nix ファイルへの変換(遅延実行の活用)
JSON を読み替えず、同じコンテンツを
.nix ファイルとして読み込む試みです。Nix の遅延実行特性(thunk)を活用し、特定の属性のみを読み込みます。
# 6.0 MiB (JSON 5.3 MiB と比較してわずかに大容量) { revisionCount = 1534; attrs = { "2048-in-terminal" = { ... }; # ... }; }
- 性能: インポートするだけで約 0.53s かかり、遅延実行の恩恵は限定的です。
- 結論: JSON よりも高価な AST 解析が発生するため、推奨されません。
4. builtins.wasm
を用いた WebAssembly での SQLite 実行(最も有望)
builtins.wasmDeterminate Systems が提供する
builtins.wasm を活用し、サンドボックス化された環境で SQLite を動かします。これにより、安全な脱出手段となります。
アプローチの詳細
- WASM モジュール: Rust で記述し、SQLite との接続ロジックを実装。
- ファイル読み込み:
では NULL バイトが含まれるバイナリが扱えませんが、builtins.readFile
API を拡張することで SQLite の VFS(仮想ファイルシステム)を構築できます。read_file_rangestatic int nixRead(sqlite3_file *f, void *buf, int amt, sqlite3_int64 off) { // 評価機から正確にバイトを取得 auto got = nix_read_file_range(p->pathId, off, buf, amt); return SQLITE_OK; } - コンパイル: SQLite を WASM ターゲットへビルド(
で VFS レイヤーをカスタマイズ)。SQLITE_OS_OTHER=1
# クエリ実行例 builtins.wasm { path = ./sqlite_nix.wasm; } { db = ./index.db; sql = "SELECT version, rev FROM versions WHERE attr = 'hello'"; }
- 性能:
- 起動コスト: 最初のクエリに約 2.5s かかる(WASM の JIT コンパイル)。
- 定常状態: その後のクエリは約 7ms。
と比較すると、30 パッケージ以上のロックファイルを使用する場合に勝ります。fromJSON
ベンチマーク結果
同じ 22 MB の SQLite インデックスに対して、異なる手法を比較しました。
| 手法 | 起動コスト | クエリあたりのコスト | メモリ効率 | 特徴 |
|---|---|---|---|---|
| builtins.fromJSON | ~0s | 0.29s (平坦線) | 中 | 一度解析するが、その後のクエリも重い。 |
| .nix ファイルインポート | 0.53s | 0.29s | 悪 | AST 解析コストが高く、遅延実行の恩恵なし。 |
| builtins.exec | 低 | ~3.8ms (80 クエリで追跡) | 中 | プロセス起動オーバーヘッドあり。 |
| builtins.importNative | ほぼなし | 0.05s (最適) | 良 | ハンドルキャッシュにより極めて高速。 |
| builtins.wasm (SQLite) | ~2.5s | ~7ms (起動後) | 良 | 初期コンパイルのコストを払えば最も高速で安全。 |
結論と展望
現在の nixpkgs-multiverse は JSON インデックスのままですが、将来的にはより壮大なアイデア(多くのデータを必要とする)への展開を検討しています。
- 哲学:
に依存することは避けたいが、WASM による解き放たれた可能性には魅了されている。allow-unsafe-native-code - 課題: JIT 待ち時間や、コンパイルされたブロブのチェックインなど、開発者体験への影響はある。
- 将来性: WebAssembly を通じてデータベース機能を安全に統合できれば、多様なデータ探索のパターンに対応できます。
現在の実装は CppNix 等のネイティブアプローチのみですが、WASM の可能性は確実な拡張手段として注目されています。