Tăng tốc độ tạo chỉ mục một phần của Postgres


8

Tôi đang cố gắng tạo các chỉ mục một phần cho một bảng tĩnh lớn (1,2TB) trong Postgres 9.4.

Dữ liệu của tôi hoàn toàn tĩnh, vì vậy tôi có thể chèn tất cả dữ liệu, sau đó tạo tất cả các chỉ mục.

Trong bảng 1,2TB này, tôi có một cột được run_idphân chia rõ ràng dữ liệu. Chúng tôi đã đạt được hiệu suất tuyệt vời bằng cách tạo các chỉ mục bao gồm một phạm vi run_ids. Đây là một ví dụ:

CREATE INDEX perception_run_frame_idx_run_266_thru_270
ON run.perception
(run_id, frame)
WHERE run_id >= 266 AND run_id <= 270;

Các chỉ mục một phần này cho chúng ta tốc độ truy vấn mong muốn. Thật không may, việc tạo ra mỗi chỉ số một phần mất khoảng 70 phút.

Có vẻ như chúng tôi bị giới hạn CPU ( topđang hiển thị 100% cho quy trình).
Có bất cứ điều gì tôi có thể làm để tăng tốc độ tạo ra các chỉ mục một phần của chúng tôi không?

Thông số hệ thống:

  • Xeon 18 lõi
  • RAM 192GB
  • 12 ổ SSD trong RAID
  • Autovacuums được TẮT
  • bảo trì_work_mem: 64GB (Quá cao?)

Bảng thông số kỹ thuật:

  • Kích thước: 1,26 TB
  • Số lượng hàng: 10,537 tỷ
  • Kích thước chỉ mục điển hình: 3,2GB (có phương sai ~ .5GB)

Bảng định nghĩa:

CREATE TABLE run.perception(
id bigint NOT NULL,
run_id bigint NOT NULL,
frame bigint NOT NULL,
by character varying(45) NOT NULL,
by_anyone bigint NOT NULL,
by_me bigint NOT NULL,
by_s_id integer,
owning_p_id bigint NOT NULL,
obj_type_set bigint,
seq integer,
subj_id bigint NOT NULL,
subj_state_frame bigint NOT NULL,
CONSTRAINT perception_pkey PRIMARY KEY (id))

(Đừng đọc quá nhiều vào tên cột - Tôi đã làm xáo trộn chúng phần nào.)

Thông tin cơ bản:

  • Chúng tôi có một nhóm riêng tại chỗ tiêu thụ dữ liệu này, nhưng thực sự chỉ có một hoặc hai người dùng. (Dữ liệu này được tạo tất cả thông qua một mô phỏng.) Người dùng chỉ bắt đầu phân tích dữ liệu sau khi chèn xong và các chỉ mục được xây dựng hoàn chỉnh. Mối quan tâm chính của chúng tôi là giảm thời gian cần thiết để tạo dữ liệu có thể sử dụng và ngay bây giờ, nút cổ chai là thời gian tạo chỉ mục.
  • Tốc độ truy vấn đã hoàn toàn đầy đủ khi sử dụng partials. Trên thực tế, tôi nghĩ rằng chúng tôi có thể tăng số lần chạy mà mỗi chỉ mục bao trùm và vẫn duy trì hiệu suất truy vấn đủ tốt.
  • Tôi đoán là chúng ta sẽ phải phân vùng bảng. Chúng tôi đang cố gắng làm cạn kiệt tất cả các lựa chọn khác trước khi đi theo con đường đó.

Thông tin bổ sung này sẽ là công cụ: các loại dữ liệu của các cột liên quan, truy vấn điển hình, cardinality (số lượng hàng), có bao nhiêu khác nhau run_id? Chia đêu? Kích thước của chỉ số kết quả trên đĩa? Dữ liệu là tĩnh, ok. Nhưng bạn có phải là người dùng duy nhất?
Erwin Brandstetter

Cập nhật với nhiều thông tin hơn.
burnsy

1
" Autovacuums được TẮT " - tại sao? Đó là một ý tưởng thực sự tồi tệ. Điều này ngăn việc thu thập số liệu thống kê và do đó sẽ mang lại các gói truy vấn xấu
a_horse_with_no_name

