Práticas recomendadas para funções duráveis do Lambda
As funções duráveis usam um modelo de execução baseado em reprodução que exige padrões diferentes das funções do Lambda tradicionais. Siga essas melhores práticas para criar fluxos de trabalho confiáveis e econômicos.
Escreva código determinístico
Durante a reprodução, sua função é executada desde o início e deve seguir o mesmo caminho de execução da execução original. O código fora das operações duráveis deve ser determinístico, produzindo os mesmos resultados com as mesmas entradas.
Encapsule as operações não determinísticas em etapas:
Geração de números aleatórios e UUIDs
Hora atual ou timestamps
Chamadas de API externas e consultas de banco de dados
Operações do sistema de arquivos
- TypeScript
-
import { withDurableExecution, DurableContext } from '@aws/durable-execution-sdk-js';
import { randomUUID } from 'crypto';
export const handler = withDurableExecution(
async (event: any, context: DurableContext) => {
// Generate transaction ID inside a step
const transactionId = await context.step('generate-transaction-id', async () => {
return randomUUID();
});
// Use the same ID throughout execution, even during replay
const payment = await context.step('process-payment', async () => {
return processPayment(event.amount, transactionId);
});
return { statusCode: 200, transactionId, payment };
}
);
- Python
-
from aws_durable_execution_sdk_python import durable_execution, DurableContext
import uuid
@durable_execution
def handler(event, context: DurableContext):
# Generate transaction ID inside a step
transaction_id = context.step(
lambda _: str(uuid.uuid4()),
name='generate-transaction-id'
)
# Use the same ID throughout execution, even during replay
payment = context.step(
lambda _: process_payment(event['amount'], transaction_id),
name='process-payment'
)
return {'statusCode': 200, 'transactionId': transaction_id, 'payment': payment}
Importante
Não use variáveis globais ou fechamentos para compartilhar o estado entre as etapas. Passe dados por meio de valores de retorno. O estado global é interrompido durante a reprodução, pois as etapas retornam resultados em cache, mas as variáveis globais são redefinidas.
Evite mutações de encerramento: as variáveis capturadas em fechamentos podem perder mutações durante a reprodução. As etapas retornam resultados em cache, mas as atualizações de variáveis fora da etapa não são reproduzidas.
- TypeScript
-
// ❌ WRONG: Mutations lost on replay
export const handler = withDurableExecution(async (event, context) => {
let total = 0;
for (const item of items) {
await context.step(async () => {
total += item.price; // ⚠️ Mutation lost on replay!
return saveItem(item);
});
}
return { total }; // Inconsistent value!
});
// ✅ CORRECT: Accumulate with return values
export const handler = withDurableExecution(async (event, context) => {
let total = 0;
for (const item of items) {
total = await context.step(async () => {
const newTotal = total + item.price;
await saveItem(item);
return newTotal; // Return updated value
});
}
return { total }; // Consistent!
});
// ✅ EVEN BETTER: Use map for parallel processing
export const handler = withDurableExecution(async (event, context) => {
const results = await context.map(
items,
async (ctx, item) => {
await ctx.step(async () => saveItem(item));
return item.price;
}
);
const total = results.getResults().reduce((sum, price) => sum + price, 0);
return { total };
});
- Python
-
# ❌ WRONG: Mutations lost on replay
@durable_execution
def handler(event, context: DurableContext):
total = 0
for item in items:
context.step(
lambda _: save_item_and_mutate(item, total), # ⚠️ Mutation lost on replay!
name=f'save-item-{item["id"]}'
)
return {'total': total} # Inconsistent value!
# ✅ CORRECT: Accumulate with return values
@durable_execution
def handler(event, context: DurableContext):
total = 0
for item in items:
total = context.step(
lambda _: save_item_and_return_total(item, total),
name=f'save-item-{item["id"]}'
)
return {'total': total} # Consistent!
# ✅ EVEN BETTER: Use map for parallel processing
@durable_execution
def handler(event, context: DurableContext):
def process_item(ctx, item):
ctx.step(lambda _: save_item(item))
return item['price']
results = context.map(items, process_item)
total = sum(results.get_results())
return {'total': total}
Design para idempotência
As operações podem ser executadas várias vezes devido a novas tentativas ou repetições. Operações não idempotentes causam efeitos colaterais duplicados, como cobrar duas vezes dos clientes ou enviar vários e-mails.
Use tokens de idempotência: gere tokens dentro das etapas e inclua-os em chamadas externas de API para evitar operações duplicadas.
- TypeScript
-
import { withDurableExecution, DurableContext } from '@aws/durable-execution-sdk-js';
export const handler = withDurableExecution(
async (event: any, context: DurableContext) => {
// Generate idempotency token once
const idempotencyToken = await context.step('generate-idempotency-token', async () => {
return crypto.randomUUID();
});
// Use token to prevent duplicate charges
const charge = await context.step('charge-payment', async () => {
return paymentService.charge({
amount: event.amount,
cardToken: event.cardToken,
idempotencyKey: idempotencyToken
});
});
return { statusCode: 200, charge };
}
);
- Python
-
from aws_durable_execution_sdk_python import durable_execution, DurableContext
import uuid
@durable_execution
def handler(event, context: DurableContext):
# Generate idempotency token once
idempotency_token = context.step(
lambda _: str(uuid.uuid4()),
name='generate-idempotency-token'
)
# Use token to prevent duplicate charges
def charge_payment(_):
return payment_service.charge(
amount=event['amount'],
card_token=event['cardToken'],
idempotency_key=idempotency_token
)
charge = context.step(charge_payment, name='charge-payment')
return {'statusCode': 200, 'charge': charge}
Use a semântica de no máximo uma vez: para operações críticas que nunca devem ser duplicadas (transações financeiras, deduções de estoque), configure o modo de execução de no máximo uma vez.
- TypeScript
-
// Critical operation that must not duplicate
await context.step('deduct-inventory', async () => {
return inventoryService.deduct(event.productId, event.quantity);
}, {
executionMode: 'AT_MOST_ONCE_PER_RETRY'
});
- Python
-
# Critical operation that must not duplicate
context.step(
lambda _: inventory_service.deduct(event['productId'], event['quantity']),
name='deduct-inventory',
config=StepConfig(execution_mode='AT_MOST_ONCE_PER_RETRY')
)
Idempotência do banco de dados: use padrões de verificação antes da gravação, atualizações condicionais ou operações de atualização para evitar registros duplicados.
Gerencie o estado de forma eficiente
Cada ponto de verificação salva o estado no armazenamento persistente. Objetos de estado grandes aumentam os custos, diminuem o controle e afetam a performance. Armazene somente dados essenciais de coordenação do fluxo de trabalho.
Mantenha o estado mínimo:
Armazene IDs e referências, não objetos completos
Obtenha dados detalhados em etapas, conforme necessário
Use o Amazon S3 ou o DynamoDB para dados grandes, passe referências no estado
Evite passar grandes cargas úteis entre as etapas
- TypeScript
-
import { withDurableExecution, DurableContext } from '@aws/durable-execution-sdk-js';
export const handler = withDurableExecution(
async (event: any, context: DurableContext) => {
// Store only the order ID, not the full order object
const orderId = event.orderId;
// Fetch data within each step as needed
await context.step('validate-order', async () => {
const order = await orderService.getOrder(orderId);
return validateOrder(order);
});
await context.step('process-payment', async () => {
const order = await orderService.getOrder(orderId);
return processPayment(order);
});
return { statusCode: 200, orderId };
}
);
- Python
-
from aws_durable_execution_sdk_python import durable_execution, DurableContext
@durable_execution
def handler(event, context: DurableContext):
# Store only the order ID, not the full order object
order_id = event['orderId']
# Fetch data within each step as needed
context.step(
lambda _: validate_order(order_service.get_order(order_id)),
name='validate-order'
)
context.step(
lambda _: process_payment(order_service.get_order(order_id)),
name='process-payment'
)
return {'statusCode': 200, 'orderId': order_id}
Projete etapas eficazes
As etapas são a unidade fundamental de trabalho em funções duráveis. Etapas bem projetadas facilitam a compreensão, a depuração e a manutenção dos fluxos de trabalho.
Princípios do projeto de etapas
Use nomes descritivos: nomes como validate-order em vez de step1 tornam os logs e os erros mais fáceis de entender
Mantenha os nomes estáticos: não use nomes dinâmicos com timestamps ou valores aleatórios. Os nomes das etapas devem ser determinísticos para a reprodução
Equilibre a granularidade: divida operações complexas em etapas focadas, mas evite pequenas etapas excessivas que aumentem a sobrecarga do ponto de verificação
Operações relacionadas a grupos: as operações que devem ter êxito ou falhar juntas pertencem à mesma etapa
Use as operações de espera com eficiência
As operações de espera suspendem a execução sem consumir recursos ou incorrer em custos. Use-as em vez de manter o Lambda em execução.
Esperas com base no tempo: use context.wait() para retardos em vez de setTimeout ou sleep.
Retornos de chamada externos: use context.waitForCallback() ao esperar por sistemas externos. Sempre defina tempos limite para evitar esperas indefinidas.
Sondagem: use context.waitForCondition() com recuo exponencial para sondar serviços externos sem sobrecarregá-los.
- TypeScript
-
// Wait 24 hours without cost
await context.wait({ seconds: 86400 });
// Wait for external callback with timeout
const result = await context.waitForCallback(
'external-job',
async (callbackId) => {
await externalService.submitJob({
data: event.data,
webhookUrl: `https://api.example.com/callbacks/${callbackId}`
});
},
{ timeout: { seconds: 3600 } }
);
- Python
-
# Wait 24 hours without cost
context.wait(86400)
# Wait for external callback with timeout
result = context.wait_for_callback(
lambda callback_id: external_service.submit_job(
data=event['data'],
webhook_url=f'https://api.example.com/callbacks/{callback_id}'
),
name='external-job',
config=WaitForCallbackConfig(timeout_seconds=3600)
)
Considerações adicionais
Tratamento de erros: faça uma nova tentativa em falhas transitórias, como tempos limite de rede e limites de taxa. Não tente novamente falhas permanentes, como entrada inválida ou erros de autenticação. Configure estratégias de novas tentativas com o máximo de tentativas e taxas de recuo apropriadas. Para obter exemplos detalhados, consulte Tratamento de erros e novas tentativas.
Performance: minimize o tamanho do ponto de verificação armazenando referências em vez de cargas úteis completas. Use context.parallel() e context.map() para executar operações independentes simultaneamente. Execute operações relacionadas em lotes para reduzir a sobrecarga dos pontos de verificação.
Versionamento: invoque funções com números de versão ou aliases para fixar execuções em versões de código específicas. Certifique-se de que as novas versões do código possam tratar o estado das versões mais antigas. Não renomeie etapas nem altere seu comportamento de forma a interromper a reprodução.
Serialização: use tipos compatíveis com JSON para entradas e resultados da operação. Converta datas em sequências ISO e objetos personalizados em objetos simples antes de passá-los para operações duráveis.
Monitoramento: habilite o registro em log estruturado com IDs de execução e nomes de etapas. Configure os alarmes do CloudWatch para as taxas de erro e a duração da execução. Use rastreamento para identificar gargalos. Para obter orientação detalhada, consulte Monitoramento e depuração.
Testes: teste o caminho ideal, o tratamento de erros e o comportamento de reprodução. Teste cenários de tempo limite para retornos de chamada e esperas. Use testes locais para reduzir o tempo de iteração. Para obter orientação detalhada, consulte Testes de funções duráveis.
Erros comuns a serem evitados: não aninhe chamadas context.step(), use contextos secundários. Encapsule as operações não determinísticas em etapas. Sempre defina tempos limite para retornos de chamada. Equilibre a granularidade das etapas com a sobrecarga do ponto de verificação. Armazene referências em vez de objetos grandes no estado.
Recursos adicionais