LegalForce Engineering Blog

LegalForceの開発チームによるブログです。

サービス無停止でElasticsearchのReindexを行うノウハウ

こんにちは。LegalForce でエンジニアをしております、勝田(@WinField95)です。

この記事は、情報検索・検索技術 Advent Calendar 2021 の 21日目の記事として執筆されました。LegalForce と LegalForce キャビネについて紹介すると共に、Reindex の実施に伴い生じた課題と具体的な対応について社内で実践している内容をお話します。


LegalForce と LegalForce キャビネ の検索機能

LegalForce は、2019年4月に正式版をリリースしたAI契約審査プラットフォームです。大きな特徴は契約書を自動的にレビューできる点であり、過去の契約書から条文の検索も可能です。2021年10月には、導入社数は1250社を突破しました。

LegalForce キャビネ は、2021年1月に正式版をリリースした契約締結後の契約リスクの制御を目的としたサービスです。紙で管理されている契約書の電子化、情報抽出や契約書の検索を行う機能を提供しています。2021年11月には、導入社数300社を突破しました。

LegalForceと LegalForce キャビネ では、検索機能は重要な位置付けです。企業によっては数万件単位の契約書を管理しています。膨大な契約書の中から、特定の条項を人力で探し出すことは非常に困難です。しかし、契約リスクに対応するためには、契約書に書かれている内容を正しく理解する必要があります。LegalForce や、LegalForce キャビネの検索機能は、膨大な数の契約書から関心を向けるべき契約書を素早く探し出します。

f:id:WinField95:20211221173354p:plain
LegalForce キャビネの全文検索機能


契約書に対する操作は API としてまとめられており、契約書データを、Elasticsearch の Document として登録することで全文検索を実現します。

LegalForce や LegalForce キャビネは、若いサービスであり、頻繁にアップデートを行なっています。検索機能の改善のため、Elasticsearch のインデックス定義 (Mapping) に対して変更を加えることがあります。この際、変更内容によっては、Index の再構築処理 (Reindex) が必要となります。

Reindex API を使用した Index 再構築

Reindexとは

Index の再構築処理のことを Reindex と呼びます。 Elasticsearch では、Reindex API が提供されており、Reindex API 経由で Index の再構築を行います。Reindex API では、source として指定した Index が持つ Document を dest として指定した Index にコピーします。

この記事では、source として指定した Index を Source Index、dest として指定した Index を Destination Index として解説を行います。

Mappingの更新と、Reindex が必要な場面

Elasticsearch 内の Document を表すデータ構造を Document Type と呼び、その Document Type を記述した情報を Mapping と呼びます。Mapping にて定義された DocumentType が表す Field のデータ型に従い、ドキュメントの Indexing がされます。

サービスの開発を続けていると Mapping の変更を行いたい場面が生じます。

例えば、下記のような変更があります。

  1. 新しい Field を追加したい。
  2. 既存の Field を削除したい。
  3. 既存の Field のデータ型を変更したい。

1. に関しては、既存の Index の Mapping を変更することで対応できます。2. と 3. に関しては、既存の Index の Field を削除したり、型を変更することはできません。この場合は、Reindex を行う必要があります。

Index Aliases を用いた Index の切り替え

Index Aliases を用いた Index を切り替える手法がElasticsearchの公式ブログ で紹介されています。Index Aliases は、Index に対して別名を付けることができる機能です。Aliases API を用いることで、Index に Alias を割り当てたり、割り当てられている Alias を外したりすることができます。Source Index に予め Alias を割り当てておき、Alias 経由で Index を参照するようにします。Reindex 実施後、Source Index に割り当てた Alias を外し、Destination Index に同名の Alias を割り当てることで、瞬時に参照先の Index を切り替えることができます。

Alias の張り替え自体は下記のコマンドで実現できます。

curl -s -HContent-Type:application/json -XPOST ${ES_ENDPOINT}/_aliases --data-binary '{
  "actions": [
    {
      "remove": {"index": "'${SRC_INDEX}'", "alias": "'${INDEX_ALIAS}'"}
    },
    {
      "add": {"index": "'${DEST_INDEX}'", "alias": "'${INDEX_ALIAS}'"}
    }
  ]
}'

サービス提供時の Reindex 実行に伴う課題

Reindex 実行後にデータの不整合が生じる場面

下記の図のように、Reindex 実行中に Source Index に対して変更が発生した場合、Destination Index との間でデータの不整合が生じます。

f:id:WinField95:20211221173759p:plain

Reindex API では、API が実行されたタイミングの Source Index 内で管理されている Document をもとに、Reindex を実施します。ただし、Index 内に大量の Document が管理されている場合、Reindex 完了までに時間を要します。

Reindex 中に Source Index に対して行われた更新や削除は、Destination Index には反映されません。Source Index と Destination Index を正しく同期し、データ不整合を防ぐためには、Reindex 実行中は Source Index に対する更新処理が発生しないように、メンテナンス期間を設ける必要があります。

サービス無停止で Reindex を実施する工夫

LegalForce や LegalForce キャビネでは、Index 切り替え直後に、下記2つの処理を実行し、サービス無停止でインデックス再構築を行なっています。また2つのインデックス間でデータ不整合が発生する期間をできるだけ短くとどめることで、メンテナンスの影響を軽減しています。

  1. 1回目の Reindex 中に Source Index へ追加、更新された Document のみを対象に、Destination Index に向けて Reindex を実行する
  2. 1回目の Reindex 中に削除された Source Index 内の Document と同等の Document を Destination Index から削除する


