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
67 changes: 67 additions & 0 deletions sqs-lambda-tenant-isolation-sam-py/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# AWS Lambda Tenant Isolation with Amazon SQS

This pattern demonstrate AWS Lambda's tenant isolation feature in Multi-tenant application. It uses single Amazon SQS for multi-tenant applucation and isolating messages using messagegroupid and invoking isolated lambda enviornments.

## Key Features

- Tenant isolation at infrastructure level (no custom routing logic)
- Execution environments never shared between tenants
- Asynchronous invocation pattern
- Automatic tenant context propagation

Learn more about this pattern at [Serverless Land Patterns](https://serverlessland.com/patterns/sqs-lambda-tenant-isolation-sam-py)

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)
* [AWS Serverless Application Model](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html) (AWS SAM) installed

## Components
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Components & How It works

  • combine
  • please be more descriptive
  • add an architecture diagram with short flow description (will replace a lot of this text)

Deployment instructions separate topic


### 1. SQS Processor (`sqs-processor/`)
- Triggered by SQS queue messages
- Extracts `customer-id` from message payload
- Invokes tenant-isolated Lambda asynchronously with `TenantId` parameter

### 2. Tenant-Isolated Processor (`tenant-isolated-processor/`)
- Configured with tenant isolation mode enabled
- Processes requests in isolated execution environments per tenant
- Accesses tenant ID via `context.identity.tenant_id`

## Message Format

```json
{
"data": "your payload here"
}
```

## Deployment Instructions

```bash
sam build
sam deploy --guided
```

## How it works

```
SQS Queue → SQS Processor Lambda → Tenant-Isolated Lambda
(reads customer-id) (processes with tenant isolation)
```

## Testing
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Testing could be more prescriptive, e.g. (below is just a sample)

  1. You send messages to the SQS queue with --message-group-id set to a tenant identifier (e.g., tenant-blue, tenant-green)
# Get the queue URL from stack outputs
QUEUE_URL=$(aws cloudformation describe-stacks \
  --stack-name <your-stack-name> \
  --query 'Stacks[0].Outputs[?OutputKey==`QueueUrl`].OutputValue' \
  --output text)
  1. The SQS processor Lambda picks up the message, reads the MessageGroupId from the SQS record attributes, and passes it as the TenantId when invoking the tenant-isolated Lambda
# Send messages for tenant-blue
aws sqs send-message \
  --queue-url $QUEUE_URL \
  --message-body '{"data": "payload for blue"}' \
  --message-group-id "tenant-blue"
  1. Lambda routes each invocation to a dedicated execution environment for that tenant
# Send messages for tenant-green
aws sqs send-message \
  --queue-url $QUEUE_URL \
  --message-body '{"data": "payload for green"}' \
  --message-group-id "tenant-green"
  1. You verify isolation by checking CloudWatch Logs — each tenant should get its own log stream
aws logs describe-log-streams \
  --log-group-name /aws/lambda/tenant-isolated-processor \
  --order-by LastEventTime \
  --descending


Send a message to the SQS queue:

```bash
aws sqs send-message \
--queue-url <QUEUE_URL> \
--message-body '{"data": "test payload"}'
```

