diff --git a/docs/lambda-features/durable-functions.md b/docs/lambda-features/durable-functions.md new file mode 100644 index 00000000000..5f2aa6c7a7b --- /dev/null +++ b/docs/lambda-features/durable-functions.md @@ -0,0 +1,185 @@ +--- +title: Durable Functions +description: Using Powertools for AWS Lambda (Python) with Lambda Durable Functions +--- + + + +[Lambda Durable Functions](https://docs.aws.amazon.com/lambda/latest/dg/durable-functions.html){target="_blank" rel="nofollow"} enable you to build resilient multi-step workflows that can execute for up to one year. They use checkpoints to track progress and automatically recover from failures through replay. + +## Key concepts + +| Concept | Description | +| --------------------- | ------------------------------------------------------------------ | +| **Durable execution** | Complete lifecycle of a durable function, from start to completion | +| **Checkpoint** | Saved state that tracks progress through the workflow | +| **Replay** | Re-execution from the beginning, skipping completed checkpoints | +| **Steps** | Business logic with built-in retries and progress tracking | +| **Waits** | Suspend execution without incurring compute charges | + +## How it works + +Durable functions use a **checkpoint/replay mechanism**: + +1. Your code runs from the beginning +2. Completed operations are skipped using stored results +3. Execution continues from where it left off +4. State is automatically managed by the SDK + +## Powertools integration + +Powertools for AWS Lambda (Python) works seamlessly with Durable Functions. The [Durable Execution SDK](https://github.com/aws/aws-durable-execution-sdk-python){target="_blank" rel="nofollow"} has native integration with Powertools Logger via `context.set_logger()`. + +???+ note "Found an issue?" + If you encounter any issues using Powertools for AWS with Durable Functions, please [open an issue](https://github.com/aws-powertools/powertools-lambda-python/issues/new?template=bug_report.yml){target="_blank"}. + +### Logger + +The Durable Execution SDK provides a `context.logger` that automatically handles **log deduplication during replays**. You can integrate Powertools Logger to get structured JSON logging while keeping the deduplication benefits. + +#### Using Powertools Logger with context.set_logger + +For the best experience, set the Powertools Logger on the durable context: + +```python hl_lines="5 10" title="Integrating Powertools Logger with Durable Functions" +--8<-- "examples/lambda_features/durable_functions/src/using_logger.py" +``` + +This gives you: + +- **JSON structured logging** from Powertools for AWS +- **Log deduplication** during replays (logs from completed operations don't repeat) +- **Automatic SDK enrichment** (execution_arn, parent_id, name, attempt) +- **Lambda context injection** (request_id, function_name, etc.) + +#### Log deduplication during replay + +When you use `context.logger`, the SDK prevents duplicate logs during replays: + +```python title="Log deduplication behavior" +--8<-- "examples/lambda_features/durable_functions/src/log_deduplication.py" +``` + +???+ warning "Direct logger usage" + If you use the Powertools Logger directly (not through `context.logger`), logs will be emitted on every replay: + + ```python + # Logs will duplicate during replays + logger.info("This appears on every replay") + + # Use context.logger instead for deduplication + context.logger.info("This appears only once") + ``` + +### Tracer + +Tracer works with Durable Functions. Each execution creates trace segments. + +???+ note "Trace continuity" + Due to the replay mechanism, traces may not show a continuous flow. Each execution (including replays) creates separate trace segments. Use the `execution_arn` to correlate traces. + +```python hl_lines="5 9" title="Using Tracer with Durable Functions" +--8<-- "examples/lambda_features/durable_functions/src/using_tracer.py" +``` + +### Metrics + +Metrics work with Durable Functions, but be aware that **metrics may be emitted multiple times** during replay if not handled carefully. + +```python hl_lines="6 10 21" title="Using Metrics with Durable Functions" +--8<-- "examples/lambda_features/durable_functions/src/using_metrics.py" +``` + +???+ tip "Accurate metrics" + Emit metrics at workflow completion rather than during intermediate steps to avoid counting replays as new executions. + +### Idempotency + +The `@idempotent` decorator integrates with Durable Functions and is **replay-aware**. It's useful for protecting the Lambda handler entry point, especially for Event Source Mapping (ESM) invocations like SQS, Kinesis, or DynamoDB Streams. + +```python hl_lines="9 15" title="Using Idempotency with Durable Functions" +--8<-- "examples/lambda_features/durable_functions/src/using_idempotency.py" +``` + +**When to use Powertools Idempotency:** + +- Protecting the Lambda handler entry point from duplicate invocations +- Methods you don't want to convert into steps but need idempotency guarantees +- Event Source Mapping triggers (SQS, Kinesis, DynamoDB Streams) + +**When you don't need it:** + +- Steps within a durable function are already idempotent via the checkpoint mechanism + +### Parser + +Parser works with Durable Functions for validating and parsing event payloads. + +```python hl_lines="9 14" title="Using Parser with Durable Functions" +--8<-- "examples/lambda_features/durable_functions/src/using_parser.py" +``` + +### Parameters + +Parameters work normally with Durable Functions. + +```python hl_lines="13" title="Using Parameters with Durable Functions" +--8<-- "examples/lambda_features/durable_functions/src/using_parameters.py" +``` + +???+ note "Parameter freshness" + For long-running workflows (hours/days), parameters fetched at the start may become stale. Consider fetching parameters within steps that need the latest values. + +## Best practices + +### Use context.logger for log deduplication + +Always use `context.set_logger()` and `context.logger` instead of using the Powertools Logger directly. This ensures logs are deduplicated during replays. + +```python title="Recommended logging pattern" +--8<-- "examples/lambda_features/durable_functions/src/best_practice_logging.py" +``` + +### Emit metrics at workflow completion + +To avoid counting replays as new executions, emit metrics only when the workflow completes successfully. + +```python title="Metrics at completion" +--8<-- "examples/lambda_features/durable_functions/src/best_practice_metrics.py" +``` + +### Use Idempotency for ESM triggers + +When your durable function is triggered by Event Source Mappings (SQS, Kinesis, DynamoDB Streams), use the `@idempotent` decorator to protect against duplicate invocations. + +```python title="Idempotency for ESM" +--8<-- "examples/lambda_features/durable_functions/src/best_practice_idempotency.py" +``` + +## FAQ + +### Do I need Idempotency utility with Durable Functions? + +It depends on your use case. Steps within a durable function are already idempotent via checkpoints. However, the `@idempotent` decorator is useful for protecting the Lambda handler entry point, especially for Event Source Mapping invocations (SQS, Kinesis, DynamoDB Streams) where the same event might trigger multiple invocations. + +### Why do I see duplicate logs? + +If you're using the logger directly instead of `context.logger`, logs will be emitted on every replay. Use `context.set_logger(logger)` and then `context.logger.info()` to get automatic log deduplication. + +### How do I correlate logs across replays? + +Use the `execution_arn` field that's automatically added to every log entry when using `context.logger`: + +```sql +fields @timestamp, @message, execution_arn +| filter execution_arn = "arn:aws:lambda:us-east-1:123456789012:function:my-function:execution-id" +| sort @timestamp asc +``` + +### Can I use Tracer with Durable Functions? + +Yes, but be aware that each execution (including replays) creates separate trace segments. Use the `execution_arn` as a correlation identifier for end-to-end visibility. + +### How should I emit metrics without duplicates? + +Emit metrics at workflow completion rather than during intermediate steps. This ensures you count completed workflows, not replay attempts. diff --git a/docs/lambda-features/index.md b/docs/lambda-features/index.md new file mode 100644 index 00000000000..30d791c8601 --- /dev/null +++ b/docs/lambda-features/index.md @@ -0,0 +1,28 @@ +--- +title: Lambda Features +description: Using Powertools with advanced Lambda features +--- + + + +This section covers how to use Powertools for AWS Lambda (Python) with advanced Lambda features like Lambda Managed Instances and Durable Functions. + +
+ +- :material-server:{ .lg .middle } __Lambda Managed Instances__ + + --- + + Run Lambda functions on EC2 instances with multi-concurrent invocations + + [:octicons-arrow-right-24: Getting started](./managed-instances.md) + +- :material-state-machine:{ .lg .middle } __Durable Functions__ + + --- + + Build resilient multi-step workflows that can execute for up to one year + + [:octicons-arrow-right-24: Getting started](./durable-functions.md) + +
diff --git a/docs/lambda-features/managed-instances.md b/docs/lambda-features/managed-instances.md new file mode 100644 index 00000000000..66a0fa9ffaa --- /dev/null +++ b/docs/lambda-features/managed-instances.md @@ -0,0 +1,166 @@ +--- +title: Lambda Managed Instances +description: Using Powertools for AWS Lambda (Python) with Lambda Managed Instances +--- + + + +[Lambda Managed Instances](https://docs.aws.amazon.com/lambda/latest/dg/lambda-managed-instances.html){target="_blank" rel="nofollow"} enables you to run Lambda functions on Amazon EC2 instances without managing infrastructure. It supports multi-concurrent invocations, EC2 pricing models, and specialized compute options like Graviton4. + +## Key differences from Lambda (default) + +| Aspect | Lambda (default) | Lambda Managed Instances | +| ---------------- | ------------------------------------------- | ----------------------------------------------- | +| **Concurrency** | Single invocation per execution environment | Multiple concurrent invocations per environment | +| **Python model** | One process, one request | Multiple processes, one request each | +| **Pricing** | Per-request duration | EC2-based with Savings Plans support | +| **Scaling** | Scale on demand with cold starts | Async scaling based on CPU, no cold starts | +| **Isolation** | Firecracker microVMs | Containers on EC2 Nitro | + +## How Lambda Python runtime handles concurrency + +Unlike Java or Node.js which use threads, the **Lambda Python runtime uses multiple processes** for concurrent requests. Each request runs in a separate process, which provides natural isolation between requests. + +This means: + +- **Memory is not shared** between concurrent requests +- **Global variables** are isolated per process +- **`/tmp` directory is shared** across all processes - use caution with file operations + +## Isolation model + +Lambda Managed Instances use a different isolation model than Lambda (default): + +| Layer | Lambda (default) | Lambda Managed Instances | +| ---------------------- | ---------------------------------------- | ------------------------------------------ | +| **Instance level** | Firecracker microVMs on shared AWS fleet | Containers on EC2 Nitro in your account | +| **Security boundary** | Execution environment | Capacity provider | +| **Function isolation** | Strong isolation via microVMs | Container-based isolation within instances | + +**Capacity providers** serve as the security boundary. Functions within the same capacity provider share the underlying EC2 instances. For workloads requiring strong isolation between functions, use separate capacity providers. + +For Python specifically, the multi-process model adds another layer of isolation - each concurrent request runs in its own process with separate memory space. + +## Powertools integration + +Powertools for AWS Lambda (Python) works seamlessly with Lambda Managed Instances. All utilities are compatible with the multi-process concurrency model used by Python. + +### Logger + +Logger works without any changes. Each process has its own logger instance. + +```python hl_lines="4 7" title="Using Logger with Managed Instances" +--8<-- "examples/lambda_features/managed_instances/src/using_logger.py" +``` + +### Tracer + +Tracer works without any changes. X-Ray traces are captured per request. + +???+ note "VPC connectivity required" + Lambda Managed Instances run in your VPC. Ensure you have [network connectivity](https://docs.aws.amazon.com/lambda/latest/dg/lambda-managed-instances-networking.html){target="_blank" rel="nofollow"} to send traces to X-Ray. + +```python hl_lines="4 8 12" title="Using Tracer with Managed Instances" +--8<-- "examples/lambda_features/managed_instances/src/using_tracer.py" +``` + +### Metrics + +Metrics work without any changes. Each process flushes metrics independently. + +???+ note "VPC connectivity required" + Ensure you have [network connectivity](https://docs.aws.amazon.com/lambda/latest/dg/lambda-managed-instances-networking.html){target="_blank" rel="nofollow"} to send metrics to CloudWatch. + +```python hl_lines="5 9 12" title="Using Metrics with Managed Instances" +--8<-- "examples/lambda_features/managed_instances/src/using_metrics.py" +``` + +### Parameters + +Parameters utility works correctly, but be aware that **cache is per-process**. + +```python hl_lines="9" title="Using Parameters with Managed Instances" +--8<-- "examples/lambda_features/managed_instances/src/using_parameters.py" +``` + +???+ tip "Cache behavior" + Since each process has its own cache, you might see more calls to SSM/Secrets Manager during initial warm-up. Once each process has cached the value, subsequent requests within that process use the cache. + +### Idempotency + +Idempotency works without any changes. It uses DynamoDB for state management, which is external to the process. + +```python hl_lines="7 10" title="Using Idempotency with Managed Instances" +--8<-- "examples/lambda_features/managed_instances/src/using_idempotency.py" +``` + +### Batch Processing + +Batch Processing works without any changes. Each batch is processed within a single process. + +```python hl_lines="5 8 14" title="Using Batch Processing with Managed Instances" +--8<-- "examples/lambda_features/managed_instances/src/using_batch.py" +``` + +???+ note "Other utilities" + All other Powertools for AWS utilities (Feature Flags, Validation, Parser, Data Masking, etc.) work without any changes. If you encounter any issues, please [open an issue](https://github.com/aws-powertools/powertools-lambda-python/issues/new?template=bug_report.yml){target="_blank"}. + +## Working with shared resources + +### The `/tmp` directory + +The `/tmp` directory is **shared across all processes** in the execution environment. Use caution when writing files. + +```python title="Safe file handling with unique names" +--8<-- "examples/lambda_features/managed_instances/src/tmp_file_handling.py" +``` + +### Database connections + +Since each process is independent, connection pooling behaves differently than in threaded runtimes. + +```python title="Database connections per process" +--8<-- "examples/lambda_features/managed_instances/src/database_connections.py" +``` + +## VPC connectivity + +Lambda Managed Instances require VPC configuration for: + +- Sending logs to CloudWatch Logs +- Sending traces to X-Ray +- Accessing AWS services (SSM, Secrets Manager, DynamoDB, etc.) + +Configure connectivity using one of these options: + +1. **VPC Endpoints** - Private connectivity without internet access +2. **NAT Gateway** - Internet access from private subnets +3. **Public subnet with Internet Gateway** - Direct internet access + +See [Networking for Lambda Managed Instances](https://docs.aws.amazon.com/lambda/latest/dg/lambda-managed-instances-networking.html){target="_blank" rel="nofollow"} for detailed setup instructions. + +## FAQ + +### Does Powertools for AWS Lambda (Python) work with Lambda Managed Instances? + +Yes, all Powertools for AWS utilities work seamlessly with Lambda Managed Instances. The multi-process model in Python provides natural isolation between concurrent requests. + +### Is my code thread-safe? + +For Python, you don't need to worry about thread safety because Lambda Managed Instances uses **multiple processes**, not threads. Each request runs in its own process with isolated memory. + +### Why is my cache not shared between requests? + +Each process maintains its own cache (for Parameters, Feature Flags, etc.). This is expected behavior. The cache will warm up independently per process, which may result in slightly more calls to backend services during initial warm-up. + +### Can I use global variables? + +Yes, but remember they are **per-process**, not shared across concurrent requests. This is actually safer than shared state. + +### How should I handle files in `/tmp`? + +Use unique file names (include request ID or UUID) to avoid conflicts between concurrent requests. Always clean up files after use to avoid filling the shared `/tmp` directory. + +### Do I need to change my existing Powertools for AWS code? + +No changes are required. Your existing code will work as-is with Lambda Managed Instances. diff --git a/examples/lambda_features/__init__.py b/examples/lambda_features/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/examples/lambda_features/durable_functions/__init__.py b/examples/lambda_features/durable_functions/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/examples/lambda_features/durable_functions/src/__init__.py b/examples/lambda_features/durable_functions/src/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/examples/lambda_features/durable_functions/src/best_practice_idempotency.py b/examples/lambda_features/durable_functions/src/best_practice_idempotency.py new file mode 100644 index 00000000000..6d412b6127b --- /dev/null +++ b/examples/lambda_features/durable_functions/src/best_practice_idempotency.py @@ -0,0 +1,21 @@ +from aws_durable_execution_sdk_python import DurableContext, durable_execution # type: ignore[import-not-found] + +from aws_lambda_powertools.utilities.idempotency import ( + DynamoDBPersistenceLayer, + idempotent, +) + +persistence_layer = DynamoDBPersistenceLayer(table_name="IdempotencyTable") + + +@idempotent(persistence_store=persistence_layer) +@durable_execution +def handler(event: dict, context: DurableContext) -> str: + # Protected against duplicate SQS/Kinesis/DynamoDB triggers + + result: str = context.step( + lambda _: "processed", + name="process", + ) + + return result diff --git a/examples/lambda_features/durable_functions/src/best_practice_logging.py b/examples/lambda_features/durable_functions/src/best_practice_logging.py new file mode 100644 index 00000000000..9855240bdef --- /dev/null +++ b/examples/lambda_features/durable_functions/src/best_practice_logging.py @@ -0,0 +1,21 @@ +from aws_durable_execution_sdk_python import DurableContext, durable_execution # type: ignore[import-not-found] + +from aws_lambda_powertools import Logger + +logger = Logger(service="my-service") + + +@durable_execution +def handler(event: dict, context: DurableContext) -> str: + context.set_logger(logger) + + # Use context.logger for deduplication + context.logger.info("Starting workflow") + + result: str = context.step( + lambda _: "processed", + name="process", + ) + + context.logger.info("Workflow completed") + return result diff --git a/examples/lambda_features/durable_functions/src/best_practice_metrics.py b/examples/lambda_features/durable_functions/src/best_practice_metrics.py new file mode 100644 index 00000000000..fcf624bbc55 --- /dev/null +++ b/examples/lambda_features/durable_functions/src/best_practice_metrics.py @@ -0,0 +1,19 @@ +from aws_durable_execution_sdk_python import DurableContext, durable_execution # type: ignore[import-not-found] + +from aws_lambda_powertools import Metrics +from aws_lambda_powertools.metrics import MetricUnit + +metrics = Metrics() + + +@metrics.log_metrics +@durable_execution +def handler(event: dict, context: DurableContext) -> str: + result: str = context.step( + lambda _: "processed", + name="process", + ) + + # Emit only at the end + metrics.add_metric(name="WorkflowCompleted", unit=MetricUnit.Count, value=1) + return result diff --git a/examples/lambda_features/durable_functions/src/log_deduplication.py b/examples/lambda_features/durable_functions/src/log_deduplication.py new file mode 100644 index 00000000000..7744a27b040 --- /dev/null +++ b/examples/lambda_features/durable_functions/src/log_deduplication.py @@ -0,0 +1,21 @@ +from aws_durable_execution_sdk_python import DurableContext, durable_execution # type: ignore[import-not-found] + +from aws_lambda_powertools import Logger + +logger = Logger(service="order-processing") + + +@durable_execution +def handler(event: dict, context: DurableContext) -> str: + context.set_logger(logger) + + # This log appears only once, even if the function is replayed + context.logger.info("Starting workflow") + + result1: str = context.step(lambda _: "step1-done", name="step_1") + context.logger.info("Step 1 completed") # Only once + + result2: str = context.step(lambda _: "step2-done", name="step_2") + context.logger.info("Step 2 completed") # Only once + + return f"{result1}-{result2}" diff --git a/examples/lambda_features/durable_functions/src/using_idempotency.py b/examples/lambda_features/durable_functions/src/using_idempotency.py new file mode 100644 index 00000000000..04654dd5c9d --- /dev/null +++ b/examples/lambda_features/durable_functions/src/using_idempotency.py @@ -0,0 +1,26 @@ +from aws_durable_execution_sdk_python import DurableContext, durable_execution # type: ignore[import-not-found] + +from aws_lambda_powertools.utilities.idempotency import ( + DynamoDBPersistenceLayer, + idempotent, +) + +persistence_layer = DynamoDBPersistenceLayer(table_name="IdempotencyTable") + + +def process_order(event: dict) -> str: + return f"processed-{event.get('order_id')}" + + +@idempotent(persistence_store=persistence_layer) +@durable_execution +def handler(event: dict, context: DurableContext) -> str: + # Idempotency protects against duplicate ESM invocations + # Steps within the workflow are already idempotent via checkpoints + + result: str = context.step( + lambda _: process_order(event), + name="process_order", + ) + + return result diff --git a/examples/lambda_features/durable_functions/src/using_logger.py b/examples/lambda_features/durable_functions/src/using_logger.py new file mode 100644 index 00000000000..c49e5bdc998 --- /dev/null +++ b/examples/lambda_features/durable_functions/src/using_logger.py @@ -0,0 +1,22 @@ +from aws_durable_execution_sdk_python import DurableContext, durable_execution # type: ignore[import-not-found] + +from aws_lambda_powertools import Logger + +logger = Logger(service="order-processing") + + +@durable_execution +def handler(event: dict, context: DurableContext) -> str: + # Set Powertools Logger on the context + context.set_logger(logger) + + # Now context.logger uses Powertools with automatic enrichment and deduplication + context.logger.info("Starting workflow", extra={"order_id": event.get("order_id")}) + + result: str = context.step( + lambda _: "processed", + name="process_order", + ) + + context.logger.info("Workflow completed", extra={"result": result}) + return result diff --git a/examples/lambda_features/durable_functions/src/using_metrics.py b/examples/lambda_features/durable_functions/src/using_metrics.py new file mode 100644 index 00000000000..bdb42b28c1d --- /dev/null +++ b/examples/lambda_features/durable_functions/src/using_metrics.py @@ -0,0 +1,23 @@ +from aws_durable_execution_sdk_python import DurableContext, durable_execution # type: ignore[import-not-found] + +from aws_lambda_powertools import Logger, Metrics +from aws_lambda_powertools.metrics import MetricUnit + +logger = Logger() +metrics = Metrics() + + +@metrics.log_metrics +@durable_execution +def handler(event: dict, context: DurableContext) -> str: + context.set_logger(logger) + + result: str = context.step( + lambda _: "processed", + name="process_order", + ) + + # Emit metrics only at workflow completion to avoid duplicates + metrics.add_metric(name="WorkflowCompleted", unit=MetricUnit.Count, value=1) + + return result diff --git a/examples/lambda_features/durable_functions/src/using_parameters.py b/examples/lambda_features/durable_functions/src/using_parameters.py new file mode 100644 index 00000000000..8fd6daca7c8 --- /dev/null +++ b/examples/lambda_features/durable_functions/src/using_parameters.py @@ -0,0 +1,20 @@ +from aws_durable_execution_sdk_python import DurableContext, durable_execution # type: ignore[import-not-found] + +from aws_lambda_powertools.utilities import parameters + + +def call_api(api_key: str) -> str: + return f"called-with-{api_key[:4]}..." + + +@durable_execution +def handler(event: dict, context: DurableContext) -> str: + # Parameters are fetched on each execution + api_key = parameters.get_secret("api-key") + + result: str = context.step( + lambda _: call_api(api_key), + name="call_api", + ) + + return result diff --git a/examples/lambda_features/durable_functions/src/using_parser.py b/examples/lambda_features/durable_functions/src/using_parser.py new file mode 100644 index 00000000000..d1dbb611de9 --- /dev/null +++ b/examples/lambda_features/durable_functions/src/using_parser.py @@ -0,0 +1,23 @@ +from aws_durable_execution_sdk_python import DurableContext, durable_execution # type: ignore[import-not-found] +from pydantic import BaseModel + +from aws_lambda_powertools.utilities.parser import event_parser + + +class OrderEvent(BaseModel): + order_id: str + amount: float + + +@event_parser(model=OrderEvent) +@durable_execution +def handler(event: OrderEvent, context: DurableContext) -> str: + # Event is already validated and parsed + context.logger.info("Processing order", extra={"order_id": event.order_id}) + + result: str = context.step( + lambda _: f"processed-{event.order_id}", + name="process_order", + ) + + return result diff --git a/examples/lambda_features/durable_functions/src/using_tracer.py b/examples/lambda_features/durable_functions/src/using_tracer.py new file mode 100644 index 00000000000..0e494ce88b5 --- /dev/null +++ b/examples/lambda_features/durable_functions/src/using_tracer.py @@ -0,0 +1,26 @@ +from aws_durable_execution_sdk_python import DurableContext, durable_execution # type: ignore[import-not-found] + +from aws_lambda_powertools import Logger, Tracer + +tracer = Tracer() +logger = Logger() + + +@tracer.capture_lambda_handler +@durable_execution +def handler(event: dict, context: DurableContext) -> str: + context.set_logger(logger) + + result: str = context.step( + lambda _: process_data(), + name="process_data", + ) + + return result + + +@tracer.capture_method +def process_data() -> str: + # This is traced on first execution + # On replay, the cached result is used + return "processed" diff --git a/examples/lambda_features/managed_instances/__init__.py b/examples/lambda_features/managed_instances/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/examples/lambda_features/managed_instances/src/__init__.py b/examples/lambda_features/managed_instances/src/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/examples/lambda_features/managed_instances/src/database_connections.py b/examples/lambda_features/managed_instances/src/database_connections.py new file mode 100644 index 00000000000..4e230554df7 --- /dev/null +++ b/examples/lambda_features/managed_instances/src/database_connections.py @@ -0,0 +1,17 @@ +import boto3 + +from aws_lambda_powertools import Logger +from aws_lambda_powertools.utilities.typing import LambdaContext + +logger = Logger() + +# Each process creates its own client +# This is fine - boto3 clients are not thread-safe anyway +dynamodb = boto3.resource("dynamodb") +table = dynamodb.Table("MyTable") + + +def lambda_handler(event: dict, context: LambdaContext) -> dict: + # Each process has its own connection + response = table.get_item(Key={"pk": event["id"]}) + return {"statusCode": 200, "body": response.get("Item")} diff --git a/examples/lambda_features/managed_instances/src/tmp_file_handling.py b/examples/lambda_features/managed_instances/src/tmp_file_handling.py new file mode 100644 index 00000000000..210c29a65a7 --- /dev/null +++ b/examples/lambda_features/managed_instances/src/tmp_file_handling.py @@ -0,0 +1,25 @@ +from pathlib import Path + +from aws_lambda_powertools import Logger +from aws_lambda_powertools.utilities.typing import LambdaContext + +logger = Logger() + + +def lambda_handler(event: dict, context: LambdaContext) -> dict: + # Use unique file names to avoid conflicts + request_id = context.aws_request_id + temp_file = Path(f"/tmp/data_{request_id}.json") + + try: + with temp_file.open("w") as f: + f.write('{"data": "example"}') + + # Process file... + + finally: + # Clean up to avoid filling /tmp + if temp_file.exists(): + temp_file.unlink() + + return {"statusCode": 200} diff --git a/examples/lambda_features/managed_instances/src/using_batch.py b/examples/lambda_features/managed_instances/src/using_batch.py new file mode 100644 index 00000000000..8a86425b415 --- /dev/null +++ b/examples/lambda_features/managed_instances/src/using_batch.py @@ -0,0 +1,16 @@ +from aws_lambda_powertools.utilities.batch import BatchProcessor, EventType, process_partial_response +from aws_lambda_powertools.utilities.batch.types import PartialItemFailureResponse +from aws_lambda_powertools.utilities.data_classes.sqs_event import SQSRecord +from aws_lambda_powertools.utilities.typing import LambdaContext + +processor = BatchProcessor(event_type=EventType.SQS) + + +def record_handler(record: SQSRecord) -> None: + # Process each record + payload = record.body # noqa: F841 + # Your processing logic here + + +def lambda_handler(event: dict, context: LambdaContext) -> PartialItemFailureResponse: + return process_partial_response(event=event, record_handler=record_handler, processor=processor, context=context) diff --git a/examples/lambda_features/managed_instances/src/using_idempotency.py b/examples/lambda_features/managed_instances/src/using_idempotency.py new file mode 100644 index 00000000000..e0e054d07f6 --- /dev/null +++ b/examples/lambda_features/managed_instances/src/using_idempotency.py @@ -0,0 +1,14 @@ +from aws_lambda_powertools.utilities.idempotency import ( + DynamoDBPersistenceLayer, + idempotent, +) +from aws_lambda_powertools.utilities.typing import LambdaContext + +persistence_layer = DynamoDBPersistenceLayer(table_name="IdempotencyTable") + + +@idempotent(persistence_store=persistence_layer) +def lambda_handler(event: dict, context: LambdaContext) -> dict: + # Idempotency is guaranteed across all concurrent requests + # DynamoDB handles the distributed locking + return {"statusCode": 200, "body": "Order processed"} diff --git a/examples/lambda_features/managed_instances/src/using_logger.py b/examples/lambda_features/managed_instances/src/using_logger.py new file mode 100644 index 00000000000..d1293120a14 --- /dev/null +++ b/examples/lambda_features/managed_instances/src/using_logger.py @@ -0,0 +1,15 @@ +from aws_lambda_powertools import Logger +from aws_lambda_powertools.utilities.typing import LambdaContext + +logger = Logger() + + +@logger.inject_lambda_context +def lambda_handler(event: dict, context: LambdaContext) -> dict: + logger.info("Processing request") + + # Each concurrent request has its own logger instance + # Correlation IDs are isolated per request + logger.append_keys(order_id=event.get("order_id")) + + return {"statusCode": 200} diff --git a/examples/lambda_features/managed_instances/src/using_metrics.py b/examples/lambda_features/managed_instances/src/using_metrics.py new file mode 100644 index 00000000000..a9804b394d5 --- /dev/null +++ b/examples/lambda_features/managed_instances/src/using_metrics.py @@ -0,0 +1,15 @@ +from aws_lambda_powertools import Logger, Metrics +from aws_lambda_powertools.metrics import MetricUnit +from aws_lambda_powertools.utilities.typing import LambdaContext + +metrics = Metrics() +logger = Logger() + + +@metrics.log_metrics +@logger.inject_lambda_context +def lambda_handler(event: dict, context: LambdaContext) -> dict: + # Metrics are flushed per request + metrics.add_metric(name="OrderProcessed", unit=MetricUnit.Count, value=1) + + return {"statusCode": 200} diff --git a/examples/lambda_features/managed_instances/src/using_parameters.py b/examples/lambda_features/managed_instances/src/using_parameters.py new file mode 100644 index 00000000000..36ded9cc9c6 --- /dev/null +++ b/examples/lambda_features/managed_instances/src/using_parameters.py @@ -0,0 +1,11 @@ +from aws_lambda_powertools.utilities import parameters +from aws_lambda_powertools.utilities.typing import LambdaContext + + +def lambda_handler(event: dict, context: LambdaContext) -> dict: + # Cache is per-process, not shared across concurrent requests + # Each process maintains its own cache + # This is generally fine - cache will warm up per process + api_key = parameters.get_secret("my-api-key", max_age=300) # noqa: F841 + + return {"statusCode": 200} diff --git a/examples/lambda_features/managed_instances/src/using_tracer.py b/examples/lambda_features/managed_instances/src/using_tracer.py new file mode 100644 index 00000000000..3cefa4edabd --- /dev/null +++ b/examples/lambda_features/managed_instances/src/using_tracer.py @@ -0,0 +1,19 @@ +from aws_lambda_powertools import Logger, Tracer +from aws_lambda_powertools.utilities.typing import LambdaContext + +tracer = Tracer() +logger = Logger() + + +@tracer.capture_lambda_handler +@logger.inject_lambda_context +def lambda_handler(event: dict, context: LambdaContext) -> dict: + order_id = event.get("order_id", "unknown") + result = process_order(order_id) + return {"statusCode": 200, "body": result} + + +@tracer.capture_method +def process_order(order_id: str) -> str: + # Each concurrent request creates its own trace + return f"Processed order {order_id}" diff --git a/mkdocs.yml b/mkdocs.yml index db49e9e45f7..ea429021eb0 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -38,6 +38,10 @@ nav: - utilities/middleware_factory.md - utilities/jmespath_functions.md - CloudFormation Custom Resources: https://github.com/aws-cloudformation/custom-resource-helper" target="_blank + - Lambda Features: + - lambda-features/index.md + - lambda-features/managed-instances.md + - lambda-features/durable-functions.md - Build recipes: - build_recipes/index.md - Getting started: build_recipes/getting-started.md