00. Kiến thức cần có trước khi đọc
Trước khi đọc bài này, bạn nên có kiến thức cơ bản về:
- AWS Lambda: Biết cách tạo function, gọi (invoke), và cấu hình trigger
- Async/Await: Hiểu cách viết code bất đồng bộ trong JavaScript/TypeScript hoặc Python
- API Gateway (không bắt buộc): Biết cách tạo HTTP endpoint cho Lambda
Nếu bạn chưa quen với Lambda, hãy đọc bài Generate Thumbnail Images using S3 Event Notification and Lambda trước.
01. Giới thiệu
Tại AWS re-Invent 2025, AWS đã công bố Lambda Durable Functions - một tính năng mới cho phép Lambda chạy các workflow dài lên đến 1 năm, với khả năng tự động tiếp tục khi gặp lỗi.
"Developers wanna build like you're building monoliths, but you want to deploy microservices" — Eric Johnson, Principal Developer Advocate, AWS
Nói đơn giản: Lambda Durable cho phép bạn viết code tuần tự, dễ đọc như ứng dụng thông thường, nhưng bên dưới nó tự động xử lý các vấn đề phức tạp của hệ thống phân tán (distributed systems).
Giải thích thuật ngữ:
- Durable (Bền vững): Không bị mất trạng thái khi có lỗi
- Checkpoint: Điểm lưu trạng thái - giống như "save game" trong game
- Replay: Chạy lại code từ đầu, nhưng bỏ qua các bước đã hoàn thành
- Orchestration: Điều phối nhiều service làm việc cùng nhau theo một luồng xử lý
02. Tại sao cần Lambda Durable?
2.1 Vấn đề với Lambda thông thường

Hãy tưởng tượng bạn đang xây dựng hệ thống xử lý đơn hàng e-commerce:
export const handler = async (event) => {
const order = await validateOrder(event.orderId);
const payment = await chargePayment(order.amount);
await reduceInventory(order.items);
await sendConfirmationEmail(order.email);
await waitForShipperConfirmation(order.id);
return { status: 'completed' };
};
Các vấn đề:
| Vấn đề | Mô tả | Hậu quả |
|---|
| Giới hạn 15 phút | Lambda chỉ chạy tối đa 15 phút | Không thể chờ shipper xác nhận |
| Không có checkpoint | Nếu reduceInventory lỗi, phải chạy lại từ đầu | Khách hàng bị trừ tiền 2 lần! |
| Phải tự xử lý retry | Phải tự viết code thử lại khi lỗi | Code phức tạp, khó bảo trì |
2.2 Lambda Durable giải quyết như thế nào?

import { withDurableExecution, DurableContext } from '@aws-lambda-powertools/durable';
export const handler = withDurableExecution(async (ctx: DurableContext, event) => {
const order = await ctx.step('validate', () => validateOrder(event.orderId));
const payment = await ctx.step('charge', () => chargePayment(order.amount));
await ctx.step('reduce-inventory', () => reduceInventory(order.items));
await ctx.step('send-email', () => sendConfirmationEmail(order.email));
await ctx.waitForCallback('shipper-confirm', {
callbackId: order.id,
timeout: '7d'
});
return { status: 'completed' };
});
Kết quả:
- Có thể chạy lên đến 1 năm
- Nếu lỗi ở bước 3, tự động tiếp tục từ bước 3 (không trừ tiền lại)
- Code vẫn đơn giản, tuần tự, dễ đọc
03. Cơ chế Checkpoint và Replay - Trái tim của Durable
Đây là phần quan trọng nhất cần hiểu. Hãy xem sơ đồ AWS cung cấp:
Để dễ hiểu hơn mình xem như bên dưới:

