ローカル環境 vs クラウド環境 - 性能テストの実践
ローカル開発環境と商用環境(AWS等)における性能の違いを理解し、現実的な性能テストを実践するためのガイドです。
なぜ環境差を理解すべきか
「ローカルで10msだったのに、本番で200msもかかる」「ローカルで100ms改善できたのに、本番では誤差の範囲内だった」——こうした経験はありませんか?
環境差を理解せずにチューニングすると、無意味な最適化に時間を費やすことになります。まずは両環境のシステム構成を見比べて、何が違うのかを把握しましょう。
システム構成の比較
まず、ローカル環境とAWS本番環境の構成を並べて見てみましょう。
ローカル環境では、全てのコンポーネントが1台のマシン上で動作します。Docker Composeで起動したコンテナ同士は仮想ネットワークで接続され、通信遅延はほぼゼロです。
一方、AWS本番環境では、EKSクラスター、Aurora Global Database、ElastiCacheなどが物理的に分散配置されます。本ドキュメントで紹介する構成は、エンタープライズ向けの本格的な構成例です。
この構成が必要になる背景:
| 要件 | 構成要素 | 目的 |
|---|---|---|
| 高可用性 | Multi-AZ配置 | 1つのAZが障害を起こしてもサービス継続 |
| 災害対策 | Aurora Global Database | リージョン障害時に別リージョンで復旧(RTO < 1分) |
| グローバル展開 | マルチリージョンEKS | 北米・アジアなど地理的に近い場所からの低レイテンシアクセス |
| スケーラビリティ | EKS + HPA | 負荷に応じた自動スケールアウト |
| 読み取り負荷分散 | Aurora Reader | 読み取りクエリをReaderにオフロード |
スタートアップや小規模サービスであれば、シングルリージョン + Multi-AZ で十分なケースも多いです。しかし、SLA 99.99%以上を求められるエンタープライズサービスや、グローバルにユーザーを抱えるサービスでは、このような構成が必要になります。
重要なのは、この分散構成こそが性能特性の差を生み出す 根本原因であるということです。可用性とパフォーマンスはトレードオフの関係にあり、高可用性を実現するための分散配置が、ネットワーク遅延という形でパフォーマンスに影響します。
ローカル環境
┌─────────────────────────────────────────────────────────────────────┐
│ 開発マシン (MacBook等) │
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Browser │ │ k6 / curl │ │ IDE │ │
│ └──────┬──────┘ └──────┬──────┘ └─────────────┘ │
│ │ localhost:443 │ │
│ └────────┬───────────┘ │
│ ▼ │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ Docker Compose │ │
│ │ ┌─────────────┐ │ │
│ │ │ nginx │ ← SSL終端、リバースプロキシ │ │
│ │ │ :443 │ worker_connections: 1024 (デフォルト) │ │
│ │ └──────┬──────┘ │ │
│ │ │ │ │
│ │ ┌──────▼──────┐ ┌─────────────┐ ┌─────────────┐ │ │
│ │ │ App (JVM) │ │ PostgreSQL │ │ Redis │ │ │
│ │ │ :8080 │ │ :5432 │ │ :6379 │ │ │
│ │ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │ │
│ │ │ │ │ │ │
│ │ └────── docker network (~0.1ms) ────┘ │ │
│ └───────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────┐ │
│ │ ローカルSSD (NVMe) │ │
│ │ 読み取り: ~50μs 書き込み: ~100μs │ │
│ └─────────────────────────────────────────────┘ │
│ │
│ 特徴: │
│ ・全コンポーネントが同一マシン上 │
│ ・ネットワーク遅延 ≈ 0 │
│ ・ストレージは超高速NVMe │
│ ・外部APIはモック(即座に応答) │
│ ・リソース競合なし │
│ ・nginxのリソース制限(worker数、接続数)が先に限界に達しやすい │
└─────────────────────────────────────────────────────────────────────┘
AWS本番環境 (EKS + Aurora Global Database)
┌─────────────────────────────────────────────────────────────────────────────────────┐
│ AWS Cloud │
│ │
│ ┌───────────────────────────────────────────────────────────────────────────────┐ │
│ │ Primary Region (ap-northeast-1) │ │
│ │ │ │
│ │ Internet │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ ┌─────────────────┐ │ │
│ │ │ API Gateway │ ← レート制限、認証、スロットリング │ │
│ │ │ (HTTP API) │ 10,000 req/sec/region (デフォルト) │ │
│ │ └────────┬────────┘ │ │
│ │ │ ~5-10ms │ │
│ │ ▼ │ │
│ │ ┌─────────────────┐ │ │
│ │ │ ALB (Ingress) │ ← SSL終端、ヘルスチェック │ │
│ │ └────────┬────────┘ │ │
│ │ │ ~1ms │ │
│ │ ▼ │ │
│ │ ┌──────────────────────────────────────────────────────────────── ────────┐ │ │
│ │ │ EKS Cluster │ │ │
│ │ │ ┌──────────────────────────────────────────────────────────────────┐ │ │ │
│ │ │ │ Availability Zone A │ │ │ │
│ │ │ │ ┌──────────────────────────────────────────────────────────┐ │ │ │ │
│ │ │ │ │ Node (m5.large) │ │ │ │ │
│ │ │ │ │ ┌──────────────┐ ┌──────────────┐ │ │ │ │ │
│ │ │ │ │ │ Pod: idp-app │ │ Pod: idp-app │ ← HPA でスケール │ │ │ │ │
│ │ │ │ │ │ CPU: 500m │ │ CPU: 500m │ │ │ │ │ │
│ │ │ │ │ │ Mem: 1Gi │ │ Mem: 1Gi │ │ │ │ │ │
│ │ │ │ │ └──────────────┘ └──────────────┘ │ │ │ │ │
│ │ │ │ └──────────────────────────────────────────────────────────┘ │ │ │ │
│ │ │ └──────────────────────────────────────────────────────────────────┘ │ │ │
│ │ │ ┌──────────────────────────────────────────────────────────────────┐ │ │ │
│ │ │ │ Availability Zone C │ │ │ │
│ │ │ │ ┌──────────────────────────────────────────────────────────┐ │ │ │ │
│ │ │ │ │ Node (m5.large) │ │ │ │ │
│ │ │ │ │ ┌──────────────┐ ┌──────────────┐ │ │ │ │ │
│ │ │ │ │ │ Pod: idp-app │ │ Pod: idp-app │ │ │ │ │ │
│ │ │ │ │ └──────────────┘ └──────────────┘ │ │ │ │ │
│ │ │ │ └──────────────────────────────────────────────────────────┘ │ │ │ │
│ │ │ └──────────────────────────────────────────────────────────────────┘ │ │ │
│ │ └────────────────────────────────────────────────────────────────────────┘ │ │
│ │ │ │ │ │
│ │ │ 1-3ms │ 0.5-1ms │ │
│ │ ▼ ▼ │ │
│ │ ┌──────────────────────────┐ ┌─────────────────┐ │ │
│ │ │ Aurora Global Database │ │ ElastiCache │ │ │
│ │ │ (Primary Cluster) │ │ Redis Cluster │ │ │
│ │ │ ┌────────┐ ┌────────┐ │ └─────────────────┘ │ │
│ │ │ │ Writer │ │ Reader │ │ │ │
│ │ │ │ (AZ-A) │ │ (AZ-C) │ │ ← 同一リージョン内Reader: ~1ms │ │
│ │ │ └────────┘ └────────┘ │ │ │
│ │ └────────────┬─────────────┘ │ │
│ │ │ │ │
│ └────────────────┼───────────────────────────────────────────────────────────────┘ │
│ │ │
│ │ レプリケーション (~1秒、ストレージレベル) │
│ ▼ │
│ ┌────────────────────────────────────────────────────────────────────────────────┐ │
│ │ Secondary Region (us-west-2) │ │
│ │ │ │
│ │ ┌──────────────────────────┐ │ │
│ │ │ Aurora Global Database │ ← DR用 / 読み取りオフロード │ │
│ │ │ (Secondary Cluster) │ │ │
│ │ │ ┌────────┐ ┌────────┐ │ │ │
│ │ │ │ Reader │ │ Reader │ │ フェイルオーバー時にWriterに昇格可能 │ │
│ │ │ │ (AZ-A) │ │ (AZ-B) │ │ (RTO < 1分) │ │
│ │ │ └────────┘ └────────┘ │ │ │
│ │ └──────────────────────────┘ │ │
│ │ │ │
│ │ ┌───────────────────────────────────────────────────────────── ───────────┐ │ │
│ │ │ EKS Cluster (us-west-2) ← 北米ユーザー向け、低レイテンシ読み取り │ │ │
│ │ │ ┌──────────────┐ ┌──────────────┐ │ │ │
│ │ │ │ Pod: idp-app │ │ Pod: idp-app │ Reader Endpointに接続 │ │ │
│ │ │ └──────────────┘ └──────────────┘ │ │ │
│ │ └────────────────────────────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────────────────────────┘ │
│ │
│ 外部サービス連携 (NAT Gateway経由): │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ 認証サービス │ │ 決済API │ │ メール送信 │ │
│ │ 50-200ms │ │ 500-2000ms │ │ 100-500ms │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │
│ 特徴: │
│ ・Pod間通信もネットワーク経由(同一Node内でも~0.1ms) │
│ ・Aurora Reader活用で読み取りスケールアウト可能 │
│ ・グローバルDBで別リージョンへの読み取りオフロード │
│ ・クロスリージョン書 き込みは Primary Region 経由(レイテンシ大) │
│ ・レプリケーションラグ(~1秒)による読み取り整合性の考慮が必要 │
└─────────────────────────────────────────────────────────────────────────────────────┘
構成図から見る性能差異
1. エントリーポイント(nginx vs API Gateway)
ローカル環境ではnginx、AWS環境ではAPI Gatewayがエントリーポイントとなります。この違いが負荷テスト時に大きな差として現れます。
| 項目 | ローカル (nginx) | AWS (API Gateway) |
|---|---|---|
| 同時接続数 | worker_connections × workers (デフォルト1024) | 10,000 req/sec/region |
| スケーラビリティ | Docker内リソース制限 | フルマネージド、自動スケール |
| レート制限 | なし(デフォルト) | バースト制限あり(設定可能) |
| レイテンシ | ~0.1ms | ~5-10ms |
| SSL終端 | CPUバウンド | ハードウェアアクセラレーション |
ローカル環境の制約:
VUs増加時のボトルネック:
─────────────────────────────────────────────────────────
VUs=50: nginx余裕あり → App/DBがボトルネック
VUs=100: nginx接続数増加 → まだ余裕
VUs=150: nginx worker_connections飽和 → 502 Bad Gateway発生
─────────────────────────────────────────────────────────
VUsを150程度まで上げると、アプリケーションやDBより先にnginxが限界を迎えることがあります。これはローカル特有の問題で、本番環境では発生しません。
ローカルで高負荷テストを行う場合の対策:
# nginx.conf でworker_connectionsを増やす
events {
worker_connections 4096; # デフォルト1024から増加
}
# upstreamのkeepalive設定
upstream app {
server app:8080;
keepalive 100; # 接続を再利用
}
本番環境(API Gateway)の特性:
- 自動スケールで接続数上限を気にする必要がほぼない
- ただしリージョン単位のレート制限(デフォルト10,000 req/sec)に注意
- 必要に応じてService Quotasで上限引き上げ可能
- レイテンシが5-10ms追加されるため、総レイテンシに影響
教訓:
- ローカルでVUs=150でエラーが出ても、本番では問題ない可能性が高い
- ローカルの負荷テストはnginxの制約を考慮して解釈する
- 本番の限界値を知りたい場合はステージング環境でテストする
2. ネットワークレイテンシ
構成図を見ると、ローカルでは全てが同一マシン内で完結しているのに対し、AWSでは各コンポーネント間にネットワークが介在しています。
| 通信経路 | ローカル | AWS (同一AZ) | AWS (クロスAZ) | AWS (クロスリージョン) |
|---|---|---|---|---|
| App ↔ DB (Writer) | ~0.1ms | 1-3ms | 2-5ms | 70-150ms |
| App ↔ DB (Reader) | ~0.1ms | 1-3ms | 1-3ms | 1-3ms (ローカルReader) |
| App ↔ Redis | ~0.1ms | 0.5-1ms | 1-2ms | - |
| App ↔ 外部API | 0ms (モック) | 20-200ms | 20-200ms | 20-200ms |
ポイント:
- ローカルのdocker networkは実質ゼロ遅延
- AWSでは同一AZ内でも1-3msの遅延
- Aurora Global DBではリージョン内Readerで読み取りスケールアウト
- クロスリージョン書き込みはPrimary経由で高レイテンシ
3. ストレージ性能
| 要素 | ローカル (NVMe SSD) | AWS (EBS gp3) |
|---|---|---|
| 読み取りレイテンシ | ~50μs | ~1ms |
| 書き込みレイテンシ | ~100μs | ~1-2ms |
| IOPS | 制限なし | 3000-16000 (設定依存) |
| スループット | ~3GB/s | 125-1000 MB/s |
ローカルSSDは桁違いに速いため、I/O待ちの問題がローカルでは見えません。
4. リソース制限
| 要素 | ローカル | AWS (EKS) |
|---|---|---|
| CPU | 常にフル性能 | requests/limitsで制限、スロットリング発生 |
| メモリ | 十分な容量 | limitsを超えるとOOMKill |
| 接続数 | 制限なし | RDSはメモリ依存で制限 |
| 帯域 | 制限なし | Nodeのインスタンスタイプで制限 |
EKS特有の考慮点:
# Pod のリソース制限例
resources:
requests:
cpu: "500m" # 0.5 CPU確保
memory: "1Gi"
limits:
cpu: "1000m" # 最大1 CPU
memory: "2Gi" # 超過でOOMKill
requests: 最低保証リソース。Nodeへの配置判断に使用limits: 上限。CPU超過→スロットリング、Memory超過→OOMKill- ローカルでは制限なしで動くコードが、本番ではスロットリングで遅延
5. マルチテナント影響
ローカルでは自分だけがリソースを使用しますが、EKSでは:
- 同一Node上の他Podとリソース競合
- CPUスロットリングによるレイテンシ増加
- RDSのバックアップ実行時に性能低下
- HPAスケールアウト時のコールドスタート遅延
- 結果として性能に変動(ジッター)が発生し、P99が安定しない
6. EKSスケーリングの遅延
リクエスト急増時:
─────────────────────────────────────────────────────────────────
t=0s 負荷増加検知
t=15s HPA がメトリクス取得(デフォルト15秒間隔)
t=15s スケールアウト判断
t=20s 新Pod起動開始
t=30s JVMウォームアップ中(まだ本来の性能が出ない)
t=60s JIT最適化完了、本来の性能に到達
─────────────────────────────────────────────────────────────────
→ 約1分間は既存Podに負荷集中