公開サイト内検索は、管理画面でAI下書き用の資料を集めるWeb検索とは別の機能です。
このブログは、D1に保存された公開記事だけを対象に、クエリの形に応じてFTS5 trigram、アプリ生成bigram、ローカライズした LIKEを使い分けます。
この記事では、3つの主経路、フォールバック、索引の同期、ローカルとリモートの運用境界をまとめます。
実装と公式資料は2026年7月23日に確認しました。
1文字、2文字、3文字以上のクエリをLIKE、bigram、trigramへ振り分ける検索ルート*図: 正規化後のクエリから1つの主経路を選び、0件または失敗時だけ次のフォールバックへ進む。複数経路の結果はmergeしない。*
公開検索とWeb検索の責務
/searchが読むのは、D1のpostsに保存されたタイトル、要約、本文、タグです。
SearXNGや外部ページの本文抽出は呼びません。
公開検索の応答を外部providerの状態から切り離し、AI下書き用の出典収集と責務を分けています。
検索対象には、正本列に加えて日本語のtitle_ja、excerpt_ja、content_jaと、英語のtitle_en、excerpt_en、content_enを含めます。
英語ページの題名を検索しても、正本titleが日本語だから見つからない、という状態を避けるためです。
主経路の選択
searchPostsはクエリをtrimし、NFKC正規化後のコードポイント数を数えます。
同時にtoBigramFtsQueryを実行し、2コードポイントから有効なグラムを作れるか確認します。
- 1コードポイント:正本、日本語、英語、タグの10 fieldへ
LIKE '%...%' - 有効なグラムがある2コードポイント:
posts_fts_bigram MATCH ? - グラムを作れない2コードポイント:ローカライズした
LIKE - 3コードポイント以上:
tokenize='trigram'のposts_fts_ja MATCH ?
SQLite公式文書は、trigram tokenizerの全文検索で3 Unicode文字未満のsubstringが一致しないと説明しています。
短語をtrigramへ送らず別経路へ分けるのは、この境界に合わせるためです。
trigram索引の役割
migrations/0010_rebuild_localized_fts.sqlは、旧contentless索引を本文保持型の通常FTS5表へ作り直します。
各索引文書は正本、日本語、英語の同種fieldを連結し、公開記事だけを格納します。
INSERT、UPDATE、DELETE トリガーはposts.rowidを共通keyとして同期します。
表名posts_fts_jaは互換性のため残していますが、索引内容は日本語だけではありません。
3コードポイント以上のクエリをphraseとしてMATCHし、FTS5のrank順で最大30件を返します。
bigram索引の役割
bigram文書は、各fieldをNFKC正規化して小文字化し、文字、数字、結合文字の区間内から隣接2コードポイントを生成します。
空白、記号、field境界はまたぎません。
通常保存では、記事書き込みの直後にbigram用の1文を置き、同じD1Database.batchで実行します。
公開記事ならINSERT OR REPLACE、下書きまたは空文書ならDELETEです。
索引文は、直前の記事書き込みに対するchanges() > 0と、対象posts行のupdated_atを確認します。
条件付きUPDATEが競合で0行なら、索引も更新しません。
updated_atは対象バージョンを追加確認しますが、同じtimestampの競合もあるため、それだけでchanges()の代わりにはなりません。
バックアップからの復元は通常保存用builderを繰り返さず、別のbulk builderを使います。
通常保存、復元、snapshot バックフィルが共有するのはbuildPostBigramDocumentのグラム生成規則です。
フォールバックの順序
この実装は複数経路の結果をmergeしません。
選択した主経路から最初の非空結果を返し、0件または例外のときだけ次へ進みます。
- 1コードポイント、gramless 2コードポイント:最初からローカライズした
LIKE - bigram 2コードポイント:bigramの0件または失敗後、ローカライズした
LIKEへ直接進む - 3コードポイント以上:trigramの0件または失敗後、旧
posts_ftsのプレフィックス クエリを試し、その後ローカライズした LIKE - D1側のLIKEも失敗:同梱した正本JSONを同じ全fieldで部分一致
bigram経路が旧プレフィックス FTSを挟まないのは、プレフィックスに一致した一部の記事がsubstring フォールバックを隠すのを防ぐためです。
失敗時はposts_fts_bigram_failed、posts_fts_ja_failed、posts_fts_fallback_failedのような限定したlog identifierを残します。
スキーマとバックフィルの運用
D1 マイグレーションは前進のみです。
適用済みの番号付きマイグレーションを編集して履歴行を削除するのではなく、修正を新しい番号のマイグレーションとして追加します。
ローカルではスキーマ適用とbigram バックフィルを分けて実行します。
bash
pnpm run d1:migrate:local
pnpm run d1:bigram:local
バックフィルは索引全体を先に空にしません。
全posts行の完全snapshotを一時ステージし、現在行と一致する公開記事だけをupsertします。
下書き、空文書、orphanは条件付きで削除し、最後にmissing、unexpected、staleと文書内容を検証します。
リモートはバックアップ、対象データベース、適用予定を確認してから明示します。
bash
pnpm run d1:migrate:remote
pnpm run d1:bigram:remote
制約
この検索はsubstring中心であり、意味検索ではありません。
記号中心の2コードポイントは有効なグラムを作れず、LIKEへ進むことがあります。
D1 クエリは最大30件を取得した後にアプリケーション側でタグフィルターを適用するため、タグ付き検索では全体の上位30件の外にある一致記事を取りこぼす可能性があります。
参考資料
関連記事