Saya punya tabel seperti ini:
CREATE TABLE products (
id serial PRIMARY KEY,
category_ids integer[],
published boolean NOT NULL,
score integer NOT NULL,
title varchar NOT NULL);
Suatu produk dapat menjadi bagian dari banyak kategori. category_ids
Kolom berisi daftar id dari semua kategori produk.
Kueri umum terlihat seperti ini (selalu mencari kategori tunggal):
SELECT * FROM products WHERE published
AND category_ids @> ARRAY[23465]
ORDER BY score DESC, title
LIMIT 20 OFFSET 8000;
Untuk mempercepatnya saya menggunakan indeks berikut:
CREATE INDEX idx_test1 ON products
USING GIN (category_ids gin__int_ops) WHERE published;
Yang satu ini sangat membantu kecuali ada terlalu banyak produk dalam satu kategori. Dengan cepat menyaring produk yang termasuk dalam kategori itu tetapi kemudian ada semacam operasi yang harus dilakukan dengan cara yang sulit (tanpa indeks).
btree_gin
Ekstensi yang sudah diinstal memungkinkan saya membuat indeks GIN multi-kolom seperti ini:
CREATE INDEX idx_test2 ON products USING GIN (
category_ids gin__int_ops, score, title) WHERE published;
Tetapi Postgres tidak ingin menggunakannya untuk menyortir . Bahkan ketika saya menghapus DESC
specifier dalam kueri.
Setiap pendekatan alternatif untuk mengoptimalkan tugas sangat disambut.
Informasi tambahan:
- PostgreSQL 9.4, dengan ekstensi intarray
- total jumlah produk saat ini adalah 260 ribu tetapi diperkirakan akan tumbuh secara signifikan (hingga 10 juta, ini adalah platform multi-tenant e-commerce)
- produk per kategori 1..10000 (dapat tumbuh hingga 100rb), rata-rata di bawah 100 tetapi kategori-kategori dengan sejumlah besar produk cenderung menarik lebih banyak permintaan
Rencana kueri berikut diperoleh dari sistem pengujian yang lebih kecil (4680 produk dalam kategori yang dipilih, total produk 200k dalam tabel):
Limit (cost=948.99..948.99 rows=1 width=72) (actual time=82.330..82.341 rows=20 loops=1)
-> Sort (cost=948.37..948.99 rows=245 width=72) (actual time=80.231..81.337 rows=4020 loops=1)
Sort Key: score, title
Sort Method: quicksort Memory: 928kB
-> Bitmap Heap Scan on products (cost=13.90..938.65 rows=245 width=72) (actual time=1.919..16.044 rows=4680 loops=1)
Recheck Cond: ((category_ids @> '{292844}'::integer[]) AND published)
Heap Blocks: exact=3441
-> Bitmap Index Scan on idx_test2 (cost=0.00..13.84 rows=245 width=0) (actual time=1.185..1.185 rows=4680 loops=1)
Index Cond: (category_ids @> '{292844}'::integer[])
Planning time: 0.202 ms
Execution time: 82.404 ms
Catatan # 1 : 82 ms mungkin tidak terlihat menakutkan tapi itu karena penyortir semacam cocok ke dalam memori. Setelah saya memilih semua kolom dari tabel produk ( SELECT * FROM ...
dan dalam kehidupan nyata ada sekitar 60 kolom) ia pergi ke Sort Method: external merge Disk: 5696kB
menggandakan waktu eksekusi. Dan itu hanya untuk 4680 produk.
Poin tindakan # 1 (berasal dari Catatan # 1): Untuk mengurangi jejak memori operasi sortir dan karenanya mempercepatnya sedikit akan lebih bijaksana untuk mengambil, mengurutkan dan membatasi id produk pertama, kemudian mengambil catatan penuh:
SELECT * FROM products WHERE id IN (
SELECT id FROM products WHERE published AND category_ids @> ARRAY[23465]
ORDER BY score DESC, title LIMIT 20 OFFSET 8000
) ORDER BY score DESC, title;
Ini membawa kita kembali ke Sort Method: quicksort Memory: 903kB
dan ~ 80 ms untuk 4680 produk. Masih bisa lambat ketika jumlah produk tumbuh hingga 100 ribu.
sumber
score
bisa NULL, tetapi Anda masih urutkan berdasarkanscore DESC
, bukanscore DESC NULLS LAST
. Satu atau yang lain sepertinya tidak benar ...score
sebenarnya BUKAN NULL - Saya sudah mengoreksi definisi tabel.Jawaban:
Saya telah melakukan banyak percobaan dan inilah temuan saya.
GIN dan sortir
Indeks GIN saat ini (pada versi 9.4) tidak dapat membantu pemesanan .
work_mem
Terima kasih Chris telah menunjukkan parameter konfigurasi ini . Ini default ke 4MB, dan dalam hal recordset Anda lebih besar, meningkatkan
work_mem
ke nilai yang tepat (dapat ditemukan dariEXPLAIN ANALYSE
) dapat secara signifikan mempercepat operasi sortir.Mulai ulang server agar perubahan diterapkan, lalu periksa kembali:
Permintaan asli
Saya telah mengisi basis data saya dengan produk 650k dengan beberapa kategori menampung hingga produk 40k. Saya sedikit menyederhanakan kueri dengan menghapus
published
klausa:Seperti yang bisa kita lihat
work_mem
tidak cukup jadi kita punyaSort Method: external merge Disk: 29656kB
(angka di sini adalah perkiraan, perlu sedikit lebih dari 32MB untuk memori cepat di memori).Kurangi jejak memori
Jangan pilih catatan lengkap untuk disortir, gunakan id, terapkan sortir, offset dan batas, lalu muat hanya 10 catatan yang kita butuhkan:
Catatan
Sort Method: quicksort Memory: 7396kB
. Hasilnya jauh lebih baik.GABUNG dan indeks B-tree tambahan
Seperti yang disarankan Chris, saya telah membuat indeks tambahan:
Pertama saya mencoba bergabung seperti ini:
Paket kueri sedikit berbeda tetapi hasilnya sama:
Bermain dengan berbagai offset dan jumlah produk saya tidak bisa membuat PostgreSQL menggunakan indeks B-tree tambahan.
Jadi saya pergi cara klasik dan membuat tabel persimpangan :
Masih tidak menggunakan indeks B-tree, resultset tidak cocok
work_mem
, karenanya hasilnya buruk.Tetapi dalam beberapa keadaan, memiliki sejumlah besar produk dan offset kecil PostgreSQL sekarang memutuskan untuk menggunakan indeks B-tree:
Ini sebenarnya cukup logis karena indeks B-tree di sini tidak menghasilkan hasil langsung, hanya digunakan sebagai panduan untuk pemindaian sekuensial.
Mari kita bandingkan dengan permintaan GIN:
Hasil GIN jauh lebih baik. Saya memeriksa dengan berbagai kombinasi jumlah produk dan mengimbangi, dalam keadaan apa pun pendekatan tabel persimpangan tidak lebih baik .
Kekuatan indeks nyata
Agar PostgreSQL dapat sepenuhnya menggunakan indeks untuk menyortir, semua
WHERE
parameter kueri dan jugaORDER BY
parameter harus berada dalam indeks B-tree tunggal. Untuk melakukan ini, saya telah menyalin bidang sortir dari produk ke tabel persimpangan:Dan ini adalah skenario terburuk dengan sejumlah besar produk dalam kategori yang dipilih dan offset besar. Ketika offset = 300 waktu eksekusi hanya 0,5 ms.
Sayangnya mempertahankan tabel persimpangan seperti itu membutuhkan usaha ekstra. Ini dapat dilakukan melalui tampilan terindeks yang terindeks, tetapi itu hanya berguna ketika data Anda jarang diperbarui, karena menyegarkan tampilan terwujud semacam itu adalah operasi yang cukup berat.
Jadi saya tetap dengan indeks GIN sejauh ini, dengan peningkatan
work_mem
dan pengurangan kueri jejak memori.sumber
work_mem
pengaturan umum di postgresql.conf. Muat ulang sudah cukup. Dan izinkan saya memperingatkan agar tidak menetapkanwork_mem
terlalu tinggi secara global di lingkungan multi-pengguna (tidak terlalu rendah, baik). Jika Anda memiliki beberapa pertanyaan yang membutuhkan lebih banyakwork_mem
, atur lebih tinggi hanya untuk sesi denganSET
- atau hanya dengan transaksiSET LOCAL
. Lihat: dba.stackexchange.com/a/48633/3684Berikut adalah beberapa kiat cepat yang dapat membantu meningkatkan kinerja Anda. Saya akan mulai dengan tip termudah, yang hampir tidak mudah di pihak Anda, dan pindah ke tip yang lebih sulit setelah yang pertama.
1.
work_mem
Jadi, saya langsung melihat bahwa jenis yang dilaporkan dalam paket penjelasan Anda
Sort Method: external merge Disk: 5696kB
mengonsumsi kurang dari 6 MB, tetapi tumpah ke disk. Anda perlu meningkatkanwork_mem
pengaturan Anda dalampostgresql.conf
file Anda menjadi cukup besar sehingga jenisnya dapat masuk ke dalam memori.EDIT: Selain itu, pada pemeriksaan lebih lanjut, saya melihat bahwa setelah menggunakan indeks untuk memeriksa
catgory_ids
yang sesuai dengan kriteria Anda, pemindaian indeks bitmap dipaksa untuk menjadi "lossy" dan harus memeriksa kembali kondisi ketika membaca baris dari dalam halaman tumpukan yang relevan . Lihat posting ini di postgresql.org untuk penjelasan yang lebih baik daripada yang saya berikan. : P Poin utamanya adalah Andawork_mem
terlalu rendah. Jika Anda belum menyetel pengaturan default di server Anda, itu tidak akan berfungsi dengan baik.Perbaikan ini pada dasarnya tidak akan membawa Anda waktu. Satu perubahan ke
postgresql.conf
, dan Anda pergi! Lihat halaman penyesuaian kinerja ini untuk tips lebih lanjut.2. Perubahan skema
Jadi, Anda telah membuat keputusan dalam desain skema Anda untuk mendenormalkan
category_ids
menjadi array integer, yang kemudian memaksa Anda untuk menggunakan indeks GIN atau GIST untuk mendapatkan akses cepat. Dalam pengalaman saya, pilihan Anda untuk indeks GIN akan lebih cepat untuk dibaca daripada GIST, jadi dalam hal ini Anda membuat pilihan yang tepat. Namun, GIN adalah indeks yang tidak disortir; pikir itu lebih seperti sebuah kunci-nilai, di mana predikat kesetaraan mudah untuk memeriksa, tapi operasi sepertiWHERE >
,WHERE <
atauORDER BY
tidak difasilitasi oleh indeks.Pendekatan yang layak adalah menormalkan desain Anda dengan menggunakan tabel jembatan / tabel persimpangan , yang digunakan untuk menentukan hubungan banyak ke banyak dalam basis data.
Dalam hal ini, Anda memiliki banyak kategori dan satu set integer yang sesuai
category_id
, dan Anda memiliki banyak produk danproduct_id
s yang sesuai . Alih-alih kolom dalam tabel produk Anda yang merupakan array integercategory_id
s, hapus kolom array ini dari skema Anda, dan buat tabel sebagaiKemudian, Anda dapat menghasilkan indeks B-tree pada dua kolom dari tabel bridge,
Hanya pendapat saya yang sederhana, tetapi perubahan ini dapat membuat perbedaan besar bagi Anda. Cobalah
work_mem
hal pertama yang berubah, paling tidak.Semoga berhasil!
EDIT:
Buat indeks tambahan untuk membantu penyortiran
Jadi, jika seiring waktu lini produk Anda meluas, permintaan tertentu dapat menghasilkan banyak hasil (ribuan, puluhan ribu?) Tetapi yang mungkin masih hanya sebagian kecil dari total lini produk Anda. Dalam kasus ini, penyortiran bahkan mungkin cukup mahal jika dilakukan dalam memori, tetapi indeks yang dirancang dengan tepat dapat digunakan untuk membantu penyortiran.
Lihat dokumentasi PostgreSQL resmi yang menggambarkan Indeks dan ORDER OLEH .
Jika Anda membuat indeks yang cocok dengan
ORDER BY
persyaratan Andamaka Postgres akan mengoptimalkan dan memutuskan apakah menggunakan indeks atau melakukan pengurutan eksplisit akan lebih hemat biaya. Perlu diingat bahwa tidak ada jaminan bahwa Postgres akan menggunakan indeks; itu akan berusaha untuk mengoptimalkan kinerja dan memilih antara menggunakan indeks atau pengurutan secara eksplisit. Jika Anda membuat indeks ini, pantau untuk melihat apakah itu digunakan cukup untuk membenarkan pembuatannya, dan jatuhkan jika sebagian besar jenis Anda dilakukan secara eksplisit.
Namun, pada titik ini, peningkatan 'ledakan terbesar untuk uang' Anda mungkin akan menggunakan lebih banyak
work_mem
, tetapi ada beberapa kasus di mana indeks dapat mendukung penyortiran.sumber
work_mem
konfigurasi Anda dimaksudkan sebagai perbaikan untuk masalah 'pengurutan pada disk' Anda, serta masalah kondisi periksa ulang Anda. Saat jumlah produk bertambah, Anda mungkin perlu memiliki indeks tambahan untuk disortir. Silakan lihat hasil edit saya di atas untuk klarifikasi.