Full-Text Index #

Hampir semua aplikasi punya fitur pencarian teks — search produk berdasarkan nama dan deskripsi, search artikel berdasarkan konten, search user berdasarkan nama dan bio. Solusi pertama yang hampir selalu dipilih developer adalah LIKE '%keyword%'. Ia sederhana, langsung bekerja, dan tidak butuh konfigurasi. Masalahnya, LIKE '%keyword%' selalu melakukan full table scan — tidak ada index yang bisa membantu wildcard di awal string. Di tabel kecil ini tidak terasa, tapi di tabel dengan ratusan ribu baris pencarian teks menjadi bottleneck yang sangat nyata. Full-Text Index (FTI) adalah solusi yang tepat untuk masalah ini, tapi ia punya karakteristik, keterbatasan, dan trade-off yang perlu dipahami sebelum dipakai. Artikel ini membahas cara kerja FTI, perbedaan implementasi di MySQL dan PostgreSQL, cara menggabungkannya dengan filter biasa untuk performa optimal, dan kapan FTI tidak lagi cukup.

Mengapa LIKE ‘%keyword%’ Selalu Gagal di Skala Besar #

Sebelum membahas solusinya, penting memahami secara konkret mengapa LIKE '%keyword%' bermasalah secara fundamental.

-- Query pencarian yang terlihat wajar
SELECT id, title, content FROM articles WHERE content LIKE '%database%';

-- EXPLAIN-nya:
-- +------+------+------+----------+-------------+
-- | type | key  | rows | filtered | Extra       |
-- +------+------+------+----------+-------------+
-- | ALL  | NULL | 850000 | 11.11 | Using where |
-- +------+------+------+----------+-------------+
-- type = ALL → full table scan
-- rows = 850.000 → setiap baris harus dibaca

-- Kenapa index tidak bisa membantu?
-- B-Tree index disortir berdasarkan nilai awal string.
-- Index bisa membantu WHERE content LIKE 'database%' (awalan pasti)
-- Tapi TIDAK BISA membantu WHERE content LIKE '%database%'
-- karena database tidak tahu di mana dalam string kata itu muncul.

Untuk tabel dengan 1 juta baris artikel dan rata-rata 2KB konten per baris:

Biaya LIKE '%keyword%' pada 1 juta artikel:
──────────────────────────────────────────────────────────────
  Data yang harus dibaca: 1.000.000 × 2KB = ~2GB
  Setiap baris: scan karakter per karakter mencari 'database'
  Waktu tipikal: 5–15 detik di server yang layak

  Jika 100 user mencari bersamaan:
    → Database membaca 200GB data per menit hanya untuk search
    → CPU meledak, I/O disk saturasi
    → Seluruh sistem melambat
──────────────────────────────────────────────────────────────

Cara Kerja Inverted Index: Fondasi FTI #

Full-Text Index menggunakan struktur yang sama sekali berbeda dari B-Tree — disebut inverted index. Alih-alih memetakan baris ke konten, inverted index memetakan setiap kata ke daftar baris yang mengandung kata tersebut.

Cara database membangun inverted index:
──────────────────────────────────────────────────────────────
  Data asli:
    Row 1: "Panduan optimasi database PostgreSQL"
    Row 2: "Index B-Tree dan Full-Text Index di MySQL"
    Row 3: "PostgreSQL vs MySQL: perbandingan performa database"

  Proses tokenisasi (tokenizer memecah teks menjadi kata):
    Row 1 → ["panduan", "optimasi", "database", "postgresql"]
    Row 2 → ["index", "b-tree", "full-text", "index", "mysql"]
    Row 3 → ["postgresql", "mysql", "perbandingan", "performa", "database"]

  Stemming (opsional — reduksi ke bentuk dasar):
    "optimasi" → "optim"
    "perbandingan" → "bandingan"  (tergantung bahasa)

  Inverted index yang dihasilkan:
    "database"    → [1, 3]
    "postgresql"  → [1, 3]
    "mysql"       → [2, 3]
    "index"       → [2]
    "performa"    → [3]
    ...

  Query: MATCH AGAINST('database mysql')
    → Cari "database" → [1, 3]
    → Cari "mysql"    → [2, 3]
    → Union: [1, 2, 3] (NATURAL LANGUAGE) atau
      Intersect: [3] (BOOLEAN AND)
    → Hitung score relevansi per baris
    → Urutkan berdasarkan score
