Pencarian teks lengkap lambat untuk istilah dengan kejadian tinggi

8

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 ANALYZEmenunjukkan 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"
danjo
sumber
1
Dari atas kepala saya: indeks GIN telah menerima peningkatan besar di Postgres 9.4 (dan beberapa lainnya di 9.5 mendatang). Pasti akan membayar untuk meningkatkan ke 9,4 saat ini. Dan saya juga akan menyelidiki kinerja GiST, bukan indeks GIN. Penyebab dalam kueri Anda adalah ORDER BY "RANK" DESC. Saya akan menyelidiki pg_trgmdengan indeks GiST dan operator kesamaan / jarak sebagai alternatif. Pertimbangkan: dba.stackexchange.com/questions/56224/… . Bahkan mungkin menghasilkan hasil yang "lebih baik" (selain lebih cepat).
Erwin Brandstetter
Di OS apa Anda menjalankan instance PostgreSQL Anda?
Kassandry
Bisakah Anda mengulanginya dengan explain (analyze, buffers), lebih disukai dengan track_io_timing diatur ke ON? 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 Anda random_page_cost, dan parameter biaya lainnya?
jjanes
@ Danjo Saya menghadapi masalah yang sama bahkan ketika saya tidak menggunakan pemesanan. Bisakah Anda memberi tahu saya cara memperbaikinya?
Sahil Bahl

Jawaban:

11

Kasus penggunaan yang dipertanyakan

... setiap entri KONTEN terdiri dari satu kata acak dan string teks yang sama untuk semua baris.

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:

Memang, itu tidak realistis ... Tapi karena saya tidak dapat mengontrol teks ...

Tingkatkan versi Postgres Anda

Menjalankan PostgreSQL 9.3.4

Saat masih menggunakan Postgres 9.3, Anda setidaknya harus meningkatkan ke rilis poin terbaru (saat ini 9.3.9). Rekomendasi resmi proyek:

Kami selalu merekomendasikan bahwa semua pengguna menjalankan rilis minor terbaru yang tersedia untuk versi utama apa pun yang digunakan.

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):

ALTER FUNCTION to_tsvector (regconfig, text) COST 100;

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 LIKEpredikat sebagai kandidat (dalam subquery dengan LIMIT 50contohnya) 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 <-> tsqueryoperator.

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:

Indeks GIN tidak lossy untuk permintaan standar, tetapi kinerjanya tergantung secara logaritmik pada jumlah kata-kata unik. ( Namun, indeks GIN hanya menyimpan kata-kata (leksem) tsvectornilai, dan bukan label bobotnya. Oleh karena itu, periksa ulang baris tabel diperlukan saat menggunakan kueri yang melibatkan bobot.)

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:

CREATE INDEX "File_contentIndex" ON "File" USING gin
(to_tsvector('english', "CONTENT");

Dan pertanyaan Anda:

SELECT "ITEMID", ts_rank(to_tsvector('english', "CONTENT")
                       , plainto_tsquery('english', 'searchTerm')) AS rank
FROM   "File"
WHERE  to_tsvector('english', "CONTENT")
       @@ plainto_tsquery('english', 'searchTerm')
ORDER  BY rank DESC
LIMIT  5;

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:

Erwin Brandstetter
sumber
Saya juga mengalami hal ini. Dengan Postgresql 9.6, kami menggunakan penulisan ulang kueri untuk sinonim jadi saya tidak berpikir menggunakan pencarian kesamaan trigram untuk membatasi jumlah dokumen akan bekerja dengan baik.
pholly
Luar biasa! USING gin (to_tsvector('english', "CONTENT")
K-Gun
1

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.

  1. Dapatkan kata-kata populer dalam korpus dengan memindai dokumen dan menghitung kemunculannya.
  2. Urutkan berdasarkan kata paling populer.
  3. precompute ts_rank dari kata-kata populer dan simpan dalam sebuah tabel.

Kueri: cari tabel ini dan dapatkan id dokumen yang diurutkan berdasarkan peringkatnya masing-masing. jika tidak ada, lakukan dengan cara lama.

Pari Rajaram
sumber