Le migliori pratiche per le funzioni durevoli Lambda - AWS Lambda

Le traduzioni sono generate tramite traduzione automatica. In caso di conflitto tra il contenuto di una traduzione e la versione originale in Inglese, quest'ultima prevarrà.

Le migliori pratiche per le funzioni durevoli Lambda

Le funzioni durevoli utilizzano un modello di esecuzione basato sulla riproduzione che richiede modelli diversi rispetto alle tradizionali funzioni Lambda. Segui queste best practice per creare flussi di lavoro affidabili e convenienti.

Scrivi codice deterministico

Durante la riproduzione, la funzione viene eseguita dall'inizio e deve seguire lo stesso percorso di esecuzione dell'esecuzione originale. Il codice al di fuori delle operazioni durevoli deve essere deterministico e produrre gli stessi risultati con gli stessi input.

Suddividi le operazioni non deterministiche in fasi:

  • Generazione di numeri casuali e UUIDs

  • Ora o timestamp correnti

  • Chiamate API esterne e interrogazioni al database

  • Operazioni sul file system

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

Non utilizzare variabili o chiusure globali per condividere lo stato tra i passaggi. Passa i dati attraverso i valori restituiti. Lo stato globale si interrompe durante la riproduzione perché i passaggi restituiscono risultati memorizzati nella cache ma le variabili globali vengono reimpostate.

Evita le mutazioni di chiusura: le variabili catturate nelle chiusure possono perdere mutazioni durante la riproduzione. I passaggi restituiscono risultati memorizzati nella cache, ma gli aggiornamenti delle variabili al di fuori del passaggio non vengono riprodotti.

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}

Progettato per l'idempotenza

Le operazioni possono essere eseguite più volte a causa di nuovi tentativi o ripetizioni. Le operazioni non idempotenti causano effetti collaterali duplicati, come addebitare due volte i clienti o inviare più e-mail.

Usa token di idempotenza: genera token all'interno dei passaggi e includili nelle chiamate API esterne per evitare operazioni duplicate.

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}

Usa la at-most-once semantica: per operazioni critiche che non devono mai essere duplicate (transazioni finanziarie, detrazioni di inventario), configura la modalità di esecuzione. at-most-once

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

Idempotenza del database: utilizza check-before-write modelli, aggiornamenti condizionali o operazioni di alterazione per evitare la duplicazione dei record.

Gestisci lo stato in modo efficiente

Ogni checkpoint salva lo stato nell'archiviazione persistente. Gli oggetti a stato di grandi dimensioni aumentano i costi, rallentano il checkpoint e influiscono sulle prestazioni. Archivia solo i dati essenziali di coordinamento del flusso di lavoro.

Mantieni lo stato minimo:

  • Archivio IDs e riferimenti, non oggetti completi

  • Recupera dati dettagliati in pochi passaggi, se necessario

  • Usa Amazon S3 o DynamoDB per dati di grandi dimensioni, trasferisci i riferimenti nello stato

  • Evita di far passare carichi utili di grandi dimensioni tra i passaggi

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}

Progetta passaggi efficaci

I gradini sono l'unità di lavoro fondamentale nelle funzioni durevoli. I passaggi ben progettati semplificano la comprensione, il debug e la manutenzione dei flussi di lavoro.

Principi di progettazione Step:

  • Usa nomi descrittivi, ad esempio validate-order invece di step1 semplificare la comprensione di log ed errori

  • Mantieni i nomi statici: non utilizzare nomi dinamici con timestamp o valori casuali. I nomi delle fasi devono essere deterministici per la riproduzione

  • Equilibra la granularità: suddividi le operazioni complesse in fasi mirate, ma evita passaggi troppo piccoli che aumentano il sovraccarico dei checkpoint

  • Operazioni relative al gruppo: le operazioni che dovrebbero avere successo o fallire rientrano nella stessa fase

Usa le operazioni di attesa in modo efficiente

Le operazioni di attesa sospendono l'esecuzione senza consumare risorse o incorrere in costi. Usali invece di mantenere Lambda in funzione.

Attese basate sul tempo: utilizzale context.wait() per i ritardi anziché o. setTimeout sleep

Callback esterni: da utilizzare in attesa di context.waitForCallback() sistemi esterni. Imposta sempre dei timeout per evitare attese indefinite.

Sondaggi: utilizza context.waitForCondition() il backoff esponenziale per interrogare i servizi esterni senza sovraccaricarli.

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

Ulteriori considerazioni

Gestione degli errori: riprova gli errori temporanei come i timeout di rete e i limiti di velocità. Non riprovare con errori permanenti come errori di input o di autenticazione non validi. Configura le strategie di riprova con un numero massimo di tentativi e tassi di backoff appropriati. Per esempi dettagliati, consulta Gestione degli errori e nuovi tentativi.

Prestazioni: riduci al minimo le dimensioni del checkpoint memorizzando i riferimenti anziché i payload completi. Usa context.parallel() ed context.map() esegui operazioni indipendenti contemporaneamente. Operazioni relative ai batch per ridurre il sovraccarico dei checkpoint.

Controllo delle versioni: richiama funzioni con numeri di versione o alias per associare le esecuzioni a versioni di codice specifiche. Assicurati che le nuove versioni del codice siano in grado di gestire lo stato delle versioni precedenti. Non rinominate i passaggi né modificate il loro comportamento in modo da interrompere la riproduzione.

Serializzazione: utilizza tipi compatibili con JSON per gli input e i risultati delle operazioni. Converti le date in stringhe ISO e gli oggetti personalizzati in oggetti semplici prima di passarli a operazioni durevoli.

Monitoraggio: abilita la registrazione strutturata con nomi di esecuzione IDs e passaggi. Imposta CloudWatch allarmi per i tassi di errore e la durata dell'esecuzione. Usa il tracciamento per identificare i punti deboli. Per una guida dettagliata, consulta Monitoraggio e debug.

Test: verifica il percorso felice, la gestione degli errori e il comportamento di riproduzione. Prova gli scenari di timeout per callback e attese. Utilizza i test locali per ridurre i tempi di iterazione. Per una guida dettagliata, consulta Testare le funzioni durevoli.

Errori comuni da evitare: non context.step() annidate le chiamate, ma utilizzate invece contesti secondari. Suddividi le operazioni non deterministiche in fasi. Imposta sempre i timeout per i callback. Bilancia la granularità dei passaggi con il sovraccarico dei checkpoint. Memorizza i riferimenti anziché gli oggetti di grandi dimensioni nello stato.

Risorse aggiuntive