Praktik terbaik untuk fungsi tahan lama Lambda - AWS Lambda

Terjemahan disediakan oleh mesin penerjemah. Jika konten terjemahan yang diberikan bertentangan dengan versi bahasa Inggris aslinya, utamakan versi bahasa Inggris.

Praktik terbaik untuk fungsi tahan lama Lambda

Fungsi tahan lama menggunakan model eksekusi berbasis replay yang membutuhkan pola berbeda dari fungsi Lambda tradisional. Ikuti praktik terbaik ini untuk membangun alur kerja yang andal dan hemat biaya.

Tulis kode deterministik

Selama pemutaran ulang, fungsi Anda berjalan dari awal dan harus mengikuti jalur eksekusi yang sama dengan proses aslinya. Kode di luar operasi tahan lama harus deterministik, menghasilkan hasil yang sama dengan input yang sama.

Bungkus operasi non-deterministik dalam langkah-langkah:

  • Pembuatan angka acak dan UUIDs

  • Waktu atau stempel waktu saat ini

  • Panggilan API eksternal dan kueri database

  • Operasi sistem file

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

Jangan gunakan variabel global atau penutupan untuk berbagi status antar langkah. Lewati data melalui nilai pengembalian. Status global rusak selama pemutaran ulang karena langkah-langkah mengembalikan hasil yang di-cache tetapi variabel global diatur ulang.

Hindari mutasi penutupan: Variabel yang ditangkap dalam penutupan dapat kehilangan mutasi selama pemutaran ulang. Langkah-langkah mengembalikan hasil cache, tetapi pembaruan variabel di luar langkah tidak diputar ulang.

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}

Desain untuk idempotensi

Operasi dapat dijalankan beberapa kali karena mencoba ulang atau memutar ulang. Operasi non-idempoten menyebabkan efek samping duplikat seperti menagih pelanggan dua kali atau mengirim beberapa email.

Gunakan token idempotensi: Hasilkan token di dalam langkah-langkah dan sertakan dengan panggilan API eksternal untuk mencegah operasi duplikat.

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}

Gunakan at-most-once semantik: Untuk operasi kritis yang tidak boleh diduplikasi (transaksi keuangan, pengurangan inventaris), konfigurasikan mode eksekusi. 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') )

Idempotensi basis data: Gunakan check-before-write pola, pembaruan bersyarat, atau operasi upsert untuk mencegah duplikat catatan.

Kelola negara secara efisien

Setiap pos pemeriksaan menyimpan status ke penyimpanan persisten. Objek status besar meningkatkan biaya, pos pemeriksaan lambat, dan kinerja dampak. Simpan hanya data koordinasi alur kerja yang penting.

Pertahankan status minimal:

  • Simpan IDs dan referensi, bukan objek penuh

  • Ambil data terperinci dalam langkah-langkah sesuai kebutuhan

  • Gunakan Amazon S3 atau DynamoDB untuk data besar, teruskan referensi dalam status

  • Hindari melewatkan muatan besar di antara langkah-langkah

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}

Rancang langkah-langkah efektif

Langkah-langkah adalah unit dasar kerja dalam fungsi yang tahan lama. Langkah-langkah yang dirancang dengan baik membuat alur kerja lebih mudah dipahami, di-debug, dan dipelihara.

Prinsip desain langkah:

  • Gunakan nama deskriptif - Nama seperti validate-order alih-alih step1 membuat log dan kesalahan lebih mudah dipahami

  • Pertahankan nama statis - Jangan gunakan nama dinamis dengan cap waktu atau nilai acak. Nama langkah harus deterministik untuk diputar ulang

  • Granularitas keseimbangan - Pecahkan operasi kompleks menjadi langkah-langkah yang terfokus, tetapi hindari langkah-langkah kecil yang berlebihan yang meningkatkan overhead pos pemeriksaan

  • Operasi terkait kelompok - Operasi yang harus berhasil atau gagal bersama termasuk dalam langkah yang sama

Gunakan operasi tunggu secara efisien

Tunggu operasi menangguhkan eksekusi tanpa menghabiskan sumber daya atau menimbulkan biaya. Gunakan mereka alih-alih menjaga Lambda tetap berjalan.

Penantian berbasis waktu: Gunakan context.wait() untuk penundaan, bukan atau. setTimeout sleep

Callback eksternal: Gunakan context.waitForCallback() saat menunggu sistem eksternal. Selalu atur batas waktu untuk mencegah penantian yang tidak terbatas.

Polling: Gunakan context.waitForCondition() dengan backoff eksponensial untuk melakukan polling layanan eksternal tanpa membebani mereka.

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

Pertimbangan tambahan

Penanganan kesalahan: Coba lagi kegagalan sementara seperti batas waktu jaringan dan batas tarif. Jangan mencoba lagi kegagalan permanen seperti kesalahan input atau otentikasi yang tidak valid. Konfigurasikan strategi coba lagi dengan upaya maksimal dan tingkat backoff yang sesuai. Untuk contoh rinci, lihat Penanganan kesalahan dan percobaan ulang.

Kinerja: Minimalkan ukuran pos pemeriksaan dengan menyimpan referensi alih-alih muatan penuh. Gunakan context.parallel() dan context.map() untuk menjalankan operasi independen secara bersamaan. Operasi terkait batch untuk mengurangi overhead pos pemeriksaan.

Versioning: Memanggil fungsi dengan nomor versi atau alias untuk menyematkan eksekusi ke versi kode tertentu. Pastikan versi kode baru dapat menangani status dari versi yang lebih lama. Jangan mengganti nama langkah atau mengubah perilaku mereka dengan cara yang merusak pemutaran ulang.

Serialisasi: Gunakan tipe yang kompatibel dengan JSON untuk input dan hasil operasi. Konversikan tanggal ke string ISO dan objek kustom menjadi objek biasa sebelum meneruskannya ke operasi yang tahan lama.

Pemantauan: Aktifkan pencatatan terstruktur dengan nama eksekusi IDs dan langkah. Siapkan CloudWatch alarm untuk tingkat kesalahan dan durasi eksekusi. Gunakan penelusuran untuk mengidentifikasi kemacetan. Untuk panduan terperinci, lihat Monitoring dan debugging.

Pengujian: Uji jalur bahagia, penanganan kesalahan, dan perilaku pemutaran ulang. Uji skenario batas waktu untuk panggilan balik dan menunggu. Gunakan pengujian lokal untuk mengurangi waktu iterasi. Untuk panduan terperinci, lihat Menguji fungsi tahan lama.

Kesalahan umum yang harus dihindari: Jangan membuat context.step() panggilan sarang, gunakan konteks anak sebagai gantinya. Bungkus operasi non-deterministik dalam langkah-langkah. Selalu atur batas waktu untuk callback. Seimbangkan granularitas langkah dengan overhead pos pemeriksaan. Simpan referensi alih-alih objek besar dalam keadaan.

Sumber daya tambahan