Saya memiliki tabel yang berisi data yang diekstrak dari dokumen teks. Data disimpan dalam kolom yang disebut "CONTENT"
sebagai tempat saya membuat indeks ini menggunakan GIN:
CREATE INDEX "File_contentIndex"
ON "File"
USING gin
(setweight(to_tsvector('english'::regconfig
, COALESCE("CONTENT", ''::character varying)::text), 'C'::"char"));
Saya menggunakan kueri berikut untuk melakukan pencarian teks lengkap di atas meja:
SELECT "ITEMID",
ts_rank(setweight(to_tsvector('english', coalesce("CONTENT",'')), 'C') ,
plainto_tsquery('english', 'searchTerm')) AS "RANK"
FROM "File"
WHERE setweight(to_tsvector('english', coalesce("CONTENT",'')), 'C')
@@ plainto_tsquery('english', 'searchTerm')
ORDER BY "RANK" DESC
LIMIT 5;
Tabel File berisi 250.000 baris dan setiap "CONTENT"
entri terdiri dari satu kata acak dan string teks yang sama untuk semua baris.
Sekarang, ketika saya mencari kata acak (1 hit di seluruh tabel) kueri berjalan sangat cepat (<100 ms). Namun, ketika saya mencari kata yang ada di semua baris, kueri berjalan sangat lambat (10 menit atau lebih).
EXPLAIN ANALYZE
menunjukkan bahwa untuk pencarian 1-hit, Pemindaian Indeks Bitmap diikuti oleh Bitmap Heap Scan dilakukan. Untuk pencarian lambat, pemindaian Seq dilakukan sebagai gantinya, yang memakan waktu begitu lama.
Memang, tidak realistis untuk memiliki data yang sama di semua baris. Tetapi karena saya tidak dapat mengontrol dokumen teks yang diunggah oleh pengguna, atau pencarian yang mereka lakukan, ada kemungkinan skenario serupa muncul (mencari istilah dengan kejadian yang sangat tinggi dalam DB). Bagaimana saya dapat meningkatkan kinerja permintaan pencarian saya untuk skenario seperti itu?
Menjalankan PostgreSQL 9.3.4
Paket kueri dari EXPLAIN ANALYZE
:
Pencarian cepat (1 hit dalam DB)
"Limit (cost=2802.89..2802.90 rows=5 width=26) (actual time=0.037..0.037 rows=1 loops=1)"
" -> Sort (cost=2802.89..2806.15 rows=1305 width=26) (actual time=0.037..0.037 rows=1 loops=1)"
" Sort Key: (ts_rank(setweight(to_tsvector('english'::regconfig, (COALESCE("CONTENT", ''::character varying))::text), 'C'::"char"), '''wfecg'''::tsquery))"
" Sort Method: quicksort Memory: 25kB"
" -> Bitmap Heap Scan on "File" (cost=38.12..2781.21 rows=1305 width=26) (actual time=0.030..0.031 rows=1 loops=1)"
" Recheck Cond: (setweight(to_tsvector('english'::regconfig, (COALESCE("CONTENT", ''::character varying))::text), 'C'::"char") @@ '''wfecg'''::tsquery)"
" -> Bitmap Index Scan on "File_contentIndex" (cost=0.00..37.79 rows=1305 width=0) (actual time=0.012..0.012 rows=1 loops=1)"
" Index Cond: (setweight(to_tsvector('english'::regconfig, (COALESCE("CONTENT", ''::character varying))::text), 'C'::"char") @@ '''wfecg'''::tsquery)"
"Total runtime: 0.069 ms"
Pencarian lambat (250 ribu klik di DB)
"Limit (cost=14876.82..14876.84 rows=5 width=26) (actual time=519667.404..519667.405 rows=5 loops=1)"
" -> Sort (cost=14876.82..15529.37 rows=261017 width=26) (actual time=519667.402..519667.402 rows=5 loops=1)"
" Sort Key: (ts_rank(setweight(to_tsvector('english'::regconfig, (COALESCE("CONTENT", ''::character varying))::text), 'C'::"char"), '''cyberspace'''::tsquery))"
" Sort Method: top-N heapsort Memory: 25kB"
" -> Seq Scan on "File" (cost=0.00..10541.43 rows=261017 width=26) (actual time=2.097..519465.953 rows=261011 loops=1)"
" Filter: (setweight(to_tsvector('english'::regconfig, (COALESCE("CONTENT", ''::character varying))::text), 'C'::"char") @@ '''cyberspace'''::tsquery)"
" Rows Removed by Filter: 6"
"Total runtime: 519667.429 ms"
ORDER BY "RANK" DESC
. Saya akan menyelidikipg_trgm
dengan indeks GiST dan operator kesamaan / jarak sebagai alternatif. Pertimbangkan: dba.stackexchange.com/questions/56224/… . Bahkan mungkin menghasilkan hasil yang "lebih baik" (selain lebih cepat).explain (analyze, buffers)
, lebih disukai dengan track_io_timing diatur keON
? Tidak mungkin perlu waktu 520 detik untuk memindai tabel itu, kecuali Anda menyimpannya di RAID floppy disk. Pasti ada sesuatu yang patologis di sana. Juga, untuk apa pengaturan Andarandom_page_cost
, dan parameter biaya lainnya?Jawaban:
Kasus penggunaan yang dipertanyakan
String teks yang sama untuk semua baris hanyalah pengiriman mati. Hapus dan gabungkan dalam tampilan jika Anda perlu menunjukkannya.
Jelas, Anda sadar akan hal itu:
Tingkatkan versi Postgres Anda
Saat masih menggunakan Postgres 9.3, Anda setidaknya harus meningkatkan ke rilis poin terbaru (saat ini 9.3.9). Rekomendasi resmi proyek:
Lebih baik lagi, tingkatkan ke 9,4 yang telah menerima perbaikan besar untuk indeks GIN .
Masalah utama 1: Perkiraan biaya
Biaya beberapa fungsi pencarian teks telah diremehkan hingga dan termasuk versi 9.4. Biaya itu dinaikkan oleh faktor 100 di versi mendatang 9.5 seperti yang dijelaskan @jjanes dalam jawaban terbarunya:
Berikut adalah utas masing-masing tempat ini dibahas dan pesan komit oleh Tom Lane.
Seperti yang Anda lihat di pesan commit,
to_tsvector()
ada di antara fungsi-fungsi itu. Anda dapat segera menerapkan perubahan (sebagai pengguna super):yang seharusnya membuatnya jauh lebih mungkin bahwa indeks fungsional Anda digunakan.
Masalah utama 2: KNN
Masalah intinya adalah Postgres harus menghitung peringkat dengan
ts_rank()
untuk 260k baris (rows=261011
) sebelum dapat memesan dan memilih 5 teratas. Ini akan mahal , bahkan setelah Anda memperbaiki masalah lain seperti yang dibahas. Ini masalah K-terdekat-tetangga (KNN) dan ada solusi untuk kasus terkait. Tapi saya tidak bisa memikirkan solusi umum untuk kasus Anda, karena perhitungan peringkat itu sendiri tergantung pada input pengguna. Saya akan mencoba untuk menghilangkan sebagian besar pertandingan peringkat rendah lebih awal sehingga perhitungan penuh hanya harus dilakukan untuk beberapa kandidat yang baik.Satu cara yang bisa saya pikirkan adalah menggabungkan pencarian teks lengkap Anda dengan pencarian kesamaan trigram - yang menawarkan implementasi yang berfungsi untuk masalah KNN. Dengan cara ini Anda dapat memilih dulu pertandingan "terbaik" dengan
LIKE
predikat sebagai kandidat (dalam subquery denganLIMIT 50
contohnya) dan kemudian memilih 5 baris peringkat teratas menurut perhitungan peringkat Anda dalam kueri utama.Atau terapkan kedua predikat dalam kueri yang sama dan pilih kecocokan terdekat menurut kesamaan trigram (yang akan menghasilkan hasil yang berbeda) seperti dalam jawaban terkait ini:
Saya melakukan penelitian lebih lanjut dan Anda bukan orang pertama yang mengalami masalah ini. Posting terkait pada pgsql-general:
Pekerjaan sedang berlangsung untuk akhirnya mengimplementasikan
tsvector <-> tsquery
operator.Oleg Bartunov dan Alexander Korotkov bahkan mempresentasikan prototipe yang berfungsi (menggunakan
><
sebagai operator alih-alih<->
saat itu) tetapi sangat kompleks untuk berintegrasi di Postgres, seluruh infrastruktur untuk indeks GIN harus dikerjakan ulang (sebagian besar sudah dilakukan sekarang).Masalah utama 3: bobot dan indeks
Dan saya mengidentifikasi satu faktor lagi yang menambah lambatnya permintaan. Per dokumentasi:
Penekanan berani saya. Begitu berat terlibat, setiap baris harus diambil dari heap (bukan hanya pemeriksaan visibilitas murah) dan nilai yang panjang harus dihapus, yang menambah biaya. Tetapi tampaknya ada solusi untuk itu:
Definisi indeks
Melihat indeks Anda lagi, tampaknya tidak masuk akal untuk memulai. Anda menetapkan bobot ke satu kolom, yang tidak berarti selama Anda tidak menggabungkan kolom lain dengan bobot yang berbeda .
COALESCE()
juga tidak masuk akal selama Anda tidak benar-benar menyatukan lebih banyak kolom.Sederhanakan indeks Anda:
Dan pertanyaan Anda:
Masih mahal untuk istilah pencarian yang cocok dengan setiap baris, tetapi mungkin jauh lebih sedikit.
Selain itu
Semua masalah ini digabungkan, biaya gila 520 detik untuk permintaan kedua Anda mulai masuk akal. Tetapi mungkin masih ada lebih banyak masalah. Apakah Anda mengkonfigurasi server Anda?
Semua saran biasa untuk pengoptimalan kinerja berlaku.
Itu membuat hidup Anda lebih mudah jika Anda tidak bekerja dengan tanda kutip ganda CaMeL:
sumber
USING gin (to_tsvector('english', "CONTENT")
Saya punya masalah serupa. Saya mengatasinya dengan mengkomputasi ts_rank dari setiap istilah permintaan teks populer terhadap bidang: tabel tuple dan menyimpannya dalam tabel pencarian. Ini menghemat banyak waktu saya (faktor 40X) selama pencarian kata-kata populer dalam teks heavy corpus.
Kueri: cari tabel ini dan dapatkan id dokumen yang diurutkan berdasarkan peringkatnya masing-masing. jika tidak ada, lakukan dengan cara lama.
sumber