【Cloud Run】ElasticsearchをCloud Runで運用する
はじめに
こんにちは! テラーノベルでサーバーサイドを担当している@yuhasです。
テラーノベルには作品や作家さんの検索機能があり、ユーザーさんの読みたい作品や興味のある作家さんを提示できる検索機能は重要な機能の一つです。
直近でその検索まわりを一新することになり、Cloud RunでElasticsearchを運用することになりました。
Cloud Runで運用することでオートスケールなど多くの恩恵を受けられる一方で、状態をもつElasticsearchをコンテナで動かしていくのは単純なことではありません。
今回はどのようにしてCloud RunでElasticsearchを運用しているのかをお話しできればと思います。
モチベーション
もともと外部の検索サービス(SaaS)を利用して検索機能を提供していましたが、インフラ面でのコストを下げたいという話があり、代替手段を模索していました。
その中で、OSSの検索エンジンを自前運用しようということになり、必要な検索機能が十分に揃っているという点からElasticsearchを選択しました。
次に、どのインフラで運用するという話になりましたが、
- 自前運用でインフラ面のコストを下げる
- 運用コストをできるだけ下げるために自動でオートスケールさせたい
という面を重視してCloud Runで運用することになりました。
どのような構成にするか
検索サービスには大きく2つの構成要素があると思います。
- 検索APIを提供するインスタンス
- 検索インスタンスの提供する検索データを更新する手段
1つ目はCloud Run上でElasticsearchを動かすとして、2つ目のデータ更新手段をどうするかを考えました。
Cloud Run上で動かす以上、一般的な検索APIが提供する検索データ更新の形である
「検索インスタンスの更新API叩いて検索データを更新する」
という方法をとることができません。
なぜなら、Cloud Run上のサービスに対して更新APIを叩いても、そのサービス上のインスタンスのうちの1つにしかデータ更新を行うことができないからです。
したがって以下のように、各Cloud Runインスタンスが
- 必要な検索データ(更新されたデータ)を自律的に収集し
- Elasticsearchのデータを更新する
という仕組みを作る必要があります。
Cloud Runにはサイドカーの仕組みがあるので、これは綺麗に実現できそうです。
また、この仕組みについて、インスタンスのライフサイクルに応じて行う必要があります。
- インスタンスが起動するとき
- できるだけ高速に起動するにはどうすればいいか?
- リクエストを受け付けられる準備完了はどのような状態か?
- インスタンスが動いているとき
- インスタンスが停止するとき
- (今回はここはあまり関係ありません)
実際の構成
以上を踏まえて、以下のような構成としました(重要なところをわかりやすくするために色々捨象したところがあります)。
まず①データ更新ジョブは、変更ストリームを取得してきます。つまり、データの変更差分をサブスクライブしているイメージです。より正確にいうとSpannerのChange Streamsを取得してきて、それを検索用のデータベースに反映します。
この検索用のデータベースには、検索で使われるものと同一のデータを保存します。検索インスタンスは簡単に落ちたり立ち上がったりすることが前提ですので、検索に使われるデータはこの中に保存して永続性を確保します。
次に②スナップショット作成ジョブは、検索用のデータベースからElasticsearchのスナップショットを作り、オブジェクトストレージ(GCS)上に保存します。
これは検索インスタンスの起動時に使われます。インスタンス起動時に毎回検索用のデータベースから全データを取得して検索インデックスを作っていると長い時間がかかってしまうので、スナップショットから検索インデックスを作ります。
①と②がうまく動くことで
- 検索用のデータベースに検索インデックスにあるべきデータと同等のデータが保存されている
- オブジェクトストレージにElasticsearchのスナップショットが(定期的に)保存されている
という状態になります。
そして最後にCloud Runで動く検索インスタンスです。
まずElasticsearchコンテナが起動し、そのあとサイドカーコンテナが起動します。
サイドカーコンテナは取得1にあるようにオブジェクトストレージからスナップショットを取得してElasticsearchに反映させます。スナップショットが作成されてから現在時刻までのデータは反映されていないので、その分は取得2にあるようにデータベースから不足分のデータを取得してきます。
これで必要なデータが完全に揃ったので、インスタンスとして起動が完了してリクエストを受け付けられる状態になります。サイドカーコンテナは実はElasticsearchのリバースプロキシの役割も果たしており、この状態になって初めてReadiness checkに対して200 OKを返すことも行います。
そのあとは取得2にあるように、定期的にデータベースから不足分のデータを取得してElasticsearchに反映し続けます。
以上の流れで、検索機能を継続的に提供し続けることができます。
まとめ
検索インスタンスをどのようにしてCloud Run上で運用しているのかについて簡単にまとめました。外部のデータベースを利用してデータの永続性を確保しながら、簡単にスケールアウト・スケールインできる構成で検索インスタンスを運用することができました。
Cloud Runのスケーリング機能を利用することができるのはとても安心できます。
一方で、検索のデータ反映をリアルタイムに行うことはできず、少しタイムラグが発生してしまう状況ではあります。今回は厳密なリアルタイム性が求められない状況だったということもありこのような構成となりましたが、よりリアルタイム性が求められるとなると難易度の高いものになりそうです。
また、今回の方法はElasticsearchにあまり依存しない方法をとっているので、他の検索エンジンでも同様の運用が可能だと思います。初期段階では、Elasticsearchの他にもMeilisearchなどを検討していました。
Discussion