シンクロ・フード エンジニアブログ

飲食店ドットコムを運営する、株式会社シンクロ・フードの技術ブログです

OpenSearch の導入による検索システム改善のための認証・認可設計と負荷検証結果

SRE チームの @fohte です。普段は音楽ゲームを嗜んでいます。

シンクロ・フードでは、弊社が提供する求人飲食店ドットコムの求人検索にて、MySQL から OpenSearch を使った全文検索に切り替えました。
本記事では、その OpenSearch を導入する上で障壁となったことや、それらの解決策について紹介します。

なお、本記事では、インフラ面で課題となった以下のテーマについて扱います。

  • 認証・認可の設計
  • 負荷検証によるスペックや設定の検討

アプリケーション面での MySQL と OpenSearch のデータ同期や検索の工夫については、以下の記事をご覧ください。

tech.synchro-food.co.jp

OpenSearch とは

OpenSearch は、オープンソースの全文検索エンジンで、Elasticsearch からフォークしたものです。
MySQL をはじめとする RDB に比べると、大量のデータを高速かつ柔軟に検索できることが期待できます。

弊社のインフラ環境では AWS をメインで利用しており、OpenSearch のインフラとして Amazon OpenSearch Service を採用しました。

Amazon OpenSearch Service は AWS が提供するフルマネージドサービスです。
冗長化やリソースの最適化といったスケーリング、バージョン更新などによるデプロイの自動化ができ、運用コストを大幅に削減されることが期待できます。

OpenSearch 導入の背景

OpenSearch の導入以前の求人検索では、MySQL で検索条件を SQL に盛り込んで検索していました。
しかし、求人の掲載数が増えるにつれて、レスポンスタイムの増加や MySQL の負荷の増大が目立つようになりました。
そこで、大量のデータを高速かつ柔軟に検索できる全文検索エンジンを導入することとなり、OpenSearch を採用しました。

認証・認可の設計

シンクロ・フードの SRE チームでは、最小権限の原則に従って権限制御をするというポリシーを策定しています。
それに伴い、OpenSearch でも、以下のように権限を最小化することを設計のゴールとしました。

  • アプリケーションからは特定の index のみの read/write 権限を与える
  • アプリケーション開発者には、テスト環境でのみ権限を与え、本番環境では何も権限を与えない
  • インフラ運用者にはフル権限 (admin 権限) を与える

Amazon OpenSearch Service での認証・認可方式

Amazon OpenSearch Service のデフォルトでは、IAM によるドメインアクセスポリシーで制御します。

また、FGAC (Fine-grained access control、きめ細やかなアクセスコントロール) と呼ばれる設定 1 が存在します。
この設定を有効化すると、OpenSearch 自体に (IAM とは異なる) ロールを作成し、OpenSearch 側で認可できます。

FGAC を有効化しない場合、OpenSearch 側でロールを作成できず、ドメインアクセスポリシーだけで制御する必要があります。

しかし、ドメインアクセスポリシーは以下の制約があり、うまく権限制御が行えませんでした。

  • 後述するダッシュボードへのログインの許可・拒否は制御できるが、ログイン後の操作の権限制御ができない
  • OpenSearch を VPC 内に入れる時、ドメインポリシーで IP ベースでの制御ができない

これらを実現できる手段が FGAC であったため、今回は FGAC を有効化しています。

Cognito を利用してダッシュボードにログインする

Amazon OpenSearch Service では、Kibana からフォークされたダッシュボード (OpenSearch-Dashboards) が利用できます。

ダッシュボードでは、ブラウザー上で任意の API リクエストを発行できたり、クラスターや index の情報を閲覧・編集できます。

そのため、インフラ運用者にのみ編集権限を与え、アプリケーション開発者には閲覧権限のみを与える等の制御が必要です。

上述の通り、FGAC を有効化すると OpenSearch にロールを作成して認可ができます。
社内のユーザーがブラウザーからアクセスした際、そのロールにマッピングするため、Cognito を利用しています。

具体的な認証・認可のフローは以下の図の通りです。

flowchart TD

