·

Tiếng Việt: Performance Analysis And Optimization

Performance Analysis And Optimization

Một câu query chạy 8 giây trong production không nói dối, nhưng bạn cần biết cách hỏi đúng câu hỏi để nó khai ra sự thật.

Vì sao bài toán hiệu năng luôn khó nếu không có AI

Vấn đề hiệu năng (performance issue) hiếm khi nằm gọn ở một tầng. Một request chậm có thể là do database, network, application code, cache layer, hay thậm chí infra (container bị throttle CPU, disk I/O nghẽn). Để chẩn đoán đúng, kỹ sư phải hiểu hệ thống theo chiều dọc — từ SQL execution plan đến cách framework serialize JSON — chứ không chỉ giỏi một mảng. Đó là lý do performance work thường rơi vào tay senior engineer: không phải vì nó cần code phức tạp, mà vì nó cần tư duy hệ thống (systems thinking) và khả năng loại trừ giả thuyết nhanh.

Cái khó thứ hai là tái hiện (reproduce). Bug hiệu năng thường chỉ xuất hiện ở scale thật — nhiều bản ghi, nhiều concurrent user, network latency thật — nên rất khó bắt được trên máy local. Cái khó thứ ba là dữ liệu profiling quá nhiều và quá dày đặc để đọc bằng mắt: một flame graph (biểu đồ ngọn lửa, thể hiện thời gian CPU theo call stack) có thể có hàng nghìn frame, một slow query log có thể dài hàng chục nghìn dòng, một APM trace (Application Performance Monitoring — theo dõi hiệu năng ứng dụng) có thể có hàng trăm span lồng nhau. Con người đọc nổi vài chục dòng đầu rồi bắt đầu đoán mò thay vì đo đạc — đây chính là thiên kiến "guess over measure" (đoán thay vì đo) mà ai làm performance lâu năm cũng từng dính.

AI thay đổi cuộc chơi ở đúng điểm yếu đó. Nó không mệt khi đọc 50.000 dòng log, không bỏ sót một cột Rows_examined bất thường, và có thể đối chiếu (cross-reference) ngay số liệu trong profiler với đoạn code tương ứng trong vài giây. AI không thay thế tư duy hệ thống của bạn, nhưng nó rút ngắn cực mạnh giai đoạn "đọc dữ liệu thô" để bạn dành thời gian cho phần quan trọng hơn: ra quyết định fix cái gì trước.

Mẹo: Đừng để AI đoán bottleneck khi chưa có số đo thật — luôn yêu cầu nó dựa trên profiling output hoặc benchmark cụ thể, không dựa trên phỏng đoán từ tên hàm.

Nhận diện bottleneck qua code review

Trước khi có log hay profiler, bạn đã có thể bắt được rất nhiều bottleneck (điểm nghẽn) chỉ bằng cách đọc code — và đây là chỗ AI cực kỳ hữu dụng như một "cặp mắt thứ hai" chuyên về hiệu năng trong quá trình code review. Các anti-pattern (mẫu hình xấu) kinh điển cần AI quét: N+1 query (gọi một query cho danh sách rồi lặp gọi thêm một query con cho từng phần tử), vòng lặp không giới hạn (unbounded loop) chạy trên tập dữ liệu có thể phình to theo thời gian, I/O đồng bộ (synchronous I/O) chặn trong hot path (đường xử lý được gọi thường xuyên nhất), thiếu phân trang (pagination) khi trả về danh sách, truy vấn trên cột chưa có index, và ở phía frontend là re-render không cần thiết do state hoặc prop thay đổi tham chiếu.

Một kỹ thuật hiệu quả là yêu cầu AI lý luận về độ phức tạp Big-O của một hàm cụ thể — không phải để có con số chính xác tuyệt đối, mà để lộ ra những chỗ độ phức tạp tăng vọt khi input tăng, ví dụ vòng lặp lồng vòng lặp thao tác trên cùng một mảng. Prompt mẫu:

Review the function `calculateOrderTotals` below for algorithmic complexity.
For each loop or query call, state its Big-O complexity relative to `orders.length`
and `items.length`. Flag anything worse than O(n log n) and explain why.
Assume `orders` can grow to 500,000 rows in production.