Điểm quan trọng cần nhớ:
- Code luôn chạy từ đầu function mỗi lần chạy lại
- Các bước đã lưu trạng thái sẽ bỏ qua việc thực thi và trả về kết quả đã lưu
- Vì vậy, không bao giờ trừ tiền 2 lần dù code chạy từ đầu
3.1 Ví dụ minh họa chi tiết
export const handler = withDurableExecution(async (ctx, event) => {
console.log('Handler bắt đầu chạy');
const step1Result = await ctx.step('step-1', async () => {
console.log('Step 1 đang thực thi');
return 'result-from-step-1';
});
console.log(`Step 1 result: ${step1Result}`);
});
Kết quả lần chạy đầu:
Handler bắt đầu chạy
Step 1 đang thực thi
Step 1 result: result-from-step-1
Kết quả khi chạy lại (step 1 đã lưu trạng thái):
Handler bắt đầu chạy
Step 1 result: result-from-step-1
(Không có "Step 1 đang thực thi" vì nó được bỏ qua!)
04. Các thành phần cơ bản
4.1 ctx.step() - Đơn vị công việc được lưu trạng thái
const result = await ctx.step('tên-step-duy-nhất', async () => {
return 'kết quả';
});
const user = await ctx.step('get-user', async () => {
const response = await fetch(`https://api.example.com/users/${userId}`);
return response.json();
});
Quy tắc đặt tên step:
- Phải duy nhất trong function
- Nên mô tả rõ step làm gì:
'validate-order', 'charge-payment', 'send-email'
4.2 ctx.wait() - Chờ đợi thông minh
Lambda Durable hỗ trợ 3 kiểu chờ:
4.2.1 Chờ theo thời gian (Timer Wait)
await ctx.step('create-order', () => createOrder(event));
await ctx.wait('wait-24h', { duration: '24h' });
await ctx.step('send-reminder', () => sendReminderEmail(event.email));
Trường hợp sử dụng: Gửi nhắc nhở sau X ngày, thử lại sau một khoảng thời gian
4.2.2 Chờ sự kiện bên ngoài (Callback Wait)
const approval = await ctx.waitForCallback('manager-approval', {
callbackId: `request-${requestId}`,
timeout: '7d'
});
if (approval.status === 'approved') {
await ctx.step('process', () => processRequest(requestId));
} else {
await ctx.step('reject', () => rejectRequest(requestId));
}
Kích hoạt callback từ bên ngoài:
aws lambda invoke-durable-callback \
--function-name MyFunction \
--callback-id "request-12345" \
--payload '{"status": "approved", "approver": "manager@company.com"}'
Trường hợp sử dụng: Phê duyệt từ người dùng, webhook thanh toán, tích hợp bên thứ ba
4.2.3 Chờ điều kiện thỏa mãn (Condition Wait)
await ctx.waitForCondition('payment-confirmed', async () => {
const status = await checkPaymentStatus(paymentId);
return status === 'SUCCESS';
}, {
interval: '30s',
timeout: '2h'
});
Trường hợp sử dụng: Đợi hệ thống bên ngoài sẵn sàng, kiểm tra trạng thái định kỳ
4.3 ctx.parallel() - Thực thi song song
const [inventory, pricing, shipping] = await ctx.parallel([
ctx.step('check-inventory', () => checkInventory(productId)),
ctx.step('get-pricing', () => getPricing(productId)),
ctx.step('calculate-shipping', () => calculateShipping(address))
]);
05. Saga Pattern - Xử lý lỗi trong hệ thống phân tán
5.1 Vấn đề: Giao dịch phân tán (Distributed Transaction)
Hãy tưởng tượng bạn đang xây dựng hệ thống đặt tour du lịch:
Đặt Tour Đà Nẵng 3N2Đ:
1. Đặt vé máy bay VN Airlines: 5,000,000 VNĐ
2. Đặt khách sạn Hilton: 3,000,000 VNĐ
3. Đặt xe đưa đón sân bay: 500,000 VNĐ
```saga-pattern-tour-booking.drawio
**Vấn đề**: Nếu bước 3 (đặt xe) lỗi, bạn cần:
- Hủy vé máy bay (bước 1) → hoàn tiền
- Hủy khách sạn (bước 2) → hoàn tiền
Đây gọi là **compensation** (bù đắp/hoàn tác).
### 5.2 Saga Pattern với Lambda Durable
```typescript
export const handler = withDurableExecution(async (ctx: DurableContext, event) => {
const { customerId, tourId, flightDate, hotelDate } = event;
const flight = await ctx.step('book-flight',
async () => {
const booking = await flightAPI.book({
customerId,
date: flightDate,
route: 'SGN-DAD'
});
return booking;
},
{
compensation: async () => {
await flightAPI.cancel(flight.bookingId);
console.log('Đã hoàn vé máy bay');
}
}
);
const hotel = await ctx.step('book-hotel',
async () => {
const booking = await hotelAPI.book({
customerId,
date: hotelDate,
hotel: 'Hilton Da Nang'
});
return booking;
},
{
compensation: async () => {
await hotelAPI.cancel(hotel.bookingId);
console.log('Đã hủy đặt phòng khách sạn');
}
}
);
const car = await ctx.step('book-car', async () => {
const booking = await carAPI.book({
customerId,
pickupTime: flightDate,
location: 'Da Nang Airport'
});
return booking;
});
return {
status: 'success',
bookings: { flight, hotel, car }
};
});
Luồng xử lý khi bước 3 lỗi:

