PostgreSQL full-text search bisa mencukupi banyak aplikasi. Gunakan aturan keputusan sederhana, query starter, dan checklist pengindeksan untuk tahu kapan menambahkan search engine.

Kebanyakan orang tidak meminta “full-text search.” Mereka meminta kotak pencarian yang terasa cepat dan menemukan apa yang mereka maksud di halaman pertama. Jika hasil lambat, berisik, atau berurutan aneh, pengguna tidak peduli apakah Anda menggunakan PostgreSQL full-text search atau engine terpisah. Mereka hanya berhenti mempercayai pencarian.
Ini satu keputusan: pertahankan pencarian di dalam Postgres, atau tambahkan engine pencarian khusus. Targetnya bukan relevansi sempurna. Ini baseline yang solid yang cepat dikirim, mudah dijalankan, dan cukup untuk cara aplikasi Anda sebenarnya digunakan.
Untuk banyak aplikasi, PostgreSQL full-text search cukup untuk jangka waktu lama. Jika Anda punya beberapa field teks (title, description, notes), perankingan dasar, dan satu atau dua filter (status, kategori, tenant), Postgres bisa menanganinya tanpa infrastruktur tambahan. Anda mendapat lebih sedikit bagian yang bergerak, backup yang lebih sederhana, dan lebih sedikit insiden “kenapa pencarian turun tapi aplikasi hidup?”.
“Cukup” biasanya berarti Anda bisa memenuhi tiga target sekaligus:
Contoh konkret: dashboard SaaS di mana pengguna mencari proyek berdasarkan nama dan catatan. Jika query seperti “onboarding checklist” mengembalikan proyek yang tepat di 5 besar, dalam waktu kurang dari satu detik, dan Anda tidak terus-menerus menyetel analyzer atau job reindexing, itu sudah “cukup.” Ketika Anda tidak bisa memenuhi target itu tanpa menambah kompleksitas, barulah pertanyaan “pencarian bawaan vs mesin pencari” menjadi relevan.
Tim sering mendeskripsikan pencarian dengan fitur, bukan hasil. Langkah berguna adalah menerjemahkan tiap fitur ke apa yang dibutuhkan untuk membangun, menyetel, dan menjaga keandalannya.
Permintaan awal biasanya terdengar seperti: toleransi typo, facets dan filter, highlights, perankingan “pintar”, dan autocomplete. Untuk versi pertama, pisahkan yang harus ada dari yang bagus untuk dimiliki. Kotak pencarian dasar biasanya hanya perlu menemukan item relevan, menangani bentuk kata umum (jamak, tense), menghormati filter sederhana, dan tetap cepat saat tabel tumbuh. Di sinilah PostgreSQL full-text search biasanya cocok.
Postgres unggul ketika konten Anda berada di field teks normal dan Anda ingin pencarian dekat dengan data: artikel bantuan, posting blog, tiket support, dokumen internal, judul dan deskripsi produk, atau catatan pada record pelanggan. Ini sebagian besar masalah “temukan record yang tepat,” bukan masalah “bangun produk pencarian”.
Yang bagus-untuk-dimiliki adalah tempat kompleksitas merayap masuk. Toleransi typo dan autocomplete kaya biasanya mendorong Anda ke tooling tambahan. Facets mungkin dilakukan di Postgres, tapi jika Anda menginginkan banyak facets, analitik mendalam, dan hitungan instan di dataset besar, engine khusus mulai terlihat lebih menarik.
Biaya tersembunyi jarang lisensi. Itu sistem kedua. Setelah menambahkan engine pencarian, Anda juga menambah sinkronisasi data dan backfill (dan bug yang mereka buat), monitoring dan upgrade, pekerjaan dukungan “kenapa pencarian menampilkan data lama?”, dan dua set knob relevansi.
Jika ragu, mulai dengan Postgres, kirim sesuatu yang sederhana, dan tambahkan engine lain hanya ketika ada kebutuhan jelas yang tidak bisa dipenuhi. Banyak aplikasi tidak pernah tumbuh melewatinya, dan Anda menghindari menjalankan serta menyinkronkan sistem kedua terlalu dini.
Gunakan aturan tiga-cek. Jika Anda lulus ketiganya, tetap dengan PostgreSQL full-text search. Jika Anda gagal satu dengan buruk, pertimbangkan engine pencarian terdedikasi.
Kebutuhan relevansi: Apakah hasil “cukup baik” dapat diterima, atau Anda butuh perankingan hampir sempurna di banyak edge case (typo, sinonim, “orang juga mencari”, hasil dipersonalisasi)? Jika Anda dapat mentolerir urutan yang kadang kurang tepat, Postgres biasanya bekerja.
Volume query dan latensi: Berapa banyak pencarian per detik yang Anda harapkan di puncak, dan berapa anggaran latensi riil Anda? Jika pencarian hanya sebagian kecil traffic dan Anda bisa menjaga query cepat dengan indeks yang tepat, Postgres oke. Jika pencarian menjadi beban utama dan mulai bersaing dengan read/write inti, itu peringatan.
Kompleksitas: Apakah Anda mencari satu atau dua field teks, atau menggabungkan banyak sinyal (tag, filter, time decay, popularitas, permission) dan banyak bahasa? Semakin kompleks logikanya, semakin Anda akan merasakan gesekan di dalam SQL.
Awal yang aman adalah sederhana: kirim baseline di Postgres, log query lambat dan pencarian “tidak ada hasil”, lalu putuskan. Banyak aplikasi tidak pernah kehabisan, dan Anda menghindari menjalankan serta menyinkronkan sistem kedua terlalu dini.
Tanda merah yang biasanya menunjukkan engine terdedikasi:
Tanda hijau untuk tetap di Postgres:
PostgreSQL full-text search adalah cara bawaan untuk mengubah teks menjadi sesuatu yang dapat dicari dengan cepat oleh database, tanpa memindai setiap baris. Ia bekerja terbaik ketika konten Anda sudah di Postgres dan Anda menginginkan pencarian cepat, layak, dengan operasional yang dapat diprediksi.
Ada tiga bagian yang perlu diketahui:
ts_rank (atau ts_rank_cd) untuk menempatkan baris yang lebih relevan di depan.Konfigurasi bahasa penting karena mengubah bagaimana Postgres memperlakukan kata. Dengan konfigurasi yang tepat, “running” dan “run” bisa cocok (stemming), dan kata-kata pengisi umum bisa diabaikan (stop words). Dengan konfigurasi yang salah, pencarian bisa terasa rusak karena kata biasa pengguna tidak lagi cocok dengan apa yang diindeks.
Prefix matching adalah fitur yang dicari orang ketika mereka ingin perilaku “typeahead-ish”, seperti mencocokkan “dev” ke “developer.” Di Postgres FTS, itu biasanya dilakukan dengan operator prefix (misalnya, term:*). Ini bisa meningkatkan kualitas yang dirasakan, tapi sering meningkatkan beban per query, jadi perlakukan sebagai upgrade opsional, bukan default.
Apa yang bukan tujuan Postgres: platform pencarian lengkap dengan semua fitur. Jika Anda butuh koreksi ejaan fuzzy, autocomplete canggih, learning-to-rank, analyzer kompleks per field, atau pengindeksan terdistribusi di banyak node, Anda berada di luar zona nyaman bawaan. Untuk banyak aplikasi, PostgreSQL full-text search memberi sebagian besar yang diharapkan pengguna dengan jauh lebih sedikit bagian yang bergerak.
Berikut bentuk kecil, realistis untuk konten yang ingin Anda cari:
-- Minimal example table
CREATE TABLE articles (
id bigserial PRIMARY KEY,
title text NOT NULL,
body text NOT NULL,
updated_at timestamptz NOT NULL DEFAULT now()
);
Baseline yang baik untuk PostgreSQL full-text search adalah: bangun query dari input pengguna, filter baris dulu (ketika bisa), lalu beri peringkat pada kecocokan yang tersisa.
-- $1 = user search text, $2 = limit, $3 = offset
WITH q AS (
SELECT websearch_to_tsquery('english', $1) AS query
)
SELECT
a.id,
a.title,
a.updated_at,
ts_rank_cd(
setweight(to_tsvector('english', coalesce(a.title, '')), 'A') ||
setweight(to_tsvector('english', coalesce(a.body, '')), 'B'),
q.query
) AS rank
FROM articles a
CROSS JOIN q
WHERE
a.updated_at >= now() - interval '2 years' -- example safe filter
AND (
setweight(to_tsvector('english', coalesce(a.title, '')), 'A') ||
setweight(to_tsvector('english', coalesce(a.body, '')), 'B')
) @@ q.query
ORDER BY rank DESC, a.updated_at DESC, a.id DESC
LIMIT $2 OFFSET $3;
Beberapa detail yang menghemat waktu nanti:
WHERE sebelum perankingan (status, tenant_id, rentang tanggal). Anda memberi peringkat lebih sedikit baris, jadi tetap cepat.ORDER BY (seperti updated_at, lalu id). Ini menjaga pagination stabil ketika banyak hasil memiliki rank sama.websearch_to_tsquery untuk input pengguna. Ia menangani kutipan dan operator sederhana sesuai ekspektasi manusia.Setelah baseline ini bekerja, pindahkan ekspresi to_tsvector(...) ke kolom yang disimpan. Itu menghindari perhitungan ulang pada setiap query dan mempermudah pengindeksan.
Kebanyakan cerita “PostgreSQL full-text search lambat” bermuara pada satu hal: database membangun dokumen pencarian di setiap query. Perbaiki itu dulu dengan menyimpan tsvector yang sudah dibangun dan mengindeksnya.
tsvector: generated column atau trigger?Generated column adalah opsi paling sederhana ketika dokumen pencarian dibangun dari kolom dalam baris yang sama. Ia tetap benar otomatis dan sulit terlupakan saat update.
Gunakan tsvector yang dipelihara trigger ketika dokumen bergantung pada tabel terkait (misalnya menggabungkan baris produk dengan nama kategorinya), atau ketika Anda ingin logika kustom yang sulit diekspresikan sebagai satu ekspresi generated. Trigger menambah bagian yang bergerak, jadi jaga mereka kecil dan uji.
Buat indeks GIN pada kolom tsvector. Itu baseline yang membuat PostgreSQL full-text search terasa instan untuk pencarian aplikasi tipikal.
Setup yang bekerja untuk banyak aplikasi:
tsvector di tabel yang sama dengan baris yang paling sering Anda cari.tsvector itu.@@ terhadap tsvector yang disimpan, bukan to_tsvector(...) yang dihitung saat query.VACUUM (ANALYZE) setelah backfill besar agar planner memahami indeks baru.Menyimpan vector di tabel yang sama biasanya lebih cepat dan sederhana. Tabel pencarian terpisah bisa masuk akal jika tabel dasar banyak write, atau jika Anda mengindeks dokumen gabungan yang membentang banyak tabel dan ingin memperbaruinya sesuai jadwal Anda sendiri.
Partial index membantu ketika Anda hanya mencari subset baris, seperti status = 'active', satu tenant di aplikasi multi-tenant, atau bahasa tertentu. Mereka mengurangi ukuran indeks dan bisa mempercepat pencarian, tapi hanya jika query Anda selalu menyertakan filter yang sama.
Anda bisa mendapatkan hasil cukup baik dengan PostgreSQL full-text search jika menjaga aturan relevansi sederhana dan dapat diprediksi.
Kemenangan termudah adalah pemberian bobot field: kecocokan di title harus dihitung lebih dari kecocokan di body. Bangun tsvector gabungan di mana title diberi bobot lebih tinggi dari description, lalu beri peringkat dengan ts_rank atau ts_rank_cd.
Jika Anda butuh item “segar” atau “populer” untuk naik ke atas, lakukan dengan hati-hati. Sedikit dorongan oke, tapi jangan sampai mengalahkan relevansi teks. Pola praktis: urutkan berdasarkan teks dulu, lalu pecahkan seri dengan kebaruan, atau tambahkan bonus terbatas sehingga item baru yang tidak relevan tidak mengalahkan kecocokan lama yang sempurna.
Sinonim dan pencocokan frasa sering kali membuat ekspektasi meleset. Sinonim tidak otomatis; Anda hanya mendapatkannya jika menambahkan tesaurus atau kamus kustom, atau memperluas istilah query sendiri (misalnya, memperlakukan “auth” sebagai “authentication”). Pencocokan frasa juga bukan default: query biasa mencocokkan kata di mana saja, bukan “frasa tepat ini.” Jika pengguna mengetik frasa berkutip atau pertanyaan panjang, pertimbangkan phraseto_tsquery atau websearch_to_tsquery untuk mencocokkan cara orang mencari.
Konten multi-bahasa butuh keputusan. Jika Anda tahu bahasa per dokumen, simpan dan hasilkan tsvector dengan konfigurasi yang tepat (English, Russian, dll.). Jika tidak, fallback aman adalah mengindeks dengan konfigurasi simple (tanpa stemming), atau menyimpan dua vector: satu spesifik-bahasa bila diketahui, satu simple untuk semuanya.
Untuk memvalidasi relevansi, jaga kecil dan konkret:
Ini biasanya cukup untuk PostgreSQL full-text search pada kotak pencarian aplikasi seperti “templates,” “docs,” atau “projects.”
Kebanyakan cerita “PostgreSQL full-text search lambat atau tidak relevan” datang dari beberapa kesalahan yang bisa dihindari. Memperbaikinya biasanya lebih sederhana daripada menambah sistem pencarian baru.
Satu jebakan umum adalah memperlakukan tsvector seperti nilai komputasi yang tetap benar sendiri. Jika Anda menyimpan tsvector di kolom tapi tidak memperbaruinya pada setiap insert dan update, hasil akan terlihat acak karena indeks tidak lagi cocok dengan teks. Jika Anda menghitung to_tsvector(...) secara langsung di query, hasil bisa benar tapi lebih lambat, dan Anda mungkin melewatkan manfaat indeks.
Cara mudah lain untuk merusak performa adalah memberi peringkat sebelum mempersempit set kandidat. ts_rank berguna, tapi biasanya harus berjalan setelah Postgres menggunakan indeks untuk menemukan baris cocok. Jika Anda menghitung rank untuk bagian besar tabel (atau join ke tabel lain terlebih dahulu), Anda bisa mengubah pencarian cepat menjadi table scan.
Orang juga berharap “contains” berperilaku seperti LIKE '%term%'. Wildcard di depan tidak cocok dengan FTS karena FTS berbasis kata (lexemes), bukan substring sembarang. Jika Anda perlu substring search untuk kode produk atau ID parsial, gunakan alat lain untuk kasus itu (misalnya indeks trigram) daripada menyalahkan FTS.
Masalah performa sering datang dari penanganan hasil, bukan pencocokan. Dua pola yang perlu diwaspadai:
OFFSET besar, yang membuat Postgres melewati semakin banyak baris saat Anda paging.Isu operasional juga penting. Bloat indeks dapat menumpuk setelah banyak update, dan reindexing bisa mahal jika Anda menunggu sampai semuanya menyakitkan. Ukur waktu query riil (dan cek EXPLAIN ANALYZE) sebelum dan setelah perubahan. Tanpa angka, mudah “memperbaiki” PostgreSQL full-text search dengan membuatnya lebih buruk di aspek lain.
Sebelum menyalahkan PostgreSQL full-text search, jalankan pemeriksaan ini. Sebagian besar bug “Postgres search lambat atau tidak relevan” berasal dari dasar yang hilang, bukan fitur itu sendiri.
Buat tsvector nyata: simpan di generated atau maintained column (jangan dihitung di setiap query), gunakan konfigurasi bahasa yang tepat (english, simple, dll.), dan terapkan bobot jika Anda mencampur field (title > subtitle > body).
Normalisasi apa yang Anda indeks: keluarkan field berisik (ID, boilerplate, teks navigasi) dari tsvector, dan pangkas blob besar jika pengguna tidak pernah mencarinya.
Buat indeks yang tepat: tambahkan indeks GIN pada kolom tsvector dan konfirmasi dipakai di EXPLAIN. Jika hanya subset yang bisa dicari (mis. status = 'published'), partial index bisa mengurangi ukuran dan mempercepat baca.
Jaga tabel sehat: dead tuples bisa memperlambat index scan. Vacuuming rutin penting, terutama pada konten yang sering diupdate.
Punya rencana reindex: migrasi besar atau indeks bengkak kadang perlu jendela reindex terkontrol.
Setelah data dan indeks terlihat benar, fokus pada bentuk query. PostgreSQL full-text search cepat ketika dapat mempersempit set kandidat lebih awal.
Filter dulu, lalu beri peringkat: terapkan filter ketat (tenant, language, published, category) sebelum perankingan. Memberi peringkat ribuan baris yang nanti Anda buang adalah pekerjaan sia-sia.
Gunakan pengurutan stabil: urutkan berdasarkan rank lalu tie-breaker seperti updated_at atau id sehingga hasil tidak lompat antar refresh.
Hindari “query melakukan semuanya”: jika Anda butuh fuzzy matching atau toleransi typo, lakukan dengan sengaja (dan ukur). Jangan secara tidak sengaja memaksa sequential scan.
Uji query nyata: kumpulkan 20 pencarian teratas, periksa relevansi secara manual, dan simpan daftar hasil yang diharapkan untuk menangkap regresi.
Perhatikan jalur lambat: log query lambat, tinjau EXPLAIN (ANALYZE, BUFFERS), dan pantau ukuran indeks serta cache hit rate supaya Anda bisa melihat ketika pertumbuhan mengubah perilaku.
Help center SaaS adalah tempat yang baik untuk memulai karena tujuannya sederhana: bantu orang menemukan artikel yang menjawab pertanyaannya. Anda punya beberapa ribu artikel, masing-masing dengan title, ringkasan singkat, dan body. Sebagian besar pengunjung mengetik 2–5 kata seperti “reset password” atau “billing invoice.”
Dengan PostgreSQL full-text search, ini bisa terasa selesai sangat cepat. Anda menyimpan tsvector untuk field gabungan, menambahkan indeks GIN, dan memberi peringkat berdasarkan relevansi. Kesuksesan terlihat seperti: hasil muncul dalam <100 ms, 3 hasil teratas biasanya benar, dan Anda tidak perlu mengasuh sistem.
Lalu produk tumbuh. Support ingin memfilter berdasarkan area produk, platform (web, iOS, Android), dan plan (free, pro, business). Penulis dokumentasi ingin sinonim, “maksud Anda”, dan penanganan typo yang lebih baik. Tim marketing ingin analitik seperti “pencarian paling banyak tanpa hasil.” Trafik naik dan pencarian menjadi salah satu endpoint tersibuk.
Itu sinyal bahwa engine khusus mungkin layak biaya:
Jalur migrasi praktis adalah menjaga Postgres sebagai sumber kebenaran, bahkan setelah menambah engine pencarian. Mulai dengan log query pencarian dan kasus tanpa-hasil, lalu jalankan job sinkron asinkron yang menyalin hanya field yang dapat dicari ke indeks baru. Jalankan keduanya paralel untuk sementara dan pindah secara bertahap, daripada bertaruh semuanya di hari pertama.
Jika pencarian Anda kebanyakan “temukan dokumen yang mengandung kata-kata ini” dan dataset Anda tidak masif, PostgreSQL full-text search biasanya cukup. Mulai di sana, buat berjalan, dan tambahkan engine khusus hanya ketika Anda bisa menyebutkan fitur yang hilang atau masalah skala.
Ringkasan berguna:
tsvector, menambah indeks GIN, dan kebutuhan perankingan Anda dasar.Langkah praktis selanjutnya: terapkan starter query dan indeks dari bagian sebelumnya, lalu log beberapa metrik sederhana selama seminggu. Lacak p95 query time, query lambat, dan sinyal keberhasilan kasar seperti “search -> click -> tidak langsung bounce” (bahkan penghitung event dasar membantu). Anda akan cepat melihat apakah butuh perankingan lebih baik atau hanya UX yang lebih baik (filter, highlighting, snippet yang lebih baik).
Mulai merencanakan engine pencarian ketika salah satu ini menjadi kebutuhan nyata (bukan sekadar bagus untuk dimiliki): autocomplete kuat atau pencarian instan setiap ketukan pada skala, toleransi typo dan koreksi ejaan kuat, facets dan agregasi banyak field dengan hitungan cepat, alat relevansi lanjutan (set sinonim, learning-to-rank, boost per-query), atau beban tinggi dan indeks besar yang sulit dijaga cepat.
Jika Anda ingin bergerak cepat di sisi aplikasi, Koder.ai (koder.ai) bisa menjadi cara berguna untuk memprototaip UI dan API pencarian melalui chat, lalu iterasi aman menggunakan snapshot dan rollback sementara Anda mengukur apa yang benar-benar dilakukan pengguna.
PostgreSQL full-text search dianggap “cukup” ketika Anda dapat memenuhi tiga hal sekaligus:
Jika Anda bisa memenuhi ini dengan tsvector yang disimpan + indeks GIN, biasanya Anda berada di posisi yang baik.
Mulai dengan PostgreSQL full-text search sebagai default. Cara ini lebih cepat dikirim, menjaga data dan pencarian di satu tempat, dan menghindari pembangunan serta pemeliharaan pipeline indeks terpisah.
Berpindah ke engine khusus ketika Anda memiliki kebutuhan jelas yang sulit dipenuhi Postgres (toleransi typo berkualitas tinggi, autocomplete kaya, faceting berat, atau beban pencarian yang bersaing dengan kerja DB inti).
Aturan sederhana: tetap di Postgres jika Anda lulus tiga cek ini:
Jika Anda gagal salah satu dengan buruk (terutama fitur relevansi seperti typo/autocomplete, atau trafik pencarian tinggi), pertimbangkan engine terdedikasi.
Gunakan Postgres FTS ketika pencarian Anda kebanyakan “temukan catatan yang tepat” di beberapa kolom seperti title/body/notes, dengan filter sederhana (tenant, status, kategori).
Cocok untuk help center, dokumen internal, tiket, pencarian artikel/blog, dan dashboard SaaS di mana pengguna mencari berdasarkan nama proyek dan catatan.
Bentuk query baseline yang baik biasanya:
websearch_to_tsquery.Simpan tsvector yang sudah dibangun dan tambahkan indeks GIN. Itu menghindari perhitungan ulang to_tsvector(...) setiap permintaan.
Langkah praktis:
Gunakan generated column ketika dokumen pencarian dibangun dari kolom di baris yang sama (sederhana dan sulit dilupakan).
Gunakan kolom tsvector yang dipelihara trigger ketika teks pencarian bergantung pada tabel terkait atau logika kustom.
Pilihan default: generated column dulu, trigger hanya bila benar-benar perlu komposisi lintas-tabel.
Mulai dari relevansi yang dapat diprediksi:
Lalu validasi dengan daftar kecil query nyata dari pengguna dan hasil teratas yang diharapkan.
Postgres FTS berbasis kata, bukan substring. Jadi ia tidak berperilaku seperti LIKE '%term%' untuk string parsial sembarang.
Jika Anda butuh substring search (kode produk, ID, fragmen), tangani terpisah (seringnya dengan indeks trigram) daripada memaksa FTS melakukan pekerjaan yang bukan desainnya.
Sinyal umum bahwa Anda sudah melampaui Postgres FTS:
Jalan praktis: tetap jaga Postgres sebagai source of truth dan tambahkan indexing asinkron ketika kebutuhan itu jelas.
@@ terhadap tsvector yang disimpan.ts_rank/ts_rank_cd plus tie-breaker stabil seperti updated_at, id.Ini menjaga hasil tetap relevan, cepat, dan stabil untuk pagination.
tsvector yang dapat dicari di tabel yang sama dengan query Anda.tsvector_column @@ tsquery.Ini adalah perbaikan paling umum ketika pencarian terasa lambat.