Bewährte Methoden für langlebige Lambda-Funktionen - AWS Lambda

Die vorliegende Übersetzung wurde maschinell erstellt. Im Falle eines Konflikts oder eines Widerspruchs zwischen dieser übersetzten Fassung und der englischen Fassung (einschließlich infolge von Verzögerungen bei der Übersetzung) ist die englische Fassung maßgeblich.

Bewährte Methoden für langlebige Lambda-Funktionen

Dauerhafte Funktionen verwenden ein auf Wiedergabe basierendes Ausführungsmodell, das andere Muster erfordert als herkömmliche Lambda-Funktionen. Folgen Sie diesen bewährten Methoden, um zuverlässige und kostengünstige Workflows zu erstellen.

Schreiben Sie deterministischen Code

Während der Wiedergabe wird Ihre Funktion von Anfang an ausgeführt und muss demselben Ausführungspfad folgen wie die ursprüngliche Ausführung. Code außerhalb dauerhafter Operationen muss deterministisch sein und bei denselben Eingaben dieselben Ergebnisse liefern.

Unterteilen Sie nichtdeterministische Operationen schrittweise:

  • Generierung von Zufallszahlen und UUIDs

  • Aktuelle Uhrzeit oder Zeitstempel

  • Externe API-Aufrufe und Datenbankabfragen

  • Dateisystemoperationen

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}
Wichtig

Verwenden Sie keine globalen Variablen oder Schließungen, um den Status zwischen Schritten zu teilen. Übergeben Sie Daten durch Rückgabewerte. Der globale Status wird während der Wiedergabe unterbrochen, weil Schritte zwischengespeicherte Ergebnisse zurückgeben, globale Variablen jedoch zurückgesetzt werden.

Vermeiden Sie Mutationen bei Verschlüssen: Variablen, die in Closures erfasst wurden, können während der Wiedergabe Mutationen verlieren. Schritte geben zwischengespeicherte Ergebnisse zurück, aber Variablenaktualisierungen außerhalb des Schritts werden nicht wiederholt.

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 für Idempotenz

Operationen können aufgrund von Wiederholungen oder Wiederholungen mehrfach ausgeführt werden. Operationen, die nicht idempotent sind, haben doppelte Nebenwirkungen, z. B. doppelte Gebühren für Kunden oder das Senden mehrerer E-Mails.

Verwenden Sie Idempotenz-Token: Generieren Sie Token innerhalb von Schritten und fügen Sie sie externen API-Aufrufen hinzu, um doppelte Operationen zu vermeiden.

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}

at-most-onceSemantik verwenden: Für kritische Operationen, die sich niemals duplizieren dürfen (Finanztransaktionen, Inventarabzüge), konfigurieren Sie den Ausführungsmodus. 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') )

Datenbankidempotenz: Verwenden Sie check-before-write Muster, bedingte Aktualisierungen oder Upsert-Operationen, um doppelte Datensätze zu verhindern.

Verwalten Sie den Status effizient

Jeder Checkpoint speichert den Status im persistenten Speicher. Große Zustandsobjekte erhöhen die Kosten, verlangsamen Checkpoints und beeinträchtigen die Leistung. Speichern Sie nur wichtige Daten zur Workflow-Koordination.

Halten Sie den Status minimal:

  • Speichern IDs und Verweise, nicht vollständige Objekte

  • Rufen Sie nach Bedarf detaillierte Daten in Schritten ab

  • Verwenden Sie Amazon S3 oder DynamoDB für große Datenmengen, übergeben Sie Referenzen im Status

  • Vermeiden Sie es, große Nutzlasten zwischen den Schritten weiterzugeben

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}

Entwerfen Sie effektive Schritte

Stufen sind die grundlegende Arbeitseinheit für langlebige Funktionen. Gut durchdachte Schritte erleichtern das Verständnis, das Debuggen und die Wartung von Workflows.