After dropping the message, review cloudwatch log for Tenant-Isolated Lambda. Different log streams should be created for each tenant.
49 changes: 49 additions & 0 deletions sqs-lambda-tenant-isolation-sam-py/example-pattern.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
{
"title": "AWS Lambda Tenant Isolation with Amazon SQS",
"description": "Lambda Isolation feature",
"language": "",
"level": "200",
"framework": "AWS SAM",
"introBox": {
"headline": "How it works",
"text": [
"This pattern demonstrate AWS Lambda's tenant isolation feature in Multi-tenant application."
]
},
"gitHub": {
"template": {
"repoURL": "https://github.com/aws-samples/serverless-patterns/tree/main/sqs-lambda-tenant-isolation-sam-py",
"templateURL": "serverless-patterns/sqs-lambda-tenant-isolation-sam-py",
"projectFolder": "sqs-lambda-tenant-isolation-sam-py"
}
},
"resources": {
"bullets": [{
"text": "AWS Lambda tenant isolation",
"link": "https://docs.aws.amazon.com/lambda/latest/dg/tenant-isolation.html"
}
]
},
"deploy": {
"text": ["sam build", "sam deploy --guided"]
},
"testing": {
"text": ["See the GitHub repo for detailed testing instructions."]
},
"cleanup": {
"text": ["Delete the stack: <code>sam delete</code>."]
},
"authors": [{
"name": "Mitesh Purohit",
"image": "",
"bio": "Sr Solution Architect, AWS",
"linkedin": "https://www.linkedin.com/in/mitup/"
},
{
"name": "Ricardo Marques",
"image": "",
"bio": "Sr Serverless Specialist, AWS",
"linkedin": "https://www.linkedin.com/in/ricardo-marques-aws/"
}
]
}
29 changes: 29 additions & 0 deletions sqs-lambda-tenant-isolation-sam-py/sqs-processor/index.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import json
import boto3
import os

lambda_client = boto3.client('lambda')
TENANT_ISOLATED_FUNCTION = os.environ['TENANT_ISOLATED_FUNCTION_NAME']

def handler(event, context):
for record in event['Records']:
body = json.loads(record['body'])

# Get message group ID from SQS attributes
attributes = record.get('attributes') or {}
message_group_id = attributes.get('MessageGroupId')

if not message_group_id:
print(f"Missing MessageGroupId in SQS record: {record}")
message_group_id = "default"

lambda_client.invoke(
FunctionName=TENANT_ISOLATED_FUNCTION,
InvocationType='Event',
Payload=json.dumps(body),
TenantId=message_group_id
)

print(f"Invoked tenant-isolated function for messagegroup: {message_group_id}")

return {'statusCode': 200}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
boto3>=1.26.0
69 changes: 69 additions & 0 deletions sqs-lambda-tenant-isolation-sam-py/template.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: Lambda Tenant Isolation Demo

Resources:
ProcessingQueue:
Type: AWS::SQS::Queue
Properties:
QueueName: tenant-isolation-queue
VisibilityTimeout: 300

TenantIsolatedFunctionRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole

TenantIsolatedFunction:
Type: AWS::Serverless::Function
Properties:
FunctionName: tenant-isolated-processor
CodeUri: tenant-isolated-processor/
Handler: index.handler
Runtime: python3.12
Timeout: 120
Role: !GetAtt TenantIsolatedFunctionRole.Arn
TenancyConfig:
TenantIsolationMode: PER_TENANT

SQSProcessorFunction:
Type: AWS::Serverless::Function
Properties:
FunctionName: sqs-processor
CodeUri: sqs-processor/
Handler: index.handler
Runtime: python3.12
Timeout: 60
Environment:
Variables:
TENANT_ISOLATED_FUNCTION_NAME: !Ref TenantIsolatedFunction
Policies:
- Statement:
- Effect: Allow
Action:
- lambda:InvokeFunction
Resource: !GetAtt TenantIsolatedFunction.Arn
Events:
SQSEvent:
Type: SQS
Properties:
Queue: !GetAtt ProcessingQueue.Arn
BatchSize: 10

Outputs:
QueueUrl:
Value: !Ref ProcessingQueue
QueueArn:
Value: !GetAtt ProcessingQueue.Arn
TenantIsolatedFunctionArn:
Value: !GetAtt TenantIsolatedFunction.Arn
SQSProcessorFunctionArn:
Value: !GetAtt SQSProcessorFunction.Arn
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import json

def handler(event, context):
tenant_id = context.tenant_id

print(f"Processing request for tenant: {tenant_id}")
print(f"Event data: {json.dumps(event)}")

# Process tenant-specific logic here
result = {
'tenant_id': tenant_id,
'message': 'Request processed successfully',
'data': event
}

return result
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# No external dependencies required