@a_horse_with_no_name Chúng tôi tự phân tích một cách phân tích sau khi tất cả dữ liệu được chèn
burnsy

Tình hình của bạn vẫn chưa rõ ràng với tôi. Các truy vấn của bạn trông như thế nào? Nếu bảng của bạn là completely static, thì bạn có ý nghĩa We have a separate team onsite that consumes this datagì? Bạn chỉ cần lập chỉ mục phạm vi run_id >= 266 AND run_id <= 270hoặc toàn bộ bảng? Tuổi thọ của mỗi chỉ số là bao nhiêu / bao nhiêu truy vấn sẽ sử dụng nó? Có bao nhiêu giá trị khác nhau cho run_id? Âm thanh như ~ 15 Mio. hàng trên mỗi run_id, sẽ làm cho nó khoảng 800 giá trị khác nhau cho run_id? Tại sao obj_type_set, by_s_id, seqkhông được định nghĩa NOT NULL? Bao nhiêu phần trăm giá trị NULL cho mỗi?
Erwin Brandstetter

Câu trả lời:


8

Chỉ số BRIN

Có sẵn kể từ Postgres 9.5 và có lẽ chỉ là những gì bạn đang tìm kiếm. Tạo chỉ mục nhanh hơn nhiều , chỉ số nhỏ hơn nhiều . Nhưng các truy vấn thường không nhanh như vậy. Hướng dẫn sử dụng:

BRIN là viết tắt của Block Range Index. BRIN được thiết kế để xử lý các bảng rất lớn trong đó các cột nhất định có một số tương quan tự nhiên với vị trí vật lý của chúng trong bảng. Một khối phạm vi là một nhóm các trang mà có thể chất liền kề trong bảng; đối với mỗi phạm vi khối, một số thông tin tóm tắt được lưu trữ bởi chỉ mục.

Đọc tiếp, có nhiều hơn.
Depesz đã chạy thử nghiệm sơ bộ.

Tối ưu cho trường hợp của bạn: Nếu bạn có thể viết hàng nhóm trên run_id, chỉ số của bạn trở nên rất nhỏ và tạo rẻ hơn nhiều.

CREATE INDEX foo ON run.perception USING brin (run_id, frame)
WHERE run_id >= 266 AND run_id <= 270;

Bạn thậm chí có thể chỉ mục toàn bộ bảng .

Bố trí bảng

Dù bạn có làm gì đi nữa, bạn có thể lưu 8 byte bị mất vào phần đệm do yêu cầu căn chỉnh trên mỗi hàng bằng cách sắp xếp các cột như thế này:

CREATE TABLE run.perception(
  id               bigint NOT NULL PRIMARY KEY
, run_id           bigint NOT NULL
, frame            bigint NOT NULL
, by_anyone        bigint NOT NULL
, by_me            bigint NOT NULL
, owning_p_id      bigint NOT NULL
, subj_id          bigint NOT NULL
, subj_state_frame bigint NOT NULL
, obj_type_set     bigint
, by_s_id          integer
, seq              integer
, by               varchar(45) NOT NULL -- or just use type text
);

Làm cho bảng của bạn nhỏ hơn 79 GB nếu không có cột nào có giá trị NULL. Chi tiết:

Ngoài ra, bạn chỉ có ba cột có thể là NULL. Bitmap NULL chiếm 8 byte cho 9 - 72 cột. Nếu chỉ có một cột số nguyên là NULL, có một trường hợp góc cho nghịch lý lưu trữ: sẽ rẻ hơn khi sử dụng giá trị giả thay thế: 4 byte bị lãng phí nhưng 8 byte được lưu bằng cách không cần bitmap NULL cho hàng. Thêm chi tiết tại đây:

Chỉ số một phần

Tùy thuộc vào các truy vấn thực tế của bạn, có thể hiệu quả hơn khi có năm chỉ số một phần này thay vì một chỉ số ở trên:

