NipoPlusのバックエンドはFirebaseを使用しています。アカウント管理やストレージ、Cloud Function、データベースなどアプリ開発に必要な機能の多くが予め用意されており、Firebaseだけである程度のサービスを割と簡単に作成することができます。
データの多くはFirebaseの1サービスである「FireStore」に保管することになります。FireStoreはリアルタイム同期やセキュリティルールなど、特徴のあるデータベースで、慣れてくるととても使いやすいデータベースですが、困ったことが1つあります。それが、「検索の貧弱さ」です。
FireStoreのクエリは貧弱である#
リレーショナルデータベースなどに比べるとどうしてもクエリの制限が気になります。2つ以上のキーで絞り込みをする場合はインデックスの作成が必要になってきますし、全文検索などもサポートされていません。
弱点を補って余る程の魅力があるFireStoreですが、実際にアプリを作ると様々な検索が必要になってきますのでFireStore単体だとどうしても行き詰まってしまうことでしょう。
例えば次の画面はNipoPlusでレポートを検索する画面です。
状態や期間、単語を使った検索をFireStore単体で実装しようとすると茨の道となります。フロントにデータを大量にロードしてJavascript側でフィルターを掛ける手もありますが、無駄な通信も多く処理負担も大きくなるため、あまり現実的な回避策とは言えません。
NipoPlusではこの問題に対して、FireStoreの他にElasitc Searchという全文検索対応のデータベースを併用することでこのような機能を実現しています。
Elastic Searchとは?#
ElasticSearchは全文検索に対応したデータベースです。FireStoreではできない全文検索や高度なクエリを行うことができます。オープンソースで開発されており、自前でサーバを用意する場合は無料で利用することもできます。
自前でサーバを用意できない場合はElastic Cloudというサーバを有償で利用することもできます。NipoPlusはElastic Cloudを使用しています。
FirestoreとElasitc Searchを組み合わせて使う#
FireStoreの便利な点と、Elastic Searchの高度な検索を組み合わせることで強力なデータベースになります。
2つのデータベースを使いますがElasticSearchはあくまでも検索専用で、メインはFireStoreです。FireStoreでデータが書き込まれたら、その変化をCloudFunctionsでキャッチしてElasticSearchへ書き込む処理をしてあげます。
Elastic Searchの準備#
まずはElasticSearchのデータベースを準備することからはじめます。ElasticSearchではデータを保存すると良しなに型を決めてくれますが、実際は予め型(スキーマ)を決めておいたほうが良いです。
スキーマの作成は記述量が多くなるのでtsファイルなどに書き、いつでも作成出来るようにしておくとデータベースの復旧などでも使い回せるのでオススメです。
例えばNipoPlusのレポートに関するスキーマは次のように作成しています(一部紹介)
スキーマができたらこれをElasticSearchに送ってインデックスを作成します。nodejsを使って書き込む例
これでElasticSearch側の準備が完了です。
なお私はこのあたりの処理をCUIでいつでも作成・破壊が出来るようにしておきました。何かとテストで作成破壊を繰り返すことになるので、面倒臭がらず先に作っておくと開発がスムーズになります。
Cloud FunctionsでFirestoreのデータをElastic Searchへプッシュする#
Cloud FunctionsでFireStoreの変化を検出し、ElasticSearchへデータを書き込むような処理を書きます。
この例ではonWriteを使っており、書き込み、更新、削除の3つを1つのCloud Funstionsで処理しています。
なお更新はIDを指定して書き込みすれば上書きしてくれるのであまり深く考える必要はありません。mySQLで言うところのupsertみたいな感覚で使えます。
これでFireStoreのデータとElasticSearchのデータをほぼ同じ状態に保つことができます。
処理の流れは絶対に Firestore => Elastic Searchの一方通行になるように意識しています。相互通信してしまうとわけがわからない事になりかねないため、この流れは絶対遵守します。
データの検索はCloud Functionsを経由して行う#
ElasticSearchのデータは大切なデータなのでセキュリティ上、安全に守られながら運用しなければなりません。FireStoreであればセキュリティルールを使うことでデータを安全に保護できますが、ElasticSearchにはセキュリティルールがありません。
ElasticSearchには独自のアクセス権限などが設定できますが、あくまでもFireStoreの補助として使う場合、ElasticSearchのアクセス権限を設定してしまうと話がこじれてしまいます。
そのため、NipoPlusでは、ElasticSearchにアクセスできるのはCloud Functionsからのみとし、Cloud Functionsで全ての権限チェックを行っています。
FirebaseのユーザIDを保証できるhttpOnCallを使うと、アクセスユーザの素性がわかるので便利です。
以下はサンプルです。
結果をフロントに返却し、フロントはフロントで受け取ったデータを良しなに表示します。
他の全文検索データベースとの比較#
Firebaseの公式サイトでも全文検索を行うには外部DBを使う方法を紹介しています。
紹介されているデータベースは以下の3種類です。
- Elastic Search
- Algolia
- Typesense
全てのデータベースを使ってみましたが、ElasticSearchは導入までの手間が最もかかる面倒くさいデータベースです。
AlgoliaやTypesenseではアクセス制限をもったAPIキーを生成出来るので、権限を適切にセットしたAPIキーをフロントに渡せばフロントから直接AlgoliaやTypesenseにアクセスできるため、
開発自体が非常に簡単に進められます。
箱から出してすぐ使えるというメリットはありますが、Algoliaはコストが高すぎる。Typesenseは検索精度が低すぎるというデメリットがあります。
単語をスペースで区切らないCJK(日本語・中国語・韓国語をまとめてCJKといいます)を母国語とする場合、検索精度を上げるために色々苦労することになります。
ElasticSearchはこの点、kuromojiやngramといった検索精度を高める仕組みをスキーマ定義の際に設定できるため比較的高い検索精度を保つことができます。
また、AlgoliaやTypesenseに比べてかなり複雑なクエリを書くことも出来るため、長い目で見るとElasticSearchを導入するのがベストな選択だと思いました。
例えばTypesenseは文字によるソートがそもそもできない。Algoliaはソートのキーを1種類しか指定できないなどの弱点がありますが、ElasticSearchはガンガン使えます。