Techouse Developers Blog

テックハウス開発者ブログ|マルチプロダクト型スタートアップ|エンジニアによる技術情報を発信|SaaS、求人プラットフォーム、DX推進

Sidekiq serverを分離してRailsの非同期処理の遅延を解消した話

ogp

はじめに

株式会社Techouseにて1年程度インターンで働いているmiyatisです。 本記事では、Sidekiq serverを分離してRailsの非同期処理の遅延を解消した話を紹介します。 特に、分離の方針とダウンタイムなしでの段階的な移行手順について詳しく説明します。

現状と課題

弊社では、ノンデスクワーカーに特化した求人サービスジョブハウスを運営しております。 ジョブハウスではサービスにご登録頂いた求職者の方に求人情報を直接お届けするために、メールマガジン (以降メルマガと呼称します) を配信しています。 メルマガの配信は、Sidekiqを用いてジョブハウスのRailsアプリケーション内で行われています。 しかし、配信数が非常に多く、そのため配信処理負荷が非常に高いため、実行に時間がかかっておりました。 そのため他のジョブの実行が大きく遅延し、最新のデータがサービスに反映されるまでの時間が大幅に増大し、データの鮮度を著しく損なっておりました。

アプローチ

前述の問題を解決するため、メルマガ配信ジョブとその他のジョブを分離し、互いに影響されることなく独立して実行できる仕組みの構成を考えました。 これによって、他のジョブはメルマガ配信の完了を待つことなく処理できるようになります。 具体的には、以下の2つのSidekiq serverへ分離することにしました。

  • メルマガを処理する専用のSidekiq server
  • それ以外の処理を行うSidekiq server

これは、以下の図のような構成になります。

Sidekiq server分離のイメージ

この構成を実現するためには、以下の2つが必要です。

  1. メルマガ専用のSidekiq serverとその他を処理するSidekiq server
  2. Railsサーバからジョブの種類 (メルマガ/その他) に応じて、それぞれのSidekiq serverに振り分ける仕組み

1. メルマガ専用のSidekiq serverとその他を処理するSidekiq server

弊社ではインフラの構築と保守運用にAWSを利用し、Terraformで管理しています。 メルマガ専用のSidekiqとその他を処理するSidekiqをそれぞれ ECS Serviceとして作成します。

# メルマガ専用SidekiqのECS Service
resource "aws_ecs_service" "ecs_sidekiq_low" {
  name    = "ecs-sidekiq-service-low"
  cluster = aws_ecs_cluster.ecs.id

  platform_version = "1.4.0"

  task_definition                    = aws_ecs_task_definition.ecs_sidekiq_low.arn
  deployment_minimum_healthy_percent = 50
  deployment_maximum_percent         = 400
  enable_execute_command             = true
  propagate_tags                     = "SERVICE"

  capacity_provider_strategy {
    capacity_provider = "FARGATE"
    weight            = var.sidekiq_ecs_service_fargate_weight
    base              = var.sidekiq_ecs_service_fargate_base
  }
  ...
}

# それ以外を処理するSidekiqのECS Service
resource "aws_ecs_service" "ecs_sidekiq" {
  name    = "ecs-sidekiq-service"
  cluster = aws_ecs_cluster.ecs.id

  platform_version = "1.4.0"

  task_definition                    = aws_ecs_task_definition.ecs_sidekiq.arn
  deployment_minimum_healthy_percent = 50
  deployment_maximum_percent         = 400
  enable_execute_command             = true
  propagate_tags                     = "SERVICE"

  capacity_provider_strategy {
    capacity_provider = "FARGATE"
    weight            = var.sidekiq_ecs_service_fargate_weight
    base              = var.sidekiq_ecs_service_fargate_base
  }
  ...
}

2. Railsサーバからジョブの種類 (メルマガ/その他) に応じて、それぞれのSidekiq serverに振り分ける仕組み

