🔖

elasticsearchのrefreshに関して

2022/11/10に公開

はじめに

elasticsearchにはRefresh APIなるものが存在する。
ドキュメントをインデックスしたら即検索できるようになると考えていた自分は、このrefreshを意識せずelasticsearchを使用していたため、elasticsearchの絡むテストを書くときにハマりかけた。今回はrefreshに関する備忘録を残しておく。

なぜrefreshが必要なのか?

前提として、elasticsearchではドキュメントをインデックスしただけで検索可能な状態になるわけではない。追加されるドキュメントは一旦In-memory bufferという領域に溜め込まれた上で、まとめて検索対象にとして反映されるようになっている。
このIn-memory bufferに溜め込まれたドキュメントを検索対象として反映する処理がrefreshと呼ばれている。

Near real-time search | Elasticsearch Guide [8.5] | Elastic

普段はなぜrefreshを意識せずに使えるのか?

elasticsearchでは上記で述べたrefreshを1秒間隔で自動的に実行している。1秒という短い間隔で追加したインデックスが検索可能の状態になるため、通常の利用時はrefereshを意識する必要がない。
(elasticsearchが「"ほぼリアルタイム"の検索プラットフォーム」とされている理由は、厳密に言うと検索できるようになるのが1秒後だからだと思われる。)

テスト時に困ったこと

「通常の利用時はrefereshを意識する必要がない」と前述したが、テスト時はそのようには行かない。
例えば、FactoryBotでレコードを作成して、検索によって作成したレコードが表示できるかを検証するspecを書くとする。
FactoryBotでレコードが作成されてからアサーションが実行されるまでの間でrefreshが実行される保証はないため、目的のレコードのドキュメントが検索可能ではない状態でアサーションが実行されてしまいテストが落ちるということが発生し得る。自分の参加している案件では、これが原因でテストがflakyとなってしまっていた。
FactoryBotでレコードを作成し、アサーションが実行される前に明示的にrefreshを行う必要があった。

明示的なrefreshを行う

ドキュメント単位でのrefreshの場合はこのRefresh APIを使用する。
今回の案件ではelasticsearch-railsを使用していたため、以下のようにしてindex_documentにrefreshオプションを渡すことで明示的なrefreshを行った。これにより、アサーションが実行される際にドキュメントが検索可能な状態になっていることが担保できるようになり、flakyテストを解消することができた。

factorybot_created_record.__elasticsearch__.index_document(refresh: :wait_for)

refreshに渡すことができる引数は以下。

true:デフォルトでは1秒間隔で実行されるrefreshを即時で実行させる。
:wait_for:次のrefreshが実行され、documentが検索可能になるまで待つ。trueとは違い、強制的にrefreshを実行させるわけではない。
false(default):refreshに関連するアクションは行わない。デフォルトの1秒間隔でrefreshが行われる。

疑問点

最初はrefresh: trueに設定していたがflakyテストは解消せず、refresh: :wait_forに変更したところテストが落ちなくなった。refresh: trueは即時refresh、refresh: :wait_forはタイミングは変えずに定期refreshを待つという認識だったので、refresh: trueでflakyテストが解消しなかった理由がわからん。
refresh: :wait_forでは「可視化」を待つという点が重要だったりするのか・・・?)

Wait for the changes made by the request to be made visible by a refresh before replying.

Discussion