06. So sánh Lambda Durable với Step Functions
| Tiêu chí | Lambda Durable | Step Functions |
|---|
| Cách viết | Code TypeScript/Python | JSON (Amazon States Language) |
| Độ khó học | Dễ (quen thuộc với lập trình viên) | Trung bình (cần học ngôn ngữ mới) |
| Gỡ lỗi | Console.log, breakpoints | Giao diện lịch sử thực thi |
| Kiểm thử đơn vị | Jest/pytest bình thường | Cần mock phức tạp |
| Trực quan hóa | Không có giao diện sơ đồ | Có giao diện workflow đẹp |
| Tích hợp AWS | Cần viết code trong step | Hơn 220 tích hợp trực tiếp |
| Chi phí | $8/triệu thao tác | $25/triệu chuyển đổi trạng thái |
6.1 Khi nào nên dùng Lambda Durable?
✅ Dùng Lambda Durable khi:
- Team của bạn thích viết code hơn kéo thả giao diện
- Logic phức tạp với nhiều if/else, vòng lặp
- Cần kiểm thử đơn vị dễ dàng
- Muốn tiết kiệm chi phí (rẻ hơn ~68%)
✅ Dùng Step Functions khi:
- Cần sơ đồ workflow trực quan cho BA/stakeholder xem
- Nhiều tích hợp AWS service (S3, DynamoDB, SQS...)
- Team có người không phải lập trình viên
- Cần giao diện lịch sử thực thi chi tiết
6.2 Có thể kết hợp cả hai