──────────────────────────────────────────────────────────────
  Tidak perlu membaca konten artikel sama sekali untuk mencari.
  Hanya lookup di inverted index → O(1) per kata.

Full-Text Index di MySQL #

Membuat Full-Text Index #

-- Pada tabel baru
CREATE TABLE articles (
    id         BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
    title      VARCHAR(500) NOT NULL,
    content    MEDIUMTEXT NOT NULL,
    status     VARCHAR(20) NOT NULL DEFAULT 'draft',
    created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
    PRIMARY KEY (id),
    INDEX idx_articles_status (status),
    FULLTEXT INDEX ft_articles_search (title, content)  -- FTI mencakup dua kolom
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- Menambahkan FTI ke tabel yang sudah ada
ALTER TABLE articles
ADD FULLTEXT INDEX ft_articles_search (title, content);

-- FTI bisa mencakup beberapa kolom — query MATCH harus menyebut semua kolom
-- yang ada di index, tidak bisa sebagian saja

NATURAL LANGUAGE MODE vs BOOLEAN MODE #

MySQL mendukung dua mode pencarian yang punya perilaku sangat berbeda:

-- NATURAL LANGUAGE MODE (default): mencari kata, mengurutkan berdasarkan relevansi
SELECT id, title,
       MATCH(title, content) AGAINST('database optimization' IN NATURAL LANGUAGE MODE) AS score
FROM articles
WHERE MATCH(title, content) AGAINST('database optimization' IN NATURAL LANGUAGE MODE)
  AND status = 'published'
ORDER BY score DESC
LIMIT 20;
-- Cocok untuk: search engine umum, "cari artikel yang paling relevan"
-- Kelemahan: kata yang terlalu umum (muncul di > 50% baris) diabaikan otomatis

-- BOOLEAN MODE: kontrol eksplisit dengan operator
SELECT id, title
FROM articles
WHERE MATCH(title, content) AGAINST(
    '+database +optimization -beginner'
    IN BOOLEAN MODE
)
  AND status = 'published'
LIMIT 20;
-- + berarti WAJIB ada kata ini
-- - berarti HARUS TIDAK ada kata ini
-- Tanpa prefix: optional (meningkatkan relevansi jika ada)
-- "frasa" (dalam tanda kutip): harus muncul sebagai frasa berurutan
-- Cocok untuk: filter yang presisi, search dengan exclude

-- Contoh operator BOOLEAN MODE yang lengkap:
SELECT id, title FROM articles
WHERE MATCH(title, content) AGAINST(
    '+postgresql +"query optimization" -beginner tutorial*'
    IN BOOLEAN MODE
);
-- postgresql   : wajib ada
-- "query optimization" : frasa ini harus muncul berurutan
-- -beginner    : jangan tampilkan artikel untuk pemula
-- tutorial*    : wildcard — cocok dengan "tutorial", "tutorials", dll.
Perbandingan NATURAL LANGUAGE vs BOOLEAN MODE:
──────────────────────────────────────────────────────────────────
  Aspek              │ NATURAL LANGUAGE    │ BOOLEAN MODE
──────────────────────────────────────────────────────────────────
  Relevansi score    │ Otomatis dihitung   │ Tidak dihitung (tidak ada rank)
  Kata wajib         │ Tidak bisa          │ + operator
  Kata dikecualikan  │ Tidak bisa          │ - operator
  Kata > 50% baris   │ Diabaikan otomatis  │ Tetap diproses
  Wildcard           │ Tidak supported     │ * di akhir kata
  Frasa exact        │ Tidak supported     │ "frasa dalam tanda kutip"
  Cocok untuk        │ Search umum         │ Filter presisi
──────────────────────────────────────────────────────────────────

Keterbatasan Bawaan MySQL yang Wajib Diketahui #

MySQL FTI punya beberapa keterbatasan yang tidak intuitif dan sering menyebabkan hasil yang membingungkan:

-- KETERBATASAN 1: Minimum word length (default: 3 karakter untuk InnoDB)
-- Kata pendek tidak diindex!
SELECT @@innodb_ft_min_token_size;  -- default: 3
-- "go", "db", "id" tidak akan diindex atau bisa dicari

-- Ubah jika perlu mendukung kata pendek:
SET GLOBAL innodb_ft_min_token_size = 2;
-- Setelah mengubah: REBUILD semua fulltext index!
-- ALTER TABLE articles DROP INDEX ft_articles_search;
-- ALTER TABLE articles ADD FULLTEXT INDEX ft_articles_search (title, content);

-- KETERBATASAN 2: Stopwords — kata yang selalu diabaikan
-- MySQL punya daftar stopwords bawaan: "the", "a", "is", "in", "at", dll.
SELECT * FROM INFORMATION_SCHEMA.INNODB_FT_DEFAULT_STOPWORD;
-- "about", "are", "as", "at", "be", "by", "com", "for", "from", ...

-- Konsekuensi: pencarian "how to use database" hanya mencari "database"
-- "how", "to", "use" adalah stopwords!

-- Nonaktifkan stopword untuk konteks teknikal:
SET GLOBAL innodb_ft_enable_stopword = 0;
-- Atau gunakan stopword list kustom

-- KETERBATASAN 3: Kata yang muncul di > 50% baris diabaikan di NATURAL LANGUAGE
-- Jika 60% artikel mengandung kata "mysql", kata itu tidak berguna untuk relevance
-- Gunakan BOOLEAN MODE untuk override perilaku ini

-- KETERBATASAN 4: Minimal 3 baris data
-- FTI tidak akan mengembalikan hasil jika tabel punya < 3 baris
-- Ini sering menyebabkan kebingungan di environment test dengan data minimal
Setelah mengubah innodb_ft_min_token_size atau konfigurasi FTI lainnya, kamu harus rebuild seluruh FULLTEXT index — perubahan konfigurasi tidak otomatis diterapkan ke index yang sudah ada. Ini harus dilakukan di maintenance window karena proses rebuild bisa lama untuk tabel besar.

Full-Text Index di PostgreSQL #

PostgreSQL menggunakan pendekatan yang lebih fleksibel dengan tipe data tsvector dan operator @@.

Setup dan Query Dasar #

-- Pendekatan 1: Full-Text Index menggunakan GIN (Generalized Inverted Index)
-- GIN cocok untuk dokumen — query cepat tapi update lebih lambat
CREATE INDEX idx_articles_fts
ON articles USING GIN(to_tsvector('english', title || ' ' || content));

-- Pendekatan 2: GIST index — update lebih cepat tapi query sedikit lebih lambat
CREATE INDEX idx_articles_fts_gist
ON articles USING GIST(to_tsvector('english', title || ' ' || content));

-- Query dengan to_tsvector + to_tsquery
SELECT id, title,
       ts_rank(
           to_tsvector('english', title || ' ' || content),
           to_tsquery('english', 'database & optimization')
       ) AS rank
FROM articles
WHERE to_tsvector('english', title || ' ' || content)
      @@ to_tsquery('english', 'database & optimization')
  AND status = 'published'
ORDER BY rank DESC
LIMIT 20;

Menggunakan Kolom Generated untuk Performa Lebih Baik #

Alih-alih menghitung to_tsvector setiap query, simpan hasilnya di kolom generated:

-- Tambahkan kolom tsvector yang diupdate otomatis
ALTER TABLE articles
ADD COLUMN search_vector tsvector
    GENERATED ALWAYS AS (
        setweight(to_tsvector('english', COALESCE(title, '')), 'A') ||
        setweight(to_tsvector('english', COALESCE(content, '')), 'B')
    ) STORED;
-- setweight 'A' untuk title (bobot lebih tinggi)
-- setweight 'B' untuk content (bobot lebih rendah)

-- Buat GIN index di kolom generated
CREATE INDEX idx_articles_search_vector
ON articles USING GIN(search_vector);

-- Query sekarang jauh lebih bersih dan cepat
SELECT id, title, ts_rank(search_vector, query) AS rank
FROM articles, to_tsquery('english', 'database & optimization') query
WHERE search_vector @@ query
  AND status = 'published'
ORDER BY rank DESC
LIMIT 20;
-- search_vector sudah tersimpan → tidak perlu hitung to_tsvector setiap query

Operator Pencarian PostgreSQL #

-- to_tsquery: kata dipisah dengan operator logika
to_tsquery('english', 'database & optimization')  -- AND: kedua kata harus ada
to_tsquery('english', 'database | mysql')          -- OR: salah satu atau keduanya
to_tsquery('english', 'database & !beginner')      -- NOT: database tapi bukan beginner
to_tsquery('english', 'optim:*')                   -- prefix: optimasi, optimistic, dll

-- plainto_tsquery: input bebas, tidak perlu operator eksplisit
plainto_tsquery('english', 'database optimization tutorial')
-- otomatis diinterpretasikan sebagai AND

-- phraseto_tsquery: pencarian frasa berurutan
phraseto_tsquery('english', 'query optimization')
-- mencari "query" diikuti langsung "optimization"

-- websearch_to_tsquery: sintaks mirip Google Search
websearch_to_tsquery('english', 'database optimization -beginner "index scan"')
-- Lebih user-friendly untuk input dari search box

Menggabungkan FTI dengan Filter B-Tree: Kunci Performa #

Full-Text Index yang dipakai tanpa filter tambahan akan mengembalikan terlalu banyak hasil dan memberikan tekanan berlebihan pada scoring. Selalu kombinasikan dengan filter B-Tree biasa untuk memperkecil search space terlebih dahulu.

Pola yang Benar #

-- ANTI-PATTERN: FTI tanpa filter tambahan
SELECT id, title, content
FROM articles
WHERE MATCH(title, content) AGAINST('database' IN NATURAL LANGUAGE MODE);
-- Mungkin mengembalikan 100.000 baris untuk kata yang umum
-- Scoring dan ranking 100.000 baris sangat mahal

-- BENAR: FTI dikombinasikan dengan filter B-Tree
SELECT id, title,
       MATCH(title, content) AGAINST('database optimization' IN NATURAL LANGUAGE MODE) AS score
FROM articles
WHERE status = 'published'          -- filter B-Tree: index di status
  AND category_id IN (1, 5, 12)     -- filter B-Tree: index di category_id
  AND created_at >= '2025-01-01'    -- filter B-Tree: index di created_at
  AND MATCH(title, content) AGAINST('database optimization' IN NATURAL LANGUAGE MODE)
ORDER BY score DESC
LIMIT 20;
-- Database pertama filter dengan B-Tree (mungkin tinggal 5.000 artikel yang relevan)
-- Baru FTI dijalankan pada 5.000 artikel itu, bukan seluruh tabel

Memahami Urutan Filter di Query Planner #

Query planner MySQL dan PostgreSQL biasanya cukup pintar untuk memilih urutan filter yang optimal, tapi ada beberapa hal yang perlu diperhatikan:

-- Di MySQL: cek apakah FTI atau B-Tree yang dipakai duluan
EXPLAIN SELECT id, title
FROM articles
WHERE status = 'published'
  AND MATCH(title, content) AGAINST('database' IN BOOLEAN MODE);

-- Output yang diharapkan:
-- +------+-------+--------------------------+-------+------+---------------------------+
-- | type | key   | key_len                  | rows  | Extra                       |
-- +------+-------+--------------------------+-------+------+---------------------------+
-- | fulltext | ft_articles_search | 0 | 1 | Using where; Ft_hints: no_ranking |
-- +------+-------+--------------------------+-------+------+---------------------------+

-- Jika MySQL memilih FTI saja dan mengabaikan index status:
-- Gunakan FORCE INDEX untuk memaksa urutan
SELECT id, title
FROM articles USE INDEX (idx_articles_status, ft_articles_search)
WHERE status = 'published'
  AND MATCH(title, content) AGAINST('database' IN BOOLEAN MODE);

Mengukur Kualitas Hasil Pencarian #

Berbeda dengan query biasa yang hasilnya pasti benar atau salah, full-text search punya dimensi kualitas (relevansi). Penting untuk mengukur apakah hasil yang dikembalikan memang relevan dengan intent pencarian.

-- MySQL: tampilkan score relevansi untuk debugging
SELECT
    id,
    title,
    MATCH(title, content) AGAINST('database optimization' IN NATURAL LANGUAGE MODE) AS relevance_score
FROM articles
WHERE MATCH(title, content) AGAINST('database optimization' IN NATURAL LANGUAGE MODE)
  AND status = 'published'
ORDER BY relevance_score DESC
LIMIT 10;

-- Score yang tinggi = lebih relevan
-- Jika semua score sangat rendah (< 0.1): kata mungkin terlalu umum atau tidak ada di index

-- PostgreSQL: tampilkan headline (snippet dengan highlight)
SELECT
    id,
    title,
    ts_rank(search_vector, query) AS rank,
    ts_headline('english', content, query,
        'MaxWords=50, MinWords=20, ShortWord=3, HighlightAll=FALSE'
    ) AS snippet
FROM articles, to_tsquery('english', 'database & optimization') query
WHERE search_vector @@ query
  AND status = 'published'
ORDER BY rank DESC
LIMIT 10;
-- ts_headline menghasilkan snippet konten dengan kata kunci di-highlight
-- Berguna untuk menampilkan preview hasil pencarian ke user

Kapan FTI di Database Tidak Lagi Cukup #

Full-Text Index bawaan database adalah solusi yang sangat baik untuk kebutuhan pencarian dasar sampai menengah. Tapi ada titik di mana ia tidak lagi cukup, dan migrasi ke search engine khusus perlu dipertimbangkan.

Tetap gunakan FTI bawaan database jika:
──────────────────────────────────────────────────────────────
  ✓ Volume data < 10 juta dokumen
  ✓ Query pencarian sederhana (kata kunci, frasa)
  ✓ Tidak butuh fitur lanjutan (fuzzy matching, autocomplete, faceting)
  ✓ Tim kecil, tidak ada kapasitas operasional untuk infra tambahan
  ✓ Bahasa yang didukung oleh tokenizer bawaan (English, beberapa Eropa)
  ✓ Search bukan core feature — hanya filter tambahan

Pertimbangkan Elasticsearch/OpenSearch jika:
──────────────────────────────────────────────────────────────
  ✗ Volume > 10 juta dokumen dan terus bertumbuh
  ✗ Butuh fuzzy matching ("databse" → "database")
  ✗ Butuh autocomplete / search-as-you-type
  ✗ Butuh faceting (filter dinamis: "1.234 hasil untuk kategori X")
  ✗ Butuh multi-language support yang baik (termasuk Bahasa Indonesia)
  ✗ Butuh highlighting yang akurat di snippet hasil
  ✗ Search adalah core feature yang sangat penting untuk bisnis
  ✗ Butuh custom scoring / ranking yang kompleks

Pertimbangkan Meilisearch/Typesense jika:
──────────────────────────────────────────────────────────────
  ✗ Butuh fuzzy matching out-of-the-box tapi tidak mau setup ES yang kompleks
  ✗ Kebutuhan sederhana tapi perlu pengalaman search yang lebih baik dari DB
  ✗ Tim kecil yang butuh solution sederhana

Pola Arsitektur dengan Search Engine Eksternal #

Pola sync database → search engine:
──────────────────────────────────────────────────────────────
  Write path:
    Request → Application → MySQL (source of truth)
                         → Event Bus / Queue
                                │
                                ▼
                         Search Indexer Worker
                                │
                                ▼
                         Elasticsearch / Meilisearch

  Read path:
    Search request → Application → Elasticsearch
                                       │ (dokumen ID)
                                       ▼
                                  MySQL (ambil data lengkap jika perlu)

  Database tetap sebagai source of truth.
  Search engine hanya sebagai read replica khusus untuk search.
──────────────────────────────────────────────────────────────

Anti-Pattern yang Harus Dihindari #

-- ✗ Anti-pattern 1: LIKE '%keyword%' untuk pencarian di tabel besar
SELECT * FROM articles WHERE content LIKE '%database%';
-- → Full table scan, index tidak berguna
-- ✓ Solusi: gunakan FULLTEXT INDEX dengan MATCH ... AGAINST

-- ✗ Anti-pattern 2: FTI tanpa filter B-Tree
SELECT * FROM articles
WHERE MATCH(title, content) AGAINST('database');
-- → Bisa mengembalikan ratusan ribu baris untuk kata umum
-- ✓ Solusi: selalu tambahkan WHERE status = ? atau filter lain sebelum FTI

-- ✗ Anti-pattern 3: ORDER BY MATCH ... AGAINST tanpa LIMIT
SELECT *, MATCH(title, content) AGAINST('database') AS score
FROM articles
WHERE MATCH(title, content) AGAINST('database')
ORDER BY score DESC;
-- → Hitung score untuk semua baris yang match, sort semua — sangat mahal
-- ✓ Solusi: selalu tambahkan LIMIT 20 atau pagination

-- ✗ Anti-pattern 4: FULLTEXT INDEX di kolom yang sering diupdate
-- Misalnya: index di kolom notes, last_comment, activity_log
-- → Setiap UPDATE ke kolom tersebut rebuild bagian inverted index
-- ✓ Solusi: FTI hanya untuk kolom yang jarang diubah (title, content utama)

-- ✗ Anti-pattern 5: mencari kata pendek tanpa mengubah min token size
-- MATCH(title) AGAINST('go' IN BOOLEAN MODE) → tidak mengembalikan hasil
-- karena "go" (2 karakter) di bawah minimum token size default (3)
-- ✓ Solusi: ubah innodb_ft_min_token_size = 2 dan rebuild index

-- ✗ Anti-pattern 6: tidak handle stopword di NATURAL LANGUAGE MODE
-- User mencari "how to use index" → hanya "index" yang dicari
-- "how", "to", "use" adalah stopwords → user bingung kenapa hasilnya sedikit
-- ✓ Solusi: gunakan BOOLEAN MODE untuk query yang butuh presisi,
--   atau nonaktifkan stopword list, atau gunakan search engine eksternal

Checklist Full-Text Index #

SETUP:
  □ FULLTEXT INDEX mencakup kolom yang tepat (title, content — bukan semua kolom)?
  □ Kolom yang di-index adalah kolom yang jarang diupdate?
  □ Untuk kolom teks bahasa Indonesia: pertimbangkan search engine eksternal
    (FTI bawaan tidak punya tokenizer Bahasa Indonesia yang baik)?
  □ Di MySQL: sudah cek innodb_ft_min_token_size? (default 3 — kata 2 char tidak diindex)
  □ Di PostgreSQL: kolom tsvector generated sudah dibuat untuk performa optimal?

QUERY:
  □ MATCH ... AGAINST selalu dikombinasikan dengan filter B-Tree (status, category, dll)?
  □ Ada LIMIT di setiap query FTI?
  □ Tidak ada LIKE '%keyword%' di tabel besar?
  □ Mode yang dipakai sesuai kebutuhan (NATURAL LANGUAGE vs BOOLEAN)?
  □ Di PostgreSQL: menggunakan websearch_to_tsquery untuk input dari user
    (lebih aman dari injection dibanding to_tsquery)?

MONITORING:
  □ Ada alert jika query FTI memakan waktu lebih dari threshold (misal > 500ms)?
  □ Index size dipantau? (FTI bisa lebih besar dari tabel utama)
  □ Write latency dipantau setelah FTI ditambahkan?

Ringkasan #

  • LIKE '%keyword%' selalu full table scan — wildcard di awal string membuat B-Tree index sama sekali tidak berguna. Untuk pencarian teks di tabel besar, ini adalah query yang harus dihilangkan.
  • FTI menggunakan inverted index — memetakan setiap kata ke daftar baris yang mengandungnya. Lookup kata menjadi O(1) tanpa perlu membaca isi dokumen sama sekali.
  • NATURAL LANGUAGE vs BOOLEAN MODE punya perbedaan fundamental — NATURAL LANGUAGE menghitung relevansi otomatis tapi mengabaikan kata umum (> 50% baris). BOOLEAN MODE memberi kontrol penuh dengan operator +, -, *, dan frasa dalam tanda kutip.
  • Minimum word length MySQL (default 3) sering mengejutkan — kata 2 karakter seperti “go”, “db”, “id” tidak diindex dan tidak bisa dicari. Ubah innodb_ft_min_token_size = 2 dan rebuild index jika perlu.
  • FTI wajib dikombinasikan dengan filter B-Tree — FTI tanpa filter bisa mengembalikan ratusan ribu baris untuk kata umum. Filter WHERE status = ? AND category_id = ? terlebih dahulu dengan B-Tree, baru FTI di-apply pada subset yang sudah jauh lebih kecil.
  • PostgreSQL tsvector generated column lebih efisien — daripada menghitung to_tsvector() setiap query, simpan hasilnya di kolom generated STORED dengan GIN index. Query menjadi bersih dan cepat.
  • FTI di database cocok sampai ~10 juta dokumen — untuk kebutuhan yang lebih kompleks (fuzzy matching, autocomplete, Bahasa Indonesia, faceting), Elasticsearch/Meilisearch adalah pilihan yang lebih tepat.
  • FTI menambah overhead write — setiap INSERT atau UPDATE pada kolom yang di-index akan memicu tokenisasi ulang dan update inverted index. Terapkan FTI hanya pada kolom yang jarang diubah.

← Sebelumnya: Bulk CUD Operation   Berikutnya: Avoid ORM →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact