Booking & tracking container
Hiểu đề
Yêu cầu chức năng
- Khách tạo, sửa, huỷ booking: cảng đi, cảng đến, ngày, loại và số container (ví dụ 2 × 40'HC từ VNDAD đến SGSIN)
- Ai cũng tra cứu được hành trình bằng số booking, số vận đơn (B/L) hoặc số container ISO 6346 (ví dụ ONEU1234567)
- Hệ thống nhận sự kiện từ cảng, tàu, bãi: Gate-in, Loaded, Departed, Arrived, Discharged, Gate-out
- Khách nhận thông báo (email, trên web) khi container đổi trạng thái hoặc bị trễ
Yêu cầu phi chức năng
- Tra cứu p95 < 300 ms
- Sẵn sàng 99,9% (≈ 43,8 phút downtime/tháng)
- Sự kiện mới hiển thị trong vòng 1 phút
- 0 booking trùng khi client gửi lại, 0 sự kiện bị mất
- Chịu đỉnh 350 sự kiện/s và 116 lượt tra cứu/s ở mức 2
Ngoài phạm vi: Tính giá cước và thanh toán; Lập kế hoạch xếp container lên tàu (stowage); Bản đồ vị trí tàu theo AIS thời gian thực; Thủ tục hải quan.
Thực thể và API
| Method | Endpoint | Ghi chú |
|---|---|---|
| POST | /v1/bookings | Header Idempotency-Key. Gửi lại cùng key thì trả booking cũ, không tạo mới. Trả 201. |
| PATCH | /v1/bookings/{bookingNo} | Header If-Match: version. Sai version trả 409 BOOKING_VERSION_CONFLICT. |
| POST | /v1/bookings/{bookingNo}/cancel | Hành động nghiệp vụ nên có endpoint riêng thay vì PATCH status. |
| GET | /v1/tracking/{containerNo} | Công khai, có rate limit theo IP, đọc qua cache Redis. Không trả tên khách hay giá cước. |
| GET | /v1/tracking/{containerNo}/stream | SSE đẩy sự kiện mới; hỗ trợ Last-Event-ID để lấy bù khi kết nối lại. |
| POST | /internal/events | Cho đối tác (cảng, bãi). Xác thực API key + chữ ký HMAC, đẩy vào hàng đợi rồi trả 202 ngay. |
Thiết kế tổng thể
Đi lần lượt từng yêu cầu chức năng. Mục tiêu ở đây là chạy đúng, việc tối ưu để dành cho phần đào sâu.
1) Khách tạo booking không bị trùng
Mạng chập chờn khiến người dùng bấm hai lần hoặc client tự retry. Idempotency key cộng ràng buộc UNIQUE đảm bảo chỉ ra một booking. Outbox đảm bảo booking đã lưu thì sự kiện BookingCreated chắc chắn được gửi.
- 1Next.js gửi POST /v1/bookings kèm Idempotency-Key k1
- 2API tìm booking theo k1; đã có thì trả lại booking cũ với 201
- 3Chưa có: BEGIN, INSERT booking, INSERT outbox, COMMIT; nếu UNIQUE báo trùng do request song song thì đọc lại và trả bản đã có
- 4Outbox relay đẩy BookingCreated sang hàng đợi; booking ở PENDING tới khi xác nhận còn chỗ trên tàu
Nguồn: Microservices Patterns ↗ (.zip) · ch.3 · §3.3.6 Handling duplicate messages (tr. 95–97), mục "Writing idempotent message handlers" (tạo order với ID do client cấp) ; Microservices Patterns ↗ (.zip) · ch.3 · §3.3.7 Transactional messaging (tr. 97–100), mục "Using a database table as a message queue" (outbox) ; Microservices Patterns ↗ (.zip) · ch.4 · §4.3.2 Countermeasures for handling the lack of isolation (trạng thái *_PENDING, tr. 128–132)
2) Nhận sự kiện tracking từ cảng, tàu, bãi
Ingest chỉ kiểm tra và xếp hàng, việc ghi để worker làm. Hàng đợi chỉ đảm bảo at-least-once, nên worker phải chịu được trùng và lệch thứ tự.
- 1Đối tác gọi /internal/events; API kiểm tra chữ ký, validate DTO, publish vào topic container-events với ordering key containerNo, trả 202
- 2Worker upsert vào Mongo theo _id = eventId bằng $setOnInsert, nhận trùng thì không làm gì
- 3Chỉ cập nhật trạng thái mới nhất nếu occurredAt mới hơn trạng thái đang lưu
- 4Ghi đè cache tracking:{containerNo}, publish thông báo; lỗi quá 5 lần thì vào dead-letter topic
Nguồn: Microservices Patterns ↗ (.zip) · ch.3 · §3.3.6 Handling duplicate messages (tr. 95–97) (at-least-once, xử lý trùng) ; Microservices Patterns ↗ (.zip) · ch.3 · §3.3.5 Competing receivers and message ordering (tr. 94–95) (ordering key / shard key) ; Grokking Modern System Design · ch.17 Distributed Messaging Queue ↗ · bài "Evaluation of a Distributed Messaging Queue's Design", mục "Functional requirements" (dead-letter queue) ; MongoDB: The Definitive Guide 2e ↗ (.zip) · ch.3 Creating, Updating, and Deleting Documents · mục "Upserts" (tr. 45)
3) Tra cứu tracking và nhận cập nhật realtime
Trang tra cứu render bằng Server Component của Next.js để có SEO và tải nhanh, sau đó mở SSE. Mongo và Redis là phía đọc (kiểu CQRS), tách khỏi Postgres là phía ghi booking.
- 1Next.js gọi GET /v1/tracking/{containerNo}, validate check digit ISO 6346 trước
- 2API đọc Redis; miss thì đọc 50 sự kiện mới nhất từ Mongo, ghi cache TTL 60 s cộng thời gian ngẫu nhiên
- 3Trình duyệt mở SSE /stream; mọi instance API subscribe kênh Redis và đẩy cho client của mình
- 4Mất kết nối thì trình duyệt tự kết nối lại với Last-Event-ID, API lấy bù từ Mongo
Nguồn: Microservices Patterns ↗ (.zip) · ch.7 · §7.2 Using the CQRS pattern (phía đọc tách khỏi phía ghi, tr. 228–236) ; Redis in Action ↗ · ch.3 · §3.6 Publish/subscribe (tr. 54–57) ; Grokking Modern System Design · ch.16 Distributed Cache ↗ · bài "Background of Distributed Cache", mục "Writing policies" (cache-aside) ; Chưa có nguồn trong thư viện: SSE/EventSource, GCP Pub/Sub, Cloud Run và chuẩn mã container ISO 6346 không có trong sách của thư viện; chỉ nêu khái niệm cơ bản
Hạ tầng theo quy mô
Cùng một sản phẩm, ba mức người dùng. Chọn từng mức để xem sơ đồ, phép tính và cấu hình server thay đổi thế nào.
Cổng của một công ty forwarder ở Việt Nam, kéo dữ liệu tracking từ API của các hãng tàu. Một API NestJS trên Cloud Run và một Postgres giữ cả booking lẫn sự kiện (cột JSONB) là đủ. Chưa cần Mongo, Redis hay hàng đợi.
Ước lượng
- Sự kiện: 20.000 container × 3 = 60.000/ngày / 86.400 ≈ 0,7/s, đỉnh ×5 ≈ 3,5/s
- Tra cứu: 5.000 khách × 4 lần = 20.000/ngày / 86.400 ≈ 0,23/s, đỉnh ×5 ≈ 1,2/s
- Booking: 300/ngày, giờ cao điểm khoảng 30 booking/giờ, chưa tới 1 booking/phút
- Lưu trữ: 60.000 × 500 B × 365 ≈ 11 GB/năm
- API: (3,5 + 1,2) / (1.000 × 0,6) ≈ 0,008 → 1 instance, chạy 2 để dự phòng
Nguồn con số: Grokking Modern System Design · ch.5 Back-of-the-envelope Calculations ↗ · bài "Examples of Resource Estimation", mục "Number of servers required", "Storage requirements" ; Grokking Modern System Design · ch.4 Non-functional System Characteristics ↗ · bài "Availability", mục "Measuring availability" (bảng The Nines of Availability) ; Quy tắc ước lượng của khoá học · /hoc/chon-server (500 req/s mỗi vCPU, chạy 60% CPU, Redis ×1,3; hệ số đỉnh ×5, số container và số sự kiện mỗi container là giả định của bài)
Bảng hạ tầng
| Thành phần | Chọn | Số lượng | Cấu hình | Vì sao |
|---|---|---|---|---|
| Web | Next.js (App Router) trên Cloud Run | 1–2 | 1 vCPU · 1 GB RAM | Trang tra cứu render phía server cho SEO; tải rất thấp. |
| API | NestJS modular monolith (booking, tracking, notification) trên Cloud Run | 2 | 2 vCPU · 4 GB RAM | Dưới 5 req/s; 2 instance tối thiểu để không mất dịch vụ khi một instance khởi động lại. |
| Database | PostgreSQL (Cloud SQL), sự kiện ở bảng riêng với cột JSONB | 1 + standby | 2 vCPU · 8 GB RAM · 50 GB SSD | 11 GB/năm và vài ghi/s vừa một node; một DB thì dễ vận hành và có transaction cho booking. |
| Đồng bộ tracking | Cloud Scheduler gọi job kéo API hãng tàu mỗi 15 phút | 1 job | Cloud Run job | Forwarder không có EDI riêng; kéo định kỳ đủ cho NFR cập nhật trong 1 giờ ở mức này. |
| SendGrid hoặc dịch vụ email có sẵn | - | - | Vài trăm email/ngày, không cần hàng đợi riêng. |
- Điểm xuất phát: một monolith NestJS chia module theo nghiệp vụ, một Postgres
- Booking đã có idempotency key và UNIQUE ngay từ đầu vì chi phí gần như bằng 0
- Sự kiện kéo định kỳ, upsert theo id nguồn nên chạy lại job không sinh trùng
Khi trở thành cổng của chính hãng tàu, sự kiện được đẩy tới liên tục thay vì kéo 15 phút một lần, NFR đòi hiển thị trong 1 phút, và một bảng sự kiện JSONB lên tới hàng TB làm chậm cả truy vấn booking.
Cấu hình và chi phí là ước lượng để học và trình bày trong phỏng vấn. Xem cách tính ở bài Chọn server.
So sánh nhanh ba mức
| Thành phần | Mức 1 | Mức 2 | Mức 3 |
|---|---|---|---|
| Người dùng | ≈ 5.000 khách · 20.000 container đang chạy (forwarder khu vực) | ≈ 500.000 khách · 2 triệu container đang chạy (cổng toàn cầu của một hãng tàu) | Hub tracking toàn ngành · ≈ 20 triệu container · 10× sự kiện |
| Chi phí | ≈ 80–200 USD/tháng (ước lượng) | ≈ 1.500–3.000 USD/tháng (ước lượng) | ≈ 30.000–60.000 USD/tháng (ước lượng) |
| Web | Next.js (App Router) trên Cloud Run 1–2 × 1 vCPU · 1 GB RAM | Next.js trên Cloud Run, trang tracking SSR cache 60 s 2 (autoscale tới 10) × 1 vCPU · 2 GB RAM | Chưa cần |
| API | NestJS modular monolith (booking, tracking, notification) trên Cloud Run 2 × 2 vCPU · 4 GB RAM | NestJS modular monolith trên Cloud Run, rate limit token bucket bằng Lua trên Redis 2 (autoscale tới 10) × 2 vCPU · 4 GB RAM | NestJS trên GKE, tách module Tracking thành service riêng 5 pod × 2 vCPU · 4 GB RAM |
| Database | PostgreSQL (Cloud SQL), sự kiện ở bảng riêng với cột JSONB 1 + standby × 2 vCPU · 8 GB RAM · 50 GB SSD | Chưa cần | Chưa cần |
| Đồng bộ tracking | Cloud Scheduler gọi job kéo API hãng tàu mỗi 15 phút 1 job × Cloud Run job | Chưa cần | Chưa cần |
SendGrid hoặc dịch vụ email có sẵn - × - | Chưa cần | Chưa cần | |
| CDN + LB | Chưa cần | Cloud CDN + HTTPS Load Balancer 1 × managed | Chưa cần |
| Event worker | Chưa cần | NestJS worker, pull subscription Pub/Sub 2 × 2 vCPU · 4 GB RAM | Chưa cần |
| Booking DB | Chưa cần | PostgreSQL (Cloud SQL HA), bảng outbox + relay polling 1 primary + standby × 2 vCPU · 8 GB RAM | Chưa cần |
| Event store | Chưa cần | MongoDB Atlas replica set, index {containerNo: 1, occurredAt: -1}, giữ online 13 tháng 3 node × M30: 2 vCPU · 8 GB RAM · 1,5 TB | MongoDB sharded, shard key {containerNo: 1, occurredAt: 1} 11 shard × 3 node × 8 vCPU · 32 GB · 2,5 TB |
| Cache + pub/sub | Chưa cần | Redis (Memorystore Standard) 1 primary + 1 replica × 1 GB | Chưa cần |
| Hàng đợi | Chưa cần | GCP Pub/Sub: container-events (ordering key containerNo), notifications, dead-letter 3 topic × managed | Chưa cần |
| Notification worker | Chưa cần | NestJS worker gửi email và web push 1–2 × 1 vCPU · 2 GB RAM | Chưa cần |
| Edge | Chưa cần | Chưa cần | Global LB + Cloud CDN, API key và rate limit theo khách B2B 1 × managed |
| Ingest + adapter | Chưa cần | Chưa cần | Một adapter cho mỗi hãng/cảng (EDI, API, file), payload gốc lưu GCS 9 pod × 2 vCPU · 4 GB RAM |
| Event bus | Chưa cần | Chưa cần | Kafka (managed), 24 partition key containerNo, giữ 7 ngày 3 broker × 8 vCPU · 32 GB RAM |
| Normalizer | Chưa cần | Chưa cần | Chuẩn hoá mã sự kiện, dedupe giữa các nguồn, ghép container với chuyến hiện tại 12 pod × 2 vCPU · 4 GB RAM |
| Trạng thái mới nhất | Chưa cần | Chưa cần | Redis Cluster 3 primary + 3 replica × 8 GB mỗi node |
| Booking + subscription | Chưa cần | Chưa cần | PostgreSQL HA + read replica 1 primary + 1 replica × 8 vCPU · 32 GB RAM |
| Webhook sender | Chưa cần | Chưa cần | Worker có retry backoff + jitter, circuit breaker và pool riêng cho từng khách 12 pod × 2 vCPU · 4 GB RAM |
Đào sâu
Mỗi câu hỏi đi từ cách đơn giản đến cách người phỏng vấn mong đợi ở cấp senior.
1) Làm sao để booking không bị tạo trùng và hai nhân viên sửa cùng lúc không ghi đè nhau?
Hai request đến gần như cùng lúc đều SELECT không thấy gì và cùng INSERT. Sửa booking thì người lưu sau ghi đè âm thầm thay đổi của người trước.
Đánh đổi: Dễ viết, nhưng sai đúng lúc tải cao hoặc mạng chập chờn.
Nguồn: Microservices Patterns ↗ (.zip) · ch.4 · §4.3.1 Overview of anomalies (lost updates, tr. 127–128)
Client sinh key một lần cho mỗi lần bấm tạo. DB là tuyến phòng thủ cuối: request song song thứ hai vi phạm UNIQUE và được trả booking đã tạo. Key gắn ý nghĩa vào request, giống cách Newman làm thao tác cộng điểm trở nên idempotent.
Đánh đổi: Chặn được tạo trùng nhưng chưa giải quyết sửa đồng thời và gửi sự kiện.
Nguồn: Microservices Patterns ↗ (.zip) · ch.3 · §3.3.6 Handling duplicate messages (tr. 95–97), mục "Writing idempotent message handlers" ; Building Microservices ↗ (.zip) · ch.11 Microservices at Scale · mục "Idempotency" (tr. 215)
Sửa bằng UPDATE ... SET version = version + 1 WHERE id = ? AND version = ?; 0 dòng thì trả 409 để người dùng tải lại. Booking và sự kiện ghi chung transaction, relay đẩy sau bằng polling bảng outbox (đủ ở quy mô này) hoặc đọc WAL bằng Debezium khi lớn. Relay có thể gửi trùng nên phía nhận vẫn phải idempotent.
Đánh đổi: Thêm bảng outbox và một tiến trình relay; pessimistic lock (SELECT FOR UPDATE) chỉ đáng khi tranh chấp cao.
Nguồn: Microservices Patterns ↗ (.zip) · ch.3 · §3.3.7 Transactional messaging (tr. 97–100), mục "Using a database table as a message queue" (polling publisher) và "Publishing events by applying the Transaction log tailing pattern" ; Microservices Patterns ↗ (.zip) · ch.6 · §6.1.3 Handling concurrent updates using optimistic locking (tr. 193–194) ; Microservices Patterns ↗ (.zip) · ch.3 · §3.3.6 Handling duplicate messages (tr. 95–97) (phía nhận vẫn phải idempotent) ; Chưa có nguồn trong thư viện: Debezium là sản phẩm cụ thể, sách chỉ nêu mẫu transaction log tailing
2) Sự kiện tracking có thể đến trùng và lệch thứ tự. Xử lý thế nào để timeline và trạng thái luôn đúng?
Pub/Sub gửi lại 'Discharged' thì timeline có hai dòng. 'Discharged' đến sau 'Gate-out' thì trạng thái hiện tại bị kéo lùi.
Đánh đổi: Không tốn gì thêm, nhưng khách thấy dữ liệu sai.
Nguồn: Microservices Patterns ↗ (.zip) · ch.3 · §3.3.6 Handling duplicate messages (tr. 95–97) (message có thể bị giao lại, redeliver sai thứ tự gây lỗi, tr. 95–96)
updateOne({_id: eventId}, {$setOnInsert: evt}, {upsert: true}) nên nhận lại bao nhiêu lần cũng chỉ có một document. Với NoSQL, lưu message id ngay trong bản ghi là cách chống trùng gọn nhất.
Đánh đổi: Hết trùng nhưng thứ tự vẫn chưa được bảo đảm.
Nguồn: Microservices Patterns ↗ (.zip) · ch.3 · §3.3.6 Handling duplicate messages (tr. 95–97), mục "Tracking messages and discarding duplicates" (lưu message id làm khóa chống trùng) ; MongoDB: The Definitive Guide 2e ↗ (.zip) · ch.3 · mục "Upserts" (tr. 45)
Mọi sự kiện cùng containerNo đi vào cùng một ordering key nên được xử lý tuần tự. Message gửi lại vẫn có thể lệch, nên trạng thái hiện tại chỉ cập nhật khi occurredAt mới hơn; sự kiện muộn vẫn vào timeline. Lỗi quá 5 lần thì vào dead-letter topic để người xem lại và replay, không chặn cả hàng.
Đánh đổi: Ordering key làm một container kẹt thì các sự kiện sau của nó cũng chờ; cần cảnh báo khi DLQ tăng.
Nguồn: Microservices Patterns ↗ (.zip) · ch.3 · §3.3.5 Competing receivers and message ordering (tr. 94–95) (shard key giữ thứ tự cho cùng một thực thể) ; Grokking Modern System Design · ch.17 Distributed Messaging Queue ↗ · bài "Evaluation of a Distributed Messaging Queue's Design", mục "Functional requirements" (dead-letter queue) ; Building Microservices ↗ (.zip) · ch.4 Integration · mục "Complexities of Asynchronous Architectures" (tr. 57–58: giới hạn retry, message hospital)
3) Cache tra cứu thế nào để khi key hết hạn, hàng nghìn request cùng lúc không đè sập Mongo?
Không cache thì mỗi lượt tra cứu là một truy vấn Mongo. TTL cố định khiến nhiều key hết hạn cùng lúc, và một container được nhiều người xem sẽ bị 1.000 request cùng truy vấn khi key vừa hết.
Đánh đổi: Ổn khi ít người dùng, sập đúng lúc tàu cập cảng và mọi người cùng mở.
Nguồn: Grokking Modern System Design · ch.16 Distributed Cache ↗ · bài "Background of Distributed Cache", mục "Cache invalidation"
Đọc Redis trước, miss mới đọc Mongo rồi ghi cache. TTL cộng thời gian ngẫu nhiên để các key không hết hạn cùng lúc. Worker ghi đè key ngay khi có sự kiện mới nên dữ liệu không cũ quá vài giây.
Đánh đổi: Vẫn còn stampede trên một key rất nóng tại thời điểm hết hạn.
Nguồn: Grokking Modern System Design · ch.16 Distributed Cache ↗ · bài "Background of Distributed Cache", mục "Writing policies" và "Eviction policies" ; Redis in Action ↗ · ch.2 · §2.3 Web page caching và §2.4 Database row caching (tr. 30–34) ; Chưa có nguồn trong thư viện: TTL jitter chống hết hạn đồng loạt không được sách nào nêu
Trong một instance, giữ Map các promise đang chạy theo key để mọi request cùng key dùng chung một lần đọc Mongo. Giữa các instance, chỉ instance lấy được SET lock:tracking:{no} <uuid> NX PX 3000 mới tải lại; các instance khác chờ chút rồi đọc cache hoặc trả bản cũ. Validate check digit ISO 6346 trước để số rác không làm tốn cache.
Đánh đổi: Code phức tạp hơn và có lúc trả dữ liệu cũ vài giây, chấp nhận được với NFR 1 phút.
Nguồn: Node.js Design Patterns 2e ↗ (.zip) · ch.9 Advanced Asynchronous Recipes · mục "Batching and caching with promises" (tr. 369) ; Redis in Action ↗ · ch.6 · §6.2.3 Building a lock in Redis (SETNX) và §6.2.5 Locks with timeouts (tr. 120–127) ; Chưa có nguồn trong thư viện: SSE/EventSource, GCP Pub/Sub, Cloud Run và chuẩn mã container ISO 6346 không có trong sách của thư viện; chỉ nêu khái niệm cơ bản
4) Đẩy cập nhật realtime cho trang tracking khi API chạy nhiều instance?
Container đổi trạng thái vài lần một ngày, nên gần như mọi lần poll đều trả về không có gì mới nhưng vẫn tốn request và cache.
Đánh đổi: Dễ làm nhất, lãng phí nhất.
Nguồn: Grokking Modern System Design · ch.18 Pub-sub ↗ · bài "Introduction to Pub-sub", mục "Use cases of pub-sub" (push thay vì kiểm tra định kỳ)
Dữ liệu chỉ đi một chiều nên SSE đủ, chạy trên HTTP thường, trình duyệt tự kết nối lại. WebSocket chỉ cần khi có tương tác hai chiều như chat với nhân viên.
Đánh đổi: Worker phát sự kiện không biết client đang nối vào instance nào.
Nguồn: Chưa có nguồn trong thư viện: SSE/EventSource, GCP Pub/Sub, Cloud Run và chuẩn mã container ISO 6346 không có trong sách của thư viện; chỉ nêu khái niệm cơ bản ; Grokking Modern System Design · ch.36 WhatsApp ↗ · bài "Detailed Design of WhatsApp", mục "Connection with a WebSocket server" (WebSocket cho hai chiều)
Mọi instance subscribe một kênh Redis và đẩy cho client của mình, không cần sticky session. Redis pub/sub không lưu tin, nên khi kết nối lại client gửi Last-Event-ID và API lấy bù từ Mongo. Ghi cho client chậm phải tôn trọng backpressure: write() trả false thì chờ 'drain', và dọn kết nối khi req đóng.
Đánh đổi: Thêm một phụ thuộc vào Redis cho realtime; Redis lỗi thì realtime tạm dừng nhưng tra cứu vẫn chạy.
Nguồn: Redis in Action ↗ · ch.3 · §3.6 Publish/subscribe (tr. 54–57; pub/sub không lưu tin, mất kết nối thì mất message) ; Node.js Design Patterns 2e ↗ (.zip) · ch.5 Coding with Streams · mục "Back-pressure" (tr. 155) ; Chưa có nguồn trong thư viện: SSE/EventSource, GCP Pub/Sub, Cloud Run và chuẩn mã container ISO 6346 không có trong sách của thư viện; chỉ nêu khái niệm cơ bản
Trong phỏng vấn
- “Ở khoảng 350 lượt ghi/giây lúc đỉnh, một Postgres primary cộng Redis là đủ. Tôi scale ra nhiều instance để đạt 99,9% và chuẩn bị cho tải gấp 10, không phải vì tải hiện tại.”
- “Tôi bắt đầu bằng modular monolith NestJS chia theo nghiệp vụ. Module tách ra đầu tiên sẽ là Tracking vì tải khác hẳn Booking.”
- “Hàng đợi chỉ đảm bảo at-least-once, nên consumer upsert theo eventId và chỉ tiến trạng thái khi occurredAt mới hơn.”
- “Ràng buộc UNIQUE trong DB là tuyến phòng thủ cuối cho idempotency, vì hai request có thể chạy song song.”
- Tách microservice ngay từ đầu khi ranh giới chưa rõ (bài học SnapCI trong Building Microservices)
- Dùng Redis pub/sub như hàng đợi bền: subscriber mất kết nối là mất tin
- Ghi DB rồi publish message trong hai bước riêng, API crash ở giữa là mất sự kiện
- Retry không có backoff và jitter, tự biến retry của mình thành một cuộc DDoS như sự cố AWS us-east-1 tháng 12/2021
Đọc thêm
Sách trong thư viện của bạn. Số chương có thể lệch nhẹ so với bản PDF.