本文へスキップ
ブログに戻る
テクニカル

SQLite FTS5とMarkdownで構築するローカルファーストのナレッジベース

William Finger11 min

クラウドナレッジベースの問題

2024年、Omnivoreがシャットダウンしました。それはGitHubのスター40,000を持っていました。サービス終了の発表から60日後、Omnivoreは消えました――60日間のエクスポート期間を逃したユーザーは、保存していたすべてのブックマーク、ハイライト、ノートを失いました。

これは異常ではありませんでした。クラウド依存のナレッジツールにとって不可避の結果でした。

リスククラウドKBローカルKB(Geneziz)
会社が倒産データすべて消失すべて手元に残る
価格が10倍に払うか失うか買い切り
APIが非推奨に連携が壊れるあなたのデータ、あなたのルール
データ侵害ノートが露出マシン上のファイル
インターネット必須オフライン=アクセス不可ネットワークなしで動作

何年にもわたって何百ものブックマークをキュレーションする開発者にとって、データの寿命の長さは派手な機能よりも重要です。Genezizのアーキテクチャはこの原則を中心に構築されています。

ストレージレイヤー:Markdown + YAMLフロントマター

Genezizのナレッジベース内のすべてのアイテムは、YAMLフロントマターヘッダー付きのプレーンなMarkdownファイルです。

markdown
---
title: React Performance Optimization Techniques
type: tool
date_added: 2026-05-15
source: https://dev.to/react-perf
tags:
  - react
  - performance
  - frontend
stars: 1420
via: @dan_abramov
---

# React Performance Optimization Techniques

## Content here...

なぜMarkdownなのか?

相互運用性。 Markdownは地球上で最も広くサポートされているマークアップフォーマットです。すべてのエディタ、IDE、ビューアーが読めます。Genezizが消えても、あなたのナレッジファイルは読めるままです――独自フォーマットへのロックインはありません。

Gitフレンドリー。 Markdownのdiffは人間が読めます。ツールの説明を編集したりタグを追加したりするとき、git diffはJSONの塊ではなく何が変わったかを示します。

AIフレンドリー。 LLMは、コードの次にMarkdownを大量に学習しています。ナレッジがすでに構造化テキストであれば、AIアシスタントに与えるのは簡単です。

フロントマターのスキーマ

--- デリミタ間のYAMLブロックは、Genezizが並び替え、フィルタリング、表示に使用する構造化メタデータを保持します。

typescript
interface ViewerIndexEntry {
  file: string;        // path relative to knowledge/
  path: string;        // URL slug
  title: string;       // display name
  type: 'tool' | 'article';
  date_added: string;  // ISO date
  source: string;      // original URL
  tags: string[];       // searchable tags
  stars: number;        // GitHub star count (for tools)
  via: string;          // who shared it
  // Enrichment fields (computed at index time):
  display_title: string;
  category: string;
  priority: number;
  rating: number;
}

このスキーマは意図的にフラットです――ネストされたオブジェクトも、複雑な型の配列もありません。これにより、クエリしやすく、マイグレーションしやすく、テキストエディタで検査しやすくなります。

検索レイヤー:SQLite FTS5

フルテキスト検索は、ほとんどのナレッジベースが失敗する場所です。そもそも持っていない(ファイルをgrepする)か、稼働中のサーバーを必要とする外部サービス(Elasticsearch、Meilisearch、Algolia)を使うかのどちらかです。

GenezizはSQLite FTS5を使います――すべてのiPhoneとAndroidデバイスを動かしているのと同じデータベースエンジンです。理由はこうです。

FTS5とは?

FTS5(Full-Text Search 5)は、SQLiteの組み込み検索拡張機能です。コンテンツのトークン化された転置インデックスを作成し、次のことを可能にします。

  • インスタントなプレフィックス/フレーズ検索("react perf" が "React Performance" にマッチ)
  • ブール演算子(AND、OR、NOT)
  • BM25風のスコアリングによる関連性ランキング
  • 100k以上のドキュメントでもほぼ瞬時の結果

Genezizはどのようにインデックスを構築するか

geneziz index を実行すると、背後で次のことが起きます。