CREATE INDEX perception_run_id266_idx ON run.perception(frame) WHERE run_id = 266;
CREATE INDEX perception_run_id266_idx ON run.perception(frame) WHERE run_id = 267;
CREATE INDEX perception_run_id266_idx ON run.perception(frame) WHERE run_id = 268;
CREATE INDEX perception_run_id266_idx ON run.perception(frame) WHERE run_id = 269;
CREATE INDEX perception_run_id266_idx ON run.perception(frame) WHERE run_id = 270;

Chạy một giao dịch cho mỗi giao dịch.

Loại bỏ run_iddưới dạng cột chỉ mục theo cách này giúp tiết kiệm 8 byte cho mỗi mục nhập chỉ mục - 32 thay vì 40 byte mỗi hàng. Mỗi chỉ mục cũng rẻ hơn để tạo, nhưng việc tạo năm thay vì chỉ mất một thời gian dài hơn cho một bảng quá lớn để lưu trong bộ đệm (như @ Jürgen và @Chris đã nhận xét). Vì vậy, điều đó có thể hoặc không hữu ích cho bạn.

Phân vùng

Dựa trên tính kế thừa - tùy chọn duy nhất lên tới Postgres 9.5.
(Phân vùng khai báo mới trong Postgres 11 hoặc tốt nhất là 12 thông minh hơn.)

Hướng dẫn sử dụng:

Tất cả các ràng buộc trên tất cả các phần tử con của bảng cha được kiểm tra trong khi loại trừ ràng buộc, vì vậy số lượng lớn các phân vùng có khả năng tăng đáng kể thời gian lập kế hoạch truy vấn. Vì vậy, phân vùng dựa trên kế thừa kế thừa sẽ hoạt động tốt với tối đa một trăm phân vùng ; đừng cố sử dụng hàng ngàn phân vùng.

Nhấn mạnh đậm của tôi. Do đó, ước tính 1000 giá trị khác nhau run_id, bạn sẽ tạo các phân vùng trải dài khoảng 10 giá trị mỗi giá trị.


maintenance_work_mem

Tôi nhớ rằng bạn đã điều chỉnh maintenance_work_memtrong lần đọc đầu tiên của tôi. Tôi sẽ để lại trích dẫn và lời khuyên trong câu trả lời của tôi để tham khảo. Mỗi tài liệu:

maintenance_work_mem (số nguyên)

Chỉ định số tiền tối đa bộ nhớ được sử dụng bởi các hoạt động bảo dưỡng, chẳng hạn như VACUUM, CREATE INDEX, và ALTER TABLE ADD FOREIGN KEY. Nó mặc định là 64 megabyte ( 64MB). Vì chỉ có một trong số các hoạt động này có thể được thực thi tại một thời điểm bởi phiên cơ sở dữ liệu và cài đặt thường không có nhiều thao tác chạy đồng thời, nên an toàn để đặt giá trị này lớn hơn đáng kể work_mem. Các cài đặt lớn hơn có thể cải thiện hiệu suất cho việc hút bụi và khôi phục các bãi chứa cơ sở dữ liệu.

Lưu ý rằng khi autovacuumchạy, tối đa autovacuum_max_workerslần bộ nhớ này có thể được phân bổ, vì vậy hãy cẩn thận không đặt giá trị mặc định quá cao. Nó có thể hữu ích để kiểm soát điều này bằng cách riêng biệt setting autovacuum_work_mem.

Tôi sẽ chỉ đặt nó ở mức cao khi cần - phụ thuộc vào kích thước chỉ mục chưa biết (với chúng tôi). Và chỉ cục bộ cho phiên thực hiện. Như trích dẫn giải thích, cài đặt chung quá cao có thể khiến máy chủ chết đói, vì tự động cũng có thể yêu cầu nhiều RAM hơn. Ngoài ra, không đặt nó cao hơn mức cần thiết, ngay cả trong phiên thực thi, RAM miễn phí có thể được sử dụng tốt trong dữ liệu bộ đệm.

Nó có thể trông như thế này:

BEGIN;

SET LOCAL maintenance_work_mem = 10GB;  -- depends on resulting index size