[paste function code]

Điểm mấu chốt là phải cho AI biết bối cảnh về scale kỳ vọng — 100 bản ghi hay 5 triệu bản ghi thay đổi hoàn toàn mức độ nghiêm trọng của cùng một đoạn code. Một vòng lặp O(n²) chạy trên 50 phần tử vô hại, nhưng trên 50.000 phần tử là thảm họa. Nếu không cung cấp thông tin tải (load) và tần suất gọi endpoint, AI sẽ đánh giá sai mức ưu tiên, có thể gắn cờ đỏ cho một đoạn code vô hại và bỏ qua chỗ thực sự nguy hiểm.

Mẹo: Khi nhờ AI review performance, luôn kèm theo con số thực tế — số bản ghi trung bình, tần suất gọi endpoint, SLA latency mong đợi — để nó ưu tiên đúng vấn đề.

Prompt AI để phân tích output từ profiling

Đây là nơi AI phát huy sức mạnh rõ nhất: đọc output thô từ profiler, slow query log, hay APM trace và tìm ra nguyên nhân gốc (root cause) thay vì chỉ lặp lại triệu chứng. Khác biệt quan trọng cần nhớ: một AI thiếu kinh nghiệm (hoặc bị prompt hời hợt) sẽ chỉ diễn giải lại con số ("query này chạy 8 giây") — bạn cần ép nó đi xa hơn, tới câu trả lời "tại sao" và "sửa thế nào".

Cấu trúc prompt tốt cho việc phân tích luôn có ba phần: (1) dữ liệu thô từ profiler/log, (2) bối cảnh liên quan — schema bảng, index hiện có, ORM đang dùng, và (3) câu hỏi cụ thể bạn muốn AI trả lời. Thiếu phần (2) là lỗi phổ biến nhất — AI không thể đoán bạn đã có index nào nếu bạn không nói.

Ví dụ cụ thể: giả sử slow query log của MySQL trả về dòng sau:

SELECT * FROM orders WHERE customer_id = 12345 ORDER BY created_at DESC;

Điểm đáng ngờ ngay lập tức: Rows_sent: 1 nhưng Rows_examined: 847392 — nghĩa là MySQL phải quét gần 850.000 dòng chỉ để trả về 1 kết quả (thực ra ở đây có thể hiểu là top-N do có ORDER BY mà không có LIMIT, nhưng con số chênh lệch cực lớn giữa examined và sent luôn là dấu hiệu thiếu index). Prompt để đưa cho AI:

Here is a slow query log entry from MySQL and the query it corresponds to.
The `orders` table has ~2 million rows and these existing indexes:
  - PRIMARY KEY (id)
  - INDEX idx_status (status)

Query log:
SELECT * FROM orders WHERE customer_id = 12345 ORDER BY created_at DESC;

Explain precisely why this query is slow given the row counts, and propose
the exact CREATE INDEX statement to fix it. Also show what the EXPLAIN
output would likely look like before and after the fix.

Với prompt này, AI thường sẽ chỉ ra: không có index trên customer_id, nên MySQL phải full table scan rồi filesort để thỏa ORDER BY created_at. Fix hợp lý là composite index (customer_id, created_at DESC), giúp MySQL vừa lọc vừa đã có sẵn thứ tự mong muốn — loại bỏ luôn bước filesort. Sau khi có gợi ý, đừng dừng ở đó — chạy EXPLAIN thật trên query, dán kết quả ngược lại cho AI để nó xác nhận index mới có được dùng đúng cách (kiểm tra cột keyrows trong output), thay vì tin lời AI nói suông.

Mẹo: Luôn dán kèm EXPLAIN (hoặc EXPLAIN ANALYZE) thật vào prompt tiếp theo để AI xác nhận index mới thực sự được optimizer chọn, không chỉ tồn tại trên bảng.

Kỹ thuật tối ưu hóa cùng AI

Một khi đã xác định đúng bottleneck, AI trở thành trợ thủ tốt để đề xuất và triển khai kỹ thuật tối ưu — nhưng vai trò quyết định vẫn phải là bạn. Nhóm kỹ thuật phổ biến nhất gồm bốn loại.

Caching (bộ nhớ đệm): memoization cho hàm tính toán nặng gọi lặp lại với cùng input, HTTP cache header (Cache-Control, ETag) cho response ít đổi, hoặc tầng cache ngoài như Redis cho dữ liệu truy vấn thường xuyên nhưng cập nhật chậm. AI có thể giúp bạn chọn TTL (thời gian sống của cache) hợp lý và cảnh báo về vấn đề cache invalidation (làm mất hiệu lực cache đúng lúc) — đây là phần dễ bị bỏ sót nhất khi thêm cache vội vàng.

Tối ưu query: thêm index đúng chỗ, viết lại N+1 query thành một truy vấn dùng JOIN hoặc batched load (dùng WHERE id IN (...) để gộp nhiều lần gọi thành một), và luôn thêm pagination cho endpoint trả về danh sách không giới hạn.

Cải thiện độ phức tạp thuật toán: chuyển từ tra cứu tuyến tính (linear lookup) trong mảng sang dùng Map/Set/hash table, hoặc tránh xử lý lồng nhau không cần thiết.

Bất đồng bộ hóa và song song hóa (async/parallelization): chuyển lời gọi tuần tự (sequential) sang chạy song song khi các tác vụ độc lập nhau, hoặc đẩy việc không cần blocking response ra background job/queue.

Khi nhờ AI đề xuất fix, nên yêu cầu nó trình bày rõ before/after — cả về độ phức tạp lẫn số liệu benchmark ước tính — để bạn hiểu được lý do đằng sau, không chỉ copy code. Nhưng nguyên tắc bất di bất dịch: không bao giờ tin tuyên bố cải thiện hiệu năng của AI mà không đo đạc thật. AI có thể nói "thay đổi này giảm 90% thời gian chạy" dựa trên suy luận lý thuyết, nhưng chỉ có benchmark thật trên dữ liệu thật (hoặc ít nhất representative dataset) mới xác nhận được điều đó. Overhead thực tế của network, connection pool, hay cache miss lần đầu thường không nằm trong mô hình suy luận của AI.

Mẹo: Sau mỗi fix AI đề xuất, chạy lại benchmark hoặc load test với cùng điều kiện baseline trước khi merge — con số thật luôn thắng lý luận lý thuyết.

Thực hành: Tối ưu một API endpoint chậm với ba bottleneck

Giả định tình huống: endpoint GET /api/orders/:customerId/summary trả về tổng quan đơn hàng của một khách hàng, đang mất trung bình 3.2 giây để phản hồi — vượt xa SLA 300ms của team. Sau khi khoanh vùng, endpoint này có ba bottleneck chồng lên nhau: (a) N+1 query khi lấy chi tiết từng đơn hàng, (b) thiếu index trên cột customer_id của bảng orders, và (c) một lời gọi API bên ngoài (external API — service tính điểm loyalty) chạy đồng bộ ngay trong request path dù kết quả không bắt buộc phải trả về ngay.

Bước 1 — Chụp baseline metric. Trước khi sửa bất cứ gì, đo hiện trạng: bật APM (hoặc console.time/middleware đo latency đơn giản nếu chưa có APM), gọi endpoint 20 lần với cùng customerId có khoảng 150 đơn hàng, ghi lại p50/p95 latency. Đồng thời bật slow query log để bắt câu SQL thực tế được sinh ra. Baseline ghi nhận: p95 = 3.4s, trong đó 151 query SQL riêng lẻ (1 query lấy danh sách order + 150 query lấy chi tiết từng order), và 1 lời gọi POST /loyalty/score mất trung bình 900ms.

Bước 2 — Đưa dữ liệu profiling và code cho AI chẩn đoán. Prompt mẫu:

This endpoint handler is taking 3.4s p95. Here's the code, the APM trace summary
(151 SQL queries, 1 external HTTP call taking ~900ms), and the slow query log
entry for the main query. Identify all distinct bottlenecks, rank them by
expected impact, and propose a fix strategy for each — don't just patch symptoms.

