- Pencarian teks lengkap PostgreSQL adalah pendekatan untuk membangun fitur pencarian tanpa mesin pencari terpisah dengan menggabungkan
tsvector,tsquery,@@,ts_rank, dan indeks GIN - Kata kunci dan dokumen dinormalisasi pada tingkat lexeme, dan kueri dapat diekspresikan dengan operator seperti AND, OR, NOT, dan FOLLOWED BY sehingga sebagian besar sintaks pencarian umum bisa diimplementasikan
- Dalam lingkungan contoh, indeks GIN mengurangi waktu pencarian dari lebih dari 200ms menjadi sekitar 4ms, tetapi ketika hasil sangat banyak, biaya pemeringkatan dan pengurutan
ts_rankdapat menjadi bottleneck - Penyesuaian relevansi dapat diimplementasikan dengan menambahkan sinyal seperti bobot judul, jumlah suara, rating, genre, dan kebaruan ke dalam ekspresi pengurutan, atau dengan memberi bobot kolom melalui
setweight - Toleransi typo, pencarian facet, autocomplete, pencarian frasa yang presisi, dan pencarian hibrida juga dimungkinkan, tetapi di PostgreSQL komponen-komponennya harus dirangkai sendiri dan batas performa pada dataset besar perlu diuji
Pendekatan PostgreSQL untuk pencarian teks lengkap
- PostgreSQL menyediakan komponen tingkat rendah untuk pencarian teks lengkap, yang dapat digabungkan untuk membangun fitur mesin pencari
- Pendekatan ini fleksibel, tetapi membutuhkan lebih banyak pekerjaan implementasi dibanding Elasticsearch, Typesense, atau Meilisearch yang memang berfokus pada pencarian teks lengkap
- Kueri contoh menggunakan dataset Kaggle Wikipedia Movie Plots
- Berisi 34.000 judul film
- Ukuran dalam format CSV sekitar 81MB
Komponen inti
- Pencarian teks lengkap PostgreSQL bekerja dengan elemen-elemen berikut
tsvector: menyimpan teks yang akan dicari sebagai daftar lexeme yang telah dinormalisasitsquery: merepresentasikan kueri pencarian yang telah dinormalisasi@@: operator pencocokan untuk memeriksa apakahtsquerycocok dengantsvectorts_rank,ts_rank_cd: menghitung skor relevansi hasil pencarian- Indeks GIN: indeks terbalik untuk mengkueri
tsvectorsecara efisien
tsvector dan konfigurasi pencarian
tsvectormenyimpan daftar lexeme yang telah diurutkan- Lexeme mirip token, tetapi merupakan string yang telah dinormalisasi agar berbagai bentuk kata yang sama disatukan
- Dalam konfigurasi bahasa Inggris, normalisasi dilakukan dengan mengubah huruf besar menjadi kecil dan menghapus sufiks
- Saat kalimat bahasa Inggris diurai dengan
to_tsvector, stop word seperti “I”, “to”, dan “an” dihapus- “refuse” dan “Refusing” sama-sama diubah menjadi
refus - Tanda baca diabaikan
- Posisi kata dan bobotnya dalam teks asli juga dicatat
- “refuse” dan “Refusing” sama-sama diubah menjadi
- Jika menggunakan konfigurasi
simplealih-alihenglish, kata disertakan sesuai bentuk yang ditemukan di teks- “refuse” dan “refusing” tetap menjadi lexeme yang berbeda
- Konfigurasi
simplesangat berguna terutama untuk kolom yang berisi label atau tag
- PostgreSQL menyediakan konfigurasi pencarian bawaan untuk banyak bahasa, tetapi tidak untuk CJK (Tionghoa, Jepang, Korea)
- Untuk bahasa yang tidak didukung, konfigurasi
simplebisa bekerja cukup praktis - Namun, belum pasti apakah ini cukup untuk CJK
- Untuk bahasa yang tidak didukung, konfigurasi
tsquery dan ekspresi kueri
tsqueryadalah tipe data untuk merepresentasikan kueri pencarian yang telah dinormalisasi- Kata pencarian harus sudah berupa lexeme yang dinormalisasi
- Beberapa istilah pencarian dapat digabungkan dengan operator AND, OR, NOT, dan FOLLOWED BY
to_tsquery,plainto_tsquery, danwebsearch_to_tsquerymembantu mengubah teks masukan pengguna menjaditsqueryyang sesuai- Peran utamanya adalah menormalisasi kata-kata dalam teks masukan
- Dengan
websearch_to_tsquery, kueri bisa dibuat lebih mirip kotak pencarian umumdarth vaderdiperlakukan sebagai AND logis yang mengharuskan kedua kata ada dalam dokumen- Pencarian OR dan pengecualian kata juga dimungkinkan
- Pencarian frasa mengekspresikan bentuk ketika kata-kata muncul berurutan
- Dalam konfigurasi bahasa Inggris, stop word seperti “the” dihapus sehingga pada beberapa pencarian frasa hampir seluruh frasa bisa hilang
- Dalam kasus seperti ini, penggunaan konfigurasi
simpledapat memberi hasil yang sesuai harapan
- Dalam kasus seperti ini, penggunaan konfigurasi
- Operator
@@digunakan untuk memeriksa apakahtsquerycocok dengantsvector
Indeks GIN dan performa pencarian
- GIN adalah singkatan dari Generalized Inverted Index, yaitu tipe indeks yang dirancang untuk kueri yang mencari nilai elemen di dalam nilai majemuk
- GIN dapat digunakan bukan hanya untuk pencarian teks tetapi juga kueri JSON
- Anda dapat membuat kolom
tsvectoryang menggabungkan beberapa kolom yang dapat dicari, lalu membuat indeks GIN pada kolom tersebut - Dalam lingkungan contoh, indeks GIN mengurangi waktu pencarian dari lebih dari 200ms menjadi sekitar 4ms
Pemeringkatan dan perhitungan relevansi
- Untuk pengalaman pencarian yang baik, hasil harus diurutkan berdasarkan relevansi
- PostgreSQL menyediakan dua fungsi pemeringkatan bawaan:
ts_rankdants_rank_cd- Keduanya mempertimbangkan frekuensi kemunculan istilah pencarian
ts_rank_cdjuga mencerminkan kedekatan antar lexeme yang cocok
- Relevansi sangat bergantung pada karakter aplikasi
- Fungsi pemeringkatan bawaan lebih merupakan titik awal, dan bila perlu dapat dibuat fungsi pemeringkatan sendiri atau digabungkan dengan faktor lain
ts_rankperlu mengakses kolomsearchdari setiap hasil- Jika banyak baris cocok dengan kondisi
WHERE, PostgreSQL harus mengunjungi semuanya untuk menghitung ranking dan melakukan pengurutan - Dalam lingkungan contoh, satu kueri dikembalikan dalam 5–7ms, tetapi kueri seperti
darth OR vaderyang perlu memberi peringkat pada lebih dari 1.000 hasil memerlukan sekitar 80ms
- Jika banyak baris cocok dengan kondisi
Tuning relevansi
- Relevansi berbasis frekuensi kata adalah default yang baik, tetapi data bisa memiliki sinyal yang lebih penting daripada frekuensi
- Dalam dataset film, sinyal-sinyal berikut dapat dimasukkan ke relevansi
- Hasil yang cocok di judul dianggap lebih penting daripada hasil yang cocok di deskripsi atau plot
- Film yang lebih populer dapat dinaikkan berdasarkan rating atau jumlah suara
- Jika pengguna menyukai komedi, film komedi bisa ditempatkan lebih tinggi
- Judul yang lebih baru dapat dianggap lebih relevan daripada judul lama
- Mesin pencari khusus menyediakan fitur untuk mengatur bagaimana kolom atau field yang berbeda memengaruhi ranking
- Lihat dokumentasi terkait untuk Elastic, Typesense, dan Meilisearch
Boosting berbasis angka, tanggal, dan nilai yang presisi
- PostgreSQL tidak menyediakan boosting berbasis kolom lain secara langsung, tetapi ranking pada akhirnya hanyalah ekspresi pengurutan, sehingga sinyal kustom bisa ditambahkan
- Untuk mencerminkan jumlah suara, ini bisa diimplementasikan dengan menambahkan boost berbasis jumlah suara ke skor ranking
- Contoh menggunakan log agar pengaruhnya lebih landai
- Koefisien
0.01digunakan agar booster berada pada skala yang mirip dengan skor ranking
- Fungsi yang lebih kompleks juga bisa dibuat, misalnya untuk menaikkan rating hanya ketika jumlah suara melewati ambang tertentu
- Jika ingin menaikkan genre tertentu, fungsi seperti
valueBoosteryang mengembalikan koefisien hanya saat nilainya cocok dengan nilai kolom tertentu dapat digunakan
Bobot kolom
- Lexeme dalam
tsvectordapat diberi bobot - PostgreSQL mendukung empat bobot: A, B, C, dan D
- A adalah bobot tertinggi
- D adalah bobot terendah dan nilai default
- Fungsi
setweightdapat digunakan untuk mengontrol bobot saat membuat kolomtsvector - Jika kolom judul diberi bobot lebih tinggi, film yang kata kuncinya muncul di judul akan naik ke bagian atas hasil dan skor rankingnya juga meningkat
- Fakta bahwa hanya ada empat kelas bobot merupakan keterbatasan, dan bobot harus diterapkan saat
tsvectordihitung
Toleransi typo dan pencarian fuzzy
- PostgreSQL tidak secara langsung mendukung pencarian fuzzy atau toleransi typo saat menggunakan
tsvectordantsquery - Dengan asumsi typo ada di sisi kueri, ini dapat diimplementasikan dengan cara berikut
- Mengindeks semua lexeme dari konten ke tabel terpisah
- Untuk setiap kata dalam kueri, mencari kandidat kata dengan similarity atau Levenshtein distance
- Merevisi kueri agar mencakup kata-kata yang ditemukan
- Menjalankan pencarian dengan kueri yang telah direvisi
- Contoh menggunakan Levenshtein distance karena itulah pendekatan yang dipakai mesin pencari untuk pencarian fuzzy
- Setelah daftar kandidat kata diperoleh, kueri perlu disesuaikan agar mencakup semua kata tersebut
Pencarian facet
- Pencarian facet banyak digunakan terutama di situs e-commerce agar pengguna dapat mempersempit cakupan pencarian secara bertahap
- Di PostgreSQL, ini dapat diimplementasikan dengan mendefinisikan kategori secara manual lalu menambahkannya ke kondisi
WHEREpencarian - Dimungkinkan juga membuat kategori secara algoritmis berdasarkan data yang ada
- Dalam contoh, facet “Decade” dibuat berdasarkan tahun film
- Jumlah kecocokan per decade juga bisa dihitung dan ditampilkan dalam tanda kurung
- Untuk mengambil beberapa facet dalam satu kueri, CTE dapat digabungkan
- Pendekatan ini dapat bekerja baik untuk dataset kecil hingga menengah, tetapi pada dataset yang sangat besar bisa menjadi lambat
Cakupan dan batasan mesin pencari PostgreSQL
- Dengan menggabungkan komponen pencarian teks lengkap PostgreSQL, kita bisa membangun mesin pencari yang cukup canggih
- Pencarian berbasis PostgreSQL juga mendukung join dan transaksi ACID
- Ini adalah kemampuan yang umumnya tidak dimiliki mesin pencari lain
- Topik pencarian lanjutan lain yang layak dibahas antara lain
- Saran kata dan autocomplete
- Pencocokan frasa yang presisi
- Pencarian hibrida yang digabungkan dengan
pg-vector
- Fitur-fitur ini memang mungkin di PostgreSQL, tetapi komponennya harus digabungkan sendiri
- Dalam beberapa kasus, performa dapat menurun pada dataset yang sangat besar
- Tulisan lanjutan bagian 2 membandingkan implementasi pencarian di PostgreSQL dengan menambahkan Elasticsearch ke infrastruktur dan menyinkronkan data
1 komentar
Komentar Hacker News
Jangan mencoba melakukan ini dengan meniru fitur Elasticsearch
Pada tahun 2000-an saya pernah membuat mesin pencari dengan MySQL 3.x yang mem-parsing data EXIF gambar lalu mengindeks taksonomi 3 tingkat beserta hitungannya; itu adalah sesuatu yang bahkan banyak vendor mahal pun gagal lakukan dengan benar, dan Autonomy bahkan tidak bisa menangani klasifikasi tingkat teratas
Saya akhirnya berhasil setelah 6 minggu menulis SQL rapuh yang performanya berubah hanya karena urutan kolom
SELECTdiubah, tetapi saya tidak ingin melakukannya lagi. Basis data pada dasarnya masih mirip, tetapi mesin pencari sudah berkembang jauh lebih pesatSebagai rasa ingin tahu intelektual, ini bisa dicoba, tetapi pencarian bukan sekadar tokenisasi lalu selesai. Segera akan muncul kebutuhan seperti penelusuran klasifikasi, multibahasa, sinonim otomatis, saran ejaan "Did you mean", performa skala besar, dan pada akhirnya Anda akan menjebak diri sendiri di jalan buntu. Demi kesehatan mental, lebih baik pakai mesin pencari, dan untuk sinkronisasi PG dengan ES juga ada alat seperti ZomboDB atau PGSync
Sebaliknya, jika analis data atau pengembang ingin mencari kolom teks di basis data besar dengan cara yang lebih fleksibel daripada
LIKE/ILIKE, mungkin lebih mudah dan cepat untuk mencapai 90% kebutuhan dengan membuat indeks/tabel full-text search di dalam basis data yang samaSaya menantikan bagian 2 yang membandingkan Postgres vs Elasticsearch. Salah satu aplikasi di perusahaan menggunakan PG untuk CRUD objek dan Elastic untuk pencarian, tetapi kami benar-benar meremehkan upaya sinkronisasi dua penyimpanan data itu, dan sekarang kami sedang mempertimbangkan untuk menghapus Elasticsearch
Setiap 5 menit sebuah cron mencari objek yang perlu diindeks di DB dengan
last_modified_at > last_indexing_started_timestamp, mengindeksnya ke Elasticsearch, lalu memperbaruilast_indexing_started_timestampke waktu dimulainya sinkronisasi. Dengan begitu, objek yang diubah antara awal dan akhir eksekusi akan tertangkap pada putaran berikutnyaJika Elasticsearch perlu dibangun ulang, cukup kosongkan waktu pengindeksan terakhir dan sinkronkan ulang dari awal; sistem jadi bisa memulihkan diri sendiri dan sinkronisasi tidak akan melenceng
Seperti pekerjaan asinkron pada umumnya, monitoring dan retry tetap diperlukan, tetapi ES stabil dan cepat sehingga masalah jarang terjadi. Namun kebutuhan konsistensinya cukup longgar sehingga cukup jika status terbaru PG sampai ke ES dalam waktu yang wajar; kalau kebutuhannya berbeda, hasilnya bisa berbeda juga
Setelah beralih ke full-text search Postgres dengan bobot pada indeks dan kueri, kami bisa menangani semua yang dibutuhkan sepenuhnya di dalam Postgres hanya dengan trigger pembaruan dan kueri pencarian yang sangat cepat
Secara keseluruhan itu sukses dan sangat mengurangi beban operasional sehingga waktu rekayasa yang dikeluarkan terbayar, tetapi ini bukan sesuatu untuk dimulai dengan santai
Tergantung kebutuhan, materialized view, view biasa, dan trigger bisa jadi lebih baik. Bisa juga full-text search bawaan tidak cocok untuk use case Anda, dan membuat alternatifnya belum tentu sesulit itu
Tanpa waktu lookup p50/p99 pada beban yang realistis, sulit menganggap ini sudah terbukti. Mesin pencari yang mengembalikan hasil dalam 1 menit bukanlah sesuatu yang "canggih", dan basis data relasional seperti Postgres pun tentu bisa melakukannya di atas kertas
Saya pada dasarnya setuju, dan pada skala besar, misalnya beberapa juta record atau lebih, kemungkinan saya tidak akan memakai pendekatan ini. Minat utama saya adalah melihat sejauh mana fungsinya bisa direplikasi
Untuk use case pencarian kecil, ada keuntungan seperti infrastruktur yang lebih sedikit untuk dipelihara, konsistensi kuat, dan join. Di Xata, kami juga memikirkan transisi mulus dari Postgres pada skala kecil ke Elasticsearch dengan perubahan yang seminimal mungkin merusak
Dari sudut pandang itu, hasil pencarian yang cepat sendiri tidak terlalu mengejutkan. Karena hasil cache bisa terus diperbarui lewat pekerjaan latar belakang lalu langsung disajikan saat ada permintaan. Caching dan waktu respons seperti ini tampaknya terpisah dari kecepatan perhitungan hasil pencarian yang sebenarnya
Ada pengguna dengan koneksi lambat, dan kata kunci tertentu seperti gempa bumi bisa melonjak hanya di wilayah tertentu
Jika kueri terlalu acak, cache tidak akan punya hasil sehingga terlihat lebih buruk dari kenyataan; jika tidak cukup acak, hasilnya akan terlihat lebih baik dari kenyataan
Karena itu, keunggulan ES dan sejenisnya ada pada penskalaan horizontal lintas banyak node atau fitur tambahan yang diletakkan di atas indeks utama
ts_ranksaja sudah bagus, tetapi biasanya Anda ingin menyesuaikan peringkat dengan metrik relevansi lain. Jika Anda bisa menyelesaikan masalah bahwa metrik itu tidak dapat dijadikan dasar peringkat utama, hasilnya secepat kueri tabel DB biasa yang memiliki indeksSaat remaja, saya pernah mencoba membuat mesin pencari dan basis data dari nol meski belum begitu paham keduanya. Saya ingin melihat seberapa jauh itu bisa dibawa, dan seberapa cepat serta seberapa relevan hasil pencarian yang bisa dikembalikan
Membuat basis data dasar dan mesin pencari sederhana dengan cepat mungkin cukup mudah bahkan bagi programmer amatir. Jika memahami algoritme dasar ilmu komputer serta cara memanfaatkan sistem operasi dan perangkat keras, itu bisa dibuat dalam satu atau dua bulan
Bahkan dengan bahasa tingkat tinggi, kecepatannya tidak buruk, sekitar 250 ribu QPS di laptop tahun 2003. Jika di-shard, skalabilitas juga bukan masalah besar. Yang lebih rumit daripada penyimpanan dan pengambilan adalah pengindeksan, locking, dan konsistensi
Masalah yang benar-benar besar adalah subjektivitas pencarian. Saat pertanyaannya menjadi apa yang sebenarnya ingin dicari, bagaimana mencari ketika kita sendiri tidak tahu apa yang dicari, bagaimana mencegah orang menyalahgunakan sistem, dan bagaimana menangani kueri serta dataset yang kompleks, tingkat kesulitannya naik beberapa orde besaran
Saya penasaran apakah benar menggunakan basis data serbaguna dan apakah angka itu akurat. Saya ingin tahu teknik apa yang dipakai untuk melampaui engine-engine seperti itu
Begitu lebih besar dari itu, dibutuhkan solusi kreatif, dan sejak saat itu berbagai trade-off mulai muncul
Salah satu dari banyak masalah saat melakukan pencarian di dalam Postgres adalah bahwa pencarian merupakan pekerjaan yang sangat memakan CPU, dan arahnya makin banyak memanfaatkan GPU. Idealnya, CPU basis data dibiarkan untuk pembaruan transaksional pada model data inti
Saya sering melihat cluster ES dan Solr saat reindexing berjalan 100% di lebih dari 10 node, atau bahkan dalam kondisi normal memakai 30~50% di lebih dari 10 node. Basis data pasangannya, misalnya, bisa saja memakai instance AWS L/XL dengan data 50~100GB dan penggunaan CPU sekitar 30%
Jika seluruh beban CPU pencarian dipindahkan ke DB utama, sekarang DB itu juga harus di-shard. Meski begitu, untuk side project, ekstensi PG untuk pencarian, recursive join, dan vector itu menyenangkan dan sederhana
Dulu saya benar-benar pernah melakukan ini, dan berhasil membuatnya cukup cepat
https://austingwalters.com/fast-full-text-search-in-postgres...
Situs web saat ini adalah https://askhn.ai
[1]: https://en.wikipedia.org/wiki/Kerning
Jika teknik seperti ini digabungkan dengan pgvector, kita juga bisa menemukan konten yang relevan lewat embedding. Rasanya cukup seperti sihir
Sebagai catatan, itu bukan "Dark" Vader melainkan Darth Vader. Saya juga dulu waktu kecil mengira itu "Dark"
Tulisan yang sangat bagus dan jelas. SQLite juga menyediakan fitur pengindeksan lanjutan dan stemming melalui plugin standar
Untuk bahasa Inggris, SQLite juga bekerja dengan baik
https://github.com/daitangio/knowledge
Layak dicoba. Cukup kuat
Ini tulisan yang bagus, tetapi bagian yang mengatakan PostgreSQL tidak mendukung fuzzy search agak kurang tepat. Ekstensi
pg_trgmdan indeks trigram GIN mendukung use case fuzzy search seperti contoh di tulisan inihttps://www.postgresonline.com/article_pfriendly/169.html
Kueri bisa menjadi jauh lebih cepat, tetapi ada biaya berupa penggunaan memori yang lebih besar dan waktu kerja saat update yang meningkat