Full Text Searches

Di PostgreSQL, full-text search (FTS) berbeda dengan pencarian string biasa. FTS fokus pada kata / lexeme (unit makna setelah preprocess seperti stemming & stopword removal) — bukan pada pencocokan substring literal. Dengan FTS kita mencari apakah dokumen mengandung kata/kumpulan kata tertentu, dapat menggabungkan operasi boolean (AND/OR/NOT), memberi peringkat (ranking), dan mengekstrak snippet yang menonjolkan hasil (highlighting). Berikut penjelasan lengkap, langkah-langkah praktis, contoh kode, dan best practice.


1) Konsep dasar: tsvector, tsquery, lexeme, stemming, stop words

  • to_tsvector(config, text) → mengubah teks menjadi tsvector (kumpulan lexeme + posisi).

    • Proses: tokenisasi → lowercase → menghapus stop words → stemming (mengubah kata ke bentuk dasar/akar).

    • Contoh:

      SELECT to_tsvector('english',
        'A car, I want a car. I would not even mind having many cars');

      Hasil (contoh):

      'car':2,6,14 'even':10 'mani':13 'mind':11 'want':4 'would':8
      • cars dan car distem menjadi car. many menjadi mani (contoh stemming bahasa Inggris).

  • to_tsquery(config, 'some & query') → membuat tsquery dari sintaks tsquery (membutuhkan operator & | ! <-> dsb.).

  • plainto_tsquery(config, 'plain text') → mengubah teks biasa menjadi tsquery dengan operator & antar kata (lebih mudah untuk input user yang bukan operator tsquery).

  • phraseto_tsquery(config, 'phrase') → untuk pencarian frase (kata berurutan).

  • @@ → operator BOOLEAN yang memeriksa apakah tsvector @@ tsquery (apakah dokumen cocok dengan query).

  • Lexeme = token yang distandarisasi (hasil stemming/dictionary). Contoh: want dan wanted → sama menjadi lexeme want.

Catatan: konfigurasi bahasa ('english', 'dutch', 'simple', dll.) mempengaruhi tokenisasi, stop words, dan stemming. Lihat pg_ts_config untuk daftar konfigurasi tersedia:


2) Contoh perilaku stemming & perbandingan bahasa

  • Inggris:

  • Dutch (konfigurasi berbeda → tokenisasi/stopword/stemming berbeda):


3) Mencari & operator tsquery

  • Basic boolean dalam to_tsquery:

    • & : AND

    • | : OR

    • ! : NOT

    • <-> : FOLLOWED BY (adjacency / kata bersebelahan) — berguna untuk frase/proximity

  • Contoh:

  • plainto_tsquery sangat cocok untuk input user biasa:


4) Indexing untuk Full-Text Search — dua pendekatan

Ada dua pola umum untuk mengindeks teks:

A. Functional index (lebih hemat ruang, lebih sederhana)

Buat index langsung dari ekspresi to_tsvector(...):

  • Kelebihan: tidak menambah kolom, lebih ringkas dari segi schema.

  • Kekurangan: setiap query yang memanfaatkan index harus mengeksekusi ekspresi; update index saat row berubah tetap terjadi; kadang sedikit lebih lambat dibanding materialized column pada runtime.

B. Materialized tsvector column + trigger + index (lebih cepat pada query baca)

  1. Tambah kolom ts bertipe tsvector:

  1. Isi kolom untuk baris existing:

  1. Buat index pada kolom tersebut:

  1. Buat trigger supaya ts selalu ter-sync saat INSERT/UPDATE:

Pada versi PostgreSQL modern gunakan EXECUTE FUNCTION, pada versi sangat lama EXECUTE PROCEDURE.

  • tsvector_update_trigger(target_column, config, column1, column2, ...)

  • Kelebihan: query baca lebih cepat (index langsung pada kolom).

  • Kekurangan: butuh ruang tambahan untuk kolom ts, trigger menambah overhead saat write.


5) Contoh query FTS lengkap (dengan ranking & snippet)

  • Cari & urutkan hasil berdasarkan relevansi (ts_rank) — contoh plainto_tsquery:

  • Menggunakan weighting (beri bobot lebih besar ke kolom title vs body):

  • Ambil snippet / highlight hasil dengan ts_headline:


6) Debugging & Statistik

  • ts_debug(config, text) → menampilkan tokenisation/dictionary/lexeme untuk tiap token — berguna untuk mengetahui mengapa query match/tidak match.

    • Output menunjukkan token, jenis token, dictionary yang dipakai, dan lexeme hasil stemming.

  • ts_stat(query_sql) → kumpulan statistik kata/lexeme (berapa dokumen mengandung kata, frekuensi total):


7) Pilihan operator & sintaks pencarian lanjutan

  • to_tsquery('english', 'foo & bar') → match dokumen yang mengandung foo AND bar (lexeme setelah stemming).

  • plainto_tsquery('english', 'foo bar') → mempermudah: akan jadi foo & bar.

  • phraseto_tsquery('english', 'exact phrase') → membantu pencarian frase bersebelahan.

  • <-> dalam to_tsquery → adjacency (kata bersebelahan, urutan memakai lexeme): contoh 'foo <-> bar'.

  • Ada juga fungsi modern seperti websearch_to_tsquery (meniru sintaks search engines — dukungan pada versi PostgreSQL tertentu); ini memudahkan pengguna memakai kutipan/+, - dsb. (cek versi PostgreSQL Anda).


