|
| 1 | +# Multi-Tenant Lambda Function Example |
| 2 | + |
| 3 | +This example demonstrates how to build a multi-tenant Lambda function using Swift and AWS Lambda's tenant isolation mode. Tenant isolation ensures that execution environments are dedicated to specific tenants, providing strict isolation for processing tenant-specific code or data. |
| 4 | + |
| 5 | +## Overview |
| 6 | + |
| 7 | +This example implements a request tracking system that maintains separate counters and request histories for each tenant. The Lambda function: |
| 8 | + |
| 9 | +- Accepts requests from multiple tenants via API Gateway |
| 10 | +- Maintains isolated execution environments per tenant |
| 11 | +- Tracks request counts and timestamps for each tenant |
| 12 | +- Returns tenant-specific data in JSON format |
| 13 | + |
| 14 | +## What is Tenant Isolation Mode? |
| 15 | + |
| 16 | +AWS Lambda's tenant isolation mode routes requests to execution environments based on a customer-specified tenant identifier. This ensures that: |
| 17 | + |
| 18 | +- **Execution environments are never reused across different tenants** - Each tenant gets dedicated execution environments |
| 19 | +- **Data isolation** - Tenant-specific data remains isolated from other tenants |
| 20 | +- **Firecracker virtualization** - Provides workload isolation at the infrastructure level |
| 21 | + |
| 22 | +### When to Use Tenant Isolation |
| 23 | + |
| 24 | +Use tenant isolation mode when building multi-tenant applications that: |
| 25 | + |
| 26 | +- **Execute end-user supplied code** - Limits the impact of potentially incorrect or malicious user code |
| 27 | +- **Process tenant-specific data** - Prevents exposure of sensitive data to other tenants |
| 28 | +- **Require strict isolation guarantees** - Such as SaaS platforms for workflow automation or code execution |
| 29 | + |
| 30 | +## Architecture |
| 31 | + |
| 32 | +The example consists of: |
| 33 | + |
| 34 | +1. **TenantData** - Immutable struct tracking tenant information: |
| 35 | + - `tenantID`: Unique identifier for the tenant |
| 36 | + - `requestCount`: Total number of requests from this tenant |
| 37 | + - `firstRequest`: ISO 8601 timestamp of the first request |
| 38 | + - `requests`: Array of individual request records |
| 39 | + |
| 40 | +2. **TenantDataStore** - Actor-based storage providing thread-safe access to tenant data across invocations |
| 41 | + |
| 42 | +3. **Lambda Handler** - Processes API Gateway requests and manages tenant data |
| 43 | + |
| 44 | +## Code Structure |
| 45 | + |
| 46 | +```swift |
| 47 | +// Immutable tenant data structure |
| 48 | +struct TenantData: Codable { |
| 49 | + let tenantID: String |
| 50 | + let requestCount: Int |
| 51 | + let firstRequest: String |
| 52 | + let requests: [TenantRequest] |
| 53 | + |
| 54 | + func addingRequest() -> TenantData { |
| 55 | + // Returns new instance with incremented count |
| 56 | + } |
| 57 | +} |
| 58 | + |
| 59 | +// Thread-safe tenant storage using Swift actors |
| 60 | +actor TenantDataStore { |
| 61 | + private var tenants: [String: TenantData] = [:] |
| 62 | + |
| 63 | + subscript(id: String) -> TenantData? { |
| 64 | + tenants[id] |
| 65 | + } |
| 66 | + |
| 67 | + func update(id: String, data: TenantData) { |
| 68 | + tenants[id] = data |
| 69 | + } |
| 70 | +} |
| 71 | + |
| 72 | +// Lambda handler extracts tenant ID from context |
| 73 | +let runtime = LambdaRuntime { |
| 74 | + (event: APIGatewayV2Request, context: LambdaContext) -> APIGatewayV2Response in |
| 75 | + |
| 76 | + guard let tenantID = context.tenantID else { |
| 77 | + return APIGatewayV2Response(statusCode: .badRequest, body: "No Tenant ID provided") |
| 78 | + } |
| 79 | + |
| 80 | + // Process request for this tenant |
| 81 | + let currentData = await tenants[tenantID] ?? TenantData(tenantID: tenantID) |
| 82 | + let updatedData = currentData.addingRequest() |
| 83 | + await tenants.update(id: tenantID, data: updatedData) |
| 84 | + |
| 85 | + return try APIGatewayV2Response(statusCode: .ok, encodableBody: updatedData) |
| 86 | +} |
| 87 | +``` |
| 88 | + |
| 89 | +## Configuration |
| 90 | + |
| 91 | +### SAM Template (template.yaml) |
| 92 | + |
| 93 | +The function is configured with tenant isolation mode in the SAM template: |
| 94 | + |
| 95 | +```yaml |
| 96 | +APIGatewayLambda: |
| 97 | + Type: AWS::Serverless::Function |
| 98 | + Properties: |
| 99 | + Runtime: provided.al2023 |
| 100 | + Architectures: |
| 101 | + - arm64 |
| 102 | + # Enable tenant isolation mode |
| 103 | + TenancyConfig: |
| 104 | + TenantIsolationMode: PER_TENANT |
| 105 | + Events: |
| 106 | + HttpApiEvent: |
| 107 | + Type: HttpApi |
| 108 | +``` |
| 109 | +
|
| 110 | +### Key Configuration Points |
| 111 | +
|
| 112 | +- **TenancyConfig.TenantIsolationMode**: Set to `PER_TENANT` to enable tenant isolation |
| 113 | +- **Immutable property**: Tenant isolation can only be enabled when creating a new function |
| 114 | +- **Required tenant-id**: All invocations must include a tenant identifier |
| 115 | + |
| 116 | +## Deployment |
| 117 | + |
| 118 | +### Prerequisites |
| 119 | + |
| 120 | +- Swift (>=6.2) |
| 121 | +- Docker (for cross-compilation to Amazon Linux) |
| 122 | +- AWS SAM CLI (>=1.147.1) |
| 123 | +- AWS CLI configured with appropriate credentials |
| 124 | + |
| 125 | +### Build and Deploy |
| 126 | + |
| 127 | +1. **Build the Lambda function**: |
| 128 | + ```bash |
| 129 | + swift package archive --allow-network-connections docker |
| 130 | + ``` |
| 131 | + |
| 132 | +2. **Deploy using SAM**: |
| 133 | + ```bash |
| 134 | + sam deploy --guided |
| 135 | + ``` |
| 136 | + |
| 137 | +3. **Note the API Gateway endpoint** from the CloudFormation outputs |
| 138 | + |
| 139 | +## Testing |
| 140 | + |
| 141 | +### Using API Gateway |
| 142 | + |
| 143 | +The tenant ID is passed as a query parameter: |
| 144 | + |
| 145 | +```bash |
| 146 | +# Request from tenant "alice" |
| 147 | +curl "https://your-api-id.execute-api.us-east-1.amazonaws.com?tenant-id=alice" |
| 148 | +
|
| 149 | +# Request from tenant "bob" |
| 150 | +curl "https://your-api-id.execute-api.us-east-1.amazonaws.com?tenant-id=bob" |
| 151 | +``` |
| 152 | + |
| 153 | +### Expected Response |
| 154 | + |
| 155 | +```json |
| 156 | +{ |
| 157 | + "tenantID": "alice", |
| 158 | + "requestCount": 3, |
| 159 | + "firstRequest": "2024-01-15T10:30:00Z", |
| 160 | + "requests": [ |
| 161 | + { |
| 162 | + "requestNumber": 1, |
| 163 | + "timestamp": "2024-01-15T10:30:00Z" |
| 164 | + }, |
| 165 | + { |
| 166 | + "requestNumber": 2, |
| 167 | + "timestamp": "2024-01-15T10:31:15Z" |
| 168 | + }, |
| 169 | + { |
| 170 | + "requestNumber": 3, |
| 171 | + "timestamp": "2024-01-15T10:32:30Z" |
| 172 | + } |
| 173 | + ] |
| 174 | +} |
| 175 | +``` |
| 176 | + |
| 177 | +## How Tenant Isolation Works |
| 178 | + |
| 179 | +1. **Request arrives** with a tenant identifier (via query parameter, header, or direct invocation) |
| 180 | +2. **Lambda routes the request** to an execution environment dedicated to that tenant |
| 181 | +3. **Environment reuse** - Subsequent requests from the same tenant reuse the same environment (warm start) |
| 182 | +4. **Isolation guarantee** - Execution environments are never shared between different tenants |
| 183 | +5. **Data persistence** - Tenant data persists in memory across invocations within the same execution environment |
| 184 | + |
| 185 | +## Important Considerations |
| 186 | + |
| 187 | +### Concurrency and Scaling |
| 188 | + |
| 189 | +- Lambda imposes a limit of **2,500 tenant-isolated execution environments** (active or idle) for every 1,000 concurrent executions |
| 190 | +- Each tenant can scale independently based on their request volume |
| 191 | +- Cold starts occur more frequently due to tenant-specific environments |
| 192 | + |
| 193 | +### Pricing |
| 194 | + |
| 195 | +- Standard Lambda pricing applies (compute time and requests) |
| 196 | +- **Additional charge** when Lambda creates a new tenant-isolated execution environment |
| 197 | +- Price depends on allocated memory and CPU architecture |
| 198 | +- See [AWS Lambda Pricing](https://aws.amazon.com/lambda/pricing) for details |
| 199 | + |
| 200 | +### Limitations |
| 201 | + |
| 202 | +Tenant isolation mode is **not supported** with: |
| 203 | +- Function URLs |
| 204 | +- Provisioned concurrency |
| 205 | +- SnapStart |
| 206 | + |
| 207 | +### Supported Invocation Methods |
| 208 | + |
| 209 | +- ✅ Synchronous invocations |
| 210 | +- ✅ Asynchronous invocations |
| 211 | +- ✅ API Gateway event triggers |
| 212 | +- ✅ AWS SDK invocations |
| 213 | + |
| 214 | +## Security Best Practices |
| 215 | + |
| 216 | +1. **Execution role applies to all tenants** - Use IAM policies to restrict access to tenant-specific resources |
| 217 | +2. **Validate tenant identifiers** - Ensure tenant IDs are properly authenticated and authorized |
| 218 | +3. **Implement tenant-aware logging** - Include tenant ID in CloudWatch logs for audit trails |
| 219 | +4. **Set appropriate timeouts** - Configure function timeout based on expected workload |
| 220 | +5. **Monitor per-tenant metrics** - Use CloudWatch to track invocations, errors, and duration per tenant |
| 221 | + |
| 222 | +## Monitoring |
| 223 | + |
| 224 | +### CloudWatch Metrics |
| 225 | + |
| 226 | +Lambda automatically publishes metrics with tenant dimensions: |
| 227 | + |
| 228 | +- `Invocations` - Number of invocations per tenant |
| 229 | +- `Duration` - Execution time per tenant |
| 230 | +- `Errors` - Error count per tenant |
| 231 | +- `Throttles` - Throttled requests per tenant |
| 232 | + |
| 233 | +### Accessing Metrics |
| 234 | + |
| 235 | +```bash |
| 236 | +# Get invocation count for a specific tenant |
| 237 | +aws cloudwatch get-metric-statistics \ |
| 238 | + --namespace AWS/Lambda \ |
| 239 | + --metric-name Invocations \ |
| 240 | + --dimensions Name=FunctionName,Value=MultiTenant Name=TenantId,Value=alice \ |
| 241 | + --start-time 2024-01-15T00:00:00Z \ |
| 242 | + --end-time 2024-01-15T23:59:59Z \ |
| 243 | + --period 3600 \ |
| 244 | + --statistics Sum |
| 245 | +``` |
| 246 | + |
| 247 | +## Learn More |
| 248 | + |
| 249 | +- [AWS Lambda Tenant Isolation Documentation](https://docs.aws.amazon.com/lambda/latest/dg/tenant-isolation.html) |
| 250 | +- [Configuring Tenant Isolation](https://docs.aws.amazon.com/lambda/latest/dg/tenant-isolation-configure.html) |
| 251 | +- [Invoking Tenant-Isolated Functions](https://docs.aws.amazon.com/lambda/latest/dg/tenant-isolation-invoke.html) |
| 252 | +- [AWS Blog: Streamlined Multi-Tenant Application Development](https://aws.amazon.com/blogs/aws/streamlined-multi-tenant-application-development-with-tenant-isolation-mode-in-aws-lambda/) |
| 253 | +- [Swift AWS Lambda Runtime](https://github.com/swift-server/swift-aws-lambda-runtime) |
| 254 | + |
| 255 | +## License |
| 256 | + |
| 257 | +This example is part of the Swift AWS Lambda Runtime project and is licensed under Apache License 2.0. |
0 commit comments