はじめに
ECS(Fargate)でコンテナを動かしていると、スケールアウト時に RDS の接続数が枯渇して障害になった、という経験はないでしょうか。
特に 大規模な業務システムでは、月末・年末など特定タイミングに処理が集中し、通常の数倍のタスクが同時起動します。このとき Aurora の max_connections を超えてアプリケーションがエラーを返すケースは珍しくありません。
本記事では、この問題の根本原因と、Amazon RDS Proxy による解決アプローチを、設定値の意味・フェイルオーバー挙動・IAM 認証の実装まで踏み込んで解説します。
1. なぜ ECS × Aurora で接続問題が起きるのか
1.1 Aurora の max_connections はメモリに比例する
Aurora MySQL・PostgreSQL の max_connections はインスタンスタイプのメモリに基づいて自動設定されます。
| インスタンス | メモリ | max_connections(概算) |
|---|---|---|
| db.t3.medium | 4 GiB | ~420 |
| db.r6g.large | 16 GiB | ~1,700 |
| db.r6g.xlarge | 32 GiB | ~3,400 |
| db.r6g.4xlarge | 128 GiB | ~13,500 |
ECS タスクが100個起動し、各タスクがコネクションプールで10本保持すると、それだけで1,000接続を消費します。月末処理でタスクが200個にスケールした瞬間、db.r6g.large の上限 1,700 をあっさり超えます。
1.2 コンテナの接続管理は難しい
VM と違い、ECS タスクは頻繁に起動・停止します。各起動ごとにコネクションプールを初期化し、停止時に解放します。しかし以下の問題が起きます:
- 接続確立コスト:SSL ハンドシェイクを含む DB 接続確立は数十〜百ミリ秒かかる
- TIME_WAIT 状態の残留:タスク停止後も TCP 接続が一定時間残り、DB 側のリソースを消費する
- フェイルオーバー時の再接続:Aurora がフェイルオーバーした際、全タスクが一斉に再接続を試みる(Thundering Herd)
2. RDS Proxy の仕組み
2.1 アーキテクチャの全体像
ECS タスク群(100〜200タスク)
│ 各タスクが localhost 感覚で接続
▼
RDS Proxy(マルチAZ冗長・VPC内)
│
├── Connection Multiplexing(接続多重化)
│ 複数クライアント接続 → 少数の DB 接続に集約
│
├── Connection Pinning(トランザクション中はピン留め)
│
└─▶ Aurora クラスター
├── Writer エンドポイント(書き込み)
└── Reader エンドポイント(読み取り)
RDS Proxy はクライアント(ECS タスク)との接続と、Aurora との接続を分離して管理します。200タスクから接続が来ても、Aurora に対して実際に張る接続数は設定した上限内に抑えられます。
2.2 Connection Multiplexing(多重化)の詳細
RDS Proxy の核心機能です。複数のクライアント接続を少数の DB 接続にマッピングします。
クライアント接続(ECS タスク)
conn_1 ──┐
conn_2 ──┤ DB接続(Aurora)
conn_3 ──┼──Proxy──▶ db_conn_A
conn_4 ──┤ db_conn_B
conn_5 ──┘ db_conn_C
多重化が有効になる条件(トランザクション外):
クライアントがトランザクション外でクエリを実行している間、Proxy は同じ DB 接続を別のクライアントのクエリにも再利用できます。
Connection Pinning(ピン留め)が発生する条件:
以下のケースでは1クライアントが1DB接続を専有します(多重化が無効):
- トランザクション中(
BEGIN〜COMMIT/ROLLBACK) - セッション変数の設定(
SET session_var = value) - プリペアドステートメントの実行
-
LOCK TABLESの使用 -
GET_LOCK()などの DB ロック関数
設計上の重要ポイント:ピン留めが多いほど Proxy の効果は薄れます。アプリケーションがトランザクションを短く保ち、セッション変数の使用を最小化することで多重化効率が上がります。
3. RDS Proxy の設定パラメータを理解する
3.1 接続プール設定
ConnectionPoolPercent(接続プール使用率の上限)
Aurora の max_connections のうち、Proxy が使用できる割合(%)
デフォルト: 100%
推奨: 読み書き用 Proxy は 70〜80%、読み取り専用は残りを割り当て
IdleClientConnectionTimeout(アイドルクライアント接続のタイムアウト)
クライアント側が何も送らずにいると切断される時間(秒)
デフォルト: 1800秒(30分)
推奨: アプリのコネクションプールの idle timeout より短く設定
設定例(Terraform):
resource "aws_db_proxy" "main" {
name = "company-rds-proxy"
debug_logging = false
engine_family = "MYSQL"
idle_client_connection_timeout = 900 # 15分
require_tls = true
role_arn = aws_iam_role.rds_proxy.arn
vpc_security_group_ids = [aws_security_group.rds_proxy.id]
vpc_subnet_ids = var.private_subnet_ids
auth {
auth_scheme = "SECRETS"
iam_auth = "REQUIRED" # IAM 認証を必須化
secret_arn = aws_secretsmanager_secret.db_credentials.arn
}
}
resource "aws_db_proxy_default_target_group" "main" {
db_proxy_name = aws_db_proxy.main.name
connection_pool_config {
connection_borrow_timeout = 120 # 接続待機の最大秒数
max_connections_percent = 75 # max_connections の 75% を使用
max_idle_connections_percent = 50 # アイドル接続の最大割合
session_pinning_filters = ["EXCLUDE_VARIABLE_SETS"] # SET 変数によるピン留めを除外
}
}
session_pinning_filters に EXCLUDE_VARIABLE_SETS を設定することで、SET コマンドによる不要なピン留めを抑制できます。多くのフレームワーク(Rails の ActiveRecord など)がセッション変数を設定するため、この設定は重要です。
3.2 Writer と Reader で Proxy を分ける
読み書き分離をしている構成では、Writer 用・Reader 用に別々の Proxy を作成します。
アプリケーション
│
├── 書き込み処理 ──▶ Proxy(Writer)──▶ Aurora Writer
│
└── 読み取り処理 ──▶ Proxy(Reader)──▶ Aurora Reader×N
# Reader 用 Proxy
resource "aws_db_proxy" "reader" {
name = "company-rds-proxy-reader"
engine_family = "MYSQL"
# ... 同様の設定
connection_pool_config {
max_connections_percent = 100 # Reader は全接続を使用可
}
}
resource "aws_db_proxy_target" "reader" {
db_proxy_name = aws_db_proxy.reader.name
target_group_name = "default"
db_cluster_identifier = aws_rds_cluster.main.id
# Proxy がクラスターの Reader エンドポイントに自動で接続を振り分ける
}
4. IAM 認証:パスワードをアプリから完全に排除する
4.1 IAM 認証の仕組み
RDS Proxy の IAM 認証では、DB パスワードの代わりに IAM トークン(有効期限15分の一時認証情報) を使用します。
ECS タスク
│
1. 自身の IAM ロールで IAM トークンを生成
│ (aws rds generate-db-auth-token)
│
2. トークンをパスワードとして Proxy に接続(TLS必須)
▼
RDS Proxy
│
3. IAM にトークンを検証させる
│
4. 検証成功 → Secrets Manager から実際の DB パスワードを取得
│
5. Aurora に接続
▼
Aurora
アプリケーションが DB パスワードを保持しない設計になるため、パスワードの漏洩リスクを根本的に排除できます。
4.2 ECS タスクロールの設定
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "rds-db:connect",
"Resource": "arn:aws:rds-db:ap-northeast-1:123456789012:dbuser:prx-XXXXXXXXXXXXXXXX/app_user"
}
]
}
rds-db:connect の Resource は arn:aws:rds-db:{region}:{account}:dbuser:{proxy_resource_id}/{db_username} の形式です。
4.3 アプリケーション側の実装(Ruby on Rails の例)
# config/database.yml
production:
adapter: mysql2
host: <%= ENV['RDS_PROXY_ENDPOINT'] %>
port: 3306
database: company_production
username: app_user
password: <%= RdsIamAuthToken.generate %>
ssl_mode: verify_full
sslca: /etc/ssl/certs/rds-ca-bundle.pem
# lib/rds_iam_auth_token.rb
require 'aws-sdk-rds'
module RdsIamAuthToken
def self.generate
Aws::RDS::AuthTokenGenerator.new(
region: ENV['AWS_REGION'],
credentials: Aws::ECSCredentials.new
).auth_token(
region: ENV['AWS_REGION'],
endpoint: ENV['RDS_PROXY_ENDPOINT'],
port: 3306,
user_name: 'app_user'
)
end
end
注意:IAM トークンの有効期限は15分です。コネクションプールが接続を長時間保持する場合、接続確立時にトークンを再生成する必要があります。接続確立のコールバック(after_connect)でトークンを毎回生成するよう実装してください。
5. フェイルオーバー時の挙動と設計
5.1 RDS Proxy なしのフェイルオーバー
障害発生
│
▼
Aurora フェイルオーバー(30〜60秒)
│
▼
全 ECS タスク(200個)が同時に再接続を試みる
│
▼ Thundering Herd 問題
Aurora が接続の波を受け、max_connections を一時的に超過
または 再接続の競合によりレイテンシが急増
5.2 RDS Proxy ありのフェイルオーバー
障害発生
│
▼
Aurora フェイルオーバー(30〜60秒)
│
▼
RDS Proxy が新しい Writer に自動的に再接続
(ECS タスクは Proxy への接続を維持し続ける)
│
▼
アプリケーション側の接続断: 最小限
(Proxy が再接続を完了するまでの数秒のみ)
RDS Proxy を使うことで、Aurora のフェイルオーバー時にアプリケーションが受ける影響を大幅に低減できます。ただし、Proxy 自体もフェイルオーバー完了まで一時的に接続エラーを返すため、アプリケーション側でのリトライ実装は引き続き必要です。
5.3 接続タイムアウトの推奨設定
アプリ側コネクションプール設定:
connect_timeout: 10秒 # Proxy への接続タイムアウト
checkout_timeout: 5秒 # プールからの接続取得タイムアウト
dead_connection_check: 有効 # 死んだ接続を検出して除去
retry_count: 3 # リトライ回数
retry_interval: 1秒 # リトライ間隔(指数バックオフ推奨)
Proxy 設定:
ConnectionBorrowTimeout: 120秒 # DB 接続が全て使用中の場合の待機時間
6. 月末・大量バッチ処理での活用
6.1 バッチ処理専用の Proxy を作る
給与計算など月末に大量の ECS タスクが起動するバッチ処理には、バッチ専用の Proxy を用意し、接続プールの上限を分けることを推奨します。
通常時:
API サーバー(50タスク)──▶ Proxy-API(max_connections × 60%)──▶ Aurora
バッチ(0タスク)──▶ Proxy-Batch(max_connections × 30%)──▶ Aurora
月末バッチ処理時:
API サーバー(50タスク)──▶ Proxy-API(max_connections × 60%)──▶ Aurora
バッチ(150タスク)──▶ Proxy-Batch(max_connections × 30%)──▶ Aurora
バッチが急増しても API サーバーの接続を圧迫しません。
6.2 バッチ処理での Connection Pinning を最小化する
バッチ処理はトランザクションをバルク処理に使うことが多く、ピン留めが発生しがちです。以下の設計を意識してください:
# NG: 1件ずつトランザクションを張る(1件 = 1接続ピン留め)
records.each do |record|
ActiveRecord::Base.transaction do
record.update!(...)
end
end
# OK: 適切なサイズでバルク処理(接続の再利用効率が上がる)
records.each_slice(100) do |batch|
ActiveRecord::Base.transaction do
batch.each { |record| record.update!(...) }
end
end
7. モニタリング:見るべきメトリクス
RDS Proxy が正常に機能しているか確認するために、以下の CloudWatch メトリクスを監視します。
| メトリクス | 意味 | アラート目安 |
|---|---|---|
DatabaseConnectionsCurrentlyBorrowed |
Proxy が Aurora に張っている接続数 | max_connections の 80% を超えたら警告 |
ClientConnectionsReceived |
クライアントから受け付けた接続数 | 急増時に要確認 |
DatabaseConnectionRequests |
Aurora への接続要求数 | ピン留めの多さの指標 |
DatabaseConnectionsCurrentlyInTransaction |
トランザクション中の接続数 | 高止まりはロック懸念 |
QueryDatabaseResponseLatency |
DB レスポンスのレイテンシ | P99 が閾値超過でアラート |
# Terraform での CloudWatch アラーム設定例
resource "aws_cloudwatch_metric_alarm" "proxy_connection_high" {
alarm_name = "rds-proxy-connections-high"
comparison_operator = "GreaterThanThreshold"
evaluation_periods = 2
metric_name = "DatabaseConnectionsCurrentlyBorrowed"
namespace = "AWS/RDS"
period = 60
statistic = "Maximum"
threshold = 1200 # max_connections の 70% 程度
dimensions = {
ProxyName = aws_db_proxy.main.name
}
alarm_actions = [aws_sns_topic.alerts.arn]
}
8. まとめ
RDS Proxy は「接続数を減らすためのプロキシ」として語られがちですが、実際に得られる価値はそれ以上です。
| 課題 | RDS Proxy による解決 |
|---|---|
| ECS スケールアウト時の接続枯渇 | 接続多重化で DB への接続数を制御 |
| フェイルオーバー時の Thundering Herd | Proxy が再接続を集約・吸収 |
| DB パスワードの管理リスク | IAM 認証でパスワードをアプリから排除 |
| 月末バッチによる API 影響 | Proxy を分けて接続枠を分離 |
| 接続確立コストの高さ | 接続プール再利用で初期化コストを削減 |
ECS でアプリケーションを運用していて RDS/Aurora を使っているなら、RDS Proxy の導入は費用対効果の高い投資です。特に スケールアウトが発生する業務システムでは、月末の処理集中や障害時の挙動が大きく改善されます。