server_admins[インフラ運用者]
non_server_admins[アプリ開発者]
server_admins & non_server_admins -- "ダッシュボードへのアクセス" --> cognito_user_pool


subgraph AWS
  cognito_admin["Cognito インフラ運用者用ロール (IAM)"]
  cognito_default["Cognito アプリ開発者用ロール (IAM)"]

  subgraph opensearch-domain["OpenSearch ドメイン"]
    opensearch[(OpenSearch)]

    admin_opensearch_role["管理者ロール"]
  end

  subgraph Cognito
    cognito_user_pool[Cognito User Pool]
    cognito_id_pool[Cognito Identity Pool]

    cognito_id_pool --> is_admin{{インフラ運用者か?}} -- Yes --> cognito_admin
    is_admin -- No --> cognito_default
  end

   cognito_admin --> admin_opensearch_role -- "フル権限" --> opensearch
end

cognito_user_pool --> cognito_id_pool

cognito_default --> reject(("拒否"))

SigV4 署名を用いてアプリケーションから認証する

一方で、アプリケーションからの認証は、Cognito を介さずに SigV4 署名 を用いて直接 OpenSearch にアクセスしています。

弊社のアプリケーションは AWS の EC2 あるいは ECS で動かしており、IAM ロールでの認証が可能だったため、Cognito は介していません。

以下に示す図は、アプリケーションからの認証・認可のフローです。

flowchart TD

subgraph AWS
  subgraph ecs_task[ECS タスク]
    app[アプリケーション]
    ecs_task_role["ECS タスクロール (IAM)"]
    app --- ecs_task_role
  end

  subgraph opensearch-domain["OpenSearch ドメイン"]
    opensearch[(OpenSearch)]
    app_opensearch_role["アプリケーション用ロール<br>(OpenSearch)"]
  end
  ecs_task_role --> app_opensearch_role -- "限定された権限" --> opensearch
end

このように、IAM ロールを OpenSearch のロールにマッピングしています。
そのロールで、特定のリソースのみアプリケーションからアクセスできるように制御しています。

IAM 認証では、具体的には OpenSearch への API リクエスト時に SigV4 で署名しています。
今回は Ruby on Rails を利用しているアプリケーションのため、faraday_middleware-aws-sigv4 gem を使い、以下のように実装しています。

client = OpenSearch::Client.new(url: ENV['OPENSEARCH_URL']) do |f|
  f.request(
    :aws_sigv4,
    service: 'es',
    region: 'ap-northeast-1',

    # ECS を利用しているため ECS のタスクロールで認証する
    credentials_provider: Aws::ECSCredentials.new,
  )
end

負荷検証によるスペックの検討

弊社では OpenSearch の利用が初めてだったため、必要なリソース量が肌感覚として不明でした。
そこで負荷検証を行い、スペックを検討することにしました。

負荷検証の方法

今回の負荷検証の目的とゴールとして、大きく以下の 2 つの観点がありました。

  • OpenSearch 側の負荷が高くなることによって、アプリケーションのレスポンスタイムが劣化しない
  • index 更新時の限界性能を把握し、アプリケーション側で更新間隔の調整等を行う

これをもとに、実際の運用を想定した並列かつ高頻度なリクエストや、限界性能を確かめるリクエストで検証しました。

なお前提として、今回の検索システムでは index に格納するドキュメントは以下の仕様となっています。

  • 40,000 件のドキュメント
  • ドキュメントには検索対象のデータを入れている
    • 1 ドキュメントあたりのデータ量は平均 10 kB

検証内容は以下の通りです。

  • 検索リクエストの投入 (最大秒間 100 リクエスト x 10 秒間)
  • _bulk API による更新リクエストの投入 (最大 250 ドキュメント x 10 並列)

以上の検証により、大きなものとして 2 つの課題が見つかりました。

メモリ不足による GC でレスポンスが遅延する

検証中、リクエスト量が増加すると、1 秒前後で返していたレスポンスが瞬間的に 30 秒前後まで劣化することがありました。
このとき OpenSearch のログ 2 を確認すると、以下のように GC が走ったログがいくつか出力されていました。

