0
0
Lập trình
Admin Team
Admin Teamtechmely

5 Bottlenecks Hiệu Suất Có Thể Làm Hại Sản Phẩm Của Bạn

Đăng vào 3 tuần trước

• 7 phút đọc

Giới thiệu

Trong quá trình phát triển sản phẩm phần mềm, hiệu suất là một yếu tố then chốt ảnh hưởng đến trải nghiệm người dùng và sự thành công trên thị trường. Tuy nhiên, có nhiều yếu tố có thể gây ra tình trạng nghẽn cổ chai (bottleneck) ảnh hưởng đến hiệu suất của ứng dụng. Bài viết này sẽ điểm qua 5 nghẽn cổ chai phổ biến và cách khắc phục chúng.

Bottleneck #1: Điểm Nóng Cơ Sở Dữ Liệu & Truy Vấn N+1

Triệu Chứng

  • Tăng độ trễ dưới tải vừa phải trên các API khác nhau.
  • Sử dụng CPU cao của DB khi chạy các bài kiểm tra tải.
  • Các bản sao đọc của DB được tiêu thụ nhanh chóng.
  • Trang/API chậm với một vài truy vấn nặng.
  • Mẫu N+1 từ ORM, chỉ số thiếu.

Giải Pháp Nhanh

  • Thêm chỉ số bao phủ cho 1-3 truy vấn chậm nhất; đảm bảo tính chọn lọc > 0.1.
  • Gộp truy vấn: thay thế tìm kiếm theo hàng bằng IN (...) hoặc JOIN.
  • Sử dụng bản sao đọc cho lưu lượng đọc nặng; chuyển các báo cáo ra ngoài bản chính.

Giải Pháp Đúng

  • Giới thiệu tiêu chuẩn lớp truy cập dữ liệu: cấm tải lười trên các bộ sưu tập ở các đường dẫn nóng.
  • Triển khai bộ nhớ đệm cho dữ liệu tham chiếu ổn định.
  • Thêm pg_stat_statements & tinh chỉnh tự động phân tích; phân vùng các bảng thực sự lớn.

Xác Minh

So sánh độ trễ p95 trước và sau cho các điểm đầu cuối bị ảnh hưởng, sử dụng CPU của DB, và số hits bộ đệm.

Bottleneck #2: Thiếu Bộ Nhớ Đệm, Cơn Bão & Chính Sách Cũ

Triệu Chứng

  • Khi lưu lượng tăng → DB bị quá tải → độ trễ tăng cao.
  • Tỷ lệ hit bộ nhớ đệm dao động (< 85% trên các khóa nóng).
  • Các cơn bão định kỳ khi hết hạn; 5xx ngẫu nhiên trong quá trình làm nóng lại.

Kiểm Tra Nhanh

Các khóa & bộ nhớ Redis hàng đầu:

bash Copy
redis-cli INFO keyspace
redis-cli --latency
redis-cli MONITOR | head  # (tạm thời; ồn ào!)

Tỷ lệ hit & số lượng khóa bị loại bỏ:

bash Copy
redis-cli INFO stats | egrep 'keyspace_hits|keyspace_misses|evicted_keys'

Giải Pháp Nhanh

  • Thêm gộp yêu cầu: chỉ một worker tính toán lại một khóa bị thiếu; các worker khác chờ.
  • Chuyển từ TTL cứng sang TTL mềm + làm nóng nền; làm nóng trước các khóa quan trọng khi triển khai.
  • Tăng bộ nhớ/tinh chỉnh chính sách maxmemory allkeys-lru nếu phù hợp; thu nhỏ kích thước tải trọng.

Giải Pháp Đúng

  • Giới thiệu bộ nhớ đệm theo cấp độ: cục bộ (trong tiến trình) → phân tán (Redis) → CDN cho nội dung công khai.
  • Chuẩn hóa quy tắc đặt tên khóa và vô hiệu hóa; thêm tiện ích single flight trong SDK của bạn.

Xác Minh

Mục tiêu > 95% tỷ lệ hit trên 20 khóa hàng đầu; p95 ổn định trong các đợt tăng lưu lượng.

Bottleneck #3: Dịch Vụ Nhiều Yêu Cầu & Tải Trọng Quá Lớn

Triệu Chứng

  • Microservices thực hiện nhiều cuộc gọi đồng bộ nhỏ; tải trọng di động/web rất lớn (JSON, hình ảnh).
  • Độ trễ tích lũy qua đồ thị cuộc gọi; các lần bắt tay TLS hoặc tra cứu DNS xuất hiện trong các dấu vết.

Kiểm Tra Nhanh

Kiểm tra phân nhánh: Trong APM của bạn, hình dung cây cuộc gọi của một yêu cầu chậm. Đếm số cuộc gọi xuống phía dưới (> 5 là dấu hiệu đỏ).

Đo nhanh kích thước tải trọng:

bash Copy
curl -w "\nThời gian: %{time_total}s\nKích thước: %{size_download} bytes\n" -sS https://api.example.com/search?q=foo -o /dev/null

Kiểm tra HTTP/2 reuse: đảm bảo keep-alive và phân phối kết nối được kích hoạt giữa các dịch vụ.

Giải Pháp Nhanh

  • Gộp các cuộc gọi phụ thuộc hoặc kết hợp chúng phía máy chủ; ưu tiên phân nhánh không đồng bộ qua hàng đợi.
  • Nén JSON/hình ảnh; kích hoạt gzip/br; loại bỏ các trường không sử dụng từ DTO.
  • Bộ đệm kết quả DNS trong các máy khách; kích hoạt HTTP/2 và keep-alive.

Giải Pháp Đúng

  • Giới thiệu backend-cho-frontend (BFF) để tập hợp nhu cầu di động/web.
  • Áp dụng GraphQL hoặc các điểm cuối trường chọn lọc để tránh over-fetching.
  • Di chuyển các cuộc gọi không quan trọng ra khỏi đường đi của người dùng (fire-and-forget hoặc công việc nền).

Xác Minh

Theo dõi các cuộc gọi xuống phía dưới/yêu cầu và byte/phản hồi; mục tiêu < 2 bước trên các đường dẫn nóng, tải trọng < 100KB (API) và < 1.5MB (web trên cùng).

Bottleneck #4: Cạn Kiệt Pool Luồng/Kết Nối & Khóa Đồng Bộ

Triệu Chứng

  • p95 tăng khi RPS mặc dù CPU thấp; xếp hàng yêu cầu; 5xx ngẫu nhiên (timeout).
  • Thống kê pool cho thấy active ≈ max; dump luồng đầy các I/O bị chặn.

Kiểm Tra Nhanh

JVM (ví dụ):

bash Copy
jcmd <pid> Thread.print | head -n 200
jcmd <pid> GC.heap_info

Thống kê kết nối pool (Hikari): xem active/idle/blocked và thời gian chờ.

Node.js:

bash Copy
node --prof app.js && node --prof-process isolate*.log | head

Giải Pháp Nhanh

  • Đặt kích thước pool phù hợp với công suất DB (ví dụ, kết nối mỗi ứng dụng ≤ 2× số lõi CPU trên DB).
  • Loại bỏ các cuộc gọi đồng bộ không cố ý trên luồng yêu cầu; sử dụng I/O không đồng bộ hoặc chuyển giao cho các pool worker.
  • Thêm thời gian chờ, cầu dao, và bức tường (giới hạn theo phụ thuộc).

Giải Pháp Đúng

  • Áp dụng đồng bộ có cấu trúc & áp lực ngược; sử dụng giới hạn tốc độ.
  • Thêm hàng đợi giới hạn ở phía trước các pool worker; loại bỏ tải sớm với lỗi hữu ích.

Xác Minh

Thời gian chờ hàng đợi → ~0 khi RPS ổn định.

Bottleneck #5: Đứng Yên GC, Rò Rỉ Bộ Nhớ & Đối Tượng Không Hiệu Quả

Triệu Chứng

  • Đồ thị bộ nhớ hình răng cưa; độ trễ định kỳ tăng cao đồng bộ với GC; OOM kills trong giai đoạn cao điểm.

Kiểm Tra 5 Phút

Kích hoạt nhật ký GC (ví dụ JVM):

bash Copy
-Xlog:gc*,safepoint:file=gc.log:tags,uptime,level

Các tác nhân hàng đầu trong heap:

bash Copy
jmap -histo:live <pid> | head -n 20

Go:

bash Copy
go tool pprof -http=:0 http://localhost:6060/debug/pprof/heap