Step Functions (Điều phối cấp cao)
│
├── Lambda Durable (Logic nghiệp vụ phức tạp)
│ └── Xử lý đơn hàng với Saga pattern
│
├── Lambda đơn giản (Tác vụ nhanh)
│ └── Gửi thông báo
│
└── Tích hợp AWS trực tiếp
└── Ghi vào DynamoDB
07. Bài thực hành: Hello Durable
Hãy tạo một Lambda Durable function đơn giản để làm quen.
7.1 Bước 1: Tạo dự án
mkdir hello-durable && cd hello-durable
npm init -y
npm install @aws-lambda-powertools/durable typescript @types/node
7.2 Bước 2: Viết code
src/index.ts:
import { withDurableExecution, DurableContext } from '@aws-lambda-powertools/durable';
interface OrderEvent {
orderId: string;
customerEmail: string;
amount: number;
}
export const handler = withDurableExecution(async (
ctx: DurableContext,
event: OrderEvent
) => {
console.log(`Đang xử lý đơn hàng: ${event.orderId}`);
const isValid = await ctx.step('validate-order', async () => {
console.log('Đang kiểm tra đơn hàng...');
await sleep(1000);
return event.amount > 0;
});
if (!isValid) {
return { status: 'failed', reason: 'Số tiền không hợp lệ' };
}
const paymentId = await ctx.step('process-payment', async () => {
console.log('Đang xử lý thanh toán...');
await sleep(2000);
return `PAY-${Date.now()}`;
});
await ctx.step('send-confirmation', async () => {
console.log(`Đang gửi email đến ${event.customerEmail}`);
await sleep(500);
});
return {
status: 'completed',
orderId: event.orderId,
paymentId: paymentId
};
});
const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
7.3 Bước 3: Cấu hình SAM
template.yaml:
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Resources:
HelloDurableFunction:
Type: AWS::Serverless::Function
Properties:
FunctionName: hello-durable
Handler: dist/index.handler
Runtime: nodejs22.x
Timeout: 900
MemorySize: 256
DurableExecution:
Enabled: true
Policies:
- Statement:
- Effect: Allow
Action:
- lambda:CheckpointDurableExecution
- lambda:GetDurableExecutionState
Resource: !Sub 'arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:hello-durable'
7.4 Bước 4: Triển khai và kiểm tra
npm run build
sam deploy --guided
aws lambda invoke \
--function-name hello-durable \
--payload '{"orderId":"ORD-001","customerEmail":"test@example.com","amount":100}' \
response.json
cat response.json
08. Nhược điểm cần chú ý
Lambda Durable không phải "viên đạn bạc"!
Mặc dù Lambda Durable mang lại nhiều lợi ích, nhưng bạn cần hiểu rõ các nhược điểm để đưa ra quyết định kiến trúc phù hợp. Đừng dùng Lambda Durable cho mọi trường hợp - hãy cân nhắc kỹ trước khi áp dụng.
8.1 Code không xác định kết quả (Non-deterministic) bên ngoài step
"Non-deterministic code" là gì?
Trong lập trình, có 2 loại code:
| Loại | Đặc điểm | Ví dụ |
|---|
| Deterministic (Xác định) | Cùng input → luôn cho cùng output | 2 + 2 luôn = 4 |
| Non-deterministic (Không xác định) | Cùng input → mỗi lần cho output khác | Math.random() mỗi lần khác |
Các hàm Non-deterministic phổ biến:
Math.random() → số ngẫu nhiên khác nhau mỗi lầnDate.now(), new Date() → thời gian khác nhau mỗi lầncrypto.randomUUID() → UUID khác nhau mỗi lần- Gọi API bên ngoài → response có thể khác nhau
Tại sao đây là vấn đề với Lambda Durable?
Hãy xem ví dụ cụ thể:
const orderId = crypto.randomUUID();
await ctx.step('create-order', () => createOrder(orderId));
await ctx.step('send-email', () => sendEmail(orderId));
Vấn đề xảy ra như sau:
Lần chạy 1:
─────────────────────────────────────────────────────────
orderId = crypto.randomUUID() → "abc-123"
create-order với "abc-123" → ✅ Thành công, lưu checkpoint
send-email với "abc-123" → ❌ LỖI! (mất mạng)
Lần chạy 2 (Replay):
─────────────────────────────────────────────────────────
orderId = crypto.randomUUID() → "xyz-789" (KHÁC vì random lại!)
create-order với "xyz-789" → ⏩ Bỏ qua (đã có checkpoint)
NHƯNG checkpoint lưu cho "abc-123"!
send-email với "xyz-789" → ✅ Gửi email cho order SAI!
KẾT QUẢ: Order "abc-123" được tạo nhưng email gửi cho "xyz-789" (không tồn tại)
Cách sửa đúng:
const orderId = await ctx.step('generate-id', () => crypto.randomUUID());
await ctx.step('create-order', () => createOrder(orderId));
await ctx.step('send-email', () => sendEmail(orderId));
Quy tắc vàng: Mọi thứ ngẫu nhiên, thời gian, UUID phải nằm trong ctx.step() để được lưu lại.
8.2 Thay đổi biến bên ngoài step - Lỗi âm thầm
"Thay đổi biến bên ngoài step" (Closure Mutation) là gì?
Khi bạn khai báo một biến bên ngoài ctx.step(), sau đó thay đổi giá trị của nó bên trong step - đây gọi là "mutation" (thay đổi).
let counter = 0;
await ctx.step('increment', () => {
counter = counter + 1;
});
Vấn đề: Lambda Durable chỉ lưu kết quả trả về từ step (qua return), KHÔNG lưu các biến bên ngoài bị thay đổi.
Xem ví dụ cụ thể để hiểu vấn đề:
let total = 0;
for (const item of items) {
await ctx.step(`process-${item.id}`, async () => {
total += item.price;
});
}
console.log(total);
Vấn đề xảy ra như sau:
Lần chạy 1:
─────────────────────────────────────────────────────────
total = 0 (khởi tạo)
process-1: total += 100 → total = 100 ✅ checkpoint
process-2: total += 200 → total = 300 ❌ LỖI! (mất mạng)
Lần chạy 2 (Replay):
─────────────────────────────────────────────────────────
total = 0 (khởi tạo LẠI từ đầu!)
process-1: ⏩ Bỏ qua (có checkpoint)
NHƯNG total vẫn = 0 (vì mutation không được lưu!)
process-2: total += 200 → total = 200 ✅ checkpoint
KẾT QUẢ: total = 200 (SAI! đáng lẽ phải là 300)
Cách sửa đúng - Dùng return để lưu kết quả:
const prices = [];
for (const item of items) {
const price = await ctx.step(`process-${item.id}`, async () => {
return item.price;
});
prices.push(price);
}
const total = prices.reduce((a, b) => a + b, 0);
console.log(total);
Tại sao cách này đúng?
Lần chạy 2 (Replay):
─────────────────────────────────────────────────────────
prices = []
process-1: ⏩ Bỏ qua → return 100 từ checkpoint → prices = [100]
process-2: ⏩ Thực thi → return 200 → prices = [100, 200]
total = 100 + 200 = 300 ✅ ĐÚNG!
Quy tắc: Không bao giờ thay đổi biến bên ngoài step. Luôn dùng return để trả về kết quả.
8.3 Phải dùng ARN đầy đủ phiên bản (Qualified ARN)
"Qualified ARN" là gì?
ARN (Amazon Resource Name) là định danh duy nhất cho mỗi tài nguyên AWS. Với Lambda, có 2 loại ARN:
| Loại | Ví dụ | Đặc điểm |
|---|
| Unqualified (Không đủ) | arn:aws:lambda:...:function:MyFunc | Không có version/alias |
| Qualified (Đầy đủ) | arn:aws:lambda:...:function:MyFunc:$LATEST | Có version hoặc alias ở cuối |
Tại sao Lambda Durable BẮT BUỘC phải dùng Qualified ARN?
Để hiểu, hãy xem cơ chế hoạt động của Replay:
TÌNH HUỐNG: Bạn deploy code mới GIỮA LÚC execution đang chạy
Ngày 1 - 9:00 AM: Execution bắt đầu với Code v1
─────────────────────────────────────────────────────────
Code v1:
step('validate', () => validateOrder(order)) ✅ checkpoint
step('charge', () => chargePayment(amount)) ❌ LỖI!
Ngày 1 - 10:00 AM: Bạn deploy Code v2 (thay đổi logic)
─────────────────────────────────────────────────────────
Code v2:
step('validate', () => validateOrderV2(order))
step('check-fraud', () => checkFraud(order))
step('charge', () => chargePaymentV2(amount))
Ngày 1 - 10:05 AM: Lambda Durable tự động Replay
─────────────────────────────────────────────────────────
Nếu dùng Unqualified ARN (KHÔNG có version):
Replay chạy Code v2 (phiên bản MỚI NHẤT):
step('validate'): ⏩ Bỏ qua, trả về checkpoint
NHƯNG checkpoint lưu kết quả của validateOrder()
còn code mới là validateOrderV2() → KHÔNG KHỚP!
step('check-fraud'): 🤯 Step này KHÔNG CÓ checkpoint!
Vì code v1 không có step này!
step('charge'): Logic chargePaymentV2() khác chargePayment()!
KẾT QUẢ: Execution bị lỗi hoặc cho kết quả sai!
Nếu dùng Qualified ARN (CÓ version):
❌ arn:aws:lambda:us-east-1:123456789:function:MyFunction
✅ arn:aws:lambda:us-east-1:123456789:function:MyFunction:7
↑
Version 7 (Code v1)
Replay vẫn chạy Code v1 (version 7):
step('validate'): ⏩ Bỏ qua, trả về checkpoint ✅ Khớp!
step('charge'): Chạy chargePayment() giống lần đầu ✅ Đúng logic!
KẾT QUẢ: Execution hoàn thành đúng như mong đợi!
Các loại Qualified ARN hợp lệ:
✅ arn:aws:lambda:us-east-1:123456789:function:MyFunction:$LATEST
→ Dùng phiên bản mới nhất TẠI THỜI ĐIỂM bắt đầu execution
✅ arn:aws:lambda:us-east-1:123456789:function:MyFunction:7
→ Dùng chính xác phiên bản số 7
✅ arn:aws:lambda:us-east-1:123456789:function:MyFunction:prod
→ Dùng alias "prod" (alias trỏ đến một version cụ thể)
Quy tắc: Luôn publish version hoặc tạo alias khi deploy Lambda Durable. Không dùng ARN không có version.
8.4 Gỡ lỗi phức tạp hơn
Vì code chạy lại nhiều lần với các step được bỏ qua, việc debug có thể khó hiểu:
- Log có thể không xuất hiện như mong đợi
- Breakpoint có thể không dừng ở nơi bạn nghĩ
- Cần hiểu rõ cơ chế replay để debug hiệu quả
8.5 Phụ thuộc vào AWS (Vendor Lock-in)
Lambda Durable là tính năng chỉ có trên AWS. Nếu bạn muốn chuyển sang cloud khác, sẽ cần viết lại toàn bộ logic orchestration.
09. Chi phí - Ước tính thực tế
Công thức:
Chi phí = (Số thao tác / 1,000,000) × $8 + Chi phí tính toán Lambda
Ví dụ thực tế:
| Quy mô | Đơn hàng/ngày | Bước/đơn | Thao tác/tháng | Chi phí/tháng |
|---|
| Startup nhỏ | 100 | 5 | 15,000 | ~$0.12 |
| Doanh nghiệp vừa | 1,000 | 5 | 150,000 | ~$1.20 |
| Doanh nghiệp lớn | 10,000 | 5 | 1,500,000 | ~$12.00 |
| Lưu lượng cao | 100,000 | 5 | 15,000,000 | ~$120.00 |
So sánh với Step Functions:
- Step Functions: 25/triệu→Doanhnghiệplớn:37.50/tháng
- Lambda Durable: 8/triệu→Doanhnghiệplớn:12.00/tháng
- Tiết kiệm ~68%
10. Sơ đồ quyết định: Chọn công nghệ nào?
Dựa vào yêu cầu của bạn, hãy theo sơ đồ sau để chọn công nghệ phù hợp:

