AWS ECS と SQS を活用したスケーリングと処理最適化の完全ガイド


## **1. はじめに**

### **1.1 背景**
クラウドアーキテクチャにおいて、スケーラブルなアプリケーションを構築することは重要な課題です。特に、マイクロサービス環境では、柔軟にリソースを増減できる仕組みが求められます。AWS の Elastic Container Service (ECS) と Simple Queue Service (SQS) を組み合わせることで、負荷に応じたスケール調整が可能になります。

本記事では、ECS とは何か、SQS とは何かを詳しく解説し、それらを組み合わせたスケーラブルなアーキテクチャの構築方法を詳細に説明します。また、ECS タスク内の並列処理を Java を用いて実装する方法についても深く掘り下げます。

### **1.2 ECS と SQS を組み合わせる理由**
ECS はコンテナのスケーリングと管理を担い、SQS は非同期メッセージングを提供します。この 2 つを組み合わせることで、リアルタイム処理を求められるマイクロサービスアーキテクチャや、バッチ処理を効率化するワークフローシステムなどで特に有効に機能します。例えば、EC サイトにおける注文処理では、SQS を使って決済や在庫管理のリクエストをキューイングし、ECS のスケールアウト機能を活用して、ピーク時の負荷に適応できます。同様に、データ分析基盤では、大量のログやセンサーデータを SQS で処理キューに格納し、ECS を使って並列処理することで、処理の遅延を最小限に抑えることができます。この 2 つを組み合わせることで、以下のような利点があります。

- **負荷分散の最適化**: SQS を使ってリクエストをキューイングし、ECS タスクが処理可能な範囲で順次処理。
- **高可用性の確保**: ECS のスケールアウト/イン機能と SQS の耐障害性を活かし、システム全体の安定性を向上。
- **コスト最適化**: 必要な分だけリソースを使用し、不要になればスケールイン。

---

## **2. ECS とは？**

### **2.1 ECS の概要**
AWS Elastic Container Service (ECS) は、Docker コンテナを簡単にデプロイ・管理できるフルマネージドなコンテナオーケストレーションサービスです。

### **2.2 ECS の特徴**
- **マネージドサービス**: Kubernetes のようなクラスタ管理が不要。
- **Fargate 対応**: インフラ管理なしでコンテナを実行可能。
- **自動スケーリング**: 負荷に応じてコンテナ数を増減可能。
- **IAM 権限管理**: コンテナごとに異なる IAM ポリシーを適用可能。

### **2.3 ECS のコンポーネント**
- **クラスター**: コンテナを実行する環境。
- **タスク定義**: コンテナの設定（CPU、メモリ、環境変数など）。
- **サービス**: タスクの管理（スケーリング、ヘルスチェック）。
- **タスク**: 実行中のコンテナ。
- **ロードバランサー**: コンテナの負荷分散を管理。
- **オートスケール**: 負荷に応じて ECS タスクを自動で増減。

---

## **3. SQS とは？**

### **3.1 SQS の概要**
AWS Simple Queue Service (SQS) は、メッセージを非同期で送受信できるフルマネージドなメッセージキューサービスです。ECS などの処理システムと組み合わせることで、システムの耐障害性を向上させることができます。

### **3.2 SQS の特徴**
- **非同期処理**: 処理をキューに蓄積し、適切なタイミングで処理可能。
- **自動スケーリング**: メッセージ量に応じたスケーリングが可能。
- **高可用性**: AWS によってフルマネージドされているため、障害耐性が高い。
- **高スループット**: 大量のメッセージ処理に最適。

### **3.3 SQS の種類**
- **標準キュー**: 高スループット、メッセージの順序保証なし。
- **FIFO キュー**: メッセージの順序を保証、重複処理を防ぐ。
- **デッドレターキュー (DLQ)**: 処理失敗メッセージを蓄積し、手動で再処理可能。

---

## **4. ECS タスク内の並列処理 (Java)**
ECS タスク内の処理を効率化するために、Java の `FixedThreadPool` を利用して並列処理を行う方法を紹介します。

