Suy nghĩ sâu hơn về random_page_cost trong PostgreSQL: Không chỉ là chi phí I/O

04 tháng 9, 2026·6 phút đọc

Bài viết phân tích lại ý nghĩa của tham số random_page_cost trong PostgreSQL, cho thấy giá trị mặc định 4.0 chưa bao giờ phản ánh đúng chi phí I/O ngẫu nhiên thực tế. Tác giả đề xuất cách nhìn mới: random_page_cost nên được coi là đại diện cho các hiệu ứng bộ nhớ đệm và active set, thay vì chỉ đo lường tốc độ ổ đĩa.

Suy nghĩ sâu hơn về random_page_cost trong PostgreSQL: Không chỉ là chi phí I/O

Cách đây vài tháng, tôi có viết về việc điều chỉnh tham số random_page_cost trong PostgreSQL để phản ánh chính xác hơn cách các thiết bị lưu trữ hiện đại xử lý truy cập ngẫu nhiên và tuần tự. Sau nhiều cuộc thảo luận thú vị, tôi bị cuốn vào những công việc khác và tạm gác lại chủ đề này.

Tuần trước, hội nghị POSETTE diễn ra với bài nói chuyện được ghi hình trước của tôi về chính chủ đề này. Điều đó khiến tôi bắt đầu suy nghĩ về random_page_cost theo một hướng hoàn toàn mới. Dưới đây là một số cập nhật và góc nhìn mới.

Kết quả đo trên ổ cứng SATA truyền thống

Tôi tình cờ tìm thấy một máy tính cũ vẫn còn gắn ổ cứng SATA quay (rotational) — loại thiết bị gần giống với những gì đã được sử dụng trong các thí nghiệm gốc khoảng năm 2000. Đó có thể là ổ PATA hoặc SCSI, nhưng vẫn là loại quay. Liệu nó có cho ra giá trị random_page_cost gần với mặc định 4.0 hay không?

Câu trả lời rõ ràng là không. Ước tính thực tế là ~125, cao gấp 2-4 lần so với ổ SSD. Vậy nên khi dùng SSD, giá trị ước tính tiến gần hơn đến mặc định — nhưng đó chỉ là sự trùng hợp ngẫu nhiên.

Có thể có một phần nào đó trong thí nghiệm cũ mà chúng ta đã không nhớ chính xác? Hoặc kết quả "thô" đã được điều chỉnh theo cách nào đó. Nhưng có vẻ như giá trị mặc định 4.0 chưa bao giờ là chi phí "thuần" của I/O ngẫu nhiên.

Phản hồi từ cộng đồng: Tăng giá trị có hại

Tôi nhận được khá nhiều phản hồi từ những người từng thử tăng random_page_cost. Theo kinh nghiệm của họ, việc này chắc chắn không cải thiện hiệu suất mà còn khiến mọi thứ tệ hơn. Làm sao có thể như vậy, nếu giá trị tăng khiến mô hình chi phí chính xác hơn?

Tôi tin rằng vấn đề nằm ở chỗ random_page_cost đang "bù đắp" cho những khiếm khuyết của mô hình chi phí. Nó không tính đến các hiệu ứng bộ nhớ đệm và tài nguyên liên quan đến các kế hoạch thực hiện nhiều I/O ngẫu nhiên.

Mọi mô hình chi phí đều là sự xấp xỉ, và là một phép xấp xỉ tương đối thô thiển. Không thể có một mô hình nhanh và rẻ mà mô phỏng chính xác từng chi tiết nhỏ. Bạn có thể làm nó chi tiết hơn, nhưng đến một lúc nào đó nó sẽ lớn như chính hệ thống ban đầu.

Mô hình chi phí của PostgreSQL có một vài lỗ hổng quan trọng:

  • Bỏ qua chi phí bộ nhớ: Chi phí của một phép toán chỉ tính từ CPU và I/O, nhưng hoàn toàn bỏ qua memory. work_mem chỉ là giới hạn an toàn cho vùng đệm làm việc, không theo dõi các loại bộ nhớ khác mà kế hoạch sử dụng.

  • Ví dụ cụ thể: Một bảng 100GB chứa 1GB dữ liệu "thú vị" (khớp với điều kiện truy vấn). Nếu bảng ở trạng thái "nguội", quét tuần tự có thể đẩy khoảng 100GB dữ liệu khác ra khỏi bộ nhớ (shared buffers hoặc page cache). Ngược lại, quét qua index chỉ cần truy cập khoảng 1% bảng (1GB dữ liệu thú vị) — dù I/O ngẫu nhiên mất nhiều thời gian hơn, nhưng nó chỉ "dùng" 1GB bộ nhớ.