10.1 Bảng tổng hợp quyết định
| Yêu cầu | Lambda | Lambda Durable | Step Functions | EC2/ECS |
|---|
| Tác vụ đơn giản, nhanh | ✅ Tốt nhất | ⚠️ Quá mức cần | ⚠️ Quá mức cần | ❌ |
| Workflow nhiều bước, < 15 phút | ⚠️ Tự code | ✅ Tốt | ✅ Tốt | ❌ |
| Workflow dài > 15 phút | ❌ | ✅ Tốt nhất | ✅ Tốt | ⚠️ Phức tạp |
| Cần visual diagram cho BA | ❌ | ❌ | ✅ Tốt nhất | ❌ |
| Logic phức tạp (if/else, loop) | ⚠️ Tự code | ✅ Tốt nhất | ⚠️ ASL khó | ⚠️ |
| Saga pattern (rollback) | ❌ | ✅ Native | ⚠️ Tự code | ⚠️ Tự code |
| Chờ human approval | ❌ | ✅ Callback | ✅ Task Token | ⚠️ Tự code |
| Chạy 24/7 (web server) | ❌ | ❌ | ❌ | ✅ Tốt nhất |
| Chi phí thấp nhất | ✅ | ✅ | ⚠️ Đắt hơn | ❌ Đắt nhất |
10.2 Ví dụ thực tế
| Trường hợp | Chọn | Lý do |
|---|
| API xử lý ảnh thumbnail | Lambda | Đơn giản, nhanh, event-driven |
| Xử lý đơn hàng e-commerce | Lambda Durable | Nhiều bước, cần Saga rollback |
| ETL pipeline chạy 2 tiếng | Lambda Durable | Dài hơn 15 phút, code-centric |
| Approval workflow cho HR | Step Functions | BA cần xem visual diagram |
| Web API backend | ECS/EC2 | Chạy liên tục, stateful |
| ML training job | ECS/EC2 | Cần GPU, chạy lâu |
11. Kết luận
Lambda Durable Functions là công cụ mới trong hệ sinh thái AWS Serverless, giúp xây dựng workflow dài hạn với code đơn giản. Tuy nhiên, nó không phải giải pháp cho mọi trường hợp - hãy dùng sơ đồ quyết định ở trên để chọn công nghệ phù hợp với yêu cầu cụ thể của bạn.
12. Tài liệu tham khảo