### **4.1 Java による並列処理の実装**
```java
private final ExecutorService service = Executors.newFixedThreadPool(3);
```

### **4.2 並列タスクの処理**
ECS タスク内でメッセージを並列に処理するために、`FixedThreadPool` を使用して複数のスレッドを作成します。以下のコードは、SQS からメッセージを取得し、並列で処理する実装例です。

```java
import java.util.concurrent.*;
import java.util.List;
import software.amazon.awssdk.services.sqs.SqsClient;
import software.amazon.awssdk.services.sqs.model.*;

public class SqsMessageProcessor {
    private final ExecutorService service = Executors.newFixedThreadPool(3);
    private final SqsClient sqsClient;
    private final String queueUrl;

    public SqsMessageProcessor(SqsClient sqsClient, String queueUrl) {
        this.sqsClient = sqsClient;
        this.queueUrl = queueUrl;
    }

    public void processMessages() {
        while (true) {
            List<Message> messages = receiveMessages();
            if (messages.isEmpty()) continue;

            for (Message message : messages) {
                service.submit(() -> handleMessage(message));
            }
        }
    }

    private List<Message> receiveMessages() {
        ReceiveMessageRequest request = ReceiveMessageRequest.builder()
                .queueUrl(queueUrl)
                .maxNumberOfMessages(10)
                .waitTimeSeconds(10)
                .build();
        
        return sqsClient.receiveMessage(request).messages();
    }

    private void handleMessage(Message message) {
        try {
            System.out.println("Processing message: " + message.body());
            Thread.sleep(5000); // 模擬的な処理
            deleteMessage(message);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }

    private void deleteMessage(Message message) {
        DeleteMessageRequest deleteRequest = DeleteMessageRequest.builder()
                .queueUrl(queueUrl)
                .receiptHandle(message.receiptHandle())
                .build();
        sqsClient.deleteMessage(deleteRequest);
    }

    public void shutdown() {
        service.shutdown();
        try {
            if (!service.awaitTermination(60, TimeUnit.SECONDS)) {
                service.shutdownNow();
            }
        } catch (InterruptedException e) {
            service.shutdownNow();
            Thread.currentThread().interrupt();
        }
    }
}
```

### **4.3 Graceful Shutdown の実装**
ECS のタスクが停止される際に、安全にスレッドを終了させるための `ShutdownHook` を追加します。

```java
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
    System.out.println("Shutting down gracefully...");
    service.shutdown();
    try {
        if (!service.awaitTermination(60, TimeUnit.SECONDS)) {
            service.shutdownNow();
        }
    } catch (InterruptedException e) {
        service.shutdownNow();
    }
}));
```

このコードにより、ECS のタスクが `SIGTERM` を受信した際に、並列処理中のタスクを適切に処理しつつ、安全にシャットダウンすることができます。

---

## **5. SQS のメッセージ処理最適化**
SQS の設定を最適化することで、処理の効率化とリソースの適切な使用を実現できます。特に、可視性タイムアウト（Visibility Timeout）や最大受信メッセージ数（MaxNumberOfMessages）の設定は、システムのパフォーマンスに大きく影響を与えます。

### **可視性タイムアウトの最適な設定**
可視性タイムアウトは、ECS タスクがメッセージを取得した後、他のコンシューマーが同じメッセージを取得できないようにする時間です。処理時間が短すぎると、まだ処理が完了していないメッセージが再取得され、重複処理が発生する可能性があります。

| メッセージ処理時間 | 推奨可視性タイムアウト |
|------------------|------------------|
| 5 秒未満 | 15 秒 |
| 10 秒 | 25 秒 |
| 30 秒 | 60 秒 |
| 60 秒以上 | 処理時間の 2 倍 |

適切な値を設定することで、メッセージの二重処理を防ぎつつ、ECS の処理負荷を最適化できます。

### **最大受信メッセージ数の設定**
`MaxNumberOfMessages` は、一度に取得するメッセージ数を指定します。ECS のスレッド数に応じて適切な値を設定すると、処理効率が向上します。