[paste handler code]
[paste trace summary]
[paste slow query log]

AI phản hồi đúng như dự đoán: N+1 query là thủ phạm lớn nhất (150 round-trip DB riêng lẻ), thiếu index khiến ngay cả query gộp cũng sẽ chậm, và lời gọi loyalty API đồng bộ cộng thêm ~900ms không cần thiết vào critical path.

Bước 3 — Sửa N+1 query. Prompt:

Rewrite this loop that fetches order details one-by-one into a single batched
query using `WHERE order_id IN (...)`, then map results back to each order
in memory. Preserve existing field names in the response.

[paste loop code]

Kết quả: 150 query con gộp thành 1 query duy nhất dùng IN (...), giảm số round-trip DB từ 151 xuống 2.

Bước 4 — Sửa thiếu index. Dựa trên slow query log ở bước 2 (tương tự mẫu đã phân tích ở phần 3), prompt AI để sinh migration:

Generate a database migration adding a composite index on `orders` to speed up
queries filtering by `customer_id` and sorting by `created_at DESC`.
Use our migration tool (Knex-style).

Migration sinh ra dạng:

exports.up = function(knex) {
  return knex.schema.alterTable('orders', (table) => {
    table.index(['customer_id', 'created_at'], 'idx_orders_customer_created');
  });
};

Chạy migration ở staging, dùng EXPLAIN xác nhận query giờ dùng index thay vì full scan.

Bước 5 — Xử lý lời gọi loyalty API. Vì điểm loyalty không bắt buộc phải có ngay trong response đầu tiên (frontend có thể hiển thị placeholder rồi cập nhật sau, hoặc giá trị này ít đổi nên cache được), chọn hướng thêm cache 5 phút bằng Redis và tách lời gọi ra khỏi critical path. Prompt:

This external loyalty-score call blocks the response for ~900ms and the score
rarely changes within a few minutes. Refactor to cache the result in Redis with
a 5-minute TTL, and fall back gracefully to a stale cached value (or null) if
the call times out after 200ms, instead of blocking the whole request.

Bước 6 — Đo lại và so sánh. Lặp lại đúng kịch bản benchmark ở bước 1: 20 lần gọi cùng customerId. Kết quả sau khi áp cả ba fix: p95 giảm từ 3.4s xuống 180ms (số query SQL còn 2, lời gọi loyalty phần lớn hit cache và có timeout an toàn khi miss). Ghi lại bảng before/after này vào PR description — không chỉ để chứng minh hiệu quả, mà còn làm tài liệu tham khảo cho lần tối ưu tiếp theo trên endpoint tương tự.

Mẹo: Luôn benchmark lại với cùng kịch bản test (cùng customerId, cùng số lần gọi, cùng môi trường) như lúc đo baseline — so sánh táo với táo, không so sánh với điều kiện khác đi.

Những điểm chính

  • Hiệu năng là vấn đề đa tầng — DB, network, code, infra — nên cần đo đạc thay vì đoán, và AI giỏi nhất ở việc đọc nhanh dữ liệu thô dày đặc (log, flame graph, trace).
  • Dùng AI như cặp mắt thứ hai trong code review để bắt N+1 query, vòng lặp không giới hạn, thiếu index, thiếu pagination — luôn kèm bối cảnh về scale thực tế.
  • Khi phân tích profiling output, cấu trúc prompt gồm dữ liệu thô + bối cảnh schema/index + câu hỏi cụ thể; luôn đối chiếu lại bằng EXPLAIN thật.
  • Bốn nhóm kỹ thuật tối ưu chính: caching, tối ưu query, cải thiện độ phức tạp thuật toán, và async/parallelization — không bao giờ tin số liệu cải thiện của AI mà không benchmark thật.
  • Khi một endpoint có nhiều bottleneck chồng lên nhau, xử lý tuần tự từng cái, đo lại sau mỗi bước, và luôn giữ cùng kịch bản benchmark từ đầu đến cuối để so sánh công bằng.