全文検索エンジンの負荷を分散した話

全文検索エンジンの負荷を分散した話

はじめに

このエントリは全5回を予定する19卒新人ブログリレーの第3回目です。

初めまして。 リクルートテクノロジーズ新卒2年目の山田 祥允です。
主にタウンワークの開発を担当しています。

今回は、タウンワークで用いている全文検索エンジン Solrにおいて、サーバの負荷分散を行なった実例について紹介したいと思います。

背景

タウンワークでは、年々増大していくSolrサーバへの負荷を軽減するため、サーバ数の増加で対応していました。

しかし、既存の構成ではサーバ数を増加することでオンラインアクセスの負荷は分散できても、データの登録処理 (インデクシング) による負荷は分散できませんでした。 これは、全てのサーバに対してデータの登録処理が実行されるため、追加したサーバに対しても結局インデクシングの処理が発生してしまうためです。

そこで、今回はSolrのレプリケーション機能を用いて、 インデクシングの負荷とオンラインアクセスによる負荷を分離することにしました。

Solrの負荷分散

Solr はサーバの負荷を分散する機能として、分散インデックスや分散検索、レプリケーションといった機能を持っています。

分散インデックスというのは、1つの大きなインデックスを小さなインデックスに分割し、複数のサーバで管理する仕組みです。
分散したインデックスに対しての検索は、分散検索を用いて行います。

レプリケーション機能については、後に詳しく述べますが、あるサーバのインデックスを複製し他のサーバに転送できる仕組みです。

分散インデックスと分散検索を使うと、インデックスの大容量化に対して対策を行えたり、インデクシングの負荷分散にはなりますが、複数のサーバーに対する検索となってしまうため検索の負荷が増大してしまいます。

一方で、レプリケーション機能はインデックスの大容量化に対しての対策はできませんが、インデクシングの負荷とオンライン検索の負荷を分離することができます。

よって今回のタウンワークの課題に対してはオンライン検索の負荷はそのままで、インデクシングの処理を切り離すことのできるレプリケーション機能が適していると判断し、レプリケーション機能による負荷分散を行いました。

Solrのレプリケーション機能

それでは、レプリケーション機能について見ていきます。

レプリケーション機能を利用することで、Solrのサーバをインデクシングを担当するマスターと検索を担当するスレーブに分割することができます。

これにより、一方の処理が高負荷になっても他方のパフォーマンスに影響が出ないようになるという利点が得られます。

レプリケーション実施方法

設定ファイル

まずは、設定ファイルによる設定方法について説明します。

solrconfig.xml の ReplicationHandler を設定することでSolrのレプリケーションを行うことができます。

例えば、masterの設定は以下のようになります。

replicateAfterbackupAfter といった項目を設定することができます。

各項目の詳細は以下の通りです。

項目名 説明
replicateAfter インデックスのレプリケーションを行うタイミングを”commit”, “optimize”, “startup”から指定する。 複数指定可能。
backupAfter インデックスのバックアップを行うタイミングを”commit”, “optimize”, “startup”から指定する。 複数指定可能。
confFiles slaveに送信する設定ファイルを指定する。
バックアップを保持する世代数。
commitReserveDuration コミット頻度が高かったり通信速度が遅い場合に、指定した時間より短い間隔でレプリケーションが行われないようにする。
maxWriteMBPerSec 1秒間に最大何MB転送するか指定する。

また、slaveは以下のような設定になります。

こちらも、 masterUrlpollInterval といった項目を設定できます。

以下のような項目があります。

項目名 説明
masterUrl masterのURLを指定する。
pollInterval masterへのポーリング間隔を指定する。 00:00:00を指定することでポーリングさせないこともできる。
compression “external”, “internal”の値を指定でき、インデックスファイルを圧縮する。
httpConnTimeout masterに接続する際のタイムアウト値をミリ秒で指定する。 デフォルトは5000。
httpReadTimeout masterからインデックスファイルを取得する際の読み込みタイムアウト値をミリ秒で指定する。 デフォルトは10000。
httpBasicAuthUser masterにBasic認証が設定されている場合ユーザー名を指定する。
httpBasicAuthPassword masterにBasic認証が設定されている場合パスワードを指定する。

master/ slave それぞれの設定を行うと、slave がmasterへポーリングし、レプリケーションを実行するようになります。

HTTP API

レプリケーションに関連するAPIも用意されています。

これを用いると、slave 側でpollingを実施するのではなく、好きなタイミングでAPIをコールしてレプリケーションを実施する、といったことが可能になります。

用意されているコマンドは以下の通りです。

