Nguyên tắc: đo trước, sửa sau. Tối ưu theo linh cảm thường tốn công vào chỗ chiếm 2% thời gian, còn nghẽn thật vẫn nguyên.
Đi từ ngoài vào trong:
1. Xác định "chậm" nghĩa là gì: endpoint nào, p50 hay p99, luôn chậm hay chỉ lúc cao điểm. p99 xấu mà p50 tốt là bài toán khác hẳn với chậm đều — p99 thường do xếp hàng, GC, cache miss hoặc một shard nóng.
2. Chia nhỏ thời gian của một request: dùng distributed tracing để thấy request tiêu thời gian ở service nào, chờ DB bao lâu, gọi bao nhiêu lượt. Số lượt gọi bất thường thường lộ ra N+1 ngay tại đây.
3. Rà từng tài nguyên theo kiểu USE (Utilization – Saturation – Errors): CPU, memory, disk I/O, network, và các hàng đợi ẩn — thread pool, connection pool. Saturation quan trọng hơn utilization: CPU 60% mà hàng đợi run queue dài vẫn là đã nghẽn.
4. Xuống tầng dữ liệu: xem query chậm, EXPLAIN ANALYZE cho query đầu bảng, kiểm tra lock và thời gian chờ lock.
5. Profiling trong tiến trình khi ba bước trên chưa chỉ ra thủ phạm: CPU profile, allocation profile, flame graph.
Điều kiện cần có sẵn: metric theo phân vị (không chỉ trung bình), log có request id xuyên suốt để nối các service, và metric theo từng phân vùng/instance — trung bình toàn cụm che mất một node hỏng.
Sau khi sửa phải đo lại. Nghẽn luôn dịch chuyển: gỡ nghẽn ở tầng app thì DB thành nghẽn mới. Dừng khi đạt mục tiêu đã đặt, không tối ưu vô hạn.