CREATE INDEX perception_run_frame_idx_run_266_thru_270 ON run.perception(run_id, frame)
WHERE run_id >= 266 AND run_id <= 270;

COMMIT;

Về SET LOCAL:

Các tác động SET LOCALchỉ kéo dài cho đến khi kết thúc giao dịch hiện tại, cho dù có cam kết hay không.

Để đo kích thước đối tượng:

Máy chủ nói chung nên được cấu hình hợp lý khác, rõ ràng.


Tôi cá là công việc của anh ấy bị ràng buộc IO vì bảng lớn hơn nhiều so với RAM. Đọc bảng thậm chí thường xuyên hơn sẽ làm cho vấn đề trở nên tồi tệ hơn, bất kể có đủ bộ nhớ để sắp xếp từng chỉ mục được tạo trong bộ nhớ hay không.
Jürgen Strobel

Tôi với Jurgen về điều này. Tôi tin rằng do kích thước của bảng, về bản chất, bạn phải thực hiện quét tuần tự đầy đủ trên bảng cho mỗi chỉ mục được tạo. Ngoài ra, tôi không chắc chắn bạn sẽ thấy tất cả sự gia tăng hiệu suất đó từ việc tạo các chỉ mục một phần riêng biệt (Tôi chắc chắn 90% bạn sẽ không thấy bất kỳ sự gia tăng nào, nhưng về điều này tôi có thể tắt.) Tôi tin rằng tốt hơn giải pháp cho việc tạo chỉ mục sẽ liên quan đến việc tạo một chỉ mục trên toàn phạm vi mà bạn muốn truy vấn dưới dạng "chỉ mục một phần" để giảm thời gian xây dựng tổng thể.
Chris

@Chris: Tôi đồng ý, 5 chỉ mục sẽ mất nhiều thời gian để tạo hơn chỉ một (ngay cả khi tất cả chúng cùng nhỏ hơn, tạo mỗi chỉ mục rẻ hơn và truy vấn có thể nhanh hơn). Suy nghĩ thêm về nó, đây sẽ là một trường hợp sử dụng hoàn hảo cho chỉ số BRIN trong Postgres 9.5.
Erwin Brandstetter

3

Có lẽ đây chỉ là quá kỹ thuật. Bạn đã thực sự thử sử dụng một chỉ mục đầy đủ chưa? Các chỉ mục một phần bao gồm toàn bộ bảng cùng nhau không mang lại nhiều lợi ích, nếu có, cho việc tra cứu chỉ mục và từ văn bản của bạn, tôi suy luận rằng bạn có các chỉ số cho tất cả run_ids không? Có thể có một số lợi thế để quét chỉ mục với các chỉ số một phần, trước tiên tôi vẫn sẽ đánh giá giải pháp một chỉ mục đơn giản.

Đối với mỗi lần tạo chỉ mục, bạn cần quét toàn bộ ràng buộc IO thông qua bảng. Vì vậy, việc tạo một số chỉ mục một phần đòi hỏi IO đọc bảng nhiều hơn so với một chỉ mục, mặc dù sắp xếp sẽ tràn vào đĩa cho chỉ mục lớn duy nhất. Nếu bạn nhấn mạnh vào các chỉ mục một phần, bạn có thể thử xây dựng song song tất cả (hoặc một vài) chỉ mục (cho phép bộ nhớ).

Để có ước tính sơ bộ về bảo trì_work_mem cần thiết để sắp xếp tất cả run_ids, đó là các gợi ý 8 byte, trong bộ nhớ bạn cần 10,5 * 8 GB + một số chi phí.


0

Bạn cũng có thể tạo các chỉ mục trên các không gian bảng khác ngoài mặc định. Các không gian bảng này có thể trỏ đến các đĩa không dư thừa (chỉ cần tạo lại các chỉ mục nếu chúng thất bại) hoặc nằm trên các mảng nhanh hơn.

Bạn cũng có thể xem xét phân vùng bảng bằng các tiêu chí giống như các chỉ mục một phần của bạn. Điều này sẽ cho phép có cùng tốc độ với chỉ mục khi truy vấn, mà không thực sự tạo ra bất kỳ chỉ mục nào cả.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.