01Vì sao đây là điểm yếu chung — và cách chữa
Phần lớn kỹ sư phần mềm tại Việt Nam ít có cơ hội chạm hệ thống thật sự lớn. Hệ quả là kiến thức về performance và scale bị học lý thuyết: thuộc tên pattern nhưng không có trực giác về bậc độ lớn. Biểu hiện điển hình:
- Nghe "scale" là nghĩ ngay tới microservices và Kafka, trong khi bài toán chỉ thiếu một index
- Không ước lượng được 5 triệu bản ghi/ngày là nhiều hay ít
- Không phân biệt được "hệ thống chậm" và "hệ thống không scale" — hai bài toán khác nhau hoàn toàn
- Thiết kế cho tải trung bình, quên tải đỉnh
01APerformance khác Scale như thế nào?
Hai khái niệm liên quan nhưng không đồng nghĩa:
| Khái niệm | Câu hỏi chính | Dấu hiệu điển hình |
|---|---|---|
| Performance | Hệ thống xử lý một request nhanh và ổn định đến mức nào? | Latency cao, query chậm, N+1 query, serialization thừa, thiếu index. |
| Scale | Hệ thống có giữ được chất lượng khi tải tăng không? | Latency tăng phi tuyến, queue phình, database chạm trần, thiếu capacity. |
Trình tự xử lý: đo và sửa bottleneck hiện tại trước, sau đó mới tính cách hấp thụ tải tăng. Không dùng microservices, cache hoặc sharding để che một lỗi performance chưa được profile.
02Bộ số phải thuộc lòng
Đây là phần cụ thể nhất, dễ dạy nhất và tạo khác biệt lớn nhất. Yêu cầu cả cohort thuộc, kiểm tra miệng đầu mỗi buổi Dojo. Không cần chính xác — cần đúng bậc độ lớn.
Độ trễ
| Thao tác | Bậc độ lớn | Ghi nhớ |
|---|---|---|
| Truy cập L1 cache | ~1 ns | Trong máy: nano tới micro |
| Truy cập bộ nhớ chính (RAM) | ~100 ns | |
| Đọc tuần tự 1 MB từ RAM | ~10 µs | |
| SSD random read | ~100 µs | Chậm hơn RAM ~1000× |
| Đọc tuần tự 1 MB từ SSD | ~1 ms | Tuần tự luôn rẻ hơn ngẫu nhiên |
| Round-trip mạng trong cùng datacenter | ~0.5 ms | Con số quan trọng nhất |
| Query Postgres có index, dữ liệu trong cache | 0.1 – 1 ms | Không index: gấp 100–10.000× |
| Round-trip Hà Nội ↔ TP.HCM | ~30 ms | Ánh sáng là trần, không tối ưu được |
| Round-trip Việt Nam ↔ Singapore | ~40 ms | Ảnh hưởng lớn khi dùng cloud region ngoài |
| Round-trip Việt Nam ↔ US East | ~230 ms | Mỗi lần gọi API xuyên lục địa |
| Ngưỡng người dùng cảm thấy "tức thì" | < 100 ms | Trên 1 s là mất mạch chú ý |
Dung lượng & thông lượng
| Thành phần | Bậc độ lớn tham chiếu |
|---|---|
| Một instance Postgres cấu hình khá — write đơn giản | vài nghìn/s |
| Một instance Postgres — read có index, cache tốt | hàng chục nghìn/s |
| Một Kafka broker | hàng trăm nghìn msg/s |
| Một Redis instance | ~100 nghìn ops/s |
| Một node ứng dụng (Java/Node), không chạm DB | vài nghìn req/s |
| Một node ứng dụng, mỗi request 2–3 query DB | vài trăm req/s |
| 1 triệu bản ghi × 1 KB | ~1 GB |
03Napkin math — quy trình 5 bước
Mọi bài toán trong Architecture Dojo đều bắt đầu bằng 5 phút này, làm trên giấy, không máy tính.
| # | Bước | Cách làm |
|---|---|---|
| 1 | Quy về đơn vị giây | 1 ngày ≈ 86.400 s, làm tròn 100.000 s cho dễ nhẩm. 1 triệu/ngày ≈ 10/s. 5 triệu/ngày ≈ 50–60/s. |
| 2 | Nhân hệ số đỉnh | Tải đỉnh thường gấp 5–10× trung bình. Với chiến dịch marketing hoặc khung giờ khuyến mãi có thể 50–100×. Luôn hỏi: đỉnh dồn trong bao lâu? |
| 3 | Tính dung lượng | Số bản ghi/ngày × kích thước bản ghi × số ngày lưu. Nhân 2–3× cho index và replica. |
| 4 | Tính băng thông | TPS × payload. Chú ý cả chiều ra (fan-out tới các service khác thường lớn hơn chiều vào). |
| 5 | So với trần thành phần | Đối chiếu bảng §02. Xác định thành phần nào chạm trần trước — đó là chỗ cần thiết kế, phần còn lại để yên. |
Ví dụ mẫu — campaign engine FMI
Yêu cầu: 5 triệu thông báo cá nhân hoá/ngày, đỉnh dồn trong 30 phút
Bước 1 5.000.000 / 100.000 s ≈ 50 msg/s trung bình
Bước 2 Đỉnh: 60% lượng dồn 30 phút
3.000.000 / 1.800 s ≈ 1.700 msg/s ← con số thiết kế
(gấp 34× trung bình — thiết kế theo 50/s là sai hoàn toàn)
Bước 3 5 triệu × 500 B × 90 ngày ≈ 225 GB → ×3 (index+replica) ≈ 675 GB
Bước 4 1.700/s × 500 B ≈ 850 KB/s ra ngoài — không đáng lo
Bước 5 1.700 write/s vào Postgres → CHẠM TRẦN một instance
1.700 msg/s vào Kafka → thoải mái (dưới 1% năng lực)
Kết luận: nút thắt nằm ở tầng ghi dữ liệu, không phải tầng vận chuyển.
Thiết kế phải tập trung vào batching / async write, không phải
vào việc chọn message broker nào.
04Thang bậc mở rộng — 7 bậc, không nhảy cóc
Lỗi phổ biến nhất: nghe "scale" là nhảy thẳng lên bậc 5–6. Dạy và áp dụng đúng thứ tự.
| Bậc | Biện pháp | Khi nào dùng · cái giá phải trả |
|---|---|---|
| 0 | Sửa cái đang sai | Index thiếu, N+1 query, thiếu connection pool, gọi API tuần tự thay vì song song, serialize thừa. Phần lớn "vấn đề performance" dừng ở đây. Giá: gần bằng 0. Nguyên tắc: chưa profile thì chưa được đề xuất kiến trúc mới. |
| 1 | Scale dọc | Tăng CPU/RAM/IOPS. Rẻ, nhanh, không thêm phức tạp. Bị đánh giá thấp một cách oan uổng. Giá: tiền, và có trần cứng. |
| 2 | Tách đọc / ghi | Read replica, CQRS nhẹ. Giải quyết phần lớn hệ thống có tỉ lệ đọc cao. Giá: replication lag → phải chấp nhận đọc dữ liệu cũ vài trăm ms. |
| 3 | Cache | Giảm tải đọc mạnh nhất với chi phí thấp nhất. Giá: ba vấn đề khó — invalidation, cache stampede khi cache chết, và cache trở thành single point of failure. |
| 4 | Async & queue | Tách việc chậm khỏi luồng đồng bộ, hấp thụ tải đỉnh. Giá: phải hiểu idempotency, ordering, DLQ, và người dùng phải chấp nhận kết quả không tức thì. |
| 5 | Sharding / partition | Chỉ khi thật cần. Giá: chọn partition key là quyết định khó đảo ngược nhất trong kiến trúc dữ liệu — sai thì phải migrate toàn bộ. Mất khả năng join và transaction xuyên shard. |
| 6 | Tách service theo trục scale | Tách phần cần scale khác nhau ra riêng — không tách theo sơ đồ tổ chức. Giá: độ phức tạp vận hành, tail latency amplification, phải có năng lực observability tương xứng. |
05Bốn hiện tượng phải hiểu bằng trực giác
① Hot partition — chia đều trên lý thuyết, lệch trong thực tế
Dữ liệu thực tế gần như luôn tuân theo phân phối lệch. Trong B2B, khoảng 10% khách hàng lớn tạo ra 70–80% giao dịch. Partition theo customer_id nghe hợp lý, nhưng shard chứa khách hàng lớn nhất sẽ chết trước trong khi các shard khác nhàn rỗi.
Cách nhận diện: hỏi "10 giá trị phổ biến nhất của partition key chiếm bao nhiêu phần trăm dữ liệu?" Nếu trên 30% thì đã có vấn đề.
Cách xử lý: composite key (customer_id + thời gian hoặc + hash bucket) · tách riêng nhóm khách hàng lớn · hoặc chọn key khác hoàn toàn.
② Tail latency amplification — chia nhỏ service không miễn phí
Một request gọi song song 10 service, mỗi service có p99 = 100 ms. Xác suất ít nhất một service rơi vào p99 là 1 − 0.99¹⁰ ≈ 9,6%. Nghĩa là gần 10% request tổng sẽ chậm ít nhất 100 ms — dù mỗi service đều "khoẻ".
Hệ quả kiến trúc: càng fan-out rộng, p99 của hệ thống càng xấu theo cấp số. Đây là lý do kỹ thuật khiến "cứ tách nhỏ service ra" là lời khuyên nguy hiểm khi chưa có hedged request, timeout hợp lý và khả năng trả kết quả một phần.
③ Cascading failure — hệ thống chết dây chuyền
Chuỗi nhân quả điển hình: một service downstream chậm → caller giữ connection lâu hơn → connection pool cạn → caller ngừng phục vụ mọi request kể cả những request không liên quan → service gọi caller cũng chết theo → toàn hệ thống sập trong vài phút.
| Biện pháp | Tác dụng |
|---|---|
| Timeout | Bắt buộc, ở mọi lời gọi ra ngoài. Không có timeout là lỗi thiết kế, không phải thiếu sót nhỏ. |
| Circuit breaker | Ngừng gọi service đang chết, trả lỗi nhanh thay vì chờ. Cho downstream cơ hội hồi phục. |
| Bulkhead | Tách pool tài nguyên theo nhóm chức năng — một phần chết không kéo cả tàu chìm. |
| Load shedding | Chủ động từ chối bớt request khi quá tải, ưu tiên request quan trọng. |
| Retry có jitter | Retry không jitter tạo ra thundering herd — làm hệ thống chết nhanh hơn. |
④ Chi phí phối hợp — Universal Scalability Law
Thêm node không làm throughput tăng tuyến tính, vì hai lý do: tranh chấp (chờ tài nguyên dùng chung) và chi phí đồng bộ (các node phải nói chuyện với nhau). Sau một ngưỡng, thêm node còn làm throughput giảm.
Ứng dụng thực tế: khi kết quả load test cho thấy tăng gấp đôi số node chỉ tăng 30% throughput, đó không phải lỗi cấu hình — đó là dấu hiệu có điểm đồng bộ hoặc tài nguyên dùng chung. Phải tìm ra điểm đó, không phải thêm node tiếp.
06Đọc và diễn giải chỉ số
| Chỉ số | Ý nghĩa và cạm bẫy |
|---|---|
| Trung bình | Gần như vô dụng cho trải nghiệm người dùng. Trung bình 200 ms có thể là 90% request 50 ms và 10% request 1,5 s. Không dùng trung bình để đặt SLO. |
| p95 / p99 | p99 = 1% request chậm nhất. Với 1 triệu request/ngày, p99 nghĩa là 10.000 người dùng mỗi ngày gặp trải nghiệm đó. Khách hàng lớn thường nằm ở đuôi vì họ có nhiều dữ liệu nhất. |
| Little's Law | Concurrency = Throughput × Latency. Rất hữu dụng để tính số connection cần: 500 req/s × 0,2 s = 100 request đồng thời → pool phải ≥ 100, nếu không đây chính là nút thắt. |
| Saturation | Mức sử dụng tài nguyên. Trên 70–80% thì latency bắt đầu tăng phi tuyến. Thiết kế cho 100% sử dụng là thiết kế cho sự cố. |
| Queue depth | Chỉ số cảnh báo sớm tốt nhất. Hàng đợi dài lên trước khi latency tăng, và tăng trước khi hệ thống sập. |
| Error budget | SLO 99,9% = được phép lỗi 43 phút/tháng. Biến "ổn định" từ cảm tính thành ngân sách có thể đàm phán với PM. |
07Ba bài toán scale gắn bối cảnh SU02
Ba đề trong Problem Bank lấy trực tiếp từ domain của phòng — đây là chỗ kiến thức chuyển thành thực chiến.
08Checklist rà soát thiết kế về performance & scale
Panel dùng checklist này ở mọi buổi Dojo. Người thiết kế dùng để tự rà trước khi trình bày.
| Nhóm | Câu hỏi |
|---|---|
| Con số | TPS trung bình và TPS đỉnh là bao nhiêu? Hệ số đỉnh lấy từ đâu? |
| Dung lượng sau 1 năm và 3 năm? Có kế hoạch xoá hoặc archive không? | |
| Mục tiêu p95/p99 là bao nhiêu? Ai là người đặt con số đó? | |
| Nút thắt | Thành phần nào chạm trần trước khi tải tăng 10×? |
| Điểm đồng bộ hoặc tài nguyên dùng chung nằm ở đâu? | |
| Có thành phần nào không thể scale ngang không? (thường là DB ghi và scheduler) | |
| Dữ liệu | Partition key là gì? 10 giá trị phổ biến nhất chiếm bao nhiêu phần trăm? |
| Truy vấn nào không dùng được index? Truy vấn nào phải quét toàn bảng? | |
| Nếu đọc từ replica, chấp nhận dữ liệu cũ bao lâu? | |
| Hỏng hóc | Mọi lời gọi ra ngoài đã có timeout chưa? Bao nhiêu? |
| Khi downstream chết 2 giờ thì điều gì xảy ra? Dữ liệu mất hay dồn lại? | |
| Retry có jitter không? Có DLQ không? Ai xử lý DLQ? | |
| Khi quá tải, hệ thống từ chối request nào trước? | |
| Chi phí | Chi phí hạ tầng ước tính mỗi tháng ở mức tải thiết kế? |
| Chi phí trên mỗi giao dịch thay đổi thế nào khi tải tăng 10×? |
Câu hỏi tổng quát về các cơ chế thường dùng
Đây là câu hỏi định hướng để review, không phải checklist cấu hình của một nền tảng cụ thể.
| Cơ chế | Câu hỏi cần hỏi trước khi chọn |
|---|---|
| Cache | Dữ liệu nào phù hợp để cache? TTL và invalidation dựa trên yêu cầu nghiệp vụ nào? Dữ liệu cũ tối đa bao lâu là chấp nhận được? Nếu cache mất hoàn toàn, hệ thống còn hoạt động không? Có nguy cơ cache stampede hoặc hot key không? |
| Queue | Queue dùng để hấp thụ burst hay tách xử lý bất đồng bộ? Message có thể duplicate hoặc đến sai thứ tự không? Consumer có idempotent không? Có DLQ và người xử lý không? Khi downstream chậm, queue sẽ đầy trong bao lâu? |
| Connection pool | Mỗi request giữ connection bao lâu? Tổng pool của mọi instance có vượt giới hạn database không? Khi pool cạn, hệ thống fail fast hay chờ vô hạn? Có transaction giữ connection trong lúc gọi API ngoài không? |
| Retry | Lỗi nào được retry và lỗi nào không? Có giới hạn số lần, timeout tổng, backoff và jitter không? Request có idempotent không? Retry ở nhiều tầng có đang khuếch đại tải lên downstream không? |