Tìm hiểu phương pháp thực tiễn của Brendan Gregg (USE, RED, flame graphs) để điều tra độ trễ và nút thắt production bằng dữ liệu, không chỉ suy đoán.

Brendan Gregg là một trong những tiếng nói có ảnh hưởng lớn về hiệu suất hệ thống, đặc biệt trong thế giới Linux. Ông đã viết sách được dùng rộng rãi, phát triển công cụ thực tế và—quan trọng nhất—chia sẻ các phương pháp rõ ràng để điều tra các sự cố thật trong production. Các đội áp dụng cách tiếp cận của ông vì nó hiệu quả khi chịu áp lực: khi độ trễ tăng vọt và mọi người cần câu trả lời, bạn cần chuyển từ “có thể là X” sang “chắc chắn là Y” với ít rối rắm nhất.
Một phương pháp hiệu suất không phải là một công cụ đơn lẻ hay một lệnh thông minh. Đó là một cách lặp lại để điều tra: một checklist cho việc nên xem gì trước, cách diễn giải những gì bạn thấy, và cách quyết định bước tiếp theo.
Chính tính lặp lại này làm giảm suy đoán. Thay vì phụ thuộc vào người có trực giác nhất (hoặc ý kiến ồn ào nhất), bạn theo một quy trình nhất quán mà:
Nhiều cuộc điều tra độ trễ sai ở năm phút đầu. Mọi người nhảy thẳng vào sửa: "thêm CPU", "khởi động lại service", "tăng cache", "tinh chỉnh GC", "chắc là mạng". Đôi khi những hành động đó có ích—nhưng thường che dấu tín hiệu, lãng phí thời gian hoặc tạo rủi ro mới.
Các phương pháp của Gregg khuyến khích bạn hoãn “giải pháp” cho đến khi bạn có thể trả lời các câu hỏi đơn giản hơn: Cái gì bị bão hòa? Cái gì đang lỗi? Cái gì chậm lại—throughput, xếp hàng, hay từng phép toán?
Hướng dẫn này giúp bạn thu hẹp phạm vi, đo các tín hiệu đúng và xác nhận nút thắt trước khi tối ưu. Mục tiêu là một workflow có cấu trúc để điều tra vấn đề độ trễ và profiling trong production để kết quả không phụ thuộc vào may rủi.
Độ trễ là một triệu chứng: người dùng chờ lâu hơn để công việc hoàn thành. Nguyên nhân thường ở chỗ khác—đua CPU, chờ đĩa hoặc mạng, tranh chấp khóa, garbage collection, xếp hàng, hoặc độ trễ phụ thuộc từ xa. Chỉ đo độ trễ cho biết có vấn đề, không cho biết nguồn gốc.
Ba tín hiệu này liên kết với nhau:
Trước khi tinh chỉnh, hãy bắt tất cả ba tín hiệu trong cùng một khoảng thời gian. Nếu không bạn có thể “sửa” độ trễ bằng cách bỏ tải hoặc fail nhanh.
Trung bình che giấu các đột biến mà người dùng nhớ. Một dịch vụ với trung bình 50 ms vẫn có thể thường xuyên có trễ 2 s.
Theo dõi percentile:
Cũng quan sát hình dạng của latency: p50 ổn nhưng p99 tăng thường chỉ ra các stall gián đoạn (ví dụ: tranh chấp khóa, I/O hiccup, pause stop-the-world) hơn là suy giảm chung.
Ngân sách độ trễ là một mô hình kế toán đơn giản: “Nếu request phải hoàn thành trong 300 ms, thời gian có thể dùng vào đâu?” Chia thành các bucket như:
Ngân sách này định khung nhiệm vụ đo đầu tiên: xác định bucket nào tăng trong giai đoạn spike, rồi điều tra khu vực đó thay vì tinh chỉnh mù quáng.
Công việc về độ trễ sai hướng khi “hệ thống chậm” là mô tả duy nhất. Các phương pháp của Gregg bắt đầu sớm hơn: ép vấn đề thành một câu hỏi cụ thể, có thể kiểm tra.
Ghi hai câu trước khi chạm công cụ:
Điều này ngăn bạn tối ưu nhầm lớp—ví dụ tối ưu CPU host trong khi vấn đề chỉ ở một endpoint hoặc một phụ thuộc hạ lưu.
Chọn một cửa sổ khớp với phản ánh sự cố và có thể bao gồm một khoảng “tốt” để so sánh nếu được.
Phạm vi điều tra rõ ràng:
Càng chính xác thì các bước sau (USE, RED, profiling) càng nhanh vì bạn sẽ biết dữ liệu nên thay đổi nếu giả thuyết đúng.
Ghi lại deploy, cấu hình, thay đổi traffic và sự kiện hạ tầng—nhưng đừng cho đó là nguyên nhân. Viết chúng như “Nếu X, thì chúng ta kỳ vọng Y,” để có thể xác nhận hoặc bác bỏ nhanh.
Một log nhỏ tránh trùng lặp công việc giữa các thành viên và làm bàn giao mượt hơn.
Time | Question | Scope | Data checked | Result | Next step
Ngay cả năm dòng như vậy cũng có thể biến một sự cố căng thẳng thành một quy trình có thể lặp lại.
USE Method (Utilization, Saturation, Errors) là checklist nhanh của Gregg để quét “bốn lớn” tài nguyên—CPU, bộ nhớ, đĩa (storage) và mạng—giúp bạn ngừng đoán và bắt đầu thu hẹp vấn đề.
Thay vì nhìn hàng chục dashboard, hãy hỏi cùng ba câu cho mỗi tài nguyên:
Áp dụng nhất quán, đây trở thành một kiểm kê nhanh nơi nào có “áp lực”.
Với CPU, utilization là % CPU bận, saturation xuất hiện dưới dạng run-queue hoặc thread chờ chạy, và lỗi có thể là throttling (trong container) hoặc interrupt bất thường.
Với bộ nhớ, utilization là bộ nhớ đã dùng, saturation thường thể hiện như paging hoặc GC thường xuyên, và lỗi bao gồm thất bại cấp phát hoặc sự kiện OOM.
Với đĩa, utilization là thời gian thiết bị bận, saturation là độ sâu hàng đợi và thời gian chờ đọc/ghi, và lỗi là lỗi I/O hoặc timeout.
Với mạng, utilization là throughput, saturation là drop/hàng đợi/độ trễ, và lỗi là retransmit, reset hoặc mất gói.
Khi người dùng báo chậm, tín hiệu saturation thường tiết lộ nhất: hàng đợi, thời gian chờ và tranh chấp có xu hướng tương quan trực tiếp với độ trễ hơn là utilization thô.
Metrics cấp dịch vụ (như latency và error rate) cho bạn biết tác động. USE cho biết chỗ để xem tiếp bằng cách xác định tài nguyên đang chịu áp lực.
Một vòng thực tế:
RED Method giữ bạn gắn với trải nghiệm người dùng trước khi đi sâu vào đồ thị host.
RED ngăn bạn đuổi theo các metric “thú vị” nhưng không ảnh hưởng người dùng. Nó ép một vòng khép kín hơn: endpoint nào chậm, với người dùng nào, từ khi nào? Nếu Duration chỉ tăng trên một route trong khi CPU tổng thể bình thường, bạn đã có điểm khởi đầu rõ ràng hơn.
Thói quen hữu ích: giữ RED phân rã theo dịch vụ và các endpoint hàng đầu (hoặc RPC chính). Điều này giúp phân biệt suy giảm rộng với thoái lui cục bộ.
RED cho biết chỗ đau. USE giúp kiểm tra tài nguyên nào chịu trách nhiệm.
Ví dụ:
Giữ bố cục tập trung:
Nếu bạn muốn workflow sự cố nhất quán, ghép phần này với kiểm kê USE trong /blog/use-method-overview để di chuyển từ “người dùng cảm nhận” sang “tài nguyên này là hạn chế” ít lãng phí hơn.
Một cuộc điều tra hiệu suất có thể nổ ra thành hàng chục biểu đồ và giả thuyết trong vài phút. Tư duy của Gregg là giữ hẹp: nhiệm vụ của bạn không phải “thu thập thêm dữ liệu,” mà là hỏi câu tiếp theo giúp loại bỏ sự không chắc chắn nhanh nhất.
Hầu hết vấn đề độ trễ do một chi phí chiếm ưu thế (hoặc một cặp nhỏ): một khóa nóng, một phụ thuộc chậm, một đĩa quá tải, một mẫu pause GC. Ưu tiên nghĩa là tìm chi phí chiếm ưu thế đó trước, vì giảm 5% ở năm nơi khác nhau hiếm khi cải thiện độ trễ người dùng.
Một bài test thực tế: “Cái gì có thể giải thích phần lớn thay đổi độ trễ chúng ta thấy?” Nếu một giả thuyết chỉ giải thích một lát rất nhỏ, đó là câu hỏi ưu tiên thấp hơn.
Dùng top-down khi bạn trả lời “Người dùng có bị ảnh hưởng không?” Bắt đầu từ endpoint (tín hiệu kiểu RED): latency, throughput, lỗi. Điều này giúp tránh tối ưu thứ không nằm trên đường dẫn quan trọng.
Dùng bottom-up khi host rõ ràng bị bệnh (triệu chứng kiểu USE): CPU saturation, áp lực bộ nhớ chạy tràn, I/O wait. Nếu một node bị pegged, bạn sẽ lãng phí thời gian nhìn percentile endpoint mà không hiểu constraint.
Khi một alert bật, chọn một nhánh và ở trên đó cho tới khi xác nhận hoặc bác bỏ:
Giới hạn bản thân vào một bộ tín hiệu khởi đầu nhỏ, rồi khoan xuống chỉ khi có thứ gì thay đổi. Nếu cần checklist để giữ tập trung, liên kết bước của bạn đến runbook như /blog/performance-incident-workflow để mọi metric mới đều có mục đích: trả lời một câu hỏi cụ thể.
Profiling production có vẻ rủi ro vì tác động lên hệ thống sống—nhưng thường là cách nhanh nhất để thay tranh luận bằng bằng chứng. Logs và dashboard nói cho bạn cái gì chậm. Profiling nói cho bạn thời gian đi đâu: hàm nào chạy nóng, thread nào chờ, và đường dẫn mã nào chiếm ưu thế trong sự cố.
Profiling là công cụ “ngân sách thời gian”. Thay vì tranh luận (“là DB” hay “là GC”), bạn có bằng chứng như “45% mẫu CPU nằm trong JSON parsing” hoặc “đa số request bị block trên mutex”. Điều đó thu hẹp bước tiếp theo thành một hoặc hai sửa cụ thể.
Mỗi loại trả lời câu hỏi khác nhau. Độ trễ cao mà CPU thấp thường chỉ ra off-CPU hoặc thời gian chờ khóa hơn là hotspot CPU.
Nhiều đội bắt đầu theo yêu cầu, rồi chuyển sang luôn bật khi tin tưởng vào độ an toàn và thấy vấn đề lặp lại.
Profiling an toàn production là kiểm soát chi phí. Ưu tiên sampling (không trace mọi event), giữ cửa sổ chụp ngắn (ví dụ 10–30 giây), và đo overhead trên canary trước. Nếu chưa chắc, bắt đầu với sampling tần suất thấp và tăng dần khi tín hiệu quá nhiễu.
Flame graph trực quan hóa nơi thời gian mẫu rơi vào trong cửa sổ profiling. Mỗi "hộp" là một hàm (hoặc frame stack), và mỗi stack cho thấy cách thực thi đến hàm đó. Chúng rất tốt để phát hiện mẫu nhanh—nhưng không tự động nói “lỗi ở đây”.
Flame graph thường đại diện cho mẫu on-CPU: thời gian chương trình thực sự chạy trên lõi CPU. Nó có thể làm nổi bật đường dẫn mã tiêu tốn CPU, parsing kém hiệu quả, serialization quá mức, hoặc hotspot thật sự.
Nó không trực tiếp chỉ ra chờ đĩa, mạng, scheduler, hoặc thời gian bị khóa (đó là off-CPU và cần profiling khác). Nó cũng không chứng minh nhân quả cho latency người dùng trừ khi bạn kết nối nó với triệu chứng đã định nghĩa.
Hộp rộng nhất dễ bị quy trách nhiệm, nhưng hỏi: đó có phải hotspot bạn có thể thay đổi hay chỉ là “thời gian ở malloc, GC, hoặc logging” vì vấn đề thật ở upstream? Cũng chú ý ngữ cảnh thiếu (JIT, inline, symbol) có thể làm một hộp trông như thủ phạm trong khi nó chỉ là thông báo.
Xem flame graph như câu trả lời cho một câu hỏi có phạm vi: endpoint nào, cửa sổ thời gian nào, host nào, và cái gì thay đổi. So sánh flame graph “trước vs sau” (hoặc “khỏe vs suy giảm”) cho cùng một đường dẫn request để tránh nhiễu profiling.
Khi độ trễ tăng, nhiều đội nhìn vào CPU% trước. Điều này dễ hiểu—nhưng thường chỉ ra sai hướng. Dịch vụ có thể “chỉ 20% CPU” mà vẫn chậm nếu thread chủ yếu dành thời gian không chạy.
CPU% trả lời “bộ xử lý bận bao nhiêu?” Nó không trả lời “request của tôi thời gian đi đâu?”. Requests có thể stall trong khi thread chờ, bị block, hoặc bị parking bởi scheduler.
Ý chính: thời gian thực của request gồm cả công việc on-CPU và thời gian chờ off-CPU.
Thời gian off-CPU thường ẩn sau các phụ thuộc và tranh chấp:
Một vài tín hiệu thường tương quan với bottleneck off-CPU:
Những triệu chứng này nói “chúng ta đang chờ”, nhưng không nói đợi gì.
Profiling off-CPU gán thời gian cho lý do bạn không chạy: bị block trong syscall, chờ lock, sleep, hoặc bị deschedule. Điều đó mạnh mẽ cho công việc độ trễ vì biến slowdown mơ hồ thành các loại hành động cụ thể: “bị block trên mutex X”, “chờ read() từ đĩa”, hoặc “kẹt trong connect() tới upstream”. Khi bạn đặt tên được việc chờ, bạn có thể đo nó, xác nhận nó và sửa nó.
Công việc hiệu suất thường thất bại ở cùng một thời điểm: ai đó thấy metric đáng ngờ, tuyên bố đó “là vấn đề”, và bắt đầu tinh chỉnh. Các phương pháp của Gregg thúc ép bạn chậm lại và chứng minh thứ đang giới hạn hệ thống trước khi thay đổi.
Một bottleneck là tài nguyên hoặc thành phần hiện tại giới hạn throughput hoặc tạo độ trễ. Nếu bạn gỡ nó, người dùng thấy cải thiện.
Một hot spot là nơi thời gian được tiêu tốn (ví dụ một hàm xuất hiện nhiều trong profile). Hot spot có thể là bottleneck thực—hoặc chỉ là công việc bận không ảnh hưởng đường chậm.
Nhiễu là mọi thứ trông có vẻ ý nghĩa nhưng không phải: job nền, spike một lần, artifact sampling, hiệu ứng cache hoặc “top talkers” không tương quan với vấn đề người dùng thấy.
Bắt đầu bằng chụp một snapshot trước rõ ràng: triệu chứng hướng tới người dùng (latency hoặc error rate) và các tín hiệu ứng viên hàng đầu (CPU saturation, queue depth, I/O đĩa, tranh chấp lock, v.v.). Rồi áp một thay đổi có kiểm soát mà chỉ ảnh hưởng tới nguyên nhân nghi ngờ.
Ví dụ kiểm tra nhân quả:
Tương quan là gợi ý, không phải phán quyết. Nếu “CPU tăng khi latency tăng”, xác minh bằng cách thay đổi khả năng CPU hoặc giảm công việc CPU và quan sát latency có theo hay không.
Ghi rõ: đã đo gì, thay đổi chính xác nào, kết quả trước/sau và cải thiện quan sát được. Điều này biến một chiến thắng một lần thành playbook có thể dùng lại cho sự cố tiếp theo—và ngăn “trực giác” chỉnh sửa lịch sử sau đó.
Sự cố hiệu suất cảm thấy khẩn cấp, và đó chính là lúc suy đoán dễ xuất hiện. Một workflow nhẹ, có thể lặp lại giúp bạn chuyển từ “có cái gì đó chậm” sang “chúng ta biết đã có gì thay đổi” mà không lo mất kiểm soát.
Phát hiện: cảnh báo trên latency và error rate nhìn thấy được từ người dùng, không chỉ CPU. Gọi trang khi p95/p99 vượt ngưỡng trong một cửa sổ kéo dài.
Phân loại: trả lời ngay ba câu: cái gì chậm, khi nào bắt đầu, ai bị ảnh hưởng? Nếu bạn không thể đặt tên phạm vi (dịch vụ, endpoint, region, cohort), bạn chưa sẵn sàng để tối ưu.
Đo: thu bằng chứng thu hẹp nút thắt. Ưu tiên chụp có giới hạn thời gian (ví dụ, 60–180 giây) để so sánh “tệ” vs “tốt”.
Sửa: thay đổi một thứ một lần, rồi đo lại cùng tín hiệu để xác nhận cải thiện và loại trừ placebo.
Giữ một dashboard chung mọi người dùng trong sự cố. Làm nó nhàm chán và nhất quán:
Mục tiêu không phải vẽ mọi thứ; mà là rút ngắn thời gian để có dữ liệu đầu tiên.
Gắn instrumentation cho các endpoint quan trọng nhất (checkout, login, search), không phải mọi endpoint. Với mỗi endpoint, thống nhất: p95 kỳ vọng, error rate tối đa, và phụ thuộc chính (DB, cache, third-party).
Trước outage tiếp theo, đồng ý bộ capture:
Ghi vào runbook ngắn (ví dụ /runbooks/latency), bao gồm ai có thể chạy capture và nơi lưu artifact.
Phương pháp của Gregg cốt lõi là thay đổi có kiểm soát và xác minh nhanh. Nếu đội bạn xây dịch vụ bằng Koder.ai (nền tảng chat-driven để sinh và lặp web, backend, mobile apps), hai tính năng khớp với tư duy ấy:
Ngay cả khi bạn không sinh mã mới trong sự cố, thói quen—diff nhỏ, kết quả đo được, và khả năng phục hồi nhanh—là những thói quen Gregg khuyến khích.
Là 10:15 sáng và dashboard cho thấy p99 API tăng từ ~120ms lên ~900ms trong giờ cao điểm. Error rate bình thường, nhưng khách báo “requests chậm”.
Bắt đầu từ dịch vụ: Rate, Errors, Duration.
Bạn phân lớp Duration theo endpoint và thấy một route chiếm p99: POST /checkout. Rate tăng 2×, lỗi bình thường, nhưng Duration spike đúng khi concurrency tăng. Điều đó nghiêng về xếp hàng hoặc tranh chấp, không phải lỗi thẳng.
Kiểm tra xem độ trễ là thời gian compute hay chờ: so sánh “handler time” ứng dụng với tổng request time (hoặc upstream vs downstream spans nếu có tracing). Handler thấp, tổng cao—request đang chờ.
Kiểm kê các nút thắt khả nghi: Utilization, Saturation, Errors cho CPU, bộ nhớ, đĩa và mạng.
CPU chỉ ~35%, nhưng run queue và context switch tăng. Đĩa và mạng ổn. Sự không khớp (CPU thấp, chờ cao) là gợi ý cổ điển: thread không phải đang đốt CPU—chúng bị block.
Bạn chụp off-CPU profile trong giai đoạn spike và thấy nhiều thời gian ở mutex quanh cache “promotion validation” chia sẻ.
Bạn thay global lock bằng per-key lock (hoặc đường đọc không khóa), deploy và quan sát p99 quay về baseline trong khi Rate vẫn cao.
Checklist sau sự cố: