OpenWork Tech Blog

社員クチコミサービスを運営しているオープンワークエンジニアによるテックブログです。

Elastic Beanstalkで動かしていたElasticsearch同期処理の課題をECS+CDK+IssueOpsで解決した話

インフラチームの西川です。
オープンワークでは検索等の処理速度改善のためElasticsearchを利用しています。
検索用のデータをElasticsearchに同期(投入)しているのですが課題を抱えており、今回はそちらを解決するために行ったことをまとめてみました。

構成

Before

1つのEC2上でfluentdとlogstashの同期プロセスをインデックスの数だけ起動。
また、開発/ステージング環境はリソース節約のためcronにより深夜帯に停止・早朝起動を実行。

After

インデックスごとにECS(Fargate)を起動。
また、定期実行処理はEventBridge+Lambdaで実行。

Elasticsearch同期処理の課題

概要 詳細 解決方法
同期速度が遅い 特に全同期が遅く、Elasticsearchのインデックス変更があるとリリースに数日かかることもある。 プラットフォームをECSに変更
リリース時に同期ずれが起きる 全同期が完了する間、旧インデックスへの更新が止まるため同期ずれが起きてしまう。 Blue/Greenデプロイメントに変更
リリース手順が煩雑 手動でリリースしているが、手順自体が複雑になっており辛い。 手順を自動化&CDK+IssueOpsの利用
同期クエリの確認が面倒 開発環境で確認しているが、いちいちElastic Beanstalkのデプロイが必要になってしまう。 ローカルでDockerを使う

同期速度が遅い

クエリを分割して並列実行させることを考えていましたが、fluentd/logstashのプラグインに独自実装をかなり詰め込まないといけなかったので断念しました。
また、AWS Glueへの載せ替えも検討しましたが、知見が無さすぎた&同期クエリに手を入れたくなかったのでこちらも断念しました。
とりあえず現状のままECSで動かしてみたら10倍ほど速くなったのでシンプルにECSに載せ替えるだけにしました。(ただし、同期クエリの実行に時間がかかるものはあまり改善がみられませんでした)
おそらく、ECSのエフェメラルストレージの恩恵を得られていると想定してます。(fluentdのバッファタイプをmemoryに変更したところ速度がほぼ変わらなかったのでファイルI/Oが相当速いはず)

リリース時に同期ずれが起きる

Elasticsearchのエイリアスを使って検索および同期先を切り替えていたため、この問題が起きてました。
同期用のエイリアスをなくし、新旧インデックスに同時に同期を行うようにしました。

リリース手順が煩雑

ローカル/作業用サーバー/同期サーバーの3つで手順を行う必要があり、かつ、gitリポジトリが2つあり、それらを切り替えながら作業をする必要があったため、かなり複雑な手順になっていました。
gitリポジトリを一つにし、CDK+IssueOpsを使うことでローカル/同期サーバーでの作業を無くしました。
また、インデックス定義手順をコンテナ化し、同期コンテナ起動前に実行するようにしました。

同期クエリの確認が面倒

ECSに載せ替えるにあたり同期処理のDocker化をしていたので、そのままDockerコンテナをローカルで実行すれば確認ができるようになりました。
ローカルPCのスペックだと全インデックスの同期はできないのでローカルではあくまで修正対象インデックスの同期クエリ確認までとしています。

開発よもやま話

今回、CDKとIssueOpsを初めて使ったため、簡単にメモします。

CDK

メリット

  • プログラミング言語の選択肢が多い
  • インフラのテストコードを簡単に作れる
  • スナップショットテストは簡単にコードを書ける上に「変わってないこと」の補償が簡単にできるのでリファクタリングしやすい
  • 環境によるリソース差異のテストも書けるのでデプロイ前に意図したものになってるかわかる
  • 自動で作ってくれるリソースがあるのでコーディング量が減る
  • CDK+Lambdaはかなり簡単にかける

    デメリット

  • プログラミング言語の選択肢は多いが、やり方を調べた時に出てくるのはほぼTypeScriptなので、最初にCDKを扱うとしたらTypeScript一択になる
  • リソースの数が増えると割と早い段階でカオスになる。(ロググループ+メトリクスフィルター+アラームを追加するだけで大分ごちゃつく)

はまったこと

  • ECSの設定をミスるとデプロイが終わらない。 ⇨circuitBreaker: { rollback: true } で回避可能。
  • 削除されないリソースがあるので再作成の時にいちいち手動で消さないといけない。⇨cdk.RemovalPolicy.DESTROYを指定することで回避可能。
  • スナップショットテストのECRのタグが毎回変わって失敗してしまう。⇨こちらの記事を参考にプラグインを作成。
module.exports = {
  test: (val: unknown) => typeof val === "string",
  serialize: (val: string) => {
    return `"${val.replace(
      /cdk-[0-9a-z]{9}-container-assets-[0-9]{12}-[a-z]{2}-[a-z]*-[0-9]{1}:[0-9a-f]{64}$/,
      "IMAGE-REPLACED:TAG-REPLACED"
    )}"`;
  },
};

IssueOps(branch-deploy)

メリット

  • 導入は楽。
  • プルリクエスト上でデプロイできるのは想像以上に良い。
  • プルリクエストテンプレートにリリース手順を書けるのであっちこっちドキュメントを移動しなくて良い。

    デメリット

  • 誤ってオペレーション用のコメントを打たないように注意が必要。

    はまったこと

  • 一度オペレーション用のコメントを書いた後に、コミットを追加するとオペレーションできなくなる。(仕様?)⇨別ブランチを切ってプルリクエストを作れば再度オペレーション可能になる。

最後に

今回CDKとIssueOpsに挑戦してみましたが、オープンワークは新しい技術の導入障壁は低いので色々と楽しくやらせていただいてます。 ご興味あれば是非! www.openwork.co.jp