@shinyaz

Powertools で Lambda AZ メタデータを取得する — Same-AZ ルーティングを3行で実装

目次

はじめに

前回の記事では、Lambda AZメタデータエンドポイントを直接呼び出してSame-AZルーティングの効果を検証した。結果としてクロスAZレイテンシが同一AZの約2.5倍であることを実測で確認できたが、直接APIアクセスではHTTPリクエスト構築、認証トークン処理、キャッシュ実装をすべて自前で行う必要があった。

本記事では、Powertools for AWS Lambda(TypeScript版)の getMetadata() を使い、Same-AZルーティングをより簡潔に実装する。加えて、Powertoolsのキャッシュ動作についても検証結果を共有する。

前提条件:

  • 前回の記事の検証環境と同等の構成(ElastiCache Valkey 3AZ、VPC内Lambda)
  • Node.js 22.x ランタイム
  • @aws-lambda-powertools/commons パッケージ

Powertools の getMetadata()

@aws-lambda-powertools/commons パッケージの utils/metadata から getMetadata() をインポートするだけでAZ IDを取得できる。

TypeScript
import { getMetadata } from "@aws-lambda-powertools/commons/utils/metadata";
 
const metadata = await getMetadata();
const azId = metadata.AvailabilityZoneID; // e.g., "apne1-az2"

直接APIアクセスと比較すると差は明らかだ。

直接APIアクセス(比較用)
const api = process.env.AWS_LAMBDA_METADATA_API!;
const token = process.env.AWS_LAMBDA_METADATA_TOKEN!;
const res = await fetch(
  `http://${api}/2026-01-15/metadata/execution-environment`,
  { headers: { Authorization: `Bearer ${token}` } },
);
const { AvailabilityZoneID: azId } = await res.json();

環境変数の参照、URLの構築、認証ヘッダの設定がすべて getMetadata() の1行に集約される。

Powertools の内部実装

ソースコードを確認すると、getMetadata() は以下の設計になっている。

項目動作
キャッシュモジュールレベルのオブジェクトに結果を保持。2回目以降はHTTPリクエストなし
キャッシュクリアclearMetadataCache() で明示的にクリア可能
ローカル開発POWERTOOLS_DEV=true または AWS_LAMBDA_INITIALIZATION_TYPE 未設定時は空オブジェクトを返す
タイムアウトデフォルト1000ms(AbortSignal.timeout)。オプションで変更可能
HTTPクライアントNode.js 組み込みの fetch API を使用

注目すべきは clearMetadataCache() の存在だ。SnapStart環境ではRestore後にキャッシュを破棄して新しいAZ IDを取得する必要があるが、この関数を使えばそのハンドリングが明示的に行える。

キャッシュ動作の検証

非VPCのLambda関数で getMetadata() のレイテンシを計測した。

TypeScript
// 1回目: 初回呼び出し
const metadata1 = await getMetadata();
 
// 2回目: キャッシュヒット
const metadata2 = await getMetadata();
 
// clearMetadataCache() 後: 再取得
clearMetadataCache();
const metadata3 = await getMetadata();
計測コールドスタートウォームスタート
初回呼び出し605ms0.05〜0.19ms(前回のキャッシュヒット)
キャッシュヒット0.07ms0.015〜0.024ms
clearMetadataCache()20ms1.9〜158ms

コールドスタート時の初回呼び出しは約600msかかるが、これはLambda実行環境の初期化オーバーヘッドも含む値だ。ウォームスタート時はモジュールレベルのキャッシュが前回の呼び出しから引き継がれるため、初回呼び出しでも0.05ms程度で完了する。

clearMetadataCache() 後の再取得は1.9〜158msとばらつきが大きい。メタデータエンドポイントへの新規HTTP接続の確立が毎回必要になるため、通常のコードパスでは clearMetadataCache() を呼ぶべきではない。SnapStartのRestore時など、明確にキャッシュ破棄が必要な場合にのみ使用する。

Same-AZ ルーティング検証

前回の記事と同条件(ElastiCache Valkey 3AZ、TLS、PING 50回)で、TypeScript Powertools版のSame-AZルーティングを検証した。

検証用Lambda関数

Lambda関数の handler 部分はシンプルだ。getMetadata() でAZ IDを取得し、各ノードへのレイテンシを計測して同一AZ/クロスAZに分類する。

index.ts(handler部分)
import { getMetadata } from "@aws-lambda-powertools/commons/utils/metadata";
 
export const handler = async () => {
  const metadata = await getMetadata();
  const lambdaAzId = (metadata as Record<string, unknown>)
    .AvailabilityZoneID as string;
 
  const nodes = process.env.CACHE_NODES!.split(",").map((e) => {
    const [id, address, port, azId, azName, role] = e.split("|");
    return { id, address, port: parseInt(port), azId, azName, role };
  });
 
  const results = await Promise.all(
    nodes.map(async (node) => ({
      ...node,
      latency: await measureValkeyLatency(node.address, node.port),
      sameAz: node.azId === lambdaAzId,
    })),
  );
 
  return { lambdaAzId, results };
};

AZ IDの取得が getMetadata() の1行で完結しているのがポイントだ。esbuild でバンドルすると関数全体がわずか5.3KBになる。

完全なLambda関数コード(レイテンシ計測ロジック含む)
index.ts
import { getMetadata } from "@aws-lambda-powertools/commons/utils/metadata";
import * as tls from "tls";
 
