Tối ưu hóa truy vấn trên một loạt dấu thời gian (một cột)


8

Tôi đang sử dụng Postgres 9.3 thông qua Heroku.

Tôi có một bảng, "lưu lượng truy cập", với các bản ghi 1M + có nhiều chèn và cập nhật mỗi ngày. Tôi cần thực hiện các thao tác SUM trên bảng này trong các khoảng thời gian khác nhau và các cuộc gọi đó có thể mất tới 40 giây và rất thích nghe đề xuất về cách cải thiện điều đó.

Tôi có chỉ số sau thay thế trên bảng này:

CREATE INDEX idx_traffic_partner_only ON traffic (dt_created) WHERE campaign_id IS NULL AND uuid_self <> uuid_partner;

Dưới đây là một ví dụ câu lệnh CHỌN:

SELECT SUM("clicks") AS clicks, SUM("impressions") AS impressions
FROM "traffic"
WHERE "uuid_self" != "uuid_partner"
AND "campaign_id" is NULL
AND "dt_created" >= 'Sun, 29 Mar 2015 00:00:00 +0000'
AND "dt_created" <= 'Mon, 27 Apr 2015 23:59:59 +0000' 

Và đây là PHÂN TÍCH GIẢI THÍCH:

Aggregate  (cost=21625.91..21625.92 rows=1 width=16) (actual time=41804.754..41804.754 rows=1 loops=1)
  ->  Index Scan using idx_traffic_partner_only on traffic  (cost=0.09..20085.11 rows=308159 width=16) (actual time=1.409..41617.976 rows=302392 loops=1)
      Index Cond: ((dt_created >= '2015-03-29'::date) AND (dt_created <= '2015-04-27'::date))
Total runtime: 41804.893 ms

http://explain.depesz.com/s/gGA

Câu hỏi này rất giống với câu hỏi khác về SE, nhưng câu hỏi này đã sử dụng một chỉ mục trên hai phạm vi dấu thời gian của cột và trình hoạch định chỉ mục cho truy vấn đó có các ước tính đã bị loại bỏ. Gợi ý chính là tạo ra một chỉ mục nhiều cột được sắp xếp, nhưng đối với các chỉ mục cột đơn lẻ không có nhiều tác dụng. Các đề xuất khác là sử dụng các chỉ mục CLUSTER / pg numpack và GIST, nhưng tôi chưa thử chúng, vì tôi muốn xem liệu có giải pháp nào tốt hơn bằng cách sử dụng các chỉ mục thông thường.

Tối ưu hóa truy vấn trên một loạt dấu thời gian (hai cột)

Để tham khảo, tôi đã thử các chỉ mục sau, không được DB sử dụng:

INDEX idx_traffic_2 ON traffic (campaign_id, uuid_self, uuid_partner, dt_created);
INDEX idx_traffic_3 ON traffic (dt_created);
INDEX idx_traffic_4 ON traffic (uuid_self);
INDEX idx_traffic_5 ON traffic (uuid_partner);

EDIT : Ran GIẢI THÍCH (PHÂN TÍCH, ĐỘNG TỪ, CHI PHÍ, BUFFERS) và đây là những kết quả:

Aggregate  (cost=20538.62..20538.62 rows=1 width=8) (actual time=526.778..526.778 rows=1 loops=1)
  Output: sum(clicks), sum(impressions)
  Buffers: shared hit=47783 read=29803 dirtied=4
  I/O Timings: read=184.936
  ->  Index Scan using idx_traffic_partner_only on public.traffic  (cost=0.09..20224.74 rows=313881 width=8) (actual time=0.049..431.501 rows=302405 loops=1)
      Output: id, uuid_self, uuid_partner, impressions, clicks, dt_created... (other fields redacted)
      Index Cond: ((traffic.dt_created >= '2015-03-29'::date) AND (traffic.dt_created <= '2015-04-27'::date))
      Buffers: shared hit=47783 read=29803 dirtied=4
      I/O Timings: read=184.936
Total runtime: 526.881 ms

http://explain.depesz.com/s/7Gu6

Bảng định nghĩa:

CREATE TABLE traffic (
    id              serial,
    uuid_self       uuid not null,
    uuid_partner    uuid not null,
    impressions     integer NOT NULL DEFAULT 1,
    clicks          integer NOT NULL DEFAULT 0,
    campaign_id     integer,
    dt_created      DATE DEFAULT CURRENT_DATE NOT NULL,
    dt_updated      TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
)

id là khóa chính và uuid_elf, uuid_partner và chiến dịch_id đều là khóa ngoại. Trường dt_updated được cập nhật với hàm postgres.


