Mengoptimalkan kueri pada rentang cap waktu (satu kolom)

8

Saya menggunakan Postgres 9.3 melalui Heroku.

Saya memiliki tabel, "traffic", dengan catatan 1M + yang memiliki banyak sisipan dan pembaruan setiap hari. Saya perlu melakukan operasi SUM di tabel ini dalam rentang waktu yang berbeda dan panggilan tersebut dapat memakan waktu hingga 40 detik dan akan sangat senang mendengar saran tentang cara meningkatkannya.

Saya memiliki indeks berikut pada tabel ini:

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

Ini adalah contoh pernyataan SELECT:

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' 

Dan ini adalah EXPLAIN ANALYZE:

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

Pertanyaan ini sangat mirip dengan yang lain di SE, tetapi yang satu menggunakan indeks di dua rentang cap waktu kolom dan perencana indeks untuk kueri itu memiliki perkiraan yang jauh. Saran utama di sana adalah untuk membuat indeks multi-kolom yang diurutkan, tetapi untuk indeks kolom tunggal yang tidak memiliki banyak efek. Saran lainnya adalah menggunakan indeks CLUSTER / pg_repack dan GIST, tapi saya belum mencobanya, karena saya ingin melihat apakah ada solusi yang lebih baik menggunakan indeks reguler.

Mengoptimalkan kueri pada rentang cap waktu (dua kolom)

Untuk referensi, saya mencoba indeks berikut ini, yang tidak digunakan oleh DB:

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 JELASKAN (ANALISIS, VERBOSE, BIAYA, BUFFER) dan ini adalah hasil:

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

Definisi tabel:

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 adalah kunci utama dan uuid_self, uuid_partner, dan campaign_id adalah kunci asing. Bidang dt_updated diperbarui dengan fungsi postgres.

Evan Appleby
sumber
explain (buffers, analyze, verbose) ...mungkin memberi lebih banyak cahaya.
Craig Ringer
Satu informasi penting tidak ada di sini: definisi tabel yang tepat untuk traffic. Juga: mengapa yang kedua EXPLAINmenunjukkan penurunan dari 42 detik menjadi 0,5 detik? Apakah jalankan pertama dengan cache dingin?
Erwin Brandstetter
Baru saja menambahkan definisi tabel ke pertanyaan. Ya, 42 hingga 0,5 detik mungkin disebabkan oleh cache yang dingin, tetapi karena ada begitu banyak pembaruan, ini mungkin merupakan kejadian yang cukup umum. Saya baru saja menjalankan EXPLAIN ANALYZE lagi dan kali ini butuh 56-an. Saya menjalankannya sekali lagi dan turun ke 0,4s.
Evan Appleby
Aman untuk mengasumsikan ada batasan PK id? Ada kendala lain? Saya melihat dua kolom yang bisa NULL. Berapa persentase nilai NULL di masing-masing? Apa yang kamu dapat untuk ini? SELECT count(*) AS ct, count(campaign_id)/ count(*) AS camp_pct, count(dt_updated)/count(*) AS upd_pct FROM traffic;
Erwin Brandstetter
Ya, ID memiliki batasan PK dan uuid_self, uuid_partner, dan campaign_id memiliki batasan FK. Campaign_id adalah 99% + NULL dan dt_updated adalah 0% NULL.
Evan Appleby

Jawaban:

3

Dua hal yang sangat aneh di sini:

  1. Kueri memilih 300k baris dari tabel dengan 1M + baris. Untuk 30% (atau apa pun di atas 5% - tergantung pada ukuran baris dan faktor lainnya) biasanya tidak membayar untuk menggunakan indeks sama sekali. Kita akan melihat pemindaian berurutan .

    Pengecualiannya adalah pemindaian hanya indeks, yang tidak saya lihat di sini. Indeks multicolumn @Craig yang disarankan akan menjadi pilihan terbaik jika Anda hanya dapat memindai indeks. Dengan banyak pembaruan seperti yang Anda sebutkan, ini mungkin tidak berhasil, dalam hal ini Anda lebih baik tanpa kolom tambahan - dan hanya indeks yang sudah Anda miliki. Anda mungkin dapat membuatnya bekerja untuk Anda dengan pengaturan autovacuum yang lebih agresif untuk tabel. Anda dapat menyesuaikan parameter untuk masing-masing tabel.

  2. Sementara Postgres akan menggunakan indeks saya tentu akan mengharapkan untuk melihat scan indeks bitmap untuk banyak baris, bukan scan indeks biasa, yang biasanya merupakan pilihan yang lebih baik untuk persentase baris yang rendah . Segera setelah Postgres mengharapkan beberapa klik per halaman data (dilihat dari statistiknya di tabel), Postgres biasanya akan beralih ke pemindaian indeks bitmap.

