Mở đầu — vì sao bài này quan trọng
Hãy tưởng tượng bạn là kiến trúc sư của một ứng dụng gọi xe ở Việt Nam. Sếp gọi bạn vào phòng họp và hỏi thẳng: "Hệ thống của chúng ta có nhanh không?". Nếu bạn trả lời "Dạ nhanh ạ, trung bình 120ms mỗi request" — bạn vừa mắc một trong những sai lầm phổ biến nhất trong nghề. Bởi vì "trung bình" gần như luôn nói dối, và "nhanh" là một từ vô nghĩa nếu không đi kèm con số cụ thể cùng góc nhìn đúng.
Performance — hiệu năng — là một trong những Non-Functional Requirement (NFR) khó nắm bắt nhất, bởi nó vừa mang tính kỹ thuật vừa mang tính cảm nhận của người dùng. Một quyết định kiến trúc "đúng" trên giấy tờ nhưng làm P99 latency tăng gấp đôi có thể khiến hàng chục nghìn người dùng bỏ app mỗi ngày mà dashboard trung bình của bạn vẫn xanh mướt, không hề báo động.
Trong toàn bộ khóa học về ra quyết định kiến trúc này, Performance là chiếc "thước đo" mà bạn sẽ mang theo suốt hành trình. Khi bạn cân nhắc Sync vs Async (Bài 21), chọn SQL hay NoSQL (Bài 26–27), hay thiết kế caching (Bài 35), câu hỏi luôn quay về: quyết định này ảnh hưởng thế nào đến latency và throughput? Bài học hôm nay dạy bạn ngôn ngữ chính xác để nói về hiệu năng — ba khái niệm nền tảng: Latency, Throughput, và đặc biệt là percentile như P99 — thứ phân biệt một kỹ sư biết đo hiệu năng thật sự với một người chỉ nhìn con số trung bình rồi tự trấn an mình.
Khái niệm cốt lõi
Latency — thời gian cho một thao tác
Latency (độ trễ) là thời gian để hoàn thành một thao tác duy nhất, tính từ lúc bắt đầu đến lúc kết thúc. Đơn vị thường là mili-giây (ms), đôi khi micro-giây (µs) với hệ thống cực nhanh, hoặc giây (s) với các tác vụ nặng.
Ví dụ: một request API lấy thông tin sản phẩm mất 85ms — đó là latency của request đó. Latency trả lời câu hỏi: "Một người dùng phải chờ bao lâu?". Đây là góc nhìn của cá nhân, của trải nghiệm.
Một điểm cực kỳ quan trọng mà người mới hay bỏ qua: latency không phải một con số duy nhất, mà là một phân phối (distribution). Trong 1 triệu request, có request 20ms, có request 800ms. Nói "latency của hệ thống là 100ms" mà không nói đó là trung bình, trung vị, hay percentile nào — là nói chưa đủ.
Throughput — số thao tác trên một đơn vị thời gian
Throughput (thông lượng) là số thao tác mà hệ thống xử lý được trong một đơn vị thời gian. Đơn vị thường gặp:
- RPS (Requests Per Second) — số request mỗi giây, dùng cho hệ thống web/API.
- TPS (Transactions Per Second) — số giao dịch mỗi giây, dùng cho hệ thống thanh toán, ngân hàng.
- QPS (Queries Per Second) — số truy vấn mỗi giây, dùng cho database.
Latency và Throughput không phải là một — và không tỉ lệ thuận
Đây là hiểu lầm tai hại nhất. Nhiều người nghĩ latency thấp thì throughput cao — sai. Hai chỉ số này độc lập và thường xung đột nhau.
Một ví dụ trực quan: hãy tưởng tượng một quán phở. Latency là thời gian từ lúc khách gọi đến lúc có tô phở trên bàn — giả sử 5 phút. Throughput là số tô phở quán bán được mỗi giờ — giả sử 60 tô. Nếu chủ quán thuê thêm 5 đầu bếp làm song song, throughput có thể lên 300 tô/giờ, nhưng latency mỗi tô vẫn là 5 phút — khách vẫn chờ đúng 5 phút. Ngược lại, nếu quán chỉ có một bếp và bắt đầu quá tải, mỗi tô vẫn nấu 5 phút nhưng khách phải xếp hàng chờ — latency cảm nhận tăng vọt lên 20 phút dù thời gian nấu không đổi.
Bài học kiến trúc rút ra: khi hệ thống tiến gần đến giới hạn throughput, latency tăng theo cấp số nhân do hiện tượng queueing (xếp hàng). Đây là lý do một hệ thống "chạy tốt ở 60% tải" có thể sụp đổ hoàn toàn ở 90% tải.
Percentile — và tại sao P99 quan trọng hơn trung bình
Đây là phần cốt lõi nhất của bài. Percentile (phân vị) cho biết: với một mốc phần trăm cho trước, bao nhiêu request nhanh hơn hoặc bằng mốc đó.
- P50 (median / trung vị): 50% request nhanh hơn giá trị này. P50 = 100ms nghĩa là một nửa số request được phục vụ trong ≤100ms.
- P95: 95% request nhanh hơn giá trị này. Chỉ 5% chậm hơn.
- P99: 99% request nhanh hơn giá trị này. Chỉ 1% chậm hơn — nhưng đó là 1% tệ nhất.
- P99.9: 99.9% request nhanh hơn — dùng cho hệ thống đòi hỏi độ tin cậy cực cao.
Tệ hơn nữa, trong kiến trúc microservices, một request của người dùng thường phải gọi nhiều service con. Nếu một trang cần gọi 10 service, mỗi service có P99 = 1%, thì xác suất request "trúng" ít nhất một cú P99 chậm là 1 − 0.99¹⁰ ≈ 9.6%. Nghĩa là gần 1/10 người dùng gặp độ trễ đuôi. Đây gọi là tail latency amplification — độ trễ đuôi bị khuếch đại. Chính vì thế các công ty lớn như Amazon, Google đo và tối ưu theo P99, P99.9 chứ không phải trung bình.
Tình huống thực tế
Ví dụ 1 — Sàn thương mại điện tử Việt Nam trong ngày sale 12.12
Một sàn TMĐT lớn (giả định tương tự Tiki hoặc Shopee) chuẩn bị cho đợt Flash Sale 12.12. Đội kỹ thuật báo cáo: "API giỏ hàng có latency trung bình 90ms, hệ thống ổn định". Sếp yên tâm. Nhưng đến 0h ngày sale, tổng đài tràn ngập khiếu nại "không thêm được vào giỏ", "app đứng".
Điều tra sau đó phát hiện: latency trung bình đúng là 90ms, nhưng P99 lên tới 4.200ms. Nguyên nhân là mỗi khi lượng request tăng vọt, connection pool tới database bị cạn, một phần request phải xếp hàng chờ connection. Với 2 triệu request trong giờ cao điểm, 1% chậm nghĩa là 20.000 lượt thêm giỏ hàng thất bại hoặc treo — mỗi lượt là một đơn hàng tiềm năng mất đi.
Bài học: Trong sự kiện tải cao, average là kẻ nói dối nguy hiểm nhất. Đội kỹ thuật sau đó thiết lập lại SLO (Service Level Objective) theo P99 < 500ms thay vì theo trung bình, tăng connection pool và thêm cơ chế queue-based load leveling. Mùa sale sau, P99 giữ ở 380ms và tỉ lệ lỗi giỏ hàng giảm 94%.
Ví dụ 2 — Ví điện tử và bài toán throughput đỉnh điểm
Một ví điện tử phổ biến ở Đông Nam Á (giả định tương tự MoMo hoặc ZaloPay) cần xử lý thanh toán trong chiến dịch "lì xì online" dịp Tết. Ngày thường throughput khoảng 800 TPS. Nhưng đội tăng trưởng dự báo đêm Giao thừa có thể chạm 15.000 TPS trong vài phút khi cả nước cùng gửi lì xì.
Ở đây bài toán khác hẳn ví dụ trên — trọng tâm là throughput đỉnh, không phải latency của một giao dịch đơn lẻ. Nếu hệ thống chỉ chịu được 5.000 TPS, thì 10.000 TPS còn lại sẽ xếp hàng, đẩy latency từ 200ms lên hàng chục giây, và người dùng sẽ nhấn lại nhiều lần — tạo ra retry storm làm tải càng nặng.
Đội kỹ thuật giải bài này bằng cách: (1) tách luồng ghi giao dịch sang xử lý bất đồng bộ qua message broker để hấp thụ đỉnh tải, (2) đặt rate limit thông minh để bảo vệ core, (3) load test mô phỏng 20.000 TPS trước Tết một tháng. Kết quả: đêm Giao thừa đạt đỉnh 14.200 TPS, P99 latency giữ dưới 900ms, không có sự cố.
Bài học: Phải xác định rõ bạn đang tối ưu latency hay throughput. Hai bài toán này cần chiến lược kiến trúc khác nhau. Và luôn thiết kế cho tải đỉnh (peak), không phải tải trung bình.
Ví dụ 3 — Feed mạng xã hội và tail latency amplification
Một ứng dụng mạng xã hội nội địa có trang News Feed. Để render feed, backend phải gọi song song 8 service: profile, danh sách bạn bè, bài đăng, quảng cáo, đề xuất, đếm like, bình luận, và trạng thái online. Trang chỉ trả về khi service chậm nhất trả lời (fan-out/gather).
Mỗi service riêng lẻ có P99 rất đẹp — chỉ 50ms. Nhưng người dùng vẫn than "feed load chậm". Đo lại đầu cuối, P99 của cả trang là 210ms. Lý do: vì trang chờ service chậm nhất, xác suất ít nhất một trong 8 service rơi vào đuôi P99 của nó là khoảng 1 − 0.99⁸ ≈ 7.7%. Độ trễ đuôi của từng service cộng dồn thành độ trễ đuôi của cả trang.
Đội kỹ thuật xử lý bằng: đặt timeout cứng cho từng service (ví dụ 80ms), nếu service đề xuất/quảng cáo quá hạn thì trả về kết quả mặc định thay vì chờ, và áp dụng hedged requests cho service quan trọng. P99 toàn trang giảm từ 210ms xuống 95ms.
Bài học: Trong hệ phân tán, P99 của từng thành phần không cộng lại một cách "hiền lành". Bạn phải đo P99 đầu cuối (end-to-end) từ góc nhìn người dùng, và thiết kế để cô lập độ trễ đuôi.
Hướng dẫn từng bước
Khi cần đánh giá và ra quyết định về hiệu năng cho một hệ thống, hãy làm theo trình tự sau:
- Xác định thao tác quan trọng (critical path). Không phải mọi API đều quan trọng như nhau. Chọn ra vài luồng ảnh hưởng trực tiếp đến doanh thu/trải nghiệm: thêm giỏ hàng, thanh toán, đăng nhập, load feed.
- Đặt mục tiêu bằng percentile, không bằng trung bình. Viết SLO cụ thể: "P99 của API thanh toán < 500ms" hoặc "P95 < 200ms và P99 < 800ms". Con số phải gắn với ngưỡng chịu đựng của người dùng và bối cảnh nghiệp vụ.
- Xác định bạn đang tối ưu latency hay throughput. Hỏi rõ: bài toán là "người dùng chờ lâu" (latency) hay "hệ thống không chịu nổi lượng truy cập" (throughput)? Câu trả lời quyết định hướng kiến trúc.
- Đo lường ở môi trường thật, dưới tải thật. Thu thập histogram latency (không chỉ average) qua công cụ như Prometheus + Grafana, hoặc APM như Datadog, New Relic. Đảm bảo đo end-to-end từ phía client.
- Load test để tìm điểm gãy. Dùng k6, Gatling, hay Locust để tăng tải dần và quan sát khi nào latency bắt đầu tăng phi tuyến — đó là giới hạn throughput thực tế của bạn.
- Tối ưu và đo lại theo vòng lặp. Mỗi thay đổi kiến trúc (thêm cache, tách service, đổi database) đều phải đo lại percentile trước và sau. Không bao giờ tin cảm giác.
Lỗi thường gặp & mẹo
Lỗi 1 — Chỉ nhìn latency trung bình. Như đã phân tích, average che giấu đuôi. Luôn nhìn ít nhất P50, P95, P99 cùng lúc. Khoảng cách lớn giữa P50 và P99 là dấu hiệu hệ thống có vấn đề tiềm ẩn (queueing, GC pause, connection cạn).
Lỗi 2 — Đo latency ở sai điểm. Đo ở phía server (không tính network, không tính DNS, TLS handshake) sẽ cho con số đẹp hơn thực tế người dùng cảm nhận rất nhiều. Hãy đo cả client-side latency khi có thể.
Lỗi 3 — Nhầm lẫn latency với throughput. Thêm nhiều máy (scale ngang) tăng throughput nhưng thường không giảm latency của một request đơn lẻ. Muốn giảm latency cần cache, tối ưu query, giảm số lượt gọi mạng — bài toán khác.
Lỗi 4 — Trung bình hóa các percentile. Một sai lầm thống kê nghiêm trọng: bạn không thể lấy trung bình các P99 của nhiều server để ra P99 tổng. Percentile không cộng/trung bình được. Phải gộp toàn bộ dữ liệu thô rồi mới tính percentile.
Mẹo 1 — Đặt timeout dựa trên percentile. Timeout hợp lý thường đặt quanh P99.9 của service phụ thuộc, không phải một con số tùy tiện như "30 giây".
Mẹo 2 — Cẩn thận với coordinated omission. Nhiều công cụ đo tải bỏ qua các request bị chậm do chính hệ thống đang treo, khiến P99 báo cáo tốt hơn thực tế. Dùng công cụ có hiệu chỉnh (như HdrHistogram) khi cần độ chính xác cao.
Mẹo 3 — Latency budget. Chia nhỏ ngân sách latency cho từng tầng: nếu mục tiêu P99 toàn trang là 300ms, thì database ≤100ms, cache ≤10ms, business logic ≤50ms, network + serialization ≤80ms... Việc phân bổ rõ ràng giúp bạn biết chỗ nào cần tối ưu.
Bài tập thực hành
Bài 1 — Tính percentile bằng tay. Cho 10 request với latency (ms): 40, 45, 50, 55, 60, 65, 70, 90, 120, 800. Hãy tính: (a) latency trung bình, (b) P50, (c) P90. So sánh trung bình với P90 và giải thích tại sao trung bình gây hiểu lầm trong trường hợp này.
Bài 2 — Phân tích tail latency. Một trang web gọi song song 5 microservice, mỗi service có P99 = 1% (99% request nhanh, 1% chậm). Tính xác suất một request của người dùng "trúng" ít nhất một service đang chậm. Nếu tăng lên 20 service thì con số này thay đổi thế nào? Bạn rút ra bài học gì về kiến trúc fan-out?
Bài 3 — Latency vs Throughput. Đội của bạn nhận hai phàn nàn: (A) "Mỗi lần bấm tìm kiếm phải chờ 3 giây mới ra kết quả", và (B) "Cứ đến giờ trưa cao điểm là cả hệ thống chậm rì, ngoài giờ đó lại nhanh". Với mỗi phàn nàn, hãy xác định đây là vấn đề latency hay throughput, và đề xuất hướng xử lý kiến trúc phù hợp.
Bài 4 — Viết SLO. Chọn một tính năng bạn quen thuộc (ví dụ: API đăng nhập của một app ngân hàng). Viết một SLO hoàn chỉnh dùng percentile, kèm giải thích tại sao bạn chọn ngưỡng đó dựa trên bối cảnh nghiệp vụ.
Tóm tắt
Bài học hôm nay trang bị cho bạn ngôn ngữ chính xác để nói về hiệu năng — nền tảng cho mọi quyết định kiến trúc về sau.
- Latency là thời gian cho một thao tác (đo bằng ms), trả lời "người dùng chờ bao lâu". Throughput là số thao tác trên đơn vị thời gian (đo bằng RPS/TPS/QPS), trả lời "hệ thống phục vụ được bao nhiêu".
- Latency và throughput độc lập và thường xung đột. Khi tiến gần giới hạn throughput, latency tăng phi tuyến do queueing.
- Trung bình che giấu nỗi đau. Luôn đo bằng percentile — P50, P95, P99 — vì đuôi phân phối mới là nơi người dùng thật sự khổ.
- Trong hệ phân tán, độ trễ đuôi bị khuếch đại (tail latency amplification): P99 của nhiều service con dồn lại thành P99 tệ hơn nhiều ở đầu cuối. Phải đo end-to-end.
- Trước khi tối ưu, hãy xác định rõ bạn đang giải bài toán latency hay throughput — hai hướng kiến trúc khác nhau.
- Đặt mục tiêu (SLO) bằng percentile, đo dưới tải thật, load test để tìm điểm gãy, rồi tối ưu theo vòng lặp.