explain (buffers, analyze, verbose) ...có thể làm sáng tỏ hơn
Craig Ringer

Một phần thông tin thiết yếu bị thiếu ở đây: định nghĩa bảng chính xác của traffic. Ngoài ra: tại sao cái thứ hai EXPLAINhiển thị giảm từ 42 giây xuống 0,5 giây? Là lần chạy đầu tiên với bộ đệm lạnh?
Erwin Brandstetter

Chỉ cần thêm định nghĩa bảng cho câu hỏi. Đúng, 42 đến 0,5 giây có lẽ là do bộ đệm lạnh, nhưng vì có quá nhiều cập nhật, nên đây có thể là một sự cố khá phổ biến. Tôi vừa chạy EXPLAIN ANALYZE một lần nữa và lần này phải mất 56 giây. Tôi đã chạy nó một lần nữa và nó đã giảm xuống .4s.
Evan Appleby

Có an toàn không khi cho rằng có một ràng buộc PK trên id? Bất kỳ hạn chế khác? Tôi thấy hai cột có thể là NULL. Bao nhiêu phần trăm giá trị NULL trong mỗi? Bạn nhận được gì cho điều này? SELECT count(*) AS ct, count(campaign_id)/ count(*) AS camp_pct, count(dt_updated)/count(*) AS upd_pct FROM traffic;
Erwin Brandstetter

Vâng, ID có ràng buộc PK và uuid_elf, uuid_partner và chiến dịch_id có các ràng buộc FK. Chiến dịch_id là 99% + NULL và dt_updated là 0% NULL.
Evan Appleby

Câu trả lời:


3

Hai điều rất kỳ lạ ở đây:

  1. Truy vấn chọn 300k hàng từ một bảng có 1M + hàng. Đối với 30% (hoặc bất cứ điều gì trên 5% - phụ thuộc vào kích thước hàng và các yếu tố khác), thông thường không phải trả tiền để sử dụng một chỉ mục. Chúng ta sẽ thấy một quét liên tiếp .

    Ngoại lệ sẽ là quét chỉ mục, mà tôi không thấy ở đây. Chỉ mục nhiều màu @Craig được đề xuất sẽ là lựa chọn tốt nhất nếu bạn chỉ quét các chỉ mục. Với rất nhiều cập nhật như bạn đã đề cập, điều này có thể không hoạt động, trong trường hợp đó bạn sẽ tốt hơn nếu không có các cột bổ sung - và chỉ là chỉ mục bạn đã có. Bạn có thể làm cho nó hoạt động với bạn với các cài đặt tự động tích cực hơn cho bảng. Bạn có thể điều chỉnh các tham số cho các bảng riêng lẻ.

  2. Mặc dù Postgres sẽ sử dụng chỉ mục, tôi chắc chắn sẽ mong đợi được xem quét chỉ mục bitmap cho nhiều hàng đó, chứ không phải quét chỉ mục đơn giản, thường là lựa chọn tốt hơn cho tỷ lệ hàng thấp . Ngay khi Postgres mong đợi nhiều lượt truy cập trên mỗi trang dữ liệu (đánh giá từ số liệu thống kê trên bảng), nó thường sẽ chuyển sang quét chỉ mục bitmap.

Đánh giá từ đó tôi sẽ nghi ngờ rằng các thiết lập chi phí của bạn là không đủ (và có thể cả thống kê bảng nữa). Bạn có thể đã đặt random_page_costvà / hoặc quá thấp , liên quan đến . Thực hiện theo các liên kết và đọc hướng dẫn.cpu_index_tuple_cost seq_page_cost

Cũng sẽ phù hợp với quan sát rằng bộ đệm lạnh là yếu tố lớn, như chúng tôi đã làm việc trong các bình luận. Hoặc bạn đang truy cập (một phần) bảng mà không ai chạm vào trong một thời gian dài hoặc bạn đang chạy trên một hệ thống kiểm tra nơi bộ đệm không được điền (chưa)?
Khác, bạn không có đủ RAM để lưu trữ hầu hết các dữ liệu có liên quan trong DB của bạn. Do đó, truy cập ngẫu nhiên đắt hơn nhiều so với truy cập tuần tự khi dữ liệu nằm trong bộ đệm. Tùy thuộc vào tình huống thực tế, bạn có thể phải điều chỉnh để có kế hoạch truy vấn tốt hơn.

Một yếu tố khác phải được đề cập để đáp ứng chậm trong lần đọc đầu tiên: Các bit Gợi ý . Đọc chi tiết trong Wiki Postgres và câu hỏi liên quan này:

Hoặc bảng cực kỳ cồng kềnh , trong trường hợp đó, quét chỉ mục sẽ có ý nghĩa và tôi sẽ tham khảo lạiCLUSTER / pg_repacktrong câu trả lời trước mà tôi đã trích dẫn. (Hoặc chỉVACUUM FULL)và điều traVACUUMcài đặtcủa bạn. Đó là những điều quan trọng vớimany inserts and updates every day.

Tùy thuộc vào UPDATEcác mẫu cũng xem xét FILLFACTORdưới 100. Nếu bạn chủ yếu chỉ cập nhật các hàng mới được thêm vào, hãy đặt mức thấp hơn FILLFACTER sau khi nén bảng của bạn, để chỉ các trang mới giữ một số chỗ lắc để cập nhật.

Lược đồ

campaign_idlà 99% + NULL và dt_updatedlà 0% NULL.

Điều chỉnh một chút các cột, để lưu 8 byte mỗi hàng (trong 99% trường hợp campaign_idlà NULL):

CREATE TABLE traffic (
    uuid_self       uuid not null REFERENCES ... ,
    uuid_partner    uuid not null REFERENCES ... ,
    id              serial PRIMARY KEY,
    impressions     integer NOT NULL DEFAULT 1,
    clicks          integer NOT NULL DEFAULT 0,
    campaign_id     integer,
    dt_created      DATE DEFAULT CURRENT_DATE NOT NULL,
    dt_updated      TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
);

Giải thích chi tiết và liên kết đến nhiều hơn:

Cân đo:


Cám ơn vì sự gợi ý. Tôi hiện đang dựa vào tính năng hút bụi tự động tích hợp được thiết lập thông qua Heroku và bảng giao thông được hút bụi mỗi ngày. Tôi sẽ xem xét thêm về việc thay đổi số liệu thống kê bảng và Fill Factor và sử dụng pg numpack và báo cáo lại.
Evan Appleby

2

Đối với tôi có vẻ như bạn đang truy vấn nhiều dữ liệu trong một chỉ mục lớn, vì vậy nó rất chậm. Không có gì sai đáng chú ý ở đó.

Nếu bạn đang sử dụng PostgreQuery 9.3 hoặc 9.4, bạn có thể thử xem liệu bạn có thể quét chỉ bằng cách biến nó thành một chỉ mục bao gồm các loại hay không.

CREATE INDEX idx_traffic_partner_only 
ON traffic (dt_created, clicks, impressions)
WHERE campaign_id IS NULL 
  AND uuid_self <> uuid_partner;

PostgreSQL không có các chỉ số hoặc hỗ trợ thực sự cho các thuật ngữ chỉ mục chỉ là giá trị, không phải là một phần của cây b, vì vậy, điều này chậm hơn và tốn kém hơn so với các tính năng đó. Nó vẫn có thể là một chiến thắng trong quá trình quét chỉ mục đơn giản nếu chân không chạy thường xuyên đủ để giữ cho bản đồ hiển thị được cập nhật.


Lý tưởng nhất là PostgreQuery sẽ hỗ trợ các trường dữ liệu phụ trợ trong một chỉ mục như trong MS-SQL Server ( cú pháp này sẽ KHÔNG LÀM VIỆC trong PostgreQuery ):

-- This will not work in PostgreSQL (at least 9.5)
-- it's an example of what I wish did work. Don't
-- comment to say it doesn't work.
--
CREATE INDEX idx_traffic_partner_only 
ON traffic (dt_created)
INCLUDING (clicks, impressions) -- auxillary data columns
WHERE campaign_id IS NULL 
  AND uuid_self <> uuid_partner;

Cám ơn vì sự gợi ý. Tôi đã thử chỉ số bao phủ và DB đã bỏ qua nó và vẫn sử dụng chỉ mục khác. Bạn có đề nghị loại bỏ chỉ mục khác và chỉ sử dụng chỉ mục che phủ (hoặc cách khác, chỉ sử dụng nhiều chỉ mục che cho từng tình huống yêu cầu nó)? Tôi cũng đã thêm EXPLAIN (ANALYZE, Verbose, COSTS, BUFFERS) trong câu hỏi ban đầu.
Evan Appleby

Lạ Có thể trình hoạch định không đủ thông minh để chọn quét chỉ mục nếu nó nhìn thấy nhiều hơn một tổng hợp, nhưng tôi đã nghĩ rằng nó có thể. Hãy thử chơi với các thông số chi phí ( random_page_costvv). Ngoài ra, đối với các mục đích thử nghiệm, chỉ xem nếu set enable_indexscan = offset enable_seqscan = offsau đó chạy lại bắt buộc chỉ quét, và nếu vậy, ước tính chi phí của nó từ phân tích giải thích là gì.
Craig Ringer
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.