これらは、Destination Index へ Alias の張り替えを実施した直後に行います。2点とも処理にかかる時間は1回目の Reindex の処理時間よりも短く済みます。例えば、LegalForce キャビネでは、不整合が発生する時間を数分以内に抑えられています。


f:id:WinField95:20211221173923p:plain

工夫の具体的な実現方法

1. 1回目の Reindex 中に Source Index へ追加、更新された Document のみを対象に、Destination Index に向けて Reindex を実行する

  • Document に created_at (作成日時)と、updated_at (更新日時) を持たせる
  • Reindex 直後に、Reindex 開始 〜 Reindex 完了の期間中に 作成された Document と、更新された Document に対象を絞ったうえで、再度 Reindex を実施する


Reindex API では、下記のように Reindex 対象をクエリで絞り込むことができます。これにより、1回目の Reindex 期間中に Source Index に発生した作成操作、更新操作のみに絞って、Destination Index に反映することができます。

{
  "source": {
    "index": SRC_INDEX,
    "query": {
      "bool": {
        "should": [
          {
            "range": {
              "created_at": {
                "gte": TIMESTAMP # Reindex開始時のタイムスタンプ
              }
            }
          },
          {
            "range": {
              "updated_at": {
                "gte": TIMESTAMP  # Reindex開始時のタイムスタンプ
              }
            }
          }
        ]
      }
    }
  },
  "dest": {
    "index": DEST_INDEX
  }
}

2. 1回目の Reindex 中に削除された Source Index 内の Document と同等の Document を Destination Index から削除する

現在の運用で実施されているプラクティスとしては、Reindex 期間中に発生した Document の Delete ログをもとに、Reindex 中に Source Index から削除された Document を、Destination Index からも削除します。現在は、GCP の Cloud Logging から、Delete ログを把握し、該当する Document を削除する手順を半自動化しています。今後は、契約書に関する Document の Delete ログを管理する Elasticsearch の Index を用意し、その Index を参照することで特定期間における Document の削除操作を把握する予定です。Elasticsearch 自身に Delete ログを持たせておけば、Cloud Logging にアクセスすることなく、Source Index で発生した削除操作を Destination Index に簡易に反映できます。

不整合を解消する工夫の別案について

今回紹介した工夫の他に別案もありました。Source Index と Destination Index を用意し、Source Index と Destination Index の両方に更新処理を反映しつつ、Source Index から Destination Index への Reindex を実施する案です。この案は、2つの独立した Index を正しく同期し続けるためのアトミック性を担保する点が難しいところです。僅かな時間の間、不整合が発生する期間が生じるものの、2つの Index が存在する期間に、予期しないエラーが発生した際のリカバリ容易性と、実装コストを鑑み、この記事で紹介した方法が適していると判断しました。しかし、全ての場合において、今回紹介した方法が優れているというわけではありません。サービスが持つ性質と、許容できる開発コストなどから、適切な手段を選択することをお勧めします。

Throttling を用いた Reindex 時の CPU 負荷の調整

Reindex 処理は、Elasticsearch に負荷がかかります。負荷のかかり具合によっては、検索結果のレスポンスタイムが長くなる、場合によってはタイムアウトするなど、検索処理に影響が発生する可能性があります。サービスを停止せずにReindexを行った結果、このような事象が発生してしまうことは良い状態とは言えません。

Elasticsearch の Reindex APIでは、requests_per_second の値で、単位時間あたりのリクエスト数を調整できるため、サービス運用に適した値をあらかじめ調べておきましょう。逆に、CPU 使用率を最大限に使用しても構わない状況であれば、requests_per_second = -1 で、Throttling をオフにすることができます。

下記の図のように、requests_per_secondの値が大きいほど、CPU 使用率が高くなり、短期間で処理が終わるような関係性です。Reindex の実行を許容できる期間、Reindex にかかる CPU 使用率の値を気にかけつつ、適切な値を設定しましょう。


requests_per_second の値と CPU 使用率の関係の計測

  • Document 数
    • 10000件 (index store size : 5.8MB)
  • 実行マシン
    • MacBook Pro (15-inch, 2018)
    • プロセッサ 2.6GHz 6コア Intel Core7
    • メモリ 16GB 2400 MHz DDR4
  • CPU 使用率計測間隔
    • 約2秒ごとに計測


f:id:WinField95:20211221174053p:plain

まとめ

Elasticsearch に対する Reindex 実行中に更新処理が実行された場合、データの不整合が生じる可能性があります。また、Reindex は、CPU 負荷が発生する処理であり、サービス運用において支障が生じない程度に CPU 負荷を抑える必要があります。この記事では、これら課題に対するプラクティスをご紹介しました。様々なサービスで生じうる課題かつ、汎用的に適用可能なテクニックとなるため、参考になれば幸いです。


LegalForce メンバー募集中

LegalForce では検索技術が様々な場所で活躍しています。今回の記事は運用のお話でしたが、検索支援コンポーネント、ランキングアルゴリズム開発や精度評価システムなどの開発を今後の視野に入れています。面白いフェーズだと思いますので、興味があれば下記の募集要項をご覧になってください。


【正社員】ソフトウェアエンジニア(検索)【LegalForce Research】


検索エンジニア以外の職種も募集していますので、よろしくお願いします!


【正社員】ソフトウェアエンジニア 【LegalForce Research】
【正社員】バックエンドエンジニア(リーダー候補)
【正社員】インフラエンジニア
【正社員】フロントエンドエンジニア
【正社員】フロントエンドエンジニア(リーダー候補)


参考
Elasticsearch実践ガイド
Elasticsearch Guide
Changing Mapping with Zero Downtime