なぜ今、Lambda の AZ を気にする必要があるのか?

クロスAZ通信のコストとレイテンシ問題
AWS を使っていると避けて通れないのが、クロスAZ(アベイラビリティゾーン間)のデータ転送コストです。同一リージョン内でも AZ をまたぐデータ転送には $0.01/GB(送受信それぞれ) の料金が発生します。これ、小規模なシステムなら誤差レベルなんですが、1日に数十GBものデータをやり取りするシステムになってくると、積み重なって馬鹿にならない金額になってきます。
レイテンシの面でも同様です。同一 AZ 内のノード間通信は通常0.1〜0.3ms程度で完結しますが、クロスAZ だと0.5〜2ms程度に増加します。絶対値だけ見ると小さく感じるかもしれませんが、ElastiCache に1リクエストあたり10回アクセスするような処理があれば、それだけで1回のLambda実行に最大20ms近い「無駄な待機」が生じていることになります。
従来の Lambda では AZ が分からなかった
Lambda は実行環境を AWS が自動的に管理するため、従来は「自分の関数がどのAZで動いているか」を知る手段がありませんでした。環境変数には AWS_REGION や AWS_DEFAULT_REGION は存在しますが、AZ レベルの情報は提供されていなかったのです。
そのため、ElastiCache や RDS のマルチAZ構成を使っていても、「同一AZのエンドポイントを使う」という当然の最適化がLambdaでは実質不可能でした。EC2 や ECS では IMDSv2(Instance Metadata Service)経由でAZ情報を取得できるため、ずっと Lambda だけ取り残されている状態だったわけです。
それが 2026年3月についに解消されました。
Lambda AZ メタデータとは
機能概要と仕様
新しく追加されたのは、Lambda 実行環境内でのみアクセス可能な ローカル HTTP メタデータ API です。構造は EC2 の IMDS に近いですが、Lambda 専用の軽量実装になっています。
主な仕様をまとめると以下の通りです。
項目 | 内容 |
|---|---|
エンドポイント |
|
認証方式 |
|
レスポンス形式 | JSON(例: |
キャッシュ |
|
SnapStart 対応 | あり(SnapStart 使用時は TTL が短縮される) |
対応ランタイム | 全ランタイム(カスタムランタイム・コンテナイメージ含む) |
追加コスト | なし |
注目ポイントは SSRF(Server-Side Request Forgery)対策として Bearer トークンが必須 になっている点です。環境変数 AWS_LAMBDA_METADATA_TOKEN に自動セットされる値を使うので、実装側で特別な処理は不要ですが、トークンなしで叩いても 401 が返ってきます。これは適切なセキュリティ設計だと思います。
AZ ID(use1-az1)と AZ 名の違い
ここ、地味に重要なポイントです。
AWS の AZ には「AZ 名」(例:us-east-1a)と「AZ ID」(例:use1-az1)の2種類の識別子があります。この2つは似ているようで、根本的に異なる概念です。
識別子 | 例 | 特徴 |
|---|---|---|
AZ 名 |
| アカウントごとに物理AZへのマッピングが異なる |
AZ ID |
| アカウント間で同じ物理ロケーションを指す |
AZ 名はアカウント間でシャッフルされています。あなたのアカウントの us-east-1a と友人のアカウントの us-east-1a は、実は異なる物理データセンターを指している可能性があります。これはAWSがAZに負荷を均等分散させるための設計です。
一方、AZ ID は物理ロケーションと1対1で対応しており、アカウントをまたいでも同じ物理AZを指す ことが保証されています。メタデータ API が返すのはこの AZ ID です。
注意点として、ElastiCache や RDS のエンドポイントを AZ ごとに選択する場合、通常は AZ 名で管理することが多いです。AZ ID から AZ 名への変換には EC2 の DescribeAvailabilityZones API を呼び出す必要があります。この変換処理をどう実装するかが、実用上の重要なポイントになります。
実際にやってみた

メタデータエンドポイントへの直接アクセス(bash/curl)
まずは最もシンプルに、bash と curl でメタデータを取得してみます。Lambda 内でシェルスクリプトを実行する場合や、動作確認をしたい場合に便利です。
#!/bin/bash
# Lambda 実行環境内でのみ動作します
# 環境変数は Lambda が自動でセット
METADATA_API="${AWS_LAMBDA_METADATA_API}"
METADATA_TOKEN="${AWS_LAMBDA_METADATA_TOKEN}"
# AZ ID を取得
RESPONSE=$(curl -s \
-H "Authorization: Bearer ${METADATA_TOKEN}" \
"http://${METADATA_API}/2026-01-15/metadata/execution-environment")
AZ_ID=$(echo "${RESPONSE}" | python3 -c "import json,sys; print(json.load(sys.stdin)['AvailabilityZoneID'])")
echo "実行中の AZ ID: ${AZ_ID}" # 例: use1-az1レスポンスは以下のような JSON です:
{
"AvailabilityZoneID": "use1-az1"
}
Powertools for AWS Lambda を使った実装(Python)
Powertools for AWS Lambda を使えば、低レベルな HTTP 呼び出しを抽象化してスッキリ書けます。
import json
import boto3
from aws_lambda_powertools.utilities.lambda_metadata import get_lambda_metadata
# EC2 クライアント(AZ ID → AZ 名の変換に使用)
ec2 = boto3.client('ec2')
# AZ ID → AZ 名のマッピングキャッシュ(Lambda の初期化フェーズで一度だけ実行)
_az_name_cache: dict[str, str] = {}
def get_az_name(az_id: str) -> str:
"""AZ ID から AZ 名を取得(キャッシュ付き)"""
if az_id in _az_name_cache:
return _az_name_cache[az_id]
response = ec2.describe_availability_zones(
Filters=[{'Name': 'zone-id', 'Values': [az_id]}]
)
az_name = response['AvailabilityZones'][0]['ZoneName']
_az_name_cache[az_id] = az_name
return az_name
def handler(event, context):
# Powertools でメタデータを取得
metadata = get_lambda_metadata()
az_id = metadata.availability_zone_id # 例: "use1-az1"
# AZ 名に変換(ElastiCache のエンドポイント選択等に使用)
az_name = get_az_name(az_id)
print(f"AZ ID: {az_id}, AZ Name: {az_name}")
return {
'statusCode': 200,
'body': json.dumps({
'az_id': az_id,
'az_name': az_name
})
}ポイントは、AZ ID → AZ 名の変換に DescribeAvailabilityZones を使う部分です。これをハンドラー内で毎回呼ぶのはコストとレイテンシの無駄なので、グローバルスコープでキャッシュ するのがセオリーです。Lambda の実行環境が再利用される間はキャッシュが生き続けます。
Powertools for AWS Lambda を使った実装(TypeScript)
TypeScript でも同様に Powertools を使えます。
import { getMetadata } from "@aws-lambda-powertools/commons/utils/metadata";
import { EC2Client, DescribeAvailabilityZonesCommand } from "@aws-sdk/client-ec2";
const ec2Client = new EC2Client({});
// AZ ID → AZ 名のキャッシュ(コールドスタート時に初期化)
const azNameCache = new Map();
async function getAzName(azId: string): Promise<string> {
if (azNameCache.has(azId)) {
return azNameCache.get(azId)!;
}
const command = new DescribeAvailabilityZonesCommand({
Filters: [{ Name: "zone-id", Values: [azId] }],
});
const response = await ec2Client.send(command);
const azName = response.AvailabilityZones?.[0]?.ZoneName ?? "";
azNameCache.set(azId, azName);
return azName;
}
// モジュールレベルで一度だけ実行(コールドスタート最適化)
const metadataPromise = getMetadata();
export const handler = async (event: unknown) => {
const metadata = await metadataPromise;
const azId = metadata.availabilityZoneId; // 例: "use1-az1"
const azName = await getAzName(azId);
console.log(`AZ ID: ${azId}, AZ Name: ${azName}`);
return {
statusCode: 200,
body: JSON.stringify({ azId, azName }),
};
};TypeScript 版では metadataPromise をモジュールスコープに出して、コールドスタート時に並行して取得が始まるようにしています。ウォームスタート時は Promise が解決済みなので即時返却されます。
ElastiCache との AZ 対応ルーティング実装例
実際のユースケースとして、ElastiCache Valkey(Redis 互換)のノードを同一AZのものに向ける実装を見てみましょう。
import os
import redis
import boto3
from aws_lambda_powertools.utilities.lambda_metadata import get_lambda_metadata
ec2 = boto3.client('ec2')
elasticache = boto3.client('elasticache')
# コールドスタート時に AZ 情報と接続先を初期化
_redis_client = None
def initialize_redis_client():
"""同一AZの ElastiCache エンドポイントに接続するクライアントを初期化"""
global _redis_client
# 実行中の AZ ID を取得
metadata = get_lambda_metadata()
az_id = metadata.availability_zone_id
# AZ ID → AZ 名に変換
az_response = ec2.describe_availability_zones(
Filters=[{'Name': 'zone-id', 'Values': [az_id]}]
)
az_name = az_response['AvailabilityZones'][0]['ZoneName']
# ElastiCache クラスターのノード一覧を取得
cluster_id = os.environ['ELASTICACHE_CLUSTER_ID']
cache_response = elasticache.describe_cache_clusters(
CacheClusterId=cluster_id,
ShowCacheNodeInfo=True
)
# 同一AZのノードを探す
nodes = cache_response['CacheClusters'][0]['CacheNodes']
same_az_nodes = [
node for node in nodes
if node.get('CustomerAvailabilityZone') == az_name
]
if same_az_nodes:
# 同一AZのノードを優先
endpoint = same_az_nodes[0]['Endpoint']
print(f"同一AZ ({az_name}) のノードを使用: {endpoint['Address']}")
else:
# フォールバック: クラスターのプライマリエンドポイント
endpoint = cache_response['CacheClusters'][0]['ConfigurationEndpoint']
print(f"フォールバック: プライマリエンドポイントを使用")
_redis_client = redis.Redis(
host=endpoint['Address'],
port=endpoint['Port'],
decode_responses=True
)
return _redis_client
def handler(event, context):
global _redis_client
if _redis_client is None:
_redis_client = initialize_redis_client()
# 以降は通常通り Redis を使用
value = _redis_client.get('my-key')
return {'statusCode': 200, 'body': value}
重要なのは、接続先の初期化はコールドスタート時(グローバルスコープまたは初回呼び出し時)に1回だけ行う 点です。毎リクエストで AZ を確認して接続を切り替えるようなことをすると、オーバーヘッドで本末転倒になります。
AZ を意識した設計パターン
同一AZルーティングパターン(Mermaid図)
全体のアーキテクチャを Mermaid で可視化するとこうなります。
graph TB
subgraph AZ1["AZ: use1-az1 (us-east-1a)"]
L1[Lambda 実行環境 #1]
E1[ElastiCache Node A]
R1[RDS Read Replica A]
end
subgraph AZ2["AZ: use1-az2 (us-east-1b)"]
L2[Lambda 実行環境 #2]
E2[ElastiCache Node B]
R2[RDS Read Replica B]
end
subgraph AZ3["AZ: use1-az3 (us-east-1c)"]
L3[Lambda 実行環境 #3]
E3[ElastiCache Node C]
R3[RDS Primary]
end
META[メタデータ API]
L1 -->|AZ ID 取得| META
META -->|use1-az1| L1
L1 -->|同一AZ接続 ✓| E1
L1 -->|同一AZ接続 ✓| R1
L2 -->|AZ ID 取得| META
META -->|use1-az2| L2
L2 -->|同一AZ接続 ✓| E2
L2 -->|クロスAZ接続 ✗| R3
style L1 fill:#ff9900,color:#000
style L2 fill:#ff9900,color:#000
style L3 fill:#ff9900,color:#000
style META fill:#232f3e,color:#fff
Lambda 実行環境がどのAZに配置されるかは AWS が制御しており、通常はリクエスト数に応じて複数のAZに分散されます。各実行環境がメタデータAPIで自分のAZ IDを確認し、同一AZのバックエンドに接続することで、クロスAZ通信を最小化できます。
ただし RDS の場合、書き込みはプライマリに集約されるため、常に同一AZとは限りません。読み取りレプリカを各AZに配置し、読み取りのみ同一AZにルーティングする パターンが現実的です。
AZ 別フォールトインジェクションテスト
AZ ID を取得できるようになったことで、特定のAZだけ意図的にエラーを発生させる テストも書けるようになりました。
import os
from aws_lambda_powertools.utilities.lambda_metadata import get_lambda_metadata
def handler(event, context):
metadata = get_lambda_metadata()
current_az_id = metadata.availability_zone_id
# 環境変数で「障害を発生させるAZ」を指定
fault_az_id = os.environ.get('FAULT_INJECTION_AZ_ID', '')
fault_rate = float(os.environ.get('FAULT_INJECTION_RATE', '0'))
if fault_az_id and current_az_id == fault_az_id:
import random
if random.random() < fault_rate:
raise Exception(f"意図的なフォールト(AZ: {current_az_id})")
# 通常処理
return {'statusCode': 200, 'body': 'ok'}
これを使って「use1-az1 でのみ 50% の確率でエラーを発生させる」という負荷テストができます。特定AZが落ちたときのフェイルオーバー動作を事前に検証するのに役立ちます。
SnapStart との組み合わせ
Java ランタイムの高速起動を実現する SnapStart とも組み合わせ可能です。ただし、SnapStart を使う場合は AZ ID のキャッシュ有効期間(max-age)が短縮される ことに注意が必要です。
SnapStart では Lambda が事前にスナップショットを作成し、コールドスタート時にそのスナップショットから復元します。スナップショット作成時点のAZと、実際にデプロイされるAZが異なる可能性があるため、メタデータAPIは意図的にキャッシュを短くして最新情報を取得させます。
import software.amazon.lambda.powertools.utilities.LambdaMetadata;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
public class AzAwareHandler implements RequestHandler<Object, String> {
// SnapStart では restore フェーズで再取得が必要
private static String azId;
@Override
public String handleRequest(Object event, Context context) {
if (azId == null) {
// Powertools Java でメタデータ取得
var metadata = LambdaMetadata.getMetadata();
azId = metadata.getAvailabilityZoneId();
System.out.println("AZ ID: " + azId);
}
return "Running in AZ: " + azId;
}
}
SnapStart を使う場合は @SnapshotCreated と @SnapshotRestored フックで AZ ID をリフレッシュする実装が推奨されます。
コスト試算:クロスAZデータ転送を削減すると
前提条件と試算モデル
実際にどれくらいコスト削減になるのか試算してみます。以下のシステムを想定します。
パラメータ | 値 |
|---|---|
Lambda 月間実行回数 | 1,000万回 |
1実行あたりのキャッシュアクセス | 5回 |
1回のキャッシュアクセスデータ量 | 1KB(平均) |
月間データ転送量(合計) | 約47.7GB |
Lambda の AZ 分散 | 3AZに均等分散(各AZに約33%) |
同一AZルーティング適用前後の比較
シナリオ | クロスAZ転送量 | 月額クロスAZコスト |
|---|---|---|
ランダムルーティング(従来) | 約31.8GB(全体の66.7%) | 約$0.64 |
同一AZルーティング(最適化後) | ほぼ0GB | 約$0.00 |
削減額 | — | 約$0.64/月 |
…正直に言います。この規模だと全然たいしたことないです。月$0.64の削減。コーヒー1杯も買えません。
ただし、データ量が100倍(1回あたり100KB)になれば月$64、1000倍(1MB)なら月$640の削減になります。大量のデータをキャッシュに読み書きするシステム、たとえば画像処理パイプラインや機械学習の推論キャッシュなどでは十分に意味のある数字になります。
レイテンシ面については金銭換算が難しいですが、クロスAZの追加レイテンシが1アクセスあたり1ms程度とすると、1実行あたり5msの削減→月1000万回なら合計5万秒の短縮です。SLAやユーザー体験への影響を考えると、高トラフィックサービスでは無視できない数字です。
現状と注意点(星野さんの視点)
「現状は使わなそう…」の理由
ぶっちゃけ、この機能は 使いどころを選ぶ 機能です。
エンジニアの星野が感じた「現状は使わなそう」という感覚は、正直なところ的を射ていると思います。その理由を整理してみます。
懸念事項 | 詳細 |
|---|---|
対象システムが限られる | ElastiCache や RDS をLambdaと組み合わせていて、かつデータ量が大きいケースに限られる |
実装コストが低くない | AZ ID→AZ名変換、エンドポイント選択ロジック、フォールバック処理など、それなりの実装量になる |
AZ偏りのリスクがある | 後述するが、同一AZへの集中によるホットスポット問題を引き起こす可能性がある |
マネージドサービスとの相性 | Aurora Serverless や Elasticache Serverless はAZ単位での制御が難しい場合がある |
同一AZ優先による「一部AZへの偏り」リスク
これ、地味に見落としがちな落とし穴です。
Lambda は通常、複数のAZに均等に分散されます。しかし、特定のAZのノードがスケールアップしていたり、Reserverd Capacity を持っていたりする場合、そのAZに実行環境が集中することがあります。
「同一AZのElastiCacheノードを使う」という設計にした場合、Lambda の集中したAZのノードに負荷が偏ってしまう可能性があります。ElastiCache のクラスターモードが有効であれば、スロットのリバランスで対処できますが、そうでない場合はノードが過負荷になるリスクがあります。
また、AZ 障害時の挙動も考慮が必要です。特定の AZ が落ちた際に、そのAZを使っていた Lambda 実行環境が他のAZにフェイルオーバーするとき、接続先も切り替わらないと障害が継続します。コールドスタート時の初期化ロジックに適切なフォールバックを組み込む必要があります。
def get_same_az_or_fallback_endpoint(az_name: str, cluster_id: str) -> dict:
"""同一AZのノードを返す。なければフォールバック"""
try:
response = elasticache.describe_cache_clusters(
CacheClusterId=cluster_id,
ShowCacheNodeInfo=True
)
nodes = response['CacheClusters'][0]['CacheNodes']
# 同一AZで「available」なノードを探す
same_az_available = [
node for node in nodes
if node.get('CustomerAvailabilityZone') == az_name
and node.get('CacheNodeStatus') == 'available'
]
if same_az_available:
return same_az_available[0]['Endpoint']
# フォールバック: 任意のavailableノード
available_nodes = [
node for node in nodes
if node.get('CacheNodeStatus') == 'available'
]
if available_nodes:
print(f"同一AZ ({az_name}) のノードなし。フォールバック使用")
return available_nodes[0]['Endpoint']
raise RuntimeError("利用可能なノードが見つかりません")
except Exception as e:
print(f"ElastiCache ノード取得エラー: {e}")
# 設定ファイルのデフォルトエンドポイントにフォールバック
return {
'Address': os.environ['ELASTICACHE_DEFAULT_ENDPOINT'],
'Port': 6379
}
Powertools のバージョン管理に注意
Powertools for AWS Lambda はバージョンによって提供される API が異なります。get_lambda_metadata や getMetadata が利用できるバージョンは執筆時点では比較的新しいため、既存プロジェクトでは依存関係のアップデートが必要になります。
Lambda レイヤーとして Powertools を使っている場合は、レイヤーのバージョンも合わせて更新してください。
まとめ
今回は AWS Lambda の新しいAZメタデータ機能について、仕様から実装パターン、コスト試算、注意点まで一通り見てきました。
改めて整理すると
- Lambda 実行環境のAZ IDをローカルメタデータAPIで取得できるようになった
- ElastiCache、RDSなどへの同一AZルーティングで、クロスAZレイテンシとコストを削減できる
- 全ランタイム対応・追加コストなし・Bearer トークンによるSSRF対策済み
- 恩恵が大きいのは大量データを扱うシステムか、マイクロ秒レベルのレイテンシを要求するシステム
- フォールバック処理とAZ偏りのリスク管理を忘れずに
星野が言う「現状は使わなそう」という感覚は正直なところかなり共感できます。ほとんどのシステムでは、この最適化を実装するコストに見合う効果を得るのは難しいでしょう。
ただ、サーバーレスアーキテクチャがさらに成熟して、より高トラフィックなワークロードに使われるようになるにつれて、この機能の重要性は増してくるはずです。「Lambda でもAZを意識した設計ができる」という事実を知っておくだけで、いざというときに引き出しから出せます。
まずは既存のシステムのクロスAZデータ転送量を計測してみて、削減効果が見込める場合に導入を検討するのが現実的な進め方です。AWS Cost Explorer の「データ転送」カテゴリや、VPC Flow Logs を使えば、現在のクロスAZトラフィックを可視化できます。試してみてください。