function measureValkeyLatency(
  host: string,
  port: number,
  iterations = 50,
): Promise<{ avgMs: number }> {
  return new Promise((resolve, reject) => {
    const socket = tls.connect({ host, port, servername: host }, () => {
      const latencies: number[] = [];
      let i = 0;
      const ping = () => {
        if (i >= iterations) {
          socket.destroy();
          latencies.sort((a, b) => a - b);
          resolve({
            avgMs:
              Math.round(
                (latencies.reduce((a, b) => a + b, 0) / latencies.length) *
                  1000,
              ) / 1000,
          });
          return;
        }
        const start = performance.now();
        socket.write("*1\r\n$4\r\nPING\r\n");
        socket.once("data", () => {
          latencies.push(performance.now() - start);
          i++;
          ping();
        });
      };
      ping();
    });
    socket.on("error", reject);
  });
}
 
export const handler = async () => {
  const metadata = await getMetadata();
  const lambdaAzId = (metadata as Record<string, unknown>)
    .AvailabilityZoneID as string;
 
  const nodes = process.env.CACHE_NODES!.split(",").map((e) => {
    const [id, address, port, azId, azName, role] = e.split("|");
    return { id, address, port: parseInt(port), azId, azName, role };
  });
 
  const results = await Promise.all(
    nodes.map(async (node) => ({
      ...node,
      latency: await measureValkeyLatency(node.address, node.port),
      sameAz: node.azId === lambdaAzId,
    })),
  );
 
  return { lambdaAzId, results };
};
デプロイ手順

ElastiCache Valkey クラスターは前回の記事のデプロイ手順で構築する。TypeScript Lambda のビルドとデプロイは以下の通り。

ターミナル(ビルド)
npm init -y
npm install @aws-lambda-powertools/commons esbuild
npx esbuild index.ts --bundle --platform=node --target=node22 --outfile=dist/index.js --format=cjs --minify
cd dist && zip function.zip index.js
ターミナル(Lambda作成)
aws lambda create-function \
  --function-name az-ts-routing-test \
  --runtime nodejs22.x \
  --handler index.handler \
  --role arn:aws:iam::<ACCOUNT_ID>:role/<ROLE_NAME> \
  --zip-file fileb://function.zip \
  --timeout 120 --memory-size 512 \
  --vpc-config SubnetIds=<SUBNET_1a>,<SUBNET_1c>,<SUBNET_1d>,SecurityGroupIds=<SG_ID> \
  --environment '{"Variables":{"CACHE_NODES":"<CACHE_NODES値>"}}'

CACHE_NODES の形式は前回の記事を参照。

計測結果

16回の呼び出し結果:

メトリクス同一AZクロスAZ
平均0.739 ms1.894 ms
最小0.428 ms1.579 ms
最大1.634 ms2.141 ms

同一AZルーティングにより、平均レイテンシが約61%削減された(1.894ms → 0.739ms)。 クロスAZは同一AZの約2.6倍のレイテンシとなる。

全16回の計測詳細データ
実行#Lambda AZ同一AZ avg (ms)クロスAZ avg (ms)オーバーヘッド
1apne1-az40.4281.855+333%
2apne1-az40.5811.926+232%
3apne1-az40.5391.883+249%
4apne1-az41.6342.130+30%
5apne1-az40.6972.141+207%
6apne1-az40.7522.014+168%
7apne1-az20.5381.707+217%
8apne1-az40.5771.905+230%
9apne1-az40.6522.055+215%
10apne1-az40.6072.036+235%
11apne1-az20.6791.579+133%
12apne1-az40.7492.014+169%
13apne1-az20.5841.637+180%
14apne1-az11.1311.929+71%
15apne1-az40.5251.889+260%
16apne1-az11.1581.599+38%

Python版(前回記事)との比較

指標Python(直接API)TypeScript(Powertools)
Same-AZ avg0.663 ms0.739 ms
Cross-AZ avg1.663 ms1.894 ms
比率2.5x2.6x
削減率~60%~61%

同一AZ/クロスAZの比率はほぼ同等だ。絶対値の差(TypeScript版がやや遅い)はランタイムの違いやElastiCacheクラスターの再構築による配置変更が影響している可能性がある。重要なのは、Powertoolsを使っても同一AZルーティングの効果は変わらないということだ。

まとめ

  • TypeScript版 Powertools は getMetadata() で即座に利用可能@aws-lambda-powertools/commons 2.32.0でAZメタデータに対応済み。キャッシュも自動で行われる。
  • Same-AZルーティングの効果はランタイムに依存しない — Python直接APIでもTypeScript Powertoolsでも、同一AZは約2.5倍高速という傾向は一貫している。
  • clearMetadataCache() は通常コードパスでは不要 — キャッシュクリア後の再取得は1.9〜158msとばらつきが大きい。SnapStart Restore時など明確な理由がある場合にのみ使用する。

クリーンアップ

前回の記事と同じ手順でリソースを削除する。TypeScript版のLambda関数名のみ異なる。

ターミナル
aws lambda delete-function --function-name az-ts-routing-test
aws lambda delete-function --function-name az-ts-powertools-test
# ElastiCache、サブネットグループ、SG、IAMロールの削除は前回記事を参照

共有する

田原 慎也

田原 慎也

ソリューションアーキテクト @ AWS

AWS ソリューションアーキテクトとして金融業界のお客様を中心に技術支援を行っています。クラウドアーキテクチャや AI/ML に関する学びをこのサイトで発信しています。このサイトの内容は個人の見解であり、所属企業の公式な意見や見解を代表するものではありません。

関連記事