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.
sumber
explain (buffers, analyze, verbose) ...
mungkin memberi lebih banyak cahaya.traffic
. Juga: mengapa yang keduaEXPLAIN
menunjukkan penurunan dari 42 detik menjadi 0,5 detik? Apakah jalankan pertama dengan cache dingin?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;
Jawaban:
Dua hal yang sangat aneh di sini:
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.
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_cost
dan / 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 ke
CLUSTER
/pg_repack
dalam jawaban saya sebelumnya yang Anda kutip. (Atau hanyaVACUUM FULL)
dan selidikiVACUUM
pengaturanAnda. Itu penting denganmany inserts and updates every day
.Tergantung pada
UPDATE
pola, pertimbangkan juga diFILLFACTOR
bawah 100. Jika sebagian besar Anda hanya memperbarui baris yang baru ditambahkan, atur lebih rendahFILLFACTER
setelah memadatkan tabel Anda, sehingga hanya halaman baru yang menyimpan ruang gerak untuk pembaruan.Skema
Sesuaikan urutan kolom sedikit, untuk menghemat 8 byte per baris (dalam 99% kasus
campaign_id
NULL):Penjelasan terperinci dan tautan ke lebih banyak:
Untuk mengukur:
sumber
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.
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 ):
sumber
random_page_cost
dll). Juga, untuk tujuan pengujian hanya melihat apakahset enable_indexscan = off
danset enable_seqscan = off
kemudian menjalankan kembali memaksa pemindaian hanya indeks, dan jika demikian, berapa perkiraan biayanya dari menjelaskan analisis.