| ECS のスレッド数 | 推奨 `MaxNumberOfMessages` |
|----------------|-----------------|
| 1 | 1 |
| 2 | 2 ～ 5 |
| 3 | 3 ～ 7 |
| 4 | 4 ～ 8 |
| 5 以上 | 5 ～ 10 |

最大 10 件まで設定可能ですが、すべてのメッセージを処理し終える前にタイムアウトすると、未処理メッセージが残る可能性があるため注意が必要です。

### **メッセージ再試行とデッドレターキューの活用**
処理に失敗したメッセージが無限にリトライされないように、デッドレターキュー（DLQ）を設定すると、エラーメッセージを安全に管理できます。通常、`MaximumReceives` を 5 ～ 10 に設定し、特定回数以上失敗したメッセージを DLQ に移動させることで、システムの安定性を向上できます。

このように、SQS の設定を適切に調整することで、ECS のパフォーマンスを最大限に引き出し、安定したメッセージ処理システムを構築できます。

---

## **6. ECS のスケールイン・スケールアウトの仕組み**
ECS のスケーリングに関する詳細な説明と、AWS Auto Scaling の設定方法を解説します。

### **6.1 オートスケーリングの設定例**
ECS では **Application Auto Scaling** を使用して、タスク数を自動的に増減できます。オートスケーリングを設定するには、次のステップを実行します。

#### **ステップ 1: スケーラブルターゲットの作成**
```sh
aws application-autoscaling register-scalable-target \
    --service-namespace ecs \
    --scalable-dimension ecs:service:DesiredCount \
    --resource-id service/my-cluster/my-service \
    --min-capacity 1 \
    --max-capacity 10
```
このコマンドは、ECS サービス `my-service` のタスク数を **1～10 の範囲でスケール** 可能にします。

#### **ステップ 2: スケーリングポリシーの作成**
```sh
aws application-autoscaling put-scaling-policy \
    --service-namespace ecs \
    --scalable-dimension ecs:service:DesiredCount \
    --resource-id service/my-cluster/my-service \
    --policy-name ecs-scale-out-policy \
    --policy-type TargetTrackingScaling \
    --target-tracking-scaling-policy-configuration file://scale-out.json
```
ここで `scale-out.json` は、CPU 使用率や SQS のメトリクスを基準にスケールするための JSON 設定ファイルです。

#### **scale-out.json の例 (CPU 使用率 70% でスケールアウト)**
```json
{
  "TargetValue": 70.0,
  "PredefinedMetricSpecification": {
    "PredefinedMetricType": "ECSServiceAverageCPUUtilization"
  },
  "ScaleOutCooldown": 60,
  "ScaleInCooldown": 300
}
```

### **6.2 監視すべきメトリクス**
ECS のスケーリングには、以下のメトリクスを活用できます。

| メトリクス | 説明 | 推奨トリガー |
|------------|---------------------------------|----------------|
| `ECSServiceAverageCPUUtilization` | ECS タスクの平均 CPU 使用率 | 70% 以上でスケールアウト |
| `ECSServiceAverageMemoryUtilization` | ECS タスクの平均メモリ使用率 | 75% 以上でスケールアウト |
| `ApproximateNumberOfMessagesVisible` | SQS に残っている未処理メッセージ数 | 10 件以上でスケールアウト |

これらの設定を活用することで、負荷に応じた効率的なオートスケーリングを実現できます。



---

## **7. まとめ**
本記事では、AWS ECS と SQS を活用したスケーラブルなアーキテクチャの構築方法を解説しました。

✅ **ECS と SQS を組み合わせてスケールしやすいシステムを構築**
✅ **ECS の `stopTimeout` を適切に設定し、Graceful Shutdown を確保**
✅ **ECS 内で Java を用いた並列処理を実装し、スループットを向上**

これらのベストプラクティスを活用し、スケーラブルで高効率な AWS インフラを構築しましょう！ 🚀