ECSオートスケール × Sidekiqで発見した“スケールインの落とし穴”

導入

こんにちは、株式会社Spacelyでバックエンドエンジニアをしているtoshichanappです。 普段はRailsアプリケーションの運用・保守を担当しています。 今回の記事では、ECSオートスケールとSidekiqの組み合わせで発生した問題と、その解決プロセスについて共有します。

ある時期から、データ不整合に関する問い合わせが増加しました。 ログを確認すると Sidekiq::Shutdown が頻繁に記録されており、 「ジョブがシャットダウン中に中断されている」という状態が確認できました。

これにより、Sidekiq が スケールインのタイミングで強制終了されている可能性 に気付き、 ECSのスケーリングの挙動を調査した結果、今回の課題が明らかになりました。


前提条件

Spacely では Rails アプリケーションを ECS on Fargate 上で運用しています。 オートスケールルールは次の 2 つを併用しています。

  • 時間ベースのスケジュールスケール
  • Sidekiq の待機ジョブ数によるスケールアウト/スケールイン

原因

ログに残っていた Sidekiq::Shutdown を手がかりに同時刻のログを調査したところ、 ある機能で瞬間的に 非常に短いジョブが大量にエンキューされる処理 が実行されており、 これらの短時間ジョブが一気に処理されることで以下のサイクルが発生していることがわかりました。

  • pending job が急増 → スケールアウト
  • すぐに pending job が 0 へ → スケールイン

このサイクルが短時間で繰り返された結果、頻繁なスケールインが発生する状態 となり、

「頻繁なスケールイン」 × 「長時間ジョブ(110秒以上)」 × 「非冪等処理」

という3つの要因が組み合わさることで、スケールインのタイミングで 実行中の長時間ジョブが中断されデータ不整合が発生する 問題が起きていました。

補足

ECS stopTimeout と Sidekiq -t オプションの関係

Spacelyの設定は以下でした。

  • ECS タスク定義の stopTimeout120秒
  • Sidekiq 起動時の -t 110shutdown で待機する最大時間(秒)

ECS の stopTimeout は、タスク停止時に SIGTERM 送信から強制終了(SIGKILL)までの猶予時間 を指定するパラメータで、
最大値は 120 秒 と公式ドキュメントに定義されています。

一方で Sidekiq の -t は graceful shutdown の待機時間を指定しており、 SIGTERM を受けたあと 指定秒数(110秒)だけ実行中ジョブの終了を待つ動作をします。

-t, --timeout NUM                Shutdown timeout

デフォルトは25秒です https://github.com/sidekiq/sidekiq/blob/c8753b9fc8e51e07c65f0bb19e637007c255d540/lib/sidekiq/config.rb#L16

🔸 不整合が発生する流れ

  1. 短時間ジョブ大量発生 → pending job が 0 → スケールイン
  2. Sidekiq が SIGTERM を受信し shutdown 開始
  3. 長時間ジョブは 110 秒を超えても完了しない
  4. 120 秒で ECS が強制終了
  5. 中断されたジョブは Sidekiq により 再キューイング
  6. 非冪等ジョブのため 二重処理や部分更新が発生

暫定対応

🧯 “短時間ジョブ専用” の ECS クラスターの新設

短時間ジョブを別 ECS に切り出すことで、

  • 長時間ジョブが巻き込まれることが減少
  • Sidekiq::Shutdown の発生が大幅に減少

といった改善が得られました。 一方でECSの新設によるコスト増が発生しましたが、夜間の待機数を減らすことでトントンになりました。


恒久対応予定

長時間ジョブに対する恒久的な解決策として、以下の3つの方向で検討を進めています。

🔧 SidekiqBatch によるジョブ分割と冪等設計(現在対応中)

根本的な解決として、以下の取り組みを進めています:

  • 長時間実行ジョブを 2 分以内に終わる粒度に分割
  • 各ジョブが冪等性を持つ設計に変更

この課題をきっかけに Sidekiq Pro ライセンスの導入 を打診したところ、 技術的課題解決への投資として即座に承認いただけました。

🏃‍♂️ ECS RunTask による長時間ジョブの切り出し

長時間ジョブをSidekiqから分離し、ECS RunTaskで実行する方法。

メリット

  • 実装がシンプル
  • スケールインの影響を受けない
  • インフラ管轄での対応が可能

デメリット

  • ジョブ管理が分散される
  • 実行状況の監視が複雑になる

🛡️ TaskProtection による実行中タスクの保護

ECSのTaskProtection機能で、長時間ジョブ実行中はタスクをスケールイン対象から除外する方法。

メリット

デメリット

  • アプリケーション側での実装が必要(Rails側でAWS SDK使用)
  • 保護の解除忘れによるスケールイン阻害リスク
  • 運用コストが高い

現在は ジョブ分割と冪等設計 を優先して進めており、最も安全で持続可能なソリューションを目指しています。


🚀 エンジニア募集中

今回のような ECS・Sidekiq・Rails の改善や、非同期処理基盤の強化に興味がある方を募集しています。 一緒により良いシステムを作りましょう! 詳細は採用ページまで!