Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 73 additions & 0 deletions lambda-az-aware-routing-cdk/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# AWS Lambda AZ-aware routing with metadata endpoint

This pattern deploys a Lambda function that uses the new Lambda metadata endpoint to discover its Availability Zone ID and demonstrates same-AZ routing to reduce cross-AZ latency and data transfer costs.

Learn more about this pattern at Serverless Land Patterns: https://serverlessland.com/patterns/lambda-az-aware-routing-cdk

Important: this application uses various AWS services and there are costs associated with these services after the Free Tier usage - please see the [AWS Pricing page](https://aws.amazon.com/pricing/) for details. You are responsible for any AWS costs incurred. No warranty is implied in this example.

## Requirements

* [Create an AWS account](https://portal.aws.amazon.com/gp/aws/developer/registration/index.html) if you do not already have one and log in. The IAM user that you use must have sufficient permissions to make necessary AWS service calls and manage AWS resources.
* [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html) installed and configured
* [Git Installed](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git)
* [Node and NPM](https://nodejs.org/en/download/) installed
* [AWS CDK](https://docs.aws.amazon.com/cdk/latest/guide/cli.html) installed

## How it works

![Architecture](architecture.png)

1. A client invokes the Lambda function.
2. The function calls the Lambda metadata endpoint (`http://${AWS_LAMBDA_METADATA_API}/2026-01-15/metadata/execution-environment`) using the `AWS_LAMBDA_METADATA_TOKEN` for authentication.
3. The endpoint returns the AZ ID (e.g., `use1-az1`) — a consistent identifier across all AWS accounts.
4. The function uses the AZ ID to make routing decisions, such as selecting same-AZ endpoints for ElastiCache, RDS, or DynamoDB DAX.
5. Same-AZ routing eliminates cross-AZ data transfer costs (~$0.01/GB) and reduces latency by ~1ms per hop.

## Deployment

1. Clone the repository and navigate to the pattern directory:
```bash
git clone https://github.com/aws-samples/serverless-patterns
cd serverless-patterns/lambda-az-aware-routing-cdk
```

2. Install dependencies:
```bash
npm install
```

3. Deploy the stack:
```bash
cdk deploy
```

## Testing

Invoke the function multiple times to see different AZ assignments:

```bash
FUNCTION_NAME=$(aws cloudformation describe-stacks \
--stack-name LambdaAzAwareRoutingStack \
--query 'Stacks[0].Outputs[?OutputKey==`FunctionName`].OutputValue' \
--output text)

# Invoke 5 times to see AZ distribution
for i in {1..5}; do
aws lambda invoke --function-name $FUNCTION_NAME --payload '{}' /dev/stdout 2>/dev/null | python3 -m json.tool
echo "---"
done
```

Expected output: Each invocation returns the AZ ID where the function is running (e.g., `use1-az1`, `use1-az2`).

## Cleanup

```bash
cdk destroy
```

----
Copyright 2026 Amazon.com, Inc. or its affiliates. All Rights Reserved.

SPDX-License-Identifier: MIT-0
12 changes: 12 additions & 0 deletions lambda-az-aware-routing-cdk/bin/app.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#!/usr/bin/env node
import "source-map-support/register";
import * as cdk from "aws-cdk-lib";
import { LambdaAzAwareRoutingStack } from "../lib/lambda-az-aware-routing-stack";

const app = new cdk.App();
new LambdaAzAwareRoutingStack(app, "LambdaAzAwareRoutingStack", {
env: {
account: process.env.CDK_DEFAULT_ACCOUNT,
region: process.env.CDK_DEFAULT_REGION,
},
});
3 changes: 3 additions & 0 deletions lambda-az-aware-routing-cdk/cdk.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"app": "npx ts-node --prefer-ts-exts bin/app.ts"
}
50 changes: 50 additions & 0 deletions lambda-az-aware-routing-cdk/example-pattern.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
{
"title": "AWS Lambda AZ-aware routing with metadata endpoint",
"description": "Deploy a Lambda function that reads its Availability Zone ID from the new metadata endpoint and demonstrates same-AZ routing to reduce cross-AZ latency.",
"language": "TypeScript",
"level": "300",
"framework": "AWS CDK",
"services": {
"from": "lambda",
"to": "dynamodb"
},
"introBox": {
"headline": "How it works",
"text": [
"This pattern deploys a Lambda function that uses the new Lambda metadata endpoint (March 2026) to discover which Availability Zone it is running in. The function reads the AZ ID (e.g., use1-az1) and demonstrates how to use it for same-AZ routing decisions.",
"Same-AZ routing eliminates cross-AZ data transfer costs and reduces latency by approximately 1ms per hop. This is valuable for latency-sensitive workloads that call downstream services like ElastiCache, RDS, or DynamoDB DAX."
]
},
"gitHub": {
"template": {
"repoURL": "https://github.com/aws-samples/serverless-patterns/tree/main/lambda-az-aware-routing-cdk",
"templateURL": "serverless-patterns/lambda-az-aware-routing-cdk",
"projectFolder": "lambda-az-aware-routing-cdk",
"templateFile": "lib/lambda-az-aware-routing-stack.ts"
}
},
"resources": {
"bullets": [
{ "text": "Lambda AZ metadata announcement", "link": "https://aws.amazon.com/about-aws/whats-new/2026/03/lambda-availability-zone-metadata/" },
{ "text": "Using the Lambda metadata endpoint", "link": "https://docs.aws.amazon.com/lambda/latest/dg/configuration-metadata-endpoint.html" },
{ "text": "AZ IDs for cross-account consistency", "link": "https://docs.aws.amazon.com/global-infrastructure/latest/regions/az-ids.html" }
]
},
"deploy": {
"text": ["cdk deploy"],
"file": "lib/lambda-az-aware-routing-stack.ts"
},
"testing": {
"text": ["See the README for testing instructions."]
},
"cleanup": {
"text": ["cdk destroy"]
},
"authors": [
{
"name": "Nithin Chandran R",
"bio": "Technical Account Manager at AWS",
"linkedin": "nithin-chandran-r"
}
]
}
54 changes: 54 additions & 0 deletions lambda-az-aware-routing-cdk/lib/lambda-az-aware-routing-stack.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import * as cdk from "aws-cdk-lib";
import * as dynamodb from "aws-cdk-lib/aws-dynamodb";
import * as lambda from "aws-cdk-lib/aws-lambda";
import * as nodejs from "aws-cdk-lib/aws-lambda-nodejs";
import { Construct } from "constructs";

export class LambdaAzAwareRoutingStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);

// DynamoDB table to record AZ routing decisions
const table = new dynamodb.Table(this, "AzRoutingTable", {
partitionKey: { name: "pk", type: dynamodb.AttributeType.STRING },
sortKey: { name: "sk", type: dynamodb.AttributeType.STRING },
billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
removalPolicy: cdk.RemovalPolicy.DESTROY,
});

// Lambda function that reads AZ metadata and demonstrates routing
const routerFn = new nodejs.NodejsFunction(this, "AzRouterFn", {
entry: "src/az-router/index.ts",
handler: "handler",
runtime: lambda.Runtime.NODEJS_22_X,
timeout: cdk.Duration.seconds(30),
memorySize: 256,
environment: {
TABLE_NAME: table.tableName,
},
bundling: {
minify: true,
sourceMap: true,
},
description:
"Reads Lambda AZ metadata and demonstrates same-AZ routing",
});

table.grantWriteData(routerFn);

// Function URL for easy testing
const fnUrl = routerFn.addFunctionUrl({
authType: lambda.FunctionUrlAuthType.AWS_IAM,
});

new cdk.CfnOutput(this, "FunctionName", {
value: routerFn.functionName,
});
new cdk.CfnOutput(this, "FunctionUrl", {
value: fnUrl.url,
});
new cdk.CfnOutput(this, "TableName", {
value: table.tableName,
});
}
}
15 changes: 15 additions & 0 deletions lambda-az-aware-routing-cdk/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"name": "lambda-az-aware-routing-cdk",
"version": "1.0.0",
"bin": { "app": "bin/app.ts" },
"scripts": { "build": "tsc", "cdk": "cdk" },
"dependencies": {
"aws-cdk-lib": "^2.180.0",
"constructs": "^10.4.2"
},
"devDependencies": {
"@types/node": "^22.0.0",
"ts-node": "^10.9.0",
"typescript": "~5.7.0"
}
}
68 changes: 68 additions & 0 deletions lambda-az-aware-routing-cdk/src/az-router/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/**
* Lambda AZ-aware routing — reads AZ metadata from the Lambda metadata endpoint
* and demonstrates same-AZ routing by writing the AZ info to DynamoDB.
*/

