Akar Masalah: SCAN TABLE pada Operator LIKE
Penggunaan operator LIKE '%keyword%' pada SQLite di React Native merupakan penyebab umum masalah performa pencarian lokal. Indeks standar B-Tree hanya dapat digunakan jika pola pencarian diawali dengan prefiks tetap (seperti LIKE 'keyword%'). Ketika wildcard % berada di awal pola, B-Tree index tidak dapat dimanfaatkan sama sekali.
Akibatnya, SQLite menjalankan SCAN TABLE. Mesin database membaca seluruh halaman (pages) dari disk ke memori secara sekuensial untuk mengecek string pada setiap baris data. Pada dataset berisi puluhan ribu rekaman, proses ini memakan waktu ratusan milidetik, memicu I/O masif, mengonsumsi CPU, dan memblokir native SQLite bridge. Di React Native, latensi tinggi pada native thread kerap menimbulkan frame drop pada antarmuka pengguna.
-- Analisis rencana eksekusi query LIKE standar
EXPLAIN QUERY PLAN
SELECT id, title, content FROM articles
WHERE content LIKE '%arsitektur%';
-- Output:
-- SCAN TABLE articlesSolusi: Inverted Index dengan FTS5
FTS5 (Full-Text Search 5) adalah modul virtual table SQLite yang mengimplementasikan inverted index. Alih-alih memindai rekaman baris demi baris, FTS5 memetakan setiap kata (token) ke daftar pointer baris (rowid) di mana kata tersebut muncul. Waktu pencarian berubah dari linier O(N) menjadi O(K), di mana K adalah jumlah kemunculan token.
FTS5 menyediakan tokenizer bawaan bernama unicode61. Tokenizer ini memisahkan kata berdasarkan standar Unicode, menangani case-folding (case-insensitive), dan secara default mampu menghapus tanda diakritik (misal: aksen bahasa).
Implementasi FTS5 External Content Table
Menyimpan data lengkap dua kali (pada tabel utama dan tabel FTS) memperbesar ukuran database file. Pendekatan yang lebih efisien adalah membuat FTS5 sebagai external content table, di mana tabel FTS hanya menyimpan indeks token dan merujuk ke primary key tabel utama.
1. Pembuatan Skema Database
-- 1. Tabel utama
CREATE TABLE articles (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
content TEXT NOT NULL,
created_at INTEGER NOT NULL
);
-- 2. FTS5 Virtual Table dengan tokenizer unicode61
CREATE VIRTUAL TABLE articles_fts USING fts5(
title,
content,
content='articles',
content_rowid='id',
tokenize='unicode61 remove_diacritics 1'
);2. Sinkronisasi Data Otomatis Menggunakan Trigger
Karena FTS5 bertindak sebagai external content table, perubahan pada tabel articles harus disinkronkan secara manual ke articles_fts. Gunakan trigger SQLite untuk menangani INSERT, DELETE, dan UPDATE.
-- Trigger Insert
CREATE TRIGGER articles_ai AFTER INSERT ON articles BEGIN
INSERT INTO articles_fts(rowid, title, content)
VALUES (new.id, new.title, new.content);
END;
-- Trigger Delete
CREATE TRIGGER articles_ad AFTER DELETE ON articles BEGIN
INSERT INTO articles_fts(articles_fts, rowid, title, content)
VALUES ('delete', old.id, old.title, old.content);
END;
-- Trigger Update
CREATE TRIGGER articles_au AFTER UPDATE ON articles BEGIN
INSERT INTO articles_fts(articles_fts, rowid, title, content)
VALUES ('delete', old.id, old.title, old.content);
INSERT INTO articles_fts(rowid, title, content)
VALUES (new.id, new.title, new.content);
END;Eksekusi Query MATCH dan Ranking BM25
Pencarian pada FTS5 menggunakan klausa MATCH. FTS5 menyediakan fungsi pembobotan relevansi berbasis algoritma BM25 (Okapi BM25). Nilai BM25 berupa angka floating-point negatif, di mana nilai yang lebih kecil (lebih negatif) menunjukkan relevansi yang lebih tinggi.
-- Verifikasi rencana eksekusi FTS5
EXPLAIN QUERY PLAN
SELECT a.id, a.title, bm25(articles_fts) AS rank
FROM articles_fts
JOIN articles a ON a.id = articles_fts.rowid
WHERE articles_fts MATCH 'arsitektur*'
ORDER BY rank ASC;
-- Output:
-- SCAN VIRTUAL TABLE INDEX 0:Rencana eksekusi menunjukkan pencarian langsung melalui indeks virtual table tanpa perlu melakukan scan ke seluruh baris tabel utama.
Metrik Performa: Sebelum vs Sesudah
Pengujian dilakukan pada perangkat Android mid-range dengan dataset 50.000 baris teks (panjang rata-rata 250 karakter per baris):
- Metode LIKE '%term%': Latensi eksekusi berkisar antara 180ms hingga 320ms. Utilisasi thread bridge tinggi, menyebabkan animasi dan respons UI freeze sementara (frame drop).
- Metode FTS5 MATCH + BM25: Latensi eksekusi turun menjadi 4ms hingga 12ms. Eksekusi selesai dalam budget satu frame (16.6ms), menghilangkan frame drop.
- Trade-off Ukuran Database: Penambahan indeks FTS5 external content meningkatkan ukuran file SQLite sekitar 20% hingga 35% dari ukuran dataset mentah.
Edge Cases dan Sanitasi Input di React Native
FTS5 memiliki sintaks query khusus yang peka terhadap karakter tertentu (seperti ", *, :, ^, AND, OR, NOT). Jika pengguna mengetik tanda kutip yang tidak tertutup atau karakter non-alfanumerik secara mentah, query akan melempar SQLite exception: fts5: syntax error.
Gunakan fungsi sanitasi pada layer TypeScript/JavaScript sebelum query dikirim ke SQLite binding (misal: op-sqlite atau react-native-quick-sqlite):
/**
* Membersihkan input pencarian untuk query FTS5.
* Memisahkan token dan menambahkan operator prefix '*'
* untuk mendukung pencarian parsial/autocomplete.
*/
export function sanitizeFts5Query(rawInput: string): string {
// Hapus karakter khusus sintaks FTS5 kecuali spasi
const cleaned = rawInput.replace(/["'\*\:\^\(\)\{\}\[\]\+\-]/g, ' ').trim();
if (!cleaned) return '';
// Tokenisasi berdasarkan whitespace dan tambahkan prefix match (*)
const tokens = cleaned.split(/\s+/);
return tokens.map(token => `"${token}"*`).join(' ');
}
// Contoh penggunaan dalam DAO/Repository:
const query = sanitizeFts5Query(userInput);
if (query) {
const results = await db.executeAsync(
`SELECT a.id, a.title, a.content, bm25(articles_fts) AS rank
FROM articles_fts
JOIN articles a ON a.id = articles_fts.rowid
WHERE articles_fts MATCH ?
ORDER BY rank ASC
LIMIT 20`,
[query]
);
}Catatan Kritis: Pastikan library native SQLite yang digunakan mengaktifkan ekstensi FTS5 pada build flag kompilasi SQLite (-DSQLITE_ENABLE_FTS5). Sebagian besar pustaka performa tinggi modern di ekosistem React Native sudah mengaktifkan modul ini secara default.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!