Khái niệm active set (tập hoạt động)

Một lý do khác là khái niệm active set. Bạn có thể có cơ sở dữ liệu 1TB, nhưng trong hầu hết hệ thống thực tế, chỉ một phần rất nhỏ dữ liệu được truy cập thường xuyên — người dùng chỉ xem đơn hàng gần đây, v.v. Đây chính là "active set" của cơ sở dữ liệu.

Chìa khóa là giữ active set trong bộ nhớ. Các kế hoạch thực hiện nhiều I/O ngẫu nhiên có xu hướng cục bộ hơn, chỉ truy cập dữ liệu "thú vị". Quét qua index thường chỉ truy cập một phần nhỏ hơn nhiều so với quét tuần tự. I/O có thể đắt hơn (ngẫu nhiên, phải ghé thăm các trang nhiều lần), nhưng chỉ là dữ liệu "thú vị".

Quét tuần tự có thể mở rộng active set một cách đáng kể — có thể đến toàn bộ cơ sở dữ liệu. Điều này không tốt trừ khi bạn có đủ RAM để chứa toàn bộ. Quét qua index cho phép active set nhỏ hơn nhiều.

Nhưng bộ lập kế hoạch (planner) hoàn toàn không biết về active set. Nó lập kế hoạch như thể mọi truy vấn đều bắt đầu với dữ liệu "nguội", chỉ xem xét hiệu ứng caching trong một số trường hợp rất hạn chế.

random_page_cost như một proxy

Planner cũng lập kế hoạch các truy vấn một cách cô lập, như thể không có truy vấn nào khác cạnh tranh tài nguyên. Một quét tuần tự có thể tốt cho một backend duy nhất, nhưng khi có 100 backend cùng quét tuần tự, bạn sẽ chạm trần băng thông lưu trữ.

Giờ tôi xem random_page_cost như một đại diện cho tất cả những hiệu ứng này.

Planner không tính chi phí bộ nhớ giống như CPU/disk, cũng không hiểu các hiệu ứng caching. Giá trị random_page_cost thấp hơn sẽ đẩy nó về các kế hoạch cục bộ hơn, giữ active set trong tầm kiểm soát — về lâu dài đây có vẻ là chiến lược chiến thắng.

Kết luận và khuyến nghị

Điều này có nghĩa là không thể có một công cụ nào cho bạn biết giá trị "đúng" của random_page_cost chỉ bằng cách đo đạc ổ đĩa và kiểm tra cách nó xử lý I/O tuần tự/ngẫu nhiên. Tôi rất muốn điều đó tồn tại, nhưng nếu random_page_cost không phải là chi phí I/O thô, cách làm đó sẽ không hiệu quả.

Việc tinh chỉnh random_page_cost phải mang tính đặc thù cho từng hệ thống, dựa trên phản hồi từ giám sát thực tế:

  • Xem xét các truy vấn hàng đầu trong pg_stat_statements
  • Xác định xem những truy vấn này có được lợi từ giá trị random_page_cost thấp hơn không
  • Điều chỉnh giá trị và theo dõi tác động — cả tích cực lẫn tiêu cực lên các truy vấn khác

Với độc giả Việt Nam đang vận hành hệ thống PostgreSQL, lời khuyên thiết thực là: đừng vội thay đổi random_page_cost chỉ dựa trên thông số kỹ thuật ổ cứng. Hãy bắt đầu từ giá trị mặc định, theo dõi hiệu suất truy vấn thực tế bằng pg_stat_statements, và thử nghiệm thay đổi trong môi trường staging trước khi áp dụng vào production. Mỗi hệ thống có đặc thù riêng, và không có công thức chung nào áp dụng được cho tất cả.

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