Prinzipien der schrittweisen Gestaltung:

  • Verwende aussagekräftige Namen — Namen wie, validate-order anstatt Logs und Fehler leichter verständlich zu step1 machen

  • Namen statisch halten — Verwenden Sie keine dynamischen Namen mit Zeitstempeln oder Zufallswerten. Schrittnamen müssen für die Wiedergabe deterministisch sein

  • Ausgewogene Granularität — Teilen Sie komplexe Vorgänge in gezielte Schritte auf, vermeiden Sie jedoch übermäßig kleine Schritte, die den Aufwand an den Checkpoints erhöhen

  • Gruppenbezogene Operationen — Operationen, die gemeinsam erfolgreich sein oder fehlschlagen sollten, gehören in denselben Schritt

Verwenden Sie Warteoperationen effizient

Wartevorgänge unterbrechen die Ausführung, ohne Ressourcen zu verbrauchen oder Kosten zu verursachen. Verwenden Sie sie, anstatt Lambda am Laufen zu halten.

Zeitbasierte Wartezeiten: Verwenden Sie diese Option context.wait() für Verzögerungen anstelle von oder. setTimeout sleep

Externe Rückrufe: context.waitForCallback() Wird verwendet, wenn auf externe Systeme gewartet wird. Stellen Sie immer Timeouts ein, um unbestimmte Wartezeiten zu vermeiden.

Polling: Verwenden Sie diese Option context.waitForCondition() mit exponentiellem Backoff, um externe Dienste abzufragen, ohne sie zu überfordern.

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

Weitere Überlegungen

Fehlerbehandlung: Versuchen Sie es erneut mit vorübergehenden Fehlern wie Netzwerk-Timeouts und Ratenbegrenzungen. Versuchen Sie es nicht erneut mit dauerhaften Fehlern wie ungültigen Eingabe- oder Authentifizierungsfehlern. Konfigurieren Sie Wiederholungsstrategien mit einer angemessenen maximalen Anzahl von Versuchen und Backoff-Raten. Ausführliche Beispiele finden Sie unter Fehlerbehandlung und Wiederholungen.

Leistung: Minimiere die Größe von Checkpoints, indem du Referenzen statt vollständiger Payloads speicherst. Verwenden Sie context.parallel() undcontext.map(), um unabhängige Operationen gleichzeitig auszuführen. Batch-bezogene Operationen zur Reduzierung des Checkpoint-Overheads.

Versionierung: Rufen Sie Funktionen mit Versionsnummern oder Aliasnamen auf, um Ausführungen an bestimmte Codeversionen zu binden. Stellen Sie sicher, dass neue Codeversionen den Status älterer Versionen verarbeiten können. Benennen Sie Schritte nicht um und ändern Sie ihr Verhalten nicht so, dass die Wiedergabe unterbrochen wird.

Serialisierung: Verwenden Sie JSON-kompatible Typen für Operationseingaben und -ergebnisse. Konvertiert Datumsangaben in ISO-Zeichenketten und benutzerdefinierte Objekte in einfache Objekte, bevor Sie sie an dauerhafte Operationen übergeben.

Überwachung: Aktivieren Sie die strukturierte Protokollierung mit Ausführungs IDs - und Schrittnamen. Richten Sie CloudWatch Alarme für Fehlerraten und Ausführungsdauer ein. Verwenden Sie Tracing, um Engpässe zu identifizieren. Eine ausführliche Anleitung finden Sie unter Überwachung und Debuggen.

Testen: Testen Sie Happy Path, Fehlerbehandlung und Wiedergabeverhalten. Testen Sie Timeout-Szenarien für Rückrufe und Wartezeiten. Verwenden Sie lokale Tests, um die Iterationszeit zu reduzieren. Eine ausführliche Anleitung finden Sie unter Dauerhafte Funktionen testen.

Häufige Fehler, die Sie vermeiden sollten: Verschachteln Sie context.step() Anrufe nicht, sondern verwenden Sie stattdessen untergeordnete Kontexte. Fassen Sie nicht deterministische Operationen schrittweise zusammen. Legen Sie immer Timeouts für Rückrufe fest. Gleichen Sie die Granularität der Schritte mit dem Checkpoint-Overhead ab. Speichern Sie Referenzen statt großer Objekte im Status.

Weitere Ressourcen