コマンド名 URL 対象 説明
enablereplication http://_master_host:port_/solr/_core_name_/replication?command=enablereplication master スレーブへのレプリケーションを有効にする。
disablereplication http://_master_host:port_/solr/_core_name_/replication?command=disablereplication master スレーブへのレプリケーションを無効にする。
indexversion http://_host:port_/solr/_core_name_/replication?command=indexversion master/slave 最新の(masterの場合replication可能な) インデックスのversionを取得する。
fetchindex http://_slave_host:port_/solr/_core_name_/replication?command=fetchindex slave master からインデックスをレプリケーションする。 masterUrl などのsolrconfig.xmlでslaveに指定できるパラメータを指定できる。
abortfetch http://_slave_host:port_/solr/_core_name_/replication?command=abortfetch slave レプリケーションを中止する。
enablepoll http://_slave_host:port_/solr/_core_name_/replication?command=enablepoll slave masterへのポーリングを有効にする。
disablepoll http://_slave_host:port_/solr/_core_name_/replication?command=disablepoll slave masterへのポーリングを無効にする。
details http://_slave_host:port_/solr/_core_name_/replication?command=details slave configの詳細とステータスを取得する。
filelist http://_host:port_/solr/_core_name_/replication?command=filelist&generation=<generation-number> master/slave Luceneのファイルのリストを取得する。
backup http://_master_host:port_/solr/_core_name_/replication?command=backup master コミットされたインデックスがあればバックアップを作成する。 numberToKeep や name, locationといったパラメータが指定できる
deletebackup http://_master_host:port_ /solr/core_name/replication?command=deletebackup master バックアップを削除する。 name やlocationといったパラメータが指定できる。

実例

ここまでで説明してきたSolrのレプリケーション機能を用いて、実際にインデクシング処理の分離を行なったので紹介します。

構成

今回はマスターを2台(稼働系、待機系), スレーブをN台の構成としました。

稼働系のマスターが落ちた場合、待機系のマスターからレプリケーションを実行します。 この切り替えが可能となるように、スレーブの設定では masterUrl を指定しませんでした。

また、業務要件上レプリケーションはポーリングではなく、バッチ処理で実施する必要があったため、スレーブの pollInterval は指定せず、APIコールによるレプリケーションを選択しました。

結果、今回の案件ではsolrconfig.xmlにレプリケーションの設定はせず、APIコールによるレプリケーションのみで実装しました。

実装

具体的な実装はお見せできませんが、以下のような処理を行うバッチを作成しました。

① slaveに対してmasterを指定してレプリケーションを実行させる
② masterのindexversionを取得
③ slaveのindexversionがmasterと一致するか確認

① では http://slave_host:port/solr/core_name/replication?command=fetchindex をコールし、masterUrl やタイムアウト値などをリクエストパラメータで指定します。
masterの稼働系サーバが落ちた場合には待機系サーバを masterUrl に指定することができます。

② では http://host:port/solr/core_name/replication?command=indexversion をコールします。

③ では http://slave_host:port/solr/core_name/replication?command=details をコールし、masterの indexVersion と一致していればレプリケーション完了と判定します。
masterの indexVersion と一致しなければ一定時間ごとに繰り返し判定を行います。

ポイントは、 indexVersion の確認時に master に対しては command=indexversion のAPIをコールし、 slave に対しては command=details をコールしている点です。

この理由は、detailsのレスポンスでは以下のように details 直下の indexVersioncommits 配下の indexVersion が存在し、この値が異なる場合があるからです。

command=indexvesion をコールして返ってくる値は details 直下の indexVersion の値になります。
しかし、slaveへのindexが完了したことを確認するためには、
commits 配下の indexVersion がmaster の indexVersion と一致していることを確認する必要があるのです。

結果

このような構成変更を行ったことで、インデクシングの負荷とオンラインアクセスの負荷を分散させることができました。

レプリケーション自体による負荷の増加が気になるところかと思いますが、ほぼネットワークの負荷のみで、CPU負荷の増大はインデクシング処理の数%程度に収まりました。

また、レプリケーションの実行時間ですが、ほぼネットワークの転送時間と等しく、60GB程度のインデックスの場合、1Gbpsの帯域を使用して1分程度で完了しました。

複数のスレーブへレプリケーションを実施する際、ネットワーク帯域の圧迫が気になるようであれば、一台ずつレプリケーションを実施するという対処法もあるかと思います。

まとめ

今回は実例を交えてSolrのレプリケーション機能を用いた負荷分散について紹介しました。

Solrの負荷分散の1つの選択肢として参考になれば幸いです。