MIXI DEVELOPERS NOTE
🔎

1週間でできた! Flutter+SQLite で全文検索機能を使った話

2024/09/20に公開

こんにちは。MIXI 開発本部 SREグループの riddle です。

最近私は 「イベント情報を掲載し、チケット購入ができるアプリ」 を運用しています。これまでアプリ内に検索機能がなく、不便だなーと思っていたので、サクッとβ版を作ってみました。
※なおこの機能は、2024/09/20時点で本番にリリースされています。

最終的に作ったもの

  1. アプリ起動時/定期的に Google Cloud Storage の全文検索用のデータ(数MB)をダウンロード
  2. ユーザがアプリ内でダウンロードしたデータを検索

検索時にネットワークを経由しないためオフライン検索が可能 ですし、 検索もとても速い です。

考えたこと

全文検索というと Elastic Search が有名ですが、

  • 個人的に経験がなかった
  • お試しでやるにはコストも期間もかかる

ので諦めました。

続いて、バックエンドで使っていた Cloud Spanner に最近搭載された全文検索機能が使えるかも?とも思ったのですが、

  • 検証するのも時間がかかる
  • サーバを経由するとなると色々と大変

ということで、もっと簡単にできる方法はないか?を探し SQLite に注目しました。

SQLite には FTS5 Extension という full-text search(全文検索機能) があり、これを使うことで簡単に全文検索を実装できることがわかりました。また SQLite は Flutter アプリでもよく用いられる技術のため、Flutter アプリに組み込むのも簡単そうです。

ちなみにこのサービスでは Web アプリも運用しているのですが Wasm を利用して SQLite をブラウザ上で動かすこともできるので一度で両方のプラットフォームに対応できるのも魅力的でした。(まだ実装していませんが)

シーケンス図

サーバ側の仕組み

クライアント側の仕組み

実装時の注意点

途中でスキーマが変更になる可能性がある

全文検索を行うためには SQLite で FTS5 Extension を使った VIRTUAL TABLE を作成する必要があります。この作業はサーバで行いますが、実際に検索を行うのはクライアントです。

そのため、今後テーブルのスキーマが変更になる際は クライアント・サーバの両方で対応が必要 です。しかし、アプリの場合は一度に全てのユーザに変更を適用することが難しいため、古いスキーマと新しいスキーマの両方を管理しなければなりません。

そこでサーバ側で複数のスキーマをサポートするように設計し、スキーマの数だけ SQLite を生成しバージョン管理して保存するようにしています。(例えば events_v1.db, events_v2.db といった感じです)

アプリ側はそのビルドに対応する SQLite を使うように設定し、アプリのアップデート時に対応するスキーマバージョンを上げるようにしています。

また、常に取得した SQLite でローカルのデータを更新するため、アプリの起動時には常に最新のデータを取得できるようにしています(マイグレーションは一切行いません)。

sqflite で FTS 5 がサポートされてなかった

sqflite は FTS5 をサポートしていなかったので今回は FTS4 を使用しました。

我々のユースケースでは FTS4 でも十分な性能が出ているため、特に問題はありませんでした。

Google Cloud Storage cache の invalidation

Google Cloud Storage はデフォルトでキャッシュを行うため、新しいファイルをアップロードしても古いファイルが返されることがあります。そこで no-cache を指定してアップロードし、キャッシュはするがアップデートがある場合は常に最新のデータを取得できるようにしました。

no-cache: オブジェクトは、キャッシュに保存されますが、Cloud Storage によって最初に検証されない限り、将来のリクエストには使用されません。

不要なカラムを検索対象から外す

我々のケースでは検索を行うには不要ですが、ウィジェットの表示には必要なカラムがありました。

例えば idstart_at などです。

CREATE VIRTUAL TABLE events USING fts4(
    id INTEGER PRIMARY KEY,
    title TEXT,
    description TEXT,
    start_at TEXT
);

そのままだと idstart_at なども検索対象になってしまうため、検索対象から外すためには以下のように notindexed を指定します。

CREATE VIRTUAL TABLE events USING fts4(
    id INTEGER PRIMARY KEY,
    title TEXT,
    description TEXT,
    start_at TEXT,
    notindexed=id, start_at
);

まとめ

今回は、SQLite の FTS5 Extension を使って全文検索機能を実装してみました。

こんな感じで設計を考え、バックエンド・インフラを構築しつつ、Flutter 側の機能を実装したところ1週間ぐらいで作れたのでとても満足しています(検索🔍もとても早いし)。

全文検索について何も知識がなかった私でも簡単に実装できたので、全文検索を導入したいけど Elastic Search はちょっと… という方にはおすすめです。

ただしこの方法は、以下のようなケースには向いていないので注意してください。

  • データ量が多い場合
  • リアルタイム性が求められる場合
  • 大規模なシステムに導入する場合
MIXI DEVELOPERS NOTE
MIXI DEVELOPERS NOTE

Discussion