はじめに
本投稿は TECOTEC Advent Calendar 2025 の2日目の記事です。
システム開発事業部の大泉です。普段はエンジニアとして Python でバックエンドのシステムの開発を行なっています。
この記事ではメッセージキューの増加・減少による ECS オートスケーリングを構築する方法を紹介します。設定手順のみ知りたい方は 設定手順 をご覧ください。
キューによるスケーリング
メッセージキューを使ったシステムではメッセージ量に応じて処理サーバ(コンシューマー)の数を動的に増減させるケースがよくあります。では実際にどのくらいメッセージが溜まったら、どれだけの処理サーバを用意すればよいのでしょうか。単純にキューサイズ(キューに溜まっているメッセージ数)に応じてサーバ数を決めればよいように思えますが、実はこれでは正しく測ることができません。現在のサーバがどの程度の処理性能を持っているかが考慮されていないためです。1サーバが1メッセージあたり何秒で処理できるか、アプリケーション上1メッセージを遅くても何秒で処理してほしいか(許容時間)という情報が必要です。なおここでは ECS を想定しているためサーバ数をタスク数と書きます。より一般的にはコンシューマーのノード数を意味します。
- 現在のキューサイズ
(メッセージ)
- 1タスクあたりの処理速度
(メッセージ/秒)
- 1メッセージを処理するのに許容される時間
(秒)
- タスク数
(タスク)
とすると、1タスクが許容時間内に処理できるメッセージ数は であり、1タスクに割り当てられたキューサイズは
です。これらが等しければ、1タスクが処理できるメッセージ数と実際に割り当てられたメッセージ数が釣り合っている状態です。つまり
を実現できれば過不足のないスケーリングとなります。
: 実バックログ(1タスクに割り当てられたキューサイズ)
: 適正バックログ(1タスクが処理できるメッセージ数)
ECS のオートスケーリングでは実バックログ の値を CloudWatch でリアルタイムに追跡し、あらかじめ決めておいた適正バックログ
と比較することでタスクを動的に増減させます。
の場合: 実バックログが適正バックログを上回っているため、スケールアウトが必要(
を増やす)
の場合: 処理能力が追いついている(または過剰な)ため、現状維持かスケールインが適切(
を減らす)
例1)処理が追いついている場合
- キューサイズ
(メッセージ)
- 1タスクあたりの処理速度
(メッセージ/秒)
- 許容時間
(秒)
- タスク数
(タスク)
この場合、 であるためスケールアウトは不要です。
例2)処理が追いつかずスケールアウトが必要な場合
- キューサイズ
(メッセージ)
- 1タスクあたりの平均処理速度
(メッセージ/秒)
- 許容時間
(秒)
- タスク数
(タスク)
この場合、 であるためタスク数が足りておらず
にスケールアウトする必要があります。
設定概要
ECS でオートスケーリングを適用するには Application Auto Scaling を使用してクラスターのサービスにオートスケーリングを設定します。ここではすでにサービスは作成済みとします。まずスケーラブルターゲットをサービスに登録し、次にスケーリングポリシーを作成します。ECS では次のスケーリングポリシーを利用可能です。
- ターゲット追跡スケーリング
- ターゲットメトリクス値に基づいてアプリケーションを自動的にスケール
- ステップスケーリング
- ステップ調整値にしたがって段階的にスケール
- 予測スケーリング
- 過去の負荷データを分析し、予想される負荷に合わせて事前にスケール
ここではターゲット追跡スケーリングを利用します。先ほどの適正バックログをターゲット値として設定します。
設定手順
- サービスにスケーラブルターゲットを登録
- スケーリングポリシーを作成
スケーリングポリシーを作成すると自動で CloudWatch アラームが作成され、オートスケーリングが始まります。
1. スケーラブルターゲットの登録
CLUSTER_NAME=autoscaling-demo-cluster SERVICE_NAME=autoscaling-demo-service aws application-autoscaling register-scalable-target \ --service-namespace ecs \ --scalable-dimension ecs:service:DesiredCount \ --resource-id service/$CLUSTER_NAME/$SERVICE_NAME \ --min-capacity 0 \ --max-capacity 10
2. スケーリングポリシーの作成
CLUSTER_NAME=autoscaling-demo-cluster SERVICE_NAME=autoscaling-demo-service POLICY_NAME=sqs-tracking-policy aws application-autoscaling put-scaling-policy \ --policy-name $POLICY_NAME \ --service-namespace ecs \ --resource-id service/$CLUSTER_NAME/$SERVICE_NAME \ --scalable-dimension ecs:service:DesiredCount \ --policy-type TargetTrackingScaling \ --target-tracking-scaling-policy-configuration file://config.json
この config.json ではカスタムメトリクス(上記説明では実バックログ )を定義しています。
TargetValue フィールドにターゲット値(上記説明では適正バックログ )を設定します。
NOTE スケールアウトの容量は自動で決定されます。
{ "CustomizedMetricSpecification": { "Metrics": [ { "Label": "キューサイズ(処理可能なメッセージ数)", "Id": "s", "MetricStat": { "Metric": { "MetricName": "ApproximateNumberOfMessagesVisible", "Namespace": "AWS/SQS", "Dimensions": [ { "Name": "QueueName", "Value": "autoscaling-demo-queue" } ] }, "Stat": "Sum" }, "ReturnData": false }, { "Label": "サービス内の実行中のタスク数(RUNNINGステータスのタスク数)", "Id": "n", "MetricStat": { "Metric": { "MetricName": "RunningTaskCount", "Namespace": "ECS/ContainerInsights", "Dimensions": [ { "Name": "ClusterName", "Value": "autoscaling-demo-cluster" }, { "Name": "ServiceName", "Value": "autoscaling-demo-service" } ] }, "Stat": "Average" }, "ReturnData": false }, { "Label": "タスクあたりの実バックログ", "Id": "e1", "Expression": "s / n", "ReturnData": true } ] }, "TargetValue": 2, "ScaleOutCooldown": 60, "ScaleInCooldown": 60 }
NOTE
config.json のId: n (タスク数)のメトリクスを取得するには Container Insights を有効化する必要があります。ここでは有効化手順を省略しますが、以下のドキュメントが参考になります。
リソース確認
オートスケーリングポリシー作成後、マネジメントコンソールからターゲット追跡スケーリングポリシーが設定されたことが確認できます。


また以下 2 つの CloudWatch アラームが自動で作成されます。
{サービス名}-AlarmHigh-*{サービス名}-AlarmLow-*
動作確認
以下は動作確認結果です。キューサイズ の増減に伴ってタスク数
が増減し、実バックログ
が以下のように緑の折れ線グラフで表現されています。赤線が
TargetValue (適正バックログ)です。
TargetValue (実バックログ 適正バックログ)の場合アラーム状態に遷移し、スケールアウトしたことが確認できました。

サンプルコード
以下は動作確認で使用したサンプルコードです。
FROM public.ecr.aws/docker/library/python:3.11-slim COPY main.py main.py CMD ["python", "-m", "main"]
import time import logging logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' ) logger = logging.getLogger(__name__) def main(): """数分コンテナを起動しておくための処理""" for i in range(36): logger.info(f"[{i+1}/36] Hello, World!") time.sleep(10) if __name__ == "__main__": main()
参考
テコテックの採用活動について
テコテックでは新卒採用、中途採用共に積極的に募集をしています。
採用サイトにて会社の雰囲気や福利厚生、募集内容をご確認いただけます。
ご興味を持っていただけましたら是非ご覧ください。
tecotec.co.jp