こんにちは。 EC基盤グループ サーチチームの 山村です。
この記事は、 Elastic Stack (Elasticsearch) Advent Calendar 2022 の 23日目です。
2か月ほど前になりますが、2022年10月26日に実施された 第50回 Elasticsearch勉強会 で発表させていただきました。 私が外部での発表するのは、2016年6月のSolr勉強会 以来で、非常に緊張しました。
日々の業務にかまけて、ブログが後回しになっていたことで大変遅くなってしまいましたが、上記の発表で話した内容とスライド資料から、話したかったポイントを抜粋するとともに、勉強会で不足していた部分について補足をします。 当日、発表を終えたところで気が抜けてしまい、Twitter で頂いていた質問に満足に答えられませんでしたので、この場で補足説明を含めて出来るだけ回答したいと思います。
発表の概要
第50回 Elasticsearch勉強会 で「モノタロウの1900万商品を検索するElasticsearch構築運用事例」として発表しました。
発表に使用したスライドと録画が公開されています。
以下のような内容で発表しました。すべてを紹介できないため、詳しくはスライドをご参照頂きながらお読みください。
- MonotaRO(モノタロウ) とはどういう会社か?
- これまでのモノタロウを支えた検索システム
- 2010年から、Apache Solr によるシステムを運用
- 有効な施策は引き続き使いながらElasticsearchにする
- 検索のユーザ体験向上の一例
- Elasticsearch を導入して課題解決とその先へ
- いくつかのPoCを行い、Elasticsearch に決定
- Solr Cloud も検討した
- Elastic Cloud も検討した
- 社内での Google Cloud Platform (Compute Engine)による運用を決断
- 商品データ構造と Elasticsearchのマッピング構成
- モノタロウの商品は、品番(SKU) → 商品グループ → カテゴリ のような構成になっているという特徴がある
- 絞り込み機能「ファセット」を充実させたUI
- Elasticsearch での構成は?
- 規模の拡大への対応
- 規模の拡大に備えるための工夫
- 商品点数の増加に耐える
- データを複数のファイル(シャード)に分割する
- クエリを分割する
- ページビュー数の増加に耐える
- ノードの数を増やす(スケールアウト)
- 運用を簡単にするためにクラスタ化と構成も必要
- 商品点数の増加に耐える
- モノタロウの規模・要件だと工夫も必要
- マスター専用サーバの構築
- 兼用サーバではなく専用で用意して安定稼働を
- シャード分割はしても、1ノードにデータが揃うように
- 複数のノードに分散して処理すると速度が低下
- Shard allocation awareness を活用
- 暖機運転を行わないとレスポンスの低下
- 公式には不要と言われているが、運用したところ必要
- マスター専用サーバの構築
- 規模の拡大に備えるための工夫
- Google Cloud Platform と Infrastructure as Code
- コード化
- すべてを Terraform でコード化
- Ansible / Packer を使用
- Google Cloud Build でイメージ作成自動化
- これから
- 必要な時に必要な物を起動(開発環境も自由度上昇)
- 複数の必要なシステムを構築 (ABテスト実施なども)
- 一括で B/G デプロイメントによる切替
- こまめなバージョンアップ が可能に
- コード化
- モノタロウの商品検索と今後
- Elasticsearchを使って、なにを目指しているのか
- モノタロウにおける商品検索への期待とは?
- まとめ
- Elasticsearch による商品検索基盤に移行しました
- Elasticsearch を採用したことで従来の課題を解消しました
- さらなる新しいことにチャレンジする基盤が出来ました
モノタロウにおけるElasticsearchデータ構造のポイント
今回の発表では、モノタロウの検索システムについて紹介するとともに、Elasticsearch の機能と使い方についても紹介できればと考えていました。(スライド 10~14ページ)
Elasticsearch を使う際にマッピング定義での「データ構造をどのようにするか」は苦労する部分ですが、モノタロウでも何種類かのPoCを経て決定しました。その中でも、特に難しかったところでもあり、キーとなったデータ構造は Flattened Field Type と Nested Field Type でした。
この2つの機能がどういったものか、どのような時に有益なのかについては、スライドでもご紹介させていただいた、アクロクエストテクノロジーさんが Elastic Community Conference 2022 で発表された資料 (ElasticsearchでECサイトにおける高速検索/集計を実現する - Speaker Deck)が分かりやすいです。
モノタロウでは商品の属性の種類が数万パターン発生しますが、それらをDynamic Field Type で行うと、フィールド定義が膨大になる、いわゆるマッピング爆発を起こします。それを回避しつつ、高速に集計処理するため、Elasticsearch 7.3 から導入された Flattened Field Type を利用しています。
この方法によって、商品属性によるファセットを充実させたUIを作るとともに、絞り込みの最適化を行うという問題の解決に役立っています。
もちろん、 Dynamic Field を利用したほうが良い部分もあり、適材適所で利用するようにしています。
もう一つの重要なデータ構造は Nested Field Type です。 Solr では Block Join と呼ばれる機能(とほぼ同等)ですが、内部構造を考えると「Join」という説明が分かりやすく、使い方を考えると「Nested」という説明が分かりやすいもので、Solr と Elasticsearch の思想の違いというのはこういう名称にも現れており、面白いなと思っていました。
(ちなみに、Solr にはインデックス(コア)を結合する SQL の JOIN に近い Join Query も存在しています。この機能は Elasticsearch には無いので、移行時の設計で苦労しました)
当初は、非正規化したデータで検索することを考えて設計していましたが、PoCにより商品グループの集計でパフォーマンスに問題があることが分かりました。その後、パフォーマンスとクロスヒットの回避を行う方法を複数検討した結果、Nested Field Type を利用することとしました。
非正規化方式と、Nested Field Type方式の特徴
改めて、非正規化方式と、Nested Field Type方式の特徴をまとめると以下のようになります。
非正規化方式 | Nested Field Type 方式 | |
_id のデータ | 品番(SKU) | 商品グループコード |
出力の特徴 | 品番での処理が速い。一方で、商品グループで表示する場合、各処理でグルーピングする必要があり遅い。 | 商品グループでの処理が速い。一方で、Nested の内容を出力する場合は遅い。 |
グループでの商品リスト出力 | 並び順が正しくならない可能性が高い | 並び順のスコア調整が通常の方法になるため容易 |
グループでのファセット集計 | 品番をグルーピングする必要があることからとても遅い | 高速に集計することが出来る |
Elasticsearch クエリの複雑性 | シンプル | 複雑 |
「モノタロウの規模・要件だと工夫も必要」についての補足
今回の発表では、モノタロウの規模・要件の話を少ししましたが、発表時間の兼ね合いで端折った部分があり、結果として伝わっていない部分がありそうでした。Twitter で頂いたご質問にお答えする形で補足させて頂ければと思います。
モノタロウのサイトで工夫している所としては、以下の3点を話しました。(スライド 19ページ)
- 専用マスターサーバ
- Shard allocation awareness
- 暖機運転
発表時に省略したのですが、この1番と2番が冒頭で話していた Elastic Cloud を採用しなかった理由の一つです。
また、Shard allocation awareness で1ノードに集めることついては、以下の質問をいただいていました。
1つのノードに揃えてしまうと落ちた時に不安だったり? #elasticsearchjp
— jonlpstudy (@jonlpstudy) 2022年10月26日
「シャードは分割するが単一ノードに配置する」というのはパラドックス的なフレーズ、私の理解が追いついていない気がする... 補足説明あると嬉しいな #elasticsearchjp
— Koji Kawamura (@Ijokarumawak) 2022年10月26日
まず、ノードのダウンについては、発生するものと考えて、Managed Instance Group の autohealing で自動修復する構成としています。
オートスケールの動作も考慮して、1台のダウンでは影響が出ないような構成としています。
今回のシャード分割の目的は、CPUリソース(CPUコア)を並列化することです。
シャードごとにCPUを利用して検索を行い、その後シャードを集約する場合(Coordinating の処理)に、ノードをまたぐのが通常のElasticsearchの挙動ですが、その際にネットワークの通信が発生するためレスポンスが遅延します。
色々試した結果、CPUコアとメモリを十分積んだ状態とすることで、1ノード中で処理が出来る事を確認しました。そして、それであれば Shard allocation awareness により1ノードで完結するほうがメリットが大きいと判断しました。
今後ファイルサイズが大きくなるなどして、Filesystem cache (Page cache)に乗らずに IO負荷が上がるなどの状況になって来ると、ノードを分散して同一AZ内で完結させるなどの対応が必要になってくると考えています。
なお、Shard allocation awareness の設定はアドバイス的な動作であるため、ディスク障害などでシャードが失われた場合には、Coordinating ノードとして他の Data ノードからデータを取得します。そのため、遅くはなるものの動作を継続することが出来るのがメリットです。(実際には、ディスク障害の場合はGCP側の制御でLBから切り離されて再起動プロセスに入る想定のため、状況は限定的かもしれません)
#elasticsearchjp 暖気運転ってのはあらかじめ用意したクエリを投げてるんだろうか??それとも実際のクエリを並列に流してるのかなぁ?
— Jun Ohtani (@johtani) 2022年10月26日
暖機運転については、Google Cloud Load Balancing でトラフィックのミラーリングも検討しましたが、より簡単に、また万が一にも既存環境に影響することが無いようにするため、事前に用意したクエリを実行する方式としています。ただし、クエリは時間経過で変更される事も考え、本番で実行されたクエリを定期的に取得して更新できるようにしています。
データ生成について
Elasticsearch 向けのデータ生成については、 【Elasticsearch】1900万点に及ぶ商品データ作成の時間を約67%短縮できた構成と工夫 - MonotaRO Tech Blog で紹介させていただいていましたが、その部分についても Twitter で以下のご質問を頂いていました。ありがとうございます。(スライド 21ページ)
#elasticsearchjp データの生成の時間は検索エンジンのインデックス作るところだけの処理時間の比較なんだろうか?それとも前処理も含めてなんだろうか?
— Jun Ohtani (@johtani) 2022年10月26日
こちらは、前処理と実際のインデックス、その後の Optimize を含めたトータルの時間です。Optimize も、公式には不要と言われる処理ではあるのですが、パフォーマンスを計測すると明らかに違いが出てくるため実行しています。
詳しくは「処理時間を短縮できた要因」などをご参照ください。
まとめ
最後に、今回のElasticsearch勉強会で発表できたことは大変良い経験となりました。
実は今回、9月末にあった社内の勉強会(ManabiCon)と、10月末のElasticsearch勉強会と、このブログを合わせて、1粒で3度おいしいを目指しました。
ManabiCon では 63ページからなる資料を作成してしまい、Elasticsearch勉強会ではそこから17ページを抜き出して34ページの発表資料にしたので結構大変ではありましたが、大変楽しく発表が出来てよかったです。運営関係者の方々をはじめ、ご聴取いただいた方々など、皆様ありがとうございました。
また、ブログ作成が遅れに遅れて12月になったため、Advent Calendar にもお誘い頂きました。結果、1粒で4度おいしいことになり、本当にありがとうございました。
ちなみに、企業理念や商品検索への期待についての話は、外部発表でありがちな採用向けのポーズではなく、ManabiConで社内向けに話した内容の抜粋です。
検索システムは、社内からの期待も、社外からの期待も受ける部分です。
やりがいはあると思います。ただ、昨今のエンジニア不足などにより人手は足りていません。是非、一緒に働く人をお待ちしています(笑)
興味がある方は、ぜひカジュアルMTGに応募ください! よろしくお願いします。
最後までお読みいただき、ありがとうございました。