Skip to content
Merged
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
151 changes: 151 additions & 0 deletions docs/lambda-features/durable-functions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
---
title: Durable Functions
description: Using Powertools for AWS Lambda (Python) with Lambda Durable Functions
---

<!-- markdownlint-disable MD043 -->

[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 |
| **Step** | Business logic with built-in retries and progress tracking |
| **Wait** | Suspend execution without incurring compute charges |

## How it works

Durable functions use a **checkpoint/replay mechanism**:

1. Your code runs always from the beginning
2. Completed operations are skipped using stored results
3. Execution of new steps 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 Logger via `context.set_logger()`.

???+ note "Found an issue?"
If you encounter any issues using Powertools for AWS Lambda (Python) 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` instance that automatically handles **log deduplication during replays**. You can integrate Logger to get structured JSON logging while keeping the deduplication benefits.

For the best experience, set the Logger on the durable context. This gives you structured JSON logging with automatic log deduplication during replays:

```python hl_lines="5 8 12 15" title="Integrating Logger with Durable Functions"
--8<-- "examples/lambda_features/durable_functions/src/using_logger.py"
```

This gives you:

- **JSON structured logging** from Powertools for AWS Lambda (Python)
- **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.)

???+ warning "Direct logger usage"
If you use the 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 be interleaved. Each execution (including replays) creates separate trace segments. Use the `execution_arn` to correlate traces.

```python hl_lines="5-6 9-10" 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. Emit metrics at workflow completion rather than during intermediate steps to avoid counting replays as new executions.

```python hl_lines="6 9 18 19 20 21" title="Using Metrics with Durable Functions"
--8<-- "examples/lambda_features/durable_functions/src/best_practice_metrics.py"
```

### 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="8 15" title="Using Idempotency with Durable Functions"
--8<-- "examples/lambda_features/durable_functions/src/using_idempotency.py"
```

???+ warning "Decorator ordering matters"
The `@idempotent` decorator must be placed **above** `@durable_execution`. This ensures the idempotency check runs first, preventing duplicate executions before the durable workflow begins. Reversing the order would cause the durable execution to start before the idempotency check, defeating its purpose.

**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

### 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"
If the replay or execution happens within the cache TTL on the same execution environment, the parameter value may come from cache. For long-running workflows (hours/days), parameters fetched at the start may become stale. Consider fetching parameters within steps that need the latest values, and customize the caching behavior with `max_age` to control freshness.

## Best practices

### 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.
28 changes: 28 additions & 0 deletions docs/lambda-features/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
---
title: Lambda Features
description: Using Powertools with advanced Lambda features
---

<!-- markdownlint-disable MD043 -->

This section covers how to use Powertools for AWS Lambda (Python) with advanced Lambda features like Lambda Managed Instances and Durable Functions.

<div class="grid cards" markdown>

- :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)

</div>
102 changes: 102 additions & 0 deletions docs/lambda-features/managed-instances.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
---
title: Lambda Managed Instances
description: Using Powertools for AWS Lambda (Python) with Lambda Managed Instances
---

<!-- markdownlint-disable MD043 -->

[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 On Demand

| Aspect | Lambda On Demand | 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 |
| **Isolation** | Firecracker microVMs | Containers on EC2 Nitro |

## How Lambda Python runtime handles concurrency

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:

- **Each process has its own memory** - global variables are isolated per process
- **`/tmp` directory is shared** across all processes - use caution with file operations

For more details on the isolation model, see [Lambda Managed Instances documentation](https://docs.aws.amazon.com/lambda/latest/dg/lambda-managed-instances.html){target="_blank" rel="nofollow"}.

## 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, Tracer, and Metrics

Core utilities work without any changes. Each process has its own instances, so correlation IDs and traces are naturally isolated 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 logs to CloudWatch, traces to X-Ray, and metrics to CloudWatch.

```python hl_lines="5 6 7 10 11 12 20 25" title="Using Logger, Tracer, and Metrics with Managed Instances"
--8<-- "examples/lambda_features/managed_instances/src/using_tracer.py"
```

### Parameters

The Parameters utility works as expected, but be aware that **caching 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. You can customize the caching behavior with `max_age` to control the TTL.

### 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"
```

## 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
4. **Egress-only Internet Gateway** - IPv6 outbound connectivity without inbound access ([learn more](https://docs.aws.amazon.com/vpc/latest/userguide/egress-only-internet-gateway.html){target="_blank" rel="nofollow"})

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 Lambda (Python) utilities work seamlessly with Lambda Managed Instances. The multi-process model in Python provides natural isolation between concurrent requests.

### Is my code thread-safe?

Lambda Managed Instances uses **multiple processes**, instead of threads. Each request runs in its own process with isolated memory. If you implement multi-threading within your handler, you are responsible for thread safety.

### 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.

### Do I need to change my existing Powertools for AWS Lambda (Python) code?

No changes are required if you are running Powertools for AWS Lambda (Python) version **3.4.0** or later. Your existing code will work as-is with Lambda Managed Instances.
Empty file.
Empty file.
Empty file.
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
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 metrics in a dedicated step to ensure they are only counted once
context.step(
lambda _: metrics.add_metric(name="WorkflowCompleted", unit=MetricUnit.Count, value=1),
name="emit_completion_metric",
)

return result
Original file line number Diff line number Diff line change
@@ -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
25 changes: 25 additions & 0 deletions examples/lambda_features/durable_functions/src/using_logger.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
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")


@logger.inject_lambda_context
@durable_execution
def handler(event: dict, context: DurableContext) -> str:
# Set Logger on the context for automatic deduplication
context.set_logger(logger)

# Logs via context.logger appear only once, even during replays
context.logger.info("Starting workflow", extra={"order_id": event.get("order_id")})

result: str = context.step(
lambda _: "processed",
name="process_order",
)

# This log won't repeat when the function replays after completing the step above
context.logger.info("Workflow completed", extra={"result": result})

return result
Loading
Loading