Menilai dari itu saya akan curiga bahwa pengaturan biaya Anda tidak memadai (dan mungkin statistik tabel juga). Anda mungkin telah menetapkan random_page_costdan / atau terlalu rendah , relatif terhadap . Ikuti tautannya dan baca manualnya.cpu_index_tuple_cost seq_page_cost

Juga cocok dengan pengamatan bahwa cache dingin adalah faktor besar, seperti yang kita kerjakan dalam komentar. Apakah Anda sedang mengakses (bagian dari) tabel yang tidak pernah disentuh orang dalam waktu yang lama atau Anda menjalankan sistem uji di mana cache belum diisi (belum)?
Jika tidak, Anda tidak memiliki cukup RAM untuk menyimpan sebagian besar data yang relevan di DB. Akibatnya, akses acak jauh lebih mahal daripada akses berurutan ketika data berada dalam cache. Bergantung pada situasi aktual Anda mungkin harus menyesuaikan diri untuk mendapatkan rencana kueri yang lebih baik.

Satu faktor lain harus disebutkan untuk respons lambat pada bacaan pertama saja: Bit petunjuk . Baca detail di Postgres Wiki dan pertanyaan terkait ini:

Atau tabelnya sangat membengkak , dalam hal ini pemindaian indeks akan masuk akal dan saya akan merujuk kembali keCLUSTER / pg_repackdalam jawaban saya sebelumnya yang Anda kutip. (Atau hanyaVACUUM FULL)dan selidikiVACUUMpengaturanAnda. Itu penting denganmany inserts and updates every day.

Tergantung pada UPDATEpola, pertimbangkan juga di FILLFACTORbawah 100. Jika sebagian besar Anda hanya memperbarui baris yang baru ditambahkan, atur lebih rendah FILLFACTER setelah memadatkan tabel Anda, sehingga hanya halaman baru yang menyimpan ruang gerak untuk pembaruan.

Skema

campaign_idadalah 99% + NULL dan dt_updated0% NULL.

Sesuaikan urutan kolom sedikit, untuk menghemat 8 byte per baris (dalam 99% kasus campaign_idNULL):

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,
);

Penjelasan terperinci dan tautan ke lebih banyak:

Untuk mengukur:

Erwin Brandstetter
sumber
Terima kasih untuk sarannya. Saat ini saya mengandalkan penyedot debu bawaan yang dipasang melalui Heroku dan tabel lalu lintas menjadi hamper setiap hari. Saya akan melihat lebih dalam mengubah statistik tabel dan Isi Faktor dan menggunakan pg_repack dan melaporkan kembali.
Evan Appleby
2

Sepertinya saya meminta banyak data dalam indeks besar, jadi lambat. Tidak ada yang salah di sana.

Jika Anda menggunakan PostgreSQL 9.3 atau 9.4, Anda bisa mencoba melihat apakah Anda bisa mendapatkan hanya pemindaian indeks dengan menjadikannya semacam indeks penutup.

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

PostgreSQL tidak memiliki indeks yang mencakup benar atau dukungan untuk istilah indeks yang hanya nilai, bukan bagian dari b-tree, jadi ini lebih lambat dan lebih mahal daripada mungkin dengan fitur-fitur itu. Mungkin masih menang atas pemindaian indeks biasa jika vakum berjalan cukup sering untuk menjaga peta visibilitas tetap up to date.


Idealnya PostgreSQL akan mendukung bidang data tambahan dalam indeks seperti di MS-SQL Server ( sintaks ini TIDAK AKAN BEKERJA di PostgreSQL ):

-- 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;
Craig Ringer
sumber
Terima kasih untuk sarannya. Saya mencoba indeks penutup dan DB mengabaikannya dan masih menggunakan indeks lainnya. Apakah Anda menyarankan menghapus indeks lain dan hanya menggunakan indeks penutup (atau sebagai alternatif, hanya menggunakan beberapa indeks penutup untuk setiap situasi yang memerlukannya)? Saya juga menambahkan EXPLAIN (ANALYZE, VERBOSE, BIAYA, BUFFER) dalam pertanyaan awal.
Evan Appleby
Aneh. Mungkin perencana tidak cukup pintar untuk memilih pemindaian hanya indeks jika melihat lebih dari satu agregat, tapi saya akan berpikir itu bisa. Coba mainkan dengan parameter biaya ( random_page_costdll). Juga, untuk tujuan pengujian hanya melihat apakah set enable_indexscan = offdan set enable_seqscan = offkemudian menjalankan kembali memaksa pemindaian hanya indeks, dan jika demikian, berapa perkiraan biayanya dari menjelaskan analisis.
Craig Ringer