import http from "http";

const METADATA_API = process.env.AWS_LAMBDA_METADATA_API;
const METADATA_TOKEN = process.env.AWS_LAMBDA_METADATA_TOKEN;

interface MetadataResponse {
AvailabilityZoneID: string;
}

let cachedAzId: string | null = null;

async function getAzId(): Promise<string> {
if (cachedAzId) return cachedAzId;

return new Promise((resolve, reject) => {
const url = `http://${METADATA_API}/2026-01-15/metadata/execution-environment`;
const req = http.get(url, { headers: { Authorization: `Bearer ${METADATA_TOKEN}` } }, (res) => {
let data = "";
res.on("data", (chunk) => (data += chunk));
res.on("end", () => {
const parsed: MetadataResponse = JSON.parse(data);
cachedAzId = parsed.AvailabilityZoneID;
resolve(cachedAzId!);
});
});
req.on("error", reject);
req.end();
});
}

// Fallback for local testing where metadata endpoint is unavailable
async function getAzIdSafe(): Promise<string> {
if (!METADATA_API || !METADATA_TOKEN) return "local-dev";
try {
return await getAzId();
} catch {
return "unknown";
}
}

export const handler = async (event: any) => {
const azId = await getAzIdSafe();
const requestId = event.requestContext?.requestId ?? "direct-invoke";

// In production, use azId to select same-AZ endpoints:
// - ElastiCache: pick the reader endpoint in the same AZ
// - RDS: pick the reader in the same AZ
// - DynamoDB DAX: pick the same-AZ DAX node
// This demo logs the AZ and returns it.

const result = {
azId,
functionName: process.env.AWS_LAMBDA_FUNCTION_NAME,
region: process.env.AWS_REGION,
message: `Lambda running in ${azId}. Use this to route to same-AZ downstream services.`,
routingExample: {
description: "In production, map AZ IDs to same-AZ endpoints",
sameAzBenefit: "Eliminates cross-AZ data transfer costs and reduces latency by ~1ms",
},
};

return { statusCode: 200, body: JSON.stringify(result) };
};
20 changes: 20 additions & 0 deletions lambda-az-aware-routing-cdk/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"lib": ["es2020"],
"declaration": true,
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"noImplicitThis": true,
"alwaysStrict": true,
"outDir": "build",
"rootDir": ".",
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"esModuleInterop": true
},
"exclude": ["node_modules", "build"]
}