8) Index type: GIN vs GiST untuk FTS

  • GIN (Generalized Inverted Index) adalah yang paling umum dipakai untuk full-text search (USING gin(ts)).

    • Kelebihan: sangat cepat untuk pencarian, efisien mencari kata/lexeme di dokumen besar.

    • Kekurangan: update index bisa lebih berat (INSERT/UPDATE lebih mahal dibanding GiST), dan index bisa relatif besar.

  • GiST juga bisa digunakan (lebih fleksibel), tetapi untuk FTS GIN biasanya pilihan utama di production kecuali ada kebutuhan khusus.

  • Untuk trigram (pg_trgm), GIN sering lebih baik untuk dataset besar; GiST kadang lebih cepat saat update, trade-off tergantung workload.


9) Maintenance, konsistensi & hal yang harus diperhatikan

  • Jika Anda mengubah konfigurasi bahasa, aturan stemming, atau dictionary → Anda harus rebuild tsvector dan reindex (REINDEX) karena hasil stemming/dictionary berubah.

  • Pilihan antara functional index vs materialized column:

    • Functional index: lebih hemat struktur, berguna jika update jarang.

    • Materialized tsvector + trigger: lebih cepat untuk query baca intensif, tapi menambah overhead pada penulisan.

  • Autovacuum & VACUUM: GIN index maintenance bisa memerlukan perhatian (bloating). Monitoring & reindexing berkala mungkin perlu.

  • Untuk performa ranking, gunakan ts_rank atau ts_rank_cd dengan parameter normalisasi jika perlu.


10) Contoh alur kerja FTS (praktis, step-by-step)

  1. Aktifkan extension (sekali per database):

  2. Tambah tabel / data:

  3. Tambah kolom ts (materialized approach):

  4. Buat index:

  5. Buat trigger untuk keep-in-sync:

  6. Query pencarian:


11) Tips & best practice

  • Pilih konfigurasi bahasa sesuai teks (search dalam bahasa Indonesia? gunakan config yang sesuai atau simple jika tidak ada konfig bahasa lokal).

  • Gunakan plainto_tsquery untuk input raw user. to_tsquery mengharuskan sintaks tsquery.

  • Beratkan (weight) kolom penting (title > subtitle > body) dengan setweight agar hasil relevansi lebih alami.

  • Gunakan ts_headline untuk menampilkan potongan teks (highlight).

  • Monitoring: cek ukuran index, frekuensi update; GIN dapat bloat setelah banyak update.

  • Rebuild tsvector & reindex jika stop words / dictionaries diubah.

  • Gabungkan pg_trgm untuk fuzzy matching pada nama/kolom yang bukan cocok dengan FTS (mis. nama orang, alamat). pg_trgm mempercepat LIKE '%...%', regex, dan similarity search.


12) Debug & analisis kenapa suatu query tidak match

  • ts_debug → lihat tokenization & mapping dictionary untuk kata bermasalah.

  • ts_stat → lihat frekuensi lexeme untuk memahami kata umum vs jarang.

  • Jika query tidak match: kemungkinan stopword dihapus, stemming mengubah bentuk, atau config bahasa salah.


  • pg_trgm (trigram)

    • Tujuan: fuzzy matching / similarity / mempercepat LIKE '%...%' dan regex.

    • Cocok untuk: nama, alamat, autocomplete, typo tolerance.

    • Index: GiST (gist_trgm_ops) atau GIN (gin_trgm_ops) — GIN sering lebih baik di dataset besar.

  • Full-Text Search (FTS)

    • Tujuan: pencarian dokumen/isi berbasis kata; mendukung stemming, stop words, ranking, phrase.

    • Cocok untuk: artikel, komentar, dokumen panjang, konten umum.

    • Index: GIN pada tsvector (pola umum).

Keduanya saling melengkapi: untuk search bar modern Anda mungkin menggunakan FTS untuk dokumen + pg_trgm untuk nama/slug/typo tolerance.


14) (Tambahan) Exclusion operators — contoh singkat (terkait indexing GiST)

Walaupun bukan bagian inti FTS, buku juga menyebut exclusion constraints yang memanfaatkan GiST. Contoh: mencegah double booking ruangan.

EXCLUDE + GiST memberikan cara kuat untuk menjaga integritas yang bergantung pada operator seperti overlap (&&) atau geometric ops.


Penutup

Full-text search di PostgreSQL adalah fitur sangat kaya: preprocessing (tokenize, stopword, stemming), berbagai jenis query, ranking & headline, debugging tools, serta index (GIN/GiST) yang membuatnya skalabel di produksi. Untuk aplikasi nyata, pertimbangkan trade-off storage vs query speed (functional index vs materialized tsvector) dan pilih konfigurasi bahasa & opclass yang sesuai.

Last updated