ジョブの振り分けはSidekiqのnamed queueという仕組みを利用して実現します。 まずSidekiqの基本的な仕組みとして、clientがジョブをRedisにエンキューし、serverがそれをポーリングして実行します。 named queueを利用することで、clientがジョブを種類ごとの異なるキューへエンキューできます。 また、serverごとで異なるキューのポーリングが可能になります。 今回はnamed queueを利用し、以下の図のような構成で振り分けを実現します。

Sidekiqのnamed queueの仕組み

キューの設定はconfig/sidekiq.ymlで行います。

---
:queues:
  - default
  - low

引用元: Sidekiq

ここでは、default, lowの3つのキューを用意しました。

そして、以下のようにメルマガのジョブはlowキューへエンキューするよう設定します。

class MailMagazineWorker
    include Sidekiq::Job
    sidekiq_options queue: :low

あとはメルマガ専用のSidekiq serverでlowキューのみをポーリングし、別のSidekiq serverではdefaultキューのみをポーリングすれば、分離した処理機構が完成します。

各Sidekiqプロセスの起動コマンドにおいて、ポーリングするキューを指定することで実現できます。

sidekiq -q default # defaultを処理するSidekiqプロセス

sidekiq -q low # lowを処理するSidekiqプロセス

引用元: Sidekiq

ECS Serviceのタスク起動コマンドにて、メルマガ専用のSidekiq serverでは-q lowを指定し、その他を処理するSidekiq serverでは-q defaultを指定します。 これで、メルマガ専用のSidekiq serverとその他の処理を行うSidekiq serverを分離できました。

上記で論理的な設計は完了しました。次に、実際に移行する計画を立てていきます。

移行計画

移行は綿密に計画を立てて段階的に実施しました。

移行手順

移行は3つの段階に分けて実施します。 大まかな方針としては、未使用のキューを作成して、最後にメルマガのジョブをそのキューに振り向けることです。

Step1. 既存のSidekiqのECS Serviceが処理するキューを制限する

まず、既存のSidekiqのECS Serviceが処理するキューを既存のキュー (default) に制限します。 前述のようにタスクの起動コマンドでdefaultを指定してあげればOKです。 この段階では、本番環境に特に影響はありません。

Step2. 空のキューを処理するSidekiq ECSサービスを作成する

次に、新しいキュー (low) を作成し、どのジョブからも使用されていない空のキューを処理するSidekiqのECS Serviceを作成します。 terraform applyを実行すれば完了です。 この段階でも、もちろん本番環境に特に影響はありません。

そして、テスト用のジョブをlowキューに向けて実行し、それが正常に処理されることを確認します。 これにより、本番影響なく新しいSidekiqのECS Serviceが正しく動作することを検証できます。

Step3. メルマガのジョブを空のキューに向ける

最後に、メルマガのジョブをlowキューに向けます。 これにより、メルマガの配信処理が新しいSidekiq のECS Serviceで実行されるようになり、サーバ分離が実現できました。

本番影響が出るのはステップ3だけであり、キューの向き先を変えるだけなのでダウンタイムは特に発生しません。 設定ミスがあった場合も、Step3のコミットを切り戻すことで、元の状態に戻すことができます。 Terraform側の変更がないため、切り戻しも容易です。

このように、綿密な計画を立てて段階的に移行を実施することで、リスクを最小限に抑えることができました。

終わりに

今回はメルマガ配信のSidekiq server分離の方針と段階的な移行手順について紹介しました。 私はインフラコードに触るのは初めてだったので、Terraformの勉強は大変でしたが、非常に学びの多い経験でした。 インフラの修正作業はTerraformとAWSのドキュメントを読んで、1つ1つの設定項目が正しいのか把握しながら進めました。 網羅性が求められるため、非常に根気のいる作業でしたが、無事に完了できてよかったです。 また、いかにリスクを押さえて移行できるかを考えるのは難しかったです。 図を書いて状態と手順を明確に整理することが大切だと感じました。 同じような課題に直面している方の参考になれば幸いです。

ここまでお読みいただきありがとうございました。


Techouseでは、社会課題の解決に一緒に取り組むエンジニアを募集しております。 ご応募お待ちしております。

jp.techouse.com