Giải Pháp Nhanh

  • Tăng heap một cách cẩn thận (tránh kích hoạt các khoảng dừng dài hơn); ưu tiên giảm cấp phát trước.
  • Pool/tái sử dụng các buffer; chuyển sang các bộ tuần tự không sao chép; tránh tạo các đối tượng tạm thời lớn.

Giải Pháp Đúng

  • Chọn GC phù hợp (ví dụ, G1/ZGC cho dịch vụ JVM độ trễ thấp); điều chỉnh young/old gen.
  • Thêm ngân sách bộ nhớ cho mỗi dịch vụ; chạy kiểm tra rò rỉ trong CI với các mô phỏng lưu lượng thực tế.

Xác Minh

Thời gian dừng GC p95 < 50ms cho các API người dùng (phụ thuộc vào stack); độ trễ ổn định qua các đợt cao điểm.

Bảng Điều Khiển Bạn Nên Có (để theo dõi các chỉ số đúng)

  • API: độ trễ p50/p95/p99, RPS, 4xx/5xx, thời gian trong các phụ thuộc xuống, thời gian chờ hàng đợi.
  • DB: QPS, số lượng truy vấn chậm, tỷ lệ hit bộ đệm, thời gian khóa, kết nối, độ trễ sao chép.
  • Bộ đệm: tỷ lệ hit/miss %, số lượng khóa bị loại bỏ, khóa hàng đầu, độ trễ.
  • Thời gian chạy: CPU, RSS, thời gian dừng GC, luồng, mô tả tệp, socket mở.
  • Hạ tầng: CPU node/iowait, lưu lượng mạng ra, độ trễ đĩa, giới hạn.

Quy Trình Triage (Chọn Một & Sửa Ngay Hôm Nay)

  • Xác định hành trình người dùng bị ảnh hưởng hàng đầu (đăng nhập, tìm kiếm, thanh toán).
  • Kéo ba dấu vết chậm nhất cho con đường đó.
  • Đối với mỗi cái, gán nơi thời gian được dành cho: Ứng dụng, DB, Bộ đệm, Mạng, Bên ngoài.
  • Chọn tác động sản phẩm cao nhất + sửa chữa nỗ lực thấp nhất.
  • Gửi nó phía sau một cờ; thử nghiệm trên 5–10%; xác minh; triển khai.

Những Cạm Bẫy Phổ Biến (Đừng Làm Điều Này)

  • Đổ thêm phần cứng vào các truy vấn kém.
  • Tăng kích thước pool vượt quá công suất xuống (gây ra sự sụp đổ).
  • Lỗi bộ đệm hoặc bí mật theo người dùng nhầm.
  • Tối ưu hóa các chỉ số không khớp với hình dạng lưu lượng thực tế.

Thực Hiện Đúng: Hình Ảnh Tốt Trông Như Thế Nào

  • Các điểm nóng với độ trễ p95 < 300ms (API) hoặc TTFB < 200ms (web) tại đỉnh dự kiến.
  • DB với > 99% tỷ lệ hit bộ đệm, không có nhật ký chậm trong trạng thái ổn định.
  • Tỷ lệ hit bộ đệm > 95% trên các khóa nóng; độ trễ ổn định trong các đợt tăng.
  • Pool luồng/kết nối hiếm khi đạt tối đa; thời gian chờ hàng đợi ≈ 0.
  • Thời gian dừng GC không thể thấy được với người dùng; bộ nhớ ổn định dưới tải.

Phụ Lục: Danh Sách Công Cụ

  • Tải: k6, JMeter, hey.
  • Theo dõi/APM: Grafana, Elastic, OpenTelemetry, Jaeger, Tempo, Zipkin, New Relic, Datadog.
  • Phân tích: async-profiler, JFR, pprof, clinic.js.
  • DB: pg_stat_statements, EXPLAIN ANALYZE, Percona Toolkit.
  • Bộ đệm: redis-cli, keydb-bench.
  • Cổng hiệu suất CI: k6 GitHub Action, Locust.
Gợi ý câu hỏi phỏng vấn
Không có dữ liệu

Không có dữ liệu

Bài viết được đề xuất
Bài viết cùng tác giả

Bình luận

Chưa có bình luận nào

Chưa có bình luận nào