Độ trễ bộ nhớ: Kẻ thầm lặng khiến CPU chạy 100% mà vẫn chậm

Công nghệ31 tháng 10, 2026·5 phút đọc

Bài viết phân tích vì sao CPU hiển thị 100% tải mà cơ sở dữ liệu vẫn chậm: phần lớn thời gian thực chất là chờ truy cập bộ nhớ DRAM. Tác giả trình bày kỹ thuật phân vùng (partitioning) cho phép toán GROUP BY tận dụng cache CPU, giúp câu truy vấn ClickBench 99 triệu dòng nhanh hơn đáng kể và giảm mạnh lượng dữ liệu đọc từ RAM.

Độ trễ bộ nhớ: Kẻ thầm lặng khiến CPU chạy 100% mà vẫn chậm

Khi thấy CPU của cơ sở dữ liệu ở mức 100%, bạn có bao giờ tự hỏi nó đang thực sự làm gì? Câu trả lời thường không phải là "đang tính toán điên cuồng", mà là đang chờ bộ nhớ. Bài viết trên blog của Pivot phân tích rất rõ vì sao độ trễ RAM lại là nút thắt cổ chai lớn nhất trong các hệ thống dữ liệu hiện đại, và cách tối ưu cache có thể đem lại cải thiện ấn tượng.

CPU 100% không có nghĩa là đang làm việc hiệu quả

Chúng ta hay hình dung CPU chạy mã tuần tự cho dễ hiểu, nhưng thực tế CPU là một cỗ máy cực kỳ phức tạp: nó có thể thực thi nhiều lệnh cùng lúc, hoặc không thực thi lệnh nào cả dù đồng hồ vẫn báo 100%. Một CPU được coi là "rảnh" chỉ khi không có tác vụ nào cần đến thời gian xử lý — điều này hoàn toàn khác với việc CPU bị bão hòa hoàn toàn.

Vậy vì sao CPU có thể "báo 100%" mà thực chất chẳng làm được bao nhiêu? Câu trả lời thường là bộ nhớ.

  • Một CPU hiện đại xử lý khoảng 4–8 lệnh mỗi nanosecond
  • Một lần truy cập RAM mất khoảng 50–100ns
  • Nghĩa là trong lúc chờ một lần đọc RAM, CPU có thể đã thực hiện được 400–800 lệnh

Sự chênh lệch này giải thích toàn bộ câu chuyện về hiệu năng.

Cache CPU: lớp đệm quyết định mọi thứ

Để bù đắp khoảng cách đó, CPU được thiết kế với hệ thống cache nhiều tầng ngay trên chip:

  • L1 cache: khoảng 1ns, dung lượng ~64KB
  • L2 cache: khoảng 3–4ns, dung lượng ~1MB
  • L3 cache: khoảng 10–15ns, có thể lên tới vài trăm MB ở các bộ xử lý cao cấp
  • DRAM: khoảng 50–100ns, dung lượng tính bằng GB

Nói cách khác, L1 nhanh hơn RAM từ 50 đến 100 lần. Vấn đề là cache rất nhỏ. Một cơ sở dữ liệu tốt phải xử lý dữ liệu theo từng "khối" nhỏ để tận dụng cache, và đây chính là mối quan tâm hàng đầu của các kỹ sư hiệu năng.

Ví dụ thực tế: GROUP BY và cái bẫy HashTable khổng lồ

Xét phép toán GROUP BY. Cách làm "ngây thơ" là mở một HashTable với khóa là cột nhóm, rồi với mỗi dòng mới kiểm tra khóa đã tồn tại chưa để cập nhật kết quả. Nghe đơn giản — nhưng nếu có hàng triệu nhóm khác nhau, gần như mọi lần truy cập HashTable đều là "cold hit" phải đi xuống RAM. CPU trông như 100% nhưng thực chất chỉ đang chờ.

Để chứng minh, nhóm Pivot chạy thử nghiệm trên bộ dữ liệu ClickBench "hits" với 99 triệu dòng, truy vấn SELECT count(*), UserID FROM hits GROUP BY UserID — với khoảng 17 triệu UserID duy nhất. Máy chủ bare metal dùng AMD EPYC 9124 Genoa, 16 nhân, 128GB DDR5, chạy trên một nhân duy nhất.

Kết quả đo bằng perf cho thấy khoảng 50% thời gian bị treo do chờ bộ nhớ, trong khi chỉ khoảng 11,4 tỷ lệnh được thực thi.

Giải pháp: chia nhỏ để vừa cache

Thay vì một HashTable khổng lồ, thuật toán được chia làm hai pha:

  1. Pha 1 — Phân vùng: Băm khóa nhóm, lấy 12 bit đầu của mã băm để chia dòng vào 4096 phân vùng riêng biệt
  2. Pha 2 — Tổng hợp: Xử lý từng phân vùng một, mỗi lần tạo HashTable nhỏ hơn nhiều và tổng hợp kết quả

Nghe có vẻ phản trực giác vì ta làm nhiều thao tác hơn, nhưng mấu chốt là các HashTable tạm thời giờ nhỏ đến mức nằm gọn trong cache CPU. Mỗi lần tra cứu, khả năng cao dữ liệu đã có sẵn trong L1/L2/L3 thay vì phải chờ RAM.

Nếu có thể biến một lần truy cập DRAM "lạnh" thành vài lệnh CPU, gần như luôn là một sự đánh đổi có lợi.

Kết quả đo được

Sau khi tối ưu, thời gian chạy giảm còn 3,3 giây, và ở lần chạy thứ hai chỉ còn 2,7 giây. Các chỉ số perf thay đổi rõ rệt:

  • Thời gian chờ bộ nhớ giảm từ ~50% xuống còn 23,5%
  • Số lệnh thực thi tăng lên 19,3 tỷ (nhiều hơn trước, nhưng đáng giá)
  • Số slot CPU bị treo do bộ nhớ giảm từ 8,36 tỷ xuống 3,79 tỷ
  • Lượng dữ liệu đọc từ DRAM giảm từ 4,5GB xuống 1,5GB

Phiên bản Pivot hoàn chỉnh còn đi xa hơn: chỉ mất 1,85 giây trên một nhân với 954MB đọc từ RAM, và khi chạy song song trên 16 nhân thì chỉ còn 171ms.

Ý nghĩa với người làm hệ thống tại Việt Nam

Với các đội ngũ vận hành cơ sở dữ liệu, data warehouse hay hệ thống phân tích log tại Việt Nam — nơi hạ tầng thường bị giới hạn về RAM — bài học này rất đáng lưu tâm:

  • Đừng chỉ nhìn chỉ số CPU khi chẩn đoán chậm. Hãy xem cả memory stall và lượng dữ liệu đọc từ DRAM
  • Các công cụ như perf trên Linux cho phép đo chính xác những chỉ số này, dù một số môi trường cloud công cộng ẩn đi vì lý do bảo mật
  • Thiết kế thuật toán thân thiện với cache đôi khi hiệu quả hơn cả việc nâng cấp phần cứng

Bài viết hứa hẹn phần tiếp theo sẽ đi sâu vào cách Pivot xử lý phép JOIN probing — một chủ đề cũng đầy bất ngờ. Nếu bạn đang làm việc với các engine phân tích dữ liệu, đây là chuỗi bài đáng theo dõi.

Chia sẻ:FacebookX
Nội dung tổng hợp bằng AI, mang tính tham khảo. Xem bài gốc ↗