[2022-12-07T09:15:13,391][WARN ][o.o.m.j.JvmGcMonitorService] [c69ee8a1e7009c34e215e585444667c8] [gc][young][1320808][1808] duration [3.2s], collections [1]__PATH__[4.1s], total [3.2s]__PATH__[12.4m], memory [952.3mb]->[504.8mb]__PATH__[2gb], all_pools {[young] [464mb]->[1mb]__PATH__[0b]}{[old] [485.3mb]->[499.8mb]__PATH__[2gb]}{[survivor] [3mb]->[4mb]__PATH__[0b]}
[2022-12-07T09:15:14,200][WARN ][o.o.m.j.JvmGcMonitorService] [c69ee8a1e7009c34e215e585444667c8] [gc][1320808] overhead, spent [3.2s] collecting in the last [4.1s]

これのログとリクエスト時刻のタイミングを照らし合わせると、この GC が起因してレスポンスタイムが遅延していると推測できました。
つまり、メモリ不足が根本的な要因と考えられるため、インスタンスタイプを変更し、メモリ量を増やしました。

再検証してみると GC の発生頻度が減り、この問題は解消できました。

余談: Auto-Tune による JVM ヒープサイズの変更の検討

今回はメモリの増強という手段を取りましたが、Amazon OpenSearch Service では Auto-Tune という機能があります。
これは、JVM ヒープサイズの変更をはじめとする、各種リソースに関する設定を自動的に変更する機能です。

docs.aws.amazon.com

例えば、Amazon OpenSearch Service での JVM に割り当てられているヒープサイズは、デフォルトでインスタンスのメモリの 50 % です。
Auto-Tune によりこのヒープサイズは変更できるため、GC の発生頻度を減らすことが期待できます。

ただし、ドキュメントに記載されている通り、JVM ヒープサイズの変更は無停止ではできません。
メンテナンスウィンドウとして設定した時間帯に Blue/Green デプロイメントされ、設定が変更されます。
つまり、負荷に応じた動的なヒープサイズ変更は期待できませんでした。

このことから、今回は Auto-Tune を使わず、単にメモリを増やす方針で GC の問題を解決することにしました。

refresh_interval は短くしないほうが良い

refresh_interval は、ドキュメントの追加や更新を反映するために、OpenSearch が自動的に refresh する間隔の設定です。

今回直面した課題では、サービスの要件として「極力早く反映してほしい」というものがありました。
OpenSearch のデフォルトでは refresh_interval は 1 秒となっており、今回も 1 秒で設定していたため、その要件自体は満たせます。
しかし、AWS のナレッジセンターでも、パフォーマンス向上のためには refresh_interval を 60 秒以上にすることを解決の一案として挙げています。

aws.amazon.com

実際、refresh_interval を 1 秒 or 60 秒に設定し検証したところ、60 秒のほうが全体的にレスポンスタイムが速く、極端に遅くなることも少ないことが分かりました。

refresh_interval を変えたときのレスポンスタイムの違い

以上の検証やナレッジセンターを参照すると、refresh_interval は短いほど負荷が高いため、極力長くしたほうが良いといえます。

ただ、今回は以下の理由により、refresh_interval は 1 秒のままにしています。

  • 前述したようにスペックを上げ、大きなパフォーマンス劣化に繋がらなくなったため
  • 要件として検索の反映ラグは少ないほうがよいため

今回の学びとして、検索までの反映のラグの少なさとレスポンスタイムの遅延のトレードオフを理解し、refresh_interval を調整する必要があるという知見を得ました。

最後に

今回は、OpenSearch を導入する上で障壁となった認証・認可の設計や、負荷検証時に発覚した課題とその解決策について紹介しました。

今回の OpenSearch 導入での知見を活かし、弊社が提供する求人検索以外のサービスでの検索システムにも OpenSearch を導入する予定です。
検索がより快適になるよう、SRE チームとアプリケーション開発チームが協同して導入を進めていきます。


  1. 実体はおそらく OpenSearch の security plugin と思われます。
  2. Amazon OpenSearch Service ではエラーログやスローログなどが見られますが、そのうちのエラーログ (ES_APPLICATION_LOGS) に出力されていました。