-
Notifications
You must be signed in to change notification settings - Fork 1k
New pattern - aurora-serverless-v2-lambda-bedrock-cdk #3094
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 1 commit
012511f
366979c
060a4bf
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,81 @@ | ||
| # Aurora Serverless v2 with Lambda and Amazon Bedrock | ||
|
|
||
| This pattern deploys an Aurora Serverless v2 PostgreSQL cluster (platform version 4) with Lambda functions that query stored knowledge and use Amazon Bedrock for AI-powered answers. Aurora Serverless v2 scales to zero when idle, making it cost-effective for agentic AI workloads. | ||
|
|
||
| Learn more about this pattern at Serverless Land Patterns: https://serverlessland.com/patterns/aurora-serverless-v2-lambda-bedrock-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 | ||
| * [Amazon Bedrock model access](https://docs.aws.amazon.com/bedrock/latest/userguide/model-access.html) enabled for Anthropic Claude Sonnet in your target region | ||
|
|
||
| ## How it works | ||
|
|
||
|  | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The README embeds
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Replaced the broken image reference with a text architecture description. |
||
|
|
||
| 1. A setup Lambda creates the `knowledge` table in Aurora PostgreSQL and seeds it with sample data. | ||
| 2. A query Lambda receives a question, searches Aurora for relevant context using SQL, and sends the context + question to Amazon Bedrock. | ||
| 3. Bedrock (Claude Sonnet) generates an answer grounded in the database context. | ||
| 4. Aurora Serverless v2 (platform version 4) automatically scales capacity based on demand and scales to zero when idle. | ||
|
|
||
| ## Deployment | ||
|
|
||
| 1. Clone the repository and navigate to the pattern directory: | ||
| ```bash | ||
| git clone https://github.com/aws-samples/serverless-patterns | ||
| cd serverless-patterns/aurora-serverless-v2-lambda-bedrock-cdk | ||
| ``` | ||
|
|
||
| 2. Install dependencies: | ||
| ```bash | ||
| npm install | ||
| ``` | ||
|
|
||
| 3. Deploy the stack: | ||
| ```bash | ||
| cdk deploy | ||
| ``` | ||
|
|
||
| 4. Seed the database: | ||
| ```bash | ||
| aws lambda invoke \ | ||
| --function-name $(aws cloudformation describe-stacks \ | ||
| --stack-name AuroraServerlessV2LambdaBedrockStack \ | ||
| --query 'Stacks[0].Outputs[?OutputKey==`SetupFunctionName`].OutputValue' \ | ||
| --output text) \ | ||
| --payload '{}' setup-output.json | ||
| ``` | ||
|
|
||
| ## Testing | ||
|
|
||
| Query the knowledge base: | ||
|
|
||
| ```bash | ||
| aws lambda invoke \ | ||
| --function-name $(aws cloudformation describe-stacks \ | ||
| --stack-name AuroraServerlessV2LambdaBedrockStack \ | ||
| --query 'Stacks[0].Outputs[?OutputKey==`QueryFunctionName`].OutputValue' \ | ||
| --output text) \ | ||
| --cli-binary-format raw-in-base64-out \ | ||
| --payload '{"question": "What is Aurora Serverless v2?"}' \ | ||
| output.json | ||
|
|
||
| cat output.json | python3 -m json.tool | ||
| ``` | ||
|
|
||
| ## Cleanup | ||
|
|
||
| ```bash | ||
| cdk destroy | ||
| ``` | ||
|
|
||
| ---- | ||
| Copyright 2026 Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
|
|
||
| SPDX-License-Identifier: MIT-0 | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| #!/usr/bin/env node | ||
| import "source-map-support/register"; | ||
| import * as cdk from "aws-cdk-lib"; | ||
| import { AuroraServerlessV2LambdaBedrockStack } from "../lib/aurora-serverless-v2-lambda-bedrock-stack"; | ||
|
|
||
| const app = new cdk.App(); | ||
| new AuroraServerlessV2LambdaBedrockStack( | ||
| app, | ||
| "AuroraServerlessV2LambdaBedrockStack", | ||
| { | ||
| env: { | ||
| account: process.env.CDK_DEFAULT_ACCOUNT, | ||
| region: process.env.CDK_DEFAULT_REGION, | ||
| }, | ||
| } | ||
| ); |
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A cached
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Deleted it and added cdk.context.json to .gitignore. Since the stack now creates its own VPC (no fromLookup), context is only needed for AZ resolution at deploy time. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| { | ||
| "availability-zones:account=742460038667:region=us-east-1": [ | ||
| "us-east-1a", | ||
| "us-east-1b", | ||
| "us-east-1c", | ||
| "us-east-1d", | ||
| "us-east-1e", | ||
| "us-east-1f" | ||
| ], | ||
| "vpc-provider:account=742460038667:filter.isDefault=true:region=us-east-1:returnAsymmetricSubnets=true": { | ||
| "vpcId": "vpc-0d2ccb9ba9da8174c", | ||
| "vpcCidrBlock": "172.31.0.0/16", | ||
| "ownerAccountId": "742460038667", | ||
| "availabilityZones": [], | ||
| "subnetGroups": [ | ||
| { | ||
| "name": "Public", | ||
| "type": "Public", | ||
| "subnets": [ | ||
| { | ||
| "subnetId": "subnet-09f81666a668b49d7", | ||
| "cidr": "172.31.16.0/20", | ||
| "availabilityZone": "us-east-1a", | ||
| "routeTableId": "rtb-0d6e0c8254189f150" | ||
| }, | ||
| { | ||
| "subnetId": "subnet-01e469e26a62f79cc", | ||
| "cidr": "172.31.32.0/20", | ||
| "availabilityZone": "us-east-1b", | ||
| "routeTableId": "rtb-0d6e0c8254189f150" | ||
| }, | ||
| { | ||
| "subnetId": "subnet-0edf680549fc43b35", | ||
| "cidr": "172.31.0.0/20", | ||
| "availabilityZone": "us-east-1c", | ||
| "routeTableId": "rtb-0d6e0c8254189f150" | ||
| }, | ||
| { | ||
| "subnetId": "subnet-0a4b73e1b77dfe7e3", | ||
| "cidr": "172.31.80.0/20", | ||
| "availabilityZone": "us-east-1d", | ||
| "routeTableId": "rtb-0d6e0c8254189f150" | ||
| }, | ||
| { | ||
| "subnetId": "subnet-007839dd58f1d60a0", | ||
| "cidr": "172.31.48.0/20", | ||
| "availabilityZone": "us-east-1e", | ||
| "routeTableId": "rtb-0d6e0c8254189f150" | ||
| }, | ||
| { | ||
| "subnetId": "subnet-03604e5a5796bce0a", | ||
| "cidr": "172.31.64.0/20", | ||
| "availabilityZone": "us-east-1f", | ||
| "routeTableId": "rtb-0d6e0c8254189f150" | ||
| } | ||
| ] | ||
| } | ||
| ] | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| { | ||
| "app": "npx ts-node --prefer-ts-exts bin/app.ts" | ||
| } |
|
marcojahn marked this conversation as resolved.
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,50 @@ | ||
| { | ||
| "title": "Aurora Serverless v2 with Lambda and Amazon Bedrock", | ||
| "description": "Deploy an Aurora Serverless v2 PostgreSQL database that scales to zero, with Lambda functions that query stored knowledge and use Amazon Bedrock for AI-powered answers.", | ||
| "language": "Python", | ||
| "level": "300", | ||
| "framework": "AWS CDK", | ||
| "services": { | ||
| "from": "auroraserverlessv2", | ||
| "to": "bedrock" | ||
| }, | ||
| "introBox": { | ||
| "headline": "How it works", | ||
| "text": [ | ||
| "This pattern deploys an Aurora Serverless v2 PostgreSQL cluster (platform version 4 with up to 30% better performance) that scales to zero when idle. A setup Lambda seeds a knowledge table, and a query Lambda retrieves relevant context from Aurora and sends it to Amazon Bedrock for AI-powered answers.", | ||
| "Aurora Serverless v2 is ideal for agentic AI workloads with burst patterns and long idle windows. The enhanced scaling algorithm in platform version 4 efficiently handles workloads where multiple tasks compete for resources." | ||
| ] | ||
| }, | ||
| "gitHub": { | ||
| "template": { | ||
| "repoURL": "https://github.com/aws-samples/serverless-patterns/tree/main/aurora-serverless-v2-lambda-bedrock-cdk", | ||
| "templateURL": "serverless-patterns/aurora-serverless-v2-lambda-bedrock-cdk", | ||
| "projectFolder": "aurora-serverless-v2-lambda-bedrock-cdk", | ||
| "templateFile": "lib/aurora-serverless-v2-lambda-bedrock-stack.ts" | ||
| } | ||
| }, | ||
| "resources": { | ||
| "bullets": [ | ||
| { "text": "Aurora Serverless v2 platform version 4", "link": "https://aws.amazon.com/about-aws/whats-new/2026/04/aurora-serverless-smarter-scaling/" }, | ||
| { "text": "Amazon Aurora Serverless", "link": "https://aws.amazon.com/rds/aurora/serverless/" }, | ||
| { "text": "Amazon Bedrock", "link": "https://aws.amazon.com/bedrock/" } | ||
| ] | ||
| }, | ||
| "deploy": { | ||
| "text": ["cdk deploy"], | ||
| "file": "lib/aurora-serverless-v2-lambda-bedrock-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" | ||
| } | ||
| ] | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,90 @@ | ||
| import * as cdk from "aws-cdk-lib"; | ||
| import * as ec2 from "aws-cdk-lib/aws-ec2"; | ||
| import * as iam from "aws-cdk-lib/aws-iam"; | ||
| import * as lambda from "aws-cdk-lib/aws-lambda"; | ||
| import * as rds from "aws-cdk-lib/aws-rds"; | ||
| import { Construct } from "constructs"; | ||
|
|
||
| export class AuroraServerlessV2LambdaBedrockStack extends cdk.Stack { | ||
| constructor(scope: Construct, id: string, props?: cdk.StackProps) { | ||
| super(scope, id, props); | ||
|
|
||
| // Use default VPC | ||
| const vpc = ec2.Vpc.fromLookup(this, "DefaultVpc", { isDefault: true }); | ||
|
|
||
| // Aurora Serverless v2 cluster (PostgreSQL, scales to zero) | ||
| const cluster = new rds.DatabaseCluster(this, "AuroraCluster", { | ||
|
marcojahn marked this conversation as resolved.
|
||
| engine: rds.DatabaseClusterEngine.auroraPostgres({ | ||
| version: rds.AuroraPostgresEngineVersion.VER_16_4, | ||
| }), | ||
| serverlessV2MinCapacity: 0, | ||
| serverlessV2MaxCapacity: 4, | ||
| writer: rds.ClusterInstance.serverlessV2("writer"), | ||
| vpc, | ||
| vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The Aurora Serverless v2 cluster is placed in
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree, moved the cluster into a dedicated VPC with PRIVATE_ISOLATED subnets. Since we use the RDS Data API, I think its okay if Lambda doesn't need to be in the VPC? |
||
| defaultDatabaseName: "appdb", | ||
| enableDataApi: true, | ||
| removalPolicy: cdk.RemovalPolicy.DESTROY, | ||
| }); | ||
|
|
||
| // Data API policy for Lambdas | ||
| const dataApiPolicy = new iam.PolicyStatement({ | ||
| actions: [ | ||
| "rds-data:ExecuteStatement", | ||
| "rds-data:BatchExecuteStatement", | ||
| ], | ||
| resources: [cluster.clusterArn], | ||
| }); | ||
|
|
||
| const envVars = { | ||
| CLUSTER_ARN: cluster.clusterArn, | ||
| SECRET_ARN: cluster.secret!.secretArn, | ||
| DB_NAME: "appdb", | ||
| }; | ||
|
|
||
| // Setup Lambda — initializes the knowledge table | ||
| const setupFn = new lambda.Function(this, "SetupFn", { | ||
| runtime: lambda.Runtime.PYTHON_3_12, | ||
| handler: "index.handler", | ||
| code: lambda.Code.fromAsset("src/setup"), | ||
| timeout: cdk.Duration.seconds(60), | ||
| memorySize: 256, | ||
| environment: envVars, | ||
| description: "Seeds Aurora knowledge table via Data API", | ||
| }); | ||
| cluster.secret!.grantRead(setupFn); | ||
| setupFn.addToRolePolicy(dataApiPolicy); | ||
|
|
||
| // Query Lambda — queries Aurora, sends context to Bedrock | ||
| const queryFn = new lambda.Function(this, "QueryFn", { | ||
| runtime: lambda.Runtime.PYTHON_3_12, | ||
| handler: "index.handler", | ||
| code: lambda.Code.fromAsset("src/query-fn"), | ||
| timeout: cdk.Duration.minutes(2), | ||
| memorySize: 512, | ||
| environment: { | ||
| ...envVars, | ||
| MODEL_ID: "us.anthropic.claude-sonnet-4-20250514-v1:0", | ||
| }, | ||
| description: "Queries Aurora knowledge base and sends to Bedrock", | ||
| }); | ||
| cluster.secret!.grantRead(queryFn); | ||
| queryFn.addToRolePolicy(dataApiPolicy); | ||
| queryFn.addToRolePolicy( | ||
| new iam.PolicyStatement({ | ||
| actions: ["bedrock:InvokeModel"], | ||
| resources: ["*"], | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The query function's IAM policy allows
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Scoped to the specific inference-profile and foundation-model ARNs for the model in use. |
||
| }) | ||
| ); | ||
|
|
||
| new cdk.CfnOutput(this, "SetupFunctionName", { | ||
| value: setupFn.functionName, | ||
| }); | ||
| new cdk.CfnOutput(this, "QueryFunctionName", { | ||
| value: queryFn.functionName, | ||
| }); | ||
| new cdk.CfnOutput(this, "ClusterEndpoint", { | ||
| value: cluster.clusterEndpoint.hostname, | ||
| }); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| { | ||
| "name": "aurora-serverless-v2-lambda-bedrock-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" | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| """Query handler — searches Aurora via Data API, sends context to Bedrock for answer.""" | ||
|
|
||
| import json | ||
| import os | ||
| import boto3 | ||
|
|
||
| rds_data = boto3.client("rds-data") | ||
| bedrock = boto3.client("bedrock-runtime") | ||
|
|
||
| CLUSTER_ARN = os.environ["CLUSTER_ARN"] | ||
| SECRET_ARN = os.environ["SECRET_ARN"] | ||
| DB_NAME = os.environ["DB_NAME"] | ||
|
|
||
|
|
||
| def handler(event, _context): | ||
| question = event.get("question", "What is Aurora Serverless v2?") | ||
| stop_words = {"what", "is", "the", "a", "an", "how", "does", "do", "can", "tell", "me", "about"} | ||
| keywords = [w for w in question.split() if w.lower().strip("?.,!") not in stop_words] | ||
| search_term = keywords[0] if keywords else "Aurora" | ||
|
|
||
| # Query Aurora for relevant knowledge via Data API | ||
| result = rds_data.execute_statement( | ||
| resourceArn=CLUSTER_ARN, | ||
| secretArn=SECRET_ARN, | ||
| database=DB_NAME, | ||
| sql="SELECT topic, content FROM knowledge WHERE content ILIKE :term OR topic ILIKE :term LIMIT 3", | ||
| parameters=[{"name": "term", "value": {"stringValue": f"%{search_term}%"}}], | ||
| ) | ||
|
|
||
| rows = result.get("records", []) | ||
| context = "\n".join( | ||
| f"[{r[0]['stringValue']}]: {r[1]['stringValue']}" for r in rows | ||
| ) if rows else "No context found." | ||
|
|
||
| # Send context + question to Bedrock | ||
| response = bedrock.invoke_model( | ||
| modelId=os.environ["MODEL_ID"], | ||
| contentType="application/json", | ||
| accept="application/json", | ||
| body=json.dumps({ | ||
| "anthropic_version": "bedrock-2023-05-31", | ||
| "max_tokens": 512, | ||
| "messages": [ | ||
| { | ||
| "role": "user", | ||
| "content": f"Context from database:\n{context}\n\nQuestion: {question}\n\nAnswer based on the context above.", | ||
| } | ||
| ], | ||
| }), | ||
| ) | ||
| answer = json.loads(response["body"].read())["content"][0]["text"] | ||
|
|
||
| return {"statusCode": 200, "body": json.dumps({"question": question, "answer": answer, "sources": len(rows)})} |
Uh oh!
There was an error while loading. Please reload this page.