「はじめての」AlgoliaからElasticsearchへの移行奮闘記
はじめに
こんにちは、GMOメディア株式会社でサービス開発エンジニアをしています、からころです。
私が所属しているチームでは、ディレクター、デザイナー、エンジニア全員で一丸となり、プリ小説のユーザー体験をより良くしていくために日々邁進しています。
プリ小説とは -
夢小説や二次創作を主体とするチャット型小説投稿サービスです。
https://novel.prcm.jp/
今回は少々長めな話にはなりますが、私が検索エンジンにはじめて触れ、奮闘した、AlgoliaからElasticsearchへの移行について、お話させていただければと思います。
最後まで読むのが億劫になる前に魅力を小出しすると、紆余曲折ありつつ、結果的に、Algoliaを利用していた頃とほぼ変わらない機能を提供することができ、移行により約50%のコスト削減にも成功しました。また、はじめて検索エンジンに触れたにも関わらず、フロントエンドからバックエンドまで一貫して1人で検索システムを実装した経験はとても大きな経験となりました。
AlgoliaやElasticsearchとは?
Algolia や Elasticsearch は、一般的に全文検索エンジンと呼ばれており、インデックス(DBでいうところのテーブルに該当します)に登録したドキュメントの検索をより詳細により早く行うことができます。
概要はさらっと説明するだけにします。以降は検索エンジンについての基礎知識がある程度ある前提で読み進めていただけると幸いです。
AlgoliaとElasticsearchの違い
具体的には以下に記す例の通りですが、基本的にAlgoliaはElasticsearchと比べて、より簡単に実用レベルで使いやすい検索エンジンです。
比較例 | Algolia | Elasticsearch |
---|---|---|
検索設定 | GUIで手軽にできる | ほぼ手動 |
ソート設定 | GUIで手軽にできる | ほぼ手動 |
インデックス作成/更新 | GUIで手軽にできる | ほぼ手動(一部GUIでも可能) |
インデックス設定 | 自動でも十分 | ほぼ手動 |
辞書・同義語の設定 | 自動でも十分 | 手動 |
インスタンス設定 | ほぼ意識する必要がない | ほぼ手動(一部GUIでも可能) |
インスタンス作成・更新 | 自動 | ほぼ手動(一部GUIでも可能) |
移行に踏み切った理由
上述したように、サイト内検索エンジンとして利用する上で、AlgoliaとElasticsearchの違いをみたとき、機能的にはメリットがほとんどないように見えます。しかし、Algoliaを検索エンジンとして運用する上での課題として、金銭的なコストの問題が上がってきます。
比較例 | Algolia | Elasticsearch(セルフマネージド) | Elasticsearch(Elastic Cloud) |
---|---|---|---|
運用コスト | ほぼなし | とても負担が大きい | 負担が大きい |
インスタンス費用 | かからない | サーバ維持費用がかかる | インスタンス費用がかかる |
リクエスト費用 | 従量課金制 | なし | なし |
上記で述べたように、AlgoliaとElasticsearchの課金体系に大きな違いがあります。
プリ小説では、秒間平均で50qpsを超える検索リクエストが行われています。そのため、今後のサービス規模拡大なども考慮した時に、Algoliaのリクエスト数による従量課金制では金銭的なコストがかなりネックになっていました。
また、インフラ専任のエンジニアをチームとして抱えていないということもあり、セルフマネージドやElastic Cloud on Kubernetesでの利用ではなく、Elastic Cloud上で動作するElasticsearchを採用することとなりました。
移行スケジュール
移行スケジュールはおおよそ下記の通りで、夏休みである7月を最終締め切りとして前倒しで動いていました。
2022年12月 Elastic社に初コンタクト・調査・移行手順作成・移行箇所の洗い出し.etc
2023年1月 バックエンド側の実装・フロントエンド側の実装
2023年2月 ドキュメントの移行バッチ作成・テスト環境でのテスト・本番環境の整備
2023年3月 Elastic AppSearchによる本番環境での段階リリースを開始
2023年4月 ~ 6月 80% ~ 100%リリースあたりでユーザーからの不満の声が集まり、一度Algoliaの検索へ戻す。
2023年7月上旬に、Elastic AppSearchによる検索では不十分であったと結論づけ、Elasticsearchへの移行へと急遽切り替える判断をしました。(後述)
最悪の場合、Elastic AppSearchから、Elasticsearchへの切り替えも目処に入るだろうと念頭に置き、ロジックはほとんど使いまわせるように設計していたため、インデックスの設定周りの調査を再度念入りに行った上で、なんとか夏休み直前に100%リリースができる状態にもっていくことができました。
検索システムの構成について
ざっくりとした移行要件として、サービスの特性上、リアルタイムでユーザーが更新したデータを検索できる必要があったため、Elasticsearchのインスタンス は、Hot data and Content tier
を選択しました。データ登録に関しては、Firestoreへの登録イベントと同じタイミングで、Elasticsearchのインデックスにドキュメントを登録することでリアルタイムに近似した検索を実現できます。
また、公式ではブラウザ用のElasticsearch用のライブラリが提供されていないことや、ブラウザから直接Elasticsearchへのアクセスをすると監視がしづらいこと、そもそもセキュリティ的に問題があることなどから、Cloud Functions経由でElasticsearch(Elastic AppSearch)へ検索リクエストを行うことを前提としました。
Elastic AppSearch の採用と挫折
移行スケジュールで前述した通り、当初はAlgoliaに似ていて検索リクエストなどの状況をGUI上で管理できる、Elastic AppSearchを採用する想定で動いていました。GUIで可視化できるのは便利である反面、
- Elasticsearchより検索クエリの設定を細かく指定しづらい。
※ 形態素解析後の単語間のAND検索ができないことが大きな要因となった(2023/07頃) - Elasticsearchを利用する場合と比べて、Elastic AppSearch分のインスタンス費用が嵩張る。
- Elasticsearchで直接検索するのと比べて通信レイテンシが2倍以上かかる。
など、さまざまな理由で最終的に採用を断念しました。
実際のシステム設計
実際のシステム設計は、登録,削除処理と検索処理に分かれており、シーケンス図は以下の通りです。また余談ですが、技術スタックとしては、Node.jsとTypeScriptを利用していて、ライブラリについてはElastic社からOSSとして提供されている ElasticsearchのNode.jsクライアント を利用しています。
登録,削除処理
クライアントからFirestoreが更新された場合に更新トリガーを利用し、Cloud Functionsを経由することでElasticsearchへの登録処理を行なっています。
検索処理
クライアントから発行された検索リクエストをCloud Functions(v2)に立てたAPIサーバ上で処理した上で、Elasticsearchへ検索リクエストを行います。
Cloud Functions(v1)ではなく、Cloud RunベースのCloud Functions(v2)を利用している理由については、コスト最適化の文脈やCloud Functions(v1)では、モニタリングがしづらいという理由からです。また、たくさんのリクエストが同時に発行されることが想定される場合はv2を利用するように Google Cloudから推奨 されています。
監視について
Elastic Cloudを利用する上で、避けて通れない運用に「監視」があると思います。監視はもちろんQPS(query per Second)の把握や、適切なインスタンス性能を設定をするために必要です。また、バーストリクエストが起こった場合に、Elasticsearchのリクエストが処理しきれなくなっていることを知らせるためのアラート監視も含まれます。
Elastic Cloudの機能にも、オートスケーリング機能やオブザーバー機能がありますが、Cloud Funtions(v2)側で、同時リクエスト数とタイムアウト時間の設定を利用して、Elastic Cloudのインスタンス性能を加味した、リクエストの制限や監視を実現しているので、一旦ここではお話ししません。
Cloud Monitoringの活用
上述しましたが、Elastic Cloudの監視については、Cloud Functions(v2)の中で動くCloud Runを監視するために、Cloud Monitoringを利用しています。
監視している値については、
- 数リクエストあたりのレイテンシの平均
- Cloud Runのインスタンス数
- 数分あたりの秒間リクエスト数平均
- 数分あたりのリクエストエラーの総数
など、になります。
また、この数値を基にアラートポリシーを作成し、Slack通知でアラートの監視を行なっています。
閾値はどうするの?
閾値については、当初このシステム構成をとる上でかなり私も悩みました。
色々考えた末にはなりますが、Elastic Cloudの管理画面で見ることのもできるElasticsearchインスタンスのCPU利用率と、Cloud Runのリクエストレイテンシを基に検証を行い、設定するといいように思います。
ElasticsearchインスタンスのCPU利用率については、40%を超えるとかなり怪しいので、その際にリクエストレイテンシが普段と変わっているかを確かめることで見定めます。また、リクエスト数も同時にある程度のバッファを見極めつつ設定します。例として、現在プリ小説の検索では、Cloud FunctionsとElasticsearchの一連の処理のレイテンシの閾値を100msに置いています。
移行結果
はじめにも書いたように、Algoliaから Elasticsearchへの移行結果としては、Cloud Functions(v2)などのコストを合算しても、最終的に50%のコスト削減を実現することができました。
また、リクエストレイテンシも平均50msに抑えることができて、検索結果もほとんどAlgoliaを利用していた時と変わらないように実装できました。(Elasticsearchの検索結果の検証はかなりしんどかったです...。)
これは余談になりますが、Algoliaでは除外検索が日本語対応されておらず(2023/10/31)、実現ができていませんでしたが、Elasticsearchへと切り替えることによって、除外検索機能も提供することができるようになりました。
移行してみての感想
私自身フロントエンド領域がメインなエンジニアでしたが、今回Elasticsearchないしは、検索エンジンをはじめて触ってみた結果、やる気さえあればそこまで尻込みする必要はないんじゃないかなあという感想になりました。※もちろん難易度は高いです...。
また、ここではお話しできませんでしたが、
- Elastic CloudのElastic AppSearchやElasticsearchのインスタンス性能を見誤っており3回ほど一時的に検索機能を停止させてしまった。
- Algoliaを利用してた頃の検索を再現するために検索クエリの調整に難儀した。
- 無停止を前提としたインデックス/インスタンス更新のバッチ処理の手順書やスクリプトもどうにか作成した。
- Kuromojiを利用した形態素解析の設定をサービスに適合させるために手探り状態でもどうにか形にできた。
- フロントエンドの無限スクロール/仮想スクロールの実装にかなり手を焼いた。
などなど、紆余曲折ありつつ、とても大きな経験になったと思います。
おわりに
今回は、「はじめての」AlgoliaからElasticsearchへの移行奮闘記というタイトルで、記事を書かせていただきました。
今後の課題としては、サービスの特性上、独自単語が数多く存在しているため、そういった独自単語に対してKuromojiのユーザー定義辞書を定義していく、同義語フィルターを利用しSEOをより強化する、Elasticsearchの機械学習系の機能などを活用できていけたらなと思っています。
ここまで読んでいただきありがとうございました。何か訂正や質問などありましたらお気軽にコメントしていただけると幸いです。
GMOメディアでは、一緒に働くメンバーを募集しています。
Discussion