DevOps & giám sát

Chỉ mục Postgres không hợp lệ âm thầm làm giảm hiệu suất ghi sau khi xây dựng thất bại

Việc xây dựng chỉ mục đồng thời (concurrent) trong Postgres bị lỗi sẽ để lại các mục nhập không hợp lệ, gây tiêu tốn tài nguyên. Tìm hiểu cách phát hiện, hiểu rõ và dọn dẹp những vấn đề cơ sở dữ liệu ẩn này.

Các rack máy chủ với đèn cảnh báo chỉ ra các vấn đề hệ thống ẩn
Ảnh minh họa tạo riêng cho bài viết

Bài được dịch tự động từ bản gốc tiếng Anh.

Một mô hình phổ biến trong việc di chuyển schema (migration) của cơ sở dữ liệu hiện đại là sử dụng CREATE INDEX CONCURRENTLY để tránh khóa bảng trong quá trình thay đổi cấu trúc. Tuy nhiên, khi thao tác này thất bại do hết thời gian chờ (timeout) hoặc xung đột dữ liệu, nó thường để lại một mục nhập chỉ mục "không hợp lệ". Những tàn dư này không hỗ trợ truy vấn nhưng có thể làm chậm đáng kể các thao tác ghi bằng cách buộc cơ sở dữ liệu phải duy trì chúng một cách không cần thiết.

Điều gì đã xảy ra

Khi một nhà phát triển hoặc agent tự động yêu cầu tạo chỉ mục unique trên một bảng đang hoạt động, phương pháp đúng là sử dụng tùy chọn xây dựng đồng thời. Nếu quy trình thất bại giữa chừng—có thể do vi phạm khóa trùng lặp, deadlock, hoặc hết thời gian câu lệnh—Postgres không đơn thuần hoàn tác toàn bộ thao tác. Thay vào đó, nó cam kết (commit) mục nhập catalog ban đầu cho chỉ mục trước khi quá trình xây dựng hoàn tất. Kết quả là một chỉ mục tồn tại trong hệ thống nhưng được đánh dấu là không hợp lệ.

Vấn đề nghiêm trọng nảy sinh khi các script migration được thử lại mà không kiểm tra trạng thái của cơ sở dữ liệu. Một sai lầm phổ biến là thêm IF NOT EXISTS vào lệnh thử lại. Điều này cho phép migration vượt qua các bước kiểm tra tích hợp liên tục (CI), nhưng bỏ qua thực tế rằng chỉ mục hiện có đang bị hỏng. Cơ sở dữ liệu tiếp tục chịu tải trọng của cấu trúc không hợp lệ này, dẫn đến suy giảm hiệu suất khó chẩn đoán vì các kế hoạch truy vấn chuẩn sẽ không hiển thị việc sử dụng nó.

Chi tiết chính

  • Chỉ mục không hợp lệ được tạo ra khi một build đồng thời thất bại sau khi giao dịch catalog ban đầu đã commit.
  • Hai cờ xác định chi phí: indisvalid = false nghĩa là chỉ mục vô dụng đối với truy vấn, trong khi indisready = false nghĩa là các thao tác ghi bỏ qua nó.
  • Nếu indisready là true, cơ sở dữ liệu vẫn cập nhật chỉ mục mỗi lần chèn hoặc cập nhật, lãng phí CPU và dung lượng lưu trữ.
  • Các ràng buộc unique từ các build thất bại vẫn có thể thực thi quy tắc duy nhất ngay cả khi planner không bao giờ sử dụng chỉ mục cho việc tra cứu.
  • Các công cụ migration như Rails, Knex và golang-migrate thường bọc các lệnh trong transaction, điều này khiến các thao tác CONCURRENTLY thất bại ngay lập tức.
  • Việc dọn dẹp yêu cầu dùng DROP INDEX CONCURRENTLY, lệnh này phải được chạy bên ngoài khối transaction.

Bối cảnh

Để hiểu lý do tại sao điều này xảy ra, hãy biết rằng một build chỉ mục đồng thời không phải là một transaction atomic đơn lẻ. Nó diễn ra qua ba giai đoạn riêng biệt trong các transaction tách biệt. Đầu tiên, chỉ mục được đăng ký trong system catalogs dưới dạng không hợp lệ. Thứ hai, cơ sở dữ liệu quét dữ liệu bảng hiện có để điền vào chỉ mục. Thứ ba, nó chờ cho bất kỳ transaction cũ nào hoàn tất trước khi đánh dấu chỉ mục là sẵn sàng để sử dụng.

Nếu quy trình bị gián đoạn sau giai đoạn đầu nhưng trước giai đoạn cuối, chỉ mục vẫn nằm trong catalog. Tùy thuộc vào thời điểm chính xác của sự cố, chỉ mục có thể ở trạng thái chưa sẵn sàng cho truy vấn nhưng vẫn đang được cập nhật bởi các thao tác ghi mới. Điều này đặc biệt nguy hiểm vì chỉ mục dường như tồn tại, nhưng không mang lại lợi ích đọc nào trong khi vẫn tích cực gây phạt cho hiệu suất ghi.

Tại sao điều này quan trọng

Đối với các đội ngũ vận hành phần mềm riêng, sự suy giảm hiệu suất âm thầm là một rủi ro vận hành lớn. Một chỉ mục không hợp lệ vẫn đang được duy trì chiếm dụng dung lượng đĩa, tăng khối lượng write-ahead log (WAL) và ngăn chặn các bản cập nhật Heap-Only Tuple (HOT). Điều này dẫn đến chi phí lưu trữ cao hơn và thời gian phản hồi ứng dụng chậm hơn trong quá trình nạp dữ liệu. Vì chỉ mục bị bỏ qua bởi query planner, các công cụ giám sát tiêu chuẩn tập trung vào hiệu suất đọc có thể không báo cáo vấn đề này.

Hơn nữa, các pipeline triển khai tự động có thể làm trầm trọng thêm vấn đề. Nếu một coding agent hoặc script CI thử lại một migration thất bại với IF NOT EXISTS, nó về cơ bản che giấu lỗi. Việc triển khai thành công, nhưng cơ sở dữ liệu tích lũy nợ kỹ thuật dưới dạng các chỉ mục hỏng. Theo thời gian, nhiều lần thử thất bại có thể để lại vài chỉ mục không hợp lệ trên một bảng duy nhất, làm tăng thêm tải trọng ghi và khiến việc bảo trì trong tương lai trở nên phức tạp hơn.

Bạn có thể làm gì

  • Truy vấn system catalog pg_index thường xuyên để tìm các mục nhập nơi indisvalid là false.
  • Kiểm tra cờ indisready để xác định xem chỉ mục không hợp lệ có còn đang tiêu thụ tài nguyên ghi hay không.
  • Sử dụng DROP INDEX CONCURRENTLY để loại bỏ các chỉ mục không hợp lệ mà không khóa bảng cho các thao tác đọc hoặc ghi.
  • Đảm bảo các công cụ migration được cấu hình để tắt việc bọc transaction cho các thao tác chỉ mục đồng thời.
  • Tránh sử dụng IF NOT EXISTS trong các lần thử lại; thay vào đó, hãy kiểm tra tính hợp lệ và xóa chỉ mục hỏng trước.
  • Thêm một bước kiểm tra sau triển khai để ném exception nếu còn bất kỳ chỉ mục không hợp lệ nào trong cơ sở dữ liệu.

Tin khác

Tất cả tin