Durable Execution: Xây dựng workflow không sợ crash trong hệ thống phân tán
Posted on: 4/22/2026 11:10:40 PM
Table of contents
- Vấn đề với workflow truyền thống
- Cơ chế hoạt động: Event History và Replay
- Temporal — Kiến trúc và code thực tế
- Azure Durable Functions — Serverless Durable Execution
- Restate — Lightweight Durable Execution
- So sánh chi tiết 3 engine
- Khi nào cần Durable Execution?
- Best Practices cho Production
- Kết luận
- Tham khảo
Vấn đề với workflow truyền thống
Hãy tưởng tượng bạn đang xây dựng một hệ thống xử lý đơn hàng e-commerce. Flow xử lý bao gồm: xác minh thanh toán → trừ kho → gửi email xác nhận → gọi API vận chuyển → cập nhật trạng thái. Nếu server crash giữa bước 3, chuyện gì xảy ra?
Với cách tiếp cận truyền thống, bạn phải tự quản lý state: lưu trạng thái vào database sau mỗi bước, viết logic retry thủ công, xử lý idempotency, và build cron job để "quét" các đơn hàng bị kẹt. Code business logic 50 dòng bỗng phình thành 500 dòng infrastructure code.
Durable Execution là gì?
Durable Execution (thực thi bền vững) là mô hình cho phép bạn viết code tuần tự bình thường, nhưng platform đảm bảo code đó sẽ chạy đến hoàn tất — dù server crash, network timeout, hay deployment giữa chừng. State được tự động persist và restore mà developer không cần viết một dòng code lưu trữ nào.
Cơ chế hoạt động: Event History và Replay
Trái tim của Durable Execution là Event History — một log bất biến (append-only) ghi lại mọi sự kiện xảy ra trong workflow. Khi một worker crash, platform sẽ replay lại event history trên một worker mới, tái tạo lại toàn bộ state mà không cần chạy lại side effects.
sequenceDiagram
participant W as Worker
participant S as Server/Scheduler
participant DB as Event Store
W->>S: Bắt đầu Workflow
S->>DB: Ghi WorkflowStarted
W->>S: Activity: Xác minh thanh toán ✓
S->>DB: Ghi ActivityCompleted(payment)
W->>S: Activity: Trừ kho ✓
S->>DB: Ghi ActivityCompleted(inventory)
Note over W: 💥 Worker CRASH!
S-->>W: Worker mới được assign
S->>DB: Đọc Event History
DB-->>S: [Started, Payment✓, Inventory✓]
S-->>W: Replay → skip payment, skip inventory
W->>S: Activity: Gửi email (tiếp tục từ bước 3)
S->>DB: Ghi ActivityCompleted(email)
Cơ chế replay: Worker mới đọc event history, skip các activity đã hoàn tất, tiếp tục từ điểm dừng
Ràng buộc Deterministic
Điểm quan trọng nhất cần hiểu: workflow code phải deterministic. Khi replay, platform chạy lại workflow code từ đầu, nhưng thay vì thực thi thật các activity, nó so khớp với event history. Nếu code không deterministic (ví dụ dùng DateTime.Now hay Random trực tiếp), replay sẽ cho kết quả khác và workflow fail.
⚠️ Những thứ KHÔNG được dùng trong workflow code
Cấm: DateTime.Now, Random, Thread.Sleep, gọi API/DB trực tiếp, đọc file, đọc biến môi trường thay đổi được.
Thay thế: Dùng API của platform — Workflow.CurrentTime, Workflow.Random, Workflow.Sleep. Mọi side effect phải đặt trong Activity.
Temporal — Kiến trúc và code thực tế
Temporal là durable execution engine phổ biến nhất hiện tại, được Netflix, DoorDash, Stripe, Snap sử dụng trong production. Temporal là open-source (MIT license) với managed cloud option.
Kiến trúc Temporal
graph TB
subgraph Client["Client Application"]
A["Temporal Client
SDK"]
end
subgraph TS["Temporal Server Cluster"]
F["Frontend Service
API Gateway"]
H["History Service
Event Storage + Replay"]
M["Matching Service
Task Queue Dispatch"]
W2["Internal Worker"]
end
subgraph Workers["Worker Fleet"]
W1["Worker 1
Workflow + Activity"]
W3["Worker 2
Workflow + Activity"]
W4["Worker N
Workflow + Activity"]
end
subgraph Storage["Persistence"]
DB2["Database
PostgreSQL / MySQL / Cassandra"]
ES["Elasticsearch
Visibility"]
end
A -->|"StartWorkflow
Signal/Query"| F
F --> H
F --> M
H --> DB2
M -->|"Dispatch Task"| W1
M -->|"Dispatch Task"| W3
M -->|"Dispatch Task"| W4
H --> ES
style A fill:#e94560,stroke:#fff,color:#fff
style F fill:#2c3e50,stroke:#fff,color:#fff
style H fill:#2c3e50,stroke:#fff,color:#fff
style M fill:#2c3e50,stroke:#fff,color:#fff
style W1 fill:#f8f9fa,stroke:#e94560,color:#2c3e50
style W3 fill:#f8f9fa,stroke:#e94560,color:#2c3e50
style W4 fill:#f8f9fa,stroke:#e94560,color:#2c3e50
style DB2 fill:#f8f9fa,stroke:#2c3e50,color:#2c3e50
style ES fill:#f8f9fa,stroke:#2c3e50,color:#2c3e50
Kiến trúc Temporal Server: Frontend nhận request, History quản lý event, Matching phân phối task đến Worker
Code ví dụ: Order Processing Workflow
Dưới đây là ví dụ workflow xử lý đơn hàng với Temporal SDK cho .NET:
// Định nghĩa Workflow Interface
[Workflow]
public class OrderWorkflow
{
[WorkflowRun]
public async Task<OrderResult> RunAsync(Order order)
{
// Bước 1: Xác minh thanh toán
var paymentResult = await Workflow.ExecuteActivityAsync(
(OrderActivities act) => act.VerifyPaymentAsync(order.PaymentInfo),
new ActivityOptions { StartToCloseTimeout = TimeSpan.FromSeconds(30) });
if (!paymentResult.Success)
return OrderResult.Failed("Payment verification failed");
// Bước 2: Trừ kho — có compensate nếu fail sau này
await Workflow.ExecuteActivityAsync(
(OrderActivities act) => act.ReserveInventoryAsync(order.Items),
new ActivityOptions
{
StartToCloseTimeout = TimeSpan.FromMinutes(1),
RetryPolicy = new RetryPolicy { MaximumAttempts = 3 }
});
// Bước 3: Gửi email xác nhận
await Workflow.ExecuteActivityAsync(
(OrderActivities act) => act.SendConfirmationEmailAsync(order),
new ActivityOptions { StartToCloseTimeout = TimeSpan.FromSeconds(15) });
// Bước 4: Tạo shipment — có thể chờ hàng giờ/ngày
var trackingId = await Workflow.ExecuteActivityAsync(
(OrderActivities act) => act.CreateShipmentAsync(order),
new ActivityOptions
{
StartToCloseTimeout = TimeSpan.FromMinutes(5),
RetryPolicy = new RetryPolicy
{
MaximumAttempts = 5,
InitialInterval = TimeSpan.FromSeconds(10),
BackoffCoefficient = 2.0
}
});
// Bước 5: Chờ xác nhận giao hàng (có thể nhiều ngày)
var delivered = await Workflow.WaitConditionAsync(
() => _deliveryConfirmed,
timeout: TimeSpan.FromDays(14));
return delivered
? OrderResult.Completed(trackingId)
: OrderResult.DeliveryTimeout(trackingId);
}
private bool _deliveryConfirmed;
[WorkflowSignal]
public async Task ConfirmDeliveryAsync()
{
_deliveryConfirmed = true;
}
[WorkflowQuery]
public string GetStatus() => _currentStatus;
}
// Activity Implementation — nơi chứa side effects
[Activity]
public class OrderActivities
{
private readonly IPaymentGateway _payment;
private readonly IInventoryService _inventory;
private readonly IEmailService _email;
public OrderActivities(
IPaymentGateway payment,
IInventoryService inventory,
IEmailService email)
{
_payment = payment;
_inventory = inventory;
_email = email;
}
[Activity]
public async Task<PaymentResult> VerifyPaymentAsync(PaymentInfo info)
=> await _payment.ChargeAsync(info);
[Activity]
public async Task ReserveInventoryAsync(List<OrderItem> items)
=> await _inventory.ReserveAsync(items);
[Activity]
public async Task SendConfirmationEmailAsync(Order order)
=> await _email.SendOrderConfirmationAsync(order.CustomerEmail, order);
[Activity]
public async Task<string> CreateShipmentAsync(Order order)
=> await _inventory.CreateShipmentAsync(order.ShippingAddress, order.Items);
}
💡 Signal và Query
Signal cho phép gửi event vào workflow đang chạy từ bên ngoài (ví dụ: webhook xác nhận giao hàng). Query cho phép đọc state hiện tại của workflow mà không ảnh hưởng đến execution. Cả hai đều là cơ chế mạnh mẽ để tương tác với long-running workflow.
Azure Durable Functions — Serverless Durable Execution
Azure Durable Functions là extension của Azure Functions, cung cấp durable execution trong môi trường serverless. Phù hợp nhất nếu bạn đã ở trong hệ sinh thái Azure và cần workflow đơn giản đến trung bình.
// Orchestrator Function — tương đương Temporal Workflow
[Function("OrderOrchestrator")]
public static async Task<OrderResult> RunOrchestrator(
[OrchestrationTrigger] TaskOrchestrationContext context)
{
var order = context.GetInput<Order>();
// Mỗi CallActivityAsync tương đương một Activity trong Temporal
var payment = await context.CallActivityAsync<PaymentResult>(
"VerifyPayment", order.PaymentInfo);
if (!payment.Success)
return OrderResult.Failed("Payment failed");
await context.CallActivityAsync("ReserveInventory", order.Items);
await context.CallActivityAsync("SendConfirmationEmail", order);
var trackingId = await context.CallActivityAsync<string>(
"CreateShipment", order);
// Durable Timer — chờ tối đa 14 ngày
using var cts = new CancellationTokenSource();
var deadline = context.CurrentUtcDateTime.AddDays(14);
var timerTask = context.CreateTimer(deadline, cts.Token);
// Chờ external event (tương tự Signal trong Temporal)
var deliveryEvent = context.WaitForExternalEvent<bool>("DeliveryConfirmed");
var winner = await Task.WhenAny(deliveryEvent, timerTask);
if (winner == deliveryEvent)
{
cts.Cancel();
return OrderResult.Completed(trackingId);
}
return OrderResult.DeliveryTimeout(trackingId);
}
// Activity Function
[Function("VerifyPayment")]
public static async Task<PaymentResult> VerifyPayment(
[ActivityTrigger] PaymentInfo info,
[FromServices] IPaymentGateway gateway)
{
return await gateway.ChargeAsync(info);
}
Các pattern phổ biến trong Durable Functions
graph LR
subgraph FC["Function Chaining"]
A1["Activity A"] --> A2["Activity B"] --> A3["Activity C"]
end
subgraph FO["Fan-out / Fan-in"]
B1["Start"] --> B2["Task 1"]
B1 --> B3["Task 2"]
B1 --> B4["Task 3"]
B2 --> B5["Aggregate"]
B3 --> B5
B4 --> B5
end
subgraph MN["Monitor"]
C1["Check"] --> C2{"Done?"}
C2 -->|No| C3["Timer"] --> C1
C2 -->|Yes| C4["Complete"]
end
style A1 fill:#e94560,stroke:#fff,color:#fff
style A2 fill:#e94560,stroke:#fff,color:#fff
style A3 fill:#e94560,stroke:#fff,color:#fff
style B1 fill:#2c3e50,stroke:#fff,color:#fff
style B5 fill:#2c3e50,stroke:#fff,color:#fff
style C1 fill:#f8f9fa,stroke:#e94560,color:#2c3e50
style C4 fill:#4CAF50,stroke:#fff,color:#fff
Ba pattern chính: Function Chaining (tuần tự), Fan-out/Fan-in (song song), Monitor (polling có timer)
Restate — Lightweight Durable Execution
Restate là engine mới nổi, tập trung vào sự đơn giản. Thay vì yêu cầu bạn tổ chức code theo workflow/activity riêng biệt, Restate cho phép "đánh dấu" bất kỳ hàm nào thành durable chỉ bằng decorator.
// Restate SDK — TypeScript
import * as restate from "@restatedev/restate-sdk";
const orderService = restate.service({
name: "OrderService",
handlers: {
processOrder: restate.handlers.handler(
async (ctx: restate.Context, order: Order) => {
// ctx.run() tạo "journal entry" — tương tự activity
const payment = await ctx.run("verify-payment", async () => {
return await paymentGateway.charge(order.paymentInfo);
});
if (!payment.success) {
return { status: "failed", reason: "payment" };
}
await ctx.run("reserve-inventory", async () => {
await inventoryService.reserve(order.items);
});
await ctx.run("send-email", async () => {
await emailService.sendConfirmation(order);
});
// Sleep durable — survive restarts
await ctx.sleep(60_000); // chờ 1 phút
const tracking = await ctx.run("create-shipment", async () => {
return await shippingService.create(order);
});
return { status: "completed", trackingId: tracking };
}
),
},
});
restate.endpoint().bind(orderService).listen(9080);
💡 Điểm khác biệt của Restate
Restate không yêu cầu chạy cluster riêng biệt phức tạp như Temporal. Restate server là một single binary nhẹ, handler là HTTP endpoint bình thường. Đặc biệt phù hợp với team nhỏ muốn durable execution mà không muốn vận hành infrastructure phức tạp.
So sánh chi tiết 3 engine
| Tiêu chí | Temporal | Azure Durable Functions | Restate |
|---|---|---|---|
| Hosting | Self-hosted hoặc Temporal Cloud | Azure serverless (consumption plan) | Self-hosted (single binary) |
| Ngôn ngữ SDK | Go, Java, TypeScript, Python, .NET, PHP | C#, JavaScript, Python, Java, PowerShell | TypeScript, Java, Kotlin, Go, Python |
| Persistence | PostgreSQL, MySQL, Cassandra, SQLite | Azure Storage / MS SQL / Netherite | RocksDB (embedded) hoặc external |
| Scalability | Hàng triệu workflow đồng thời | Auto-scale, giới hạn bởi storage backend | Tốt cho medium scale |
| Versioning | Worker Versioning (2026), Build ID | Task hub versioning, code conditions | Service versioning qua deployment |
| Observability | Web UI, Metrics, Tracing tích hợp | Azure Monitor, Application Insights | OpenTelemetry, Admin API |
| Độ phức tạp vận hành | Trung bình — cần cluster + DB | Thấp — fully managed | Thấp — single binary |
| Pricing | OSS miễn phí, Cloud tính theo action | Pay-per-execution (serverless) | OSS miễn phí, Cloud option |
| Phù hợp cho | Enterprise, high-throughput, multi-cloud | Azure-native, serverless workloads | Team nhỏ, startup, lightweight needs |
Khi nào cần Durable Execution?
Durable Execution không phải silver bullet. Dưới đây là checklist giúp bạn quyết định:
✅ Nên dùng khi
- Long-running processes: Workflow kéo dài hàng giờ, ngày, hoặc tuần (đơn hàng, onboarding, subscription billing)
- Saga pattern phức tạp: Cần compensating transactions qua nhiều service với rollback logic rõ ràng
- Human-in-the-loop: Workflow cần chờ approval từ người dùng (leave request, expense report)
- Scheduled jobs phức tạp: Cron jobs có state, cần retry, cần monitoring (data pipeline, report generation)
- Reliable async operations: Gọi external API không đáng tin cậy, cần retry + timeout + fallback
❌ Không nên dùng khi
- Request-response đơn giản: API trả kết quả trong vài millisecond — overhead không đáng
- Stateless processing: Xử lý event không cần track state (log aggregation, simple ETL)
- Real-time strict: Yêu cầu latency < 10ms — replay mechanism thêm overhead
- Team chưa sẵn sàng: Đường cong học tập khá dốc, đặc biệt deterministic constraint
Best Practices cho Production
1. Thiết kế Activity đúng cách
// ❌ SAI: Activity quá lớn, làm quá nhiều việc
[Activity]
public async Task ProcessEntireOrder(Order order)
{
await VerifyPayment(order);
await ReserveInventory(order);
await SendEmail(order); // Nếu fail ở đây, phải retry TẤT CẢ
await CreateShipment(order);
}
// ✅ ĐÚNG: Mỗi Activity là một đơn vị retry độc lập
[Activity]
public async Task<PaymentResult> VerifyPayment(PaymentInfo info) { ... }
[Activity]
public async Task ReserveInventory(List<OrderItem> items) { ... }
[Activity]
public async Task SendEmail(Order order) { ... }
2. Đảm bảo Activity idempotent
Activity có thể bị retry bất kỳ lúc nào (network timeout sau khi đã execute thành công). Mỗi activity phải idempotent — chạy nhiều lần cho cùng kết quả.
[Activity]
public async Task<PaymentResult> ChargePayment(string orderId, decimal amount)
{
// Dùng idempotency key để đảm bảo không charge 2 lần
var idempotencyKey = $"order-payment-{orderId}";
return await _paymentGateway.ChargeAsync(amount, idempotencyKey);
}
3. Versioning workflow an toàn
graph TB
subgraph V1["Version 1 (đang chạy)"]
A["Step A"] --> B["Step B"] --> C["Step C"]
end
subgraph V2["Version 2 (mới deploy)"]
D["Step A"] --> E["Step B"] --> F["Step B2 (mới)"] --> G["Step C"]
end
subgraph Strategy["Chiến lược"]
S1["Workflow mới → V2"]
S2["Workflow đang chạy → V1 cho đến khi hoàn tất"]
end
style A fill:#f8f9fa,stroke:#e94560,color:#2c3e50
style B fill:#f8f9fa,stroke:#e94560,color:#2c3e50
style C fill:#f8f9fa,stroke:#e94560,color:#2c3e50
style D fill:#e94560,stroke:#fff,color:#fff
style E fill:#e94560,stroke:#fff,color:#fff
style F fill:#4CAF50,stroke:#fff,color:#fff
style G fill:#e94560,stroke:#fff,color:#fff
style S1 fill:#f8f9fa,stroke:#2c3e50,color:#2c3e50
style S2 fill:#f8f9fa,stroke:#2c3e50,color:#2c3e50
Worker Versioning: workflow đang chạy tiếp tục trên code cũ, workflow mới dùng code mới
4. Testing workflow
// Temporal cung cấp test framework để test workflow mà không cần server
[Fact]
public async Task OrderWorkflow_SuccessfulPayment_CompletesOrder()
{
var env = await WorkflowEnvironment.StartTimeSkippingAsync();
var worker = new TemporalWorker(env.Client, new TemporalWorkerOptions("test-queue")
.AddWorkflow<OrderWorkflow>()
.AddAllActivities(new OrderActivities(
mockPayment.Object,
mockInventory.Object,
mockEmail.Object)));
await worker.ExecuteAsync(async () =>
{
var result = await env.Client.ExecuteWorkflowAsync(
(OrderWorkflow wf) => wf.RunAsync(testOrder),
new WorkflowOptions { Id = "test-order-1", TaskQueue = "test-queue" });
Assert.Equal("completed", result.Status);
});
}
// TimeSkipping environment: Workflow.Sleep(14 ngày)
// thực tế chạy trong milliseconds khi test
Kết luận
Durable Execution đang thay đổi cách chúng ta xây dựng hệ thống phân tán. Thay vì dành 80% effort cho infrastructure code (retry, state management, recovery), bạn tập trung 100% vào business logic và để platform xử lý phần còn lại.
Chọn Temporal nếu bạn cần scale lớn, multi-cloud, và team có đủ resource vận hành. Chọn Azure Durable Functions nếu đã ở trong Azure ecosystem và ưu tiên serverless. Chọn Restate nếu team nhỏ, cần nhanh gọn, và chưa cần scale khổng lồ.
Dù chọn engine nào, hãy nhớ: Activity phải idempotent, Workflow phải deterministic, và luôn có chiến lược versioning trước khi lên production.
Tham khảo
EF Core 10 Deep Dive: Vector Search, JSON Type, Named Filters và LeftJoin
WebTransport API: Giao thức truyền tải thế hệ mới vượt mặt WebSocket
Disclaimer: The opinions expressed in this blog are solely my own and do not reflect the views or opinions of my employer or any affiliated organizations. The content provided is for informational and educational purposes only and should not be taken as professional advice. While I strive to provide accurate and up-to-date information, I make no warranties or guarantees about the completeness, reliability, or accuracy of the content. Readers are encouraged to verify the information and seek independent advice as needed. I disclaim any liability for decisions or actions taken based on the content of this blog.