1. Scan knowledge/tools/*.md and knowledge/articles/*.md
2. Parse YAML frontmatter → structured entries
3. Enrich each entry:
   - Generate display_title (cleaned, shortened)
   - Classify category from tags/content
   - Compute priority & rating scores
4. Write viewer-index.json (camelCase, React-ready)
5. Create .state/search.db (SQLite + FTS5 virtual table):
   - CREATE VIRTUAL TABLE USING fts5(...)
   - INSERT all entries with title + description + tags + content preview

結果の search.db は、数千エントリでも通常5MB未満です。瞬時に開けます――サーバーを起動する必要も、インデックスの遅延もありません。

クエリの例

geneziz search "react hooks" を実行すると、フローは次のとおりです。

python
# 1. Open SQLite connection to .state/search.db
# 2. SELECT * FROM fts5_main WHERE knowledge MATCH 'react hooks'
# 3. Return ranked results with highlighted snippets
# 4. Format for CLI or web display

FTS5がトークン化、ステミング(英語)、ランキングを自動的に処理します。別個のトークナイザーサービスや関連性チューニングのパイプラインは不要です。

ビューワーインデックス:Web向けのcamelCase

議論する価値のあるアーキテクチャ上の選択が一つあります。GenezizはインデックスをcamelCaseで書き出すということです。snake_caseではありません。

json
{
  "displayTitle": "React Perf Techniques",
  "dateAdded": "2026-05-15",
  "category": "frontend",
  "priority": 8,
  "rating": 4.7
}

なぜか? 受け手がTypeScript/JavaScriptで書かれたReact Webアプリだからです。インデックスをcamelCaseに保てば、変換レイヤーはゼロです――フロントエンドはJSONを読んで直接レンダリングします。snake_to_camel 変換も、ミスマッチのリスクもありません。

Pythonバックエンドは _to_camel_dict() ユーティリティでcamelCaseを生成します。これは意図的な選択です。データを、作り手ではなく受け手のために整形するということです。

アトミックな書き込み:クラッシュ安全性

ナレッジファイルは、破損を防ぐためにアトミックな書き込みパターン(tempへ書き込み + rename)を使います。

python
def atomic_write(path: str, content: str) -> None:
    tmp = path + '.tmp'
    with open(tmp, 'w', encoding='utf-8') as f:
        f.write(content)
    os.replace(tmp, path)  # Atomic on POSIX, near-atomic on Windows

プロセスが書き込み途中でクラッシュしても、古いファイルか新しいファイルのいずれかが得られます――書きかけの破損ファイルは決して生じません。このパターンはあらゆる場所で使われています:bookmarks.md、viewer-index.json、ステートファイル。

完全なデータフロー

X.com / GitHub
    │
    ▼
geneziz fetch / geneziz sync
    │
    ▼
Raw data → AI processing (optional)
    │
    ▼
Markdown files + YAML frontmatter
    │  (knowledge/tools/*.md)
    │  (knowledge/articles/*.md)
    │
    ▼
geneziz index
    │
    ├──→ viewer-index.json (camelCase, enriched)
    └──→ .state/search.db (SQLite FTS5)
    │
    ▼
Web viewer (React) reads both files
    │
    ▼
User searches, browses, reads articles

このパイプラインのすべてのステップはテキストエディタで検査可能です。viewer-index.json を開いて、Webアプリが見るものと正確に同じものを見られます。任意のSQLiteブラウザで search.db を開いてクエリを実行できます。任意の .md ファイルを開いてノートを読めます。

なぜこのアーキテクチャが開発者にとって有利なのか

1. ランタイムの依存がゼロ

Elasticsearchクラスタなし。Redisキャッシュなし。バックグラウンドワーカープロセスなし。検索DBはミリ秒で開く単一のSQLiteファイルです。ナレッジベース全体がオフラインで動作します。

2. Gitネイティブ

すべてがMarkdown + JSONなので、あなたのナレッジベースはgitリポジトリです。バージョン履歴、ブランチング、PRレビュー――コードのためにすでに使っているツールをすべて使えます。

3. AIレディ

LLMはMarkdownをネイティブに読めます。MCPサーバーは、フォーマット変換なしで、あなたのナレッジをClaude/GPT/Cursorに供給します。「Rustについて何を保存したっけ?」と尋ねると、答えはあなたのファイルから直接得られます。しかも、そのAIステップに外部アシスタントは必要ありません。Genezizは独自のローカルインテリジェンス「Geneziz AI」を搭載しています――約1.6GB、オフライン、無料。クラウドを選ばない限り、AIレディなパイプラインにクラウドは一切不要です。

4. マイグレーション耐性

プレーンテキストは腐りません。2020年のMarkdownファイルは2030年に同じように動作します。スキーママイグレーションも、ORMのバージョン競合も、「アップグレードしたら古いデータと互換性がなくなった」もありません。

トレードオフ

このアーキテクチャは魔法ではありません。意図的な制限があります。

  • 共同編集なし ―― 同時に書けるのは1人だけ(個人KBには問題なし)
  • リアルタイム同期なし ―― 手動の geneziz fetch の後に geneziz process(バグではなく意図的)
  • 検索は英語のみ ―― FTS5はCJKのトークン化をうまく処理しません(ほとんどの開発者向けには許容範囲)
  • 大規模データセット ―― 100k以上のエントリがあると、FTS5は遅くなります(ただし、内蔵のセマンティック検索が意味ベースの検索を大規模に処理します)

これらはバグではありません。システムをシンプルに、高速に、そして自分のものに保つ設計上の制約です。

Genezizを試す

この投稿の内容はすべて、Geneziz に実装済みです - プレーンなMarkdownによる保存、瞬時のFTS5検索、内蔵セマンティック検索、そしてローカルで動くGeneziz AI。買い切りで、サブスクリプションはありません。

シェアのノート

この投稿はGenezizブログシリーズの一部です。役に立ったら、自分のナレッジベースを構築している開発者にシェアしてください。

関連記事

目次