- Mengimplementasikan arsitektur mesin pencari yang berjalan tanpa layanan eksternal dengan memanfaatkan database yang sudah ada, berfokus pada tokenisasi, bobot, dan skoring
- Ide intinya adalah men-tokenisasi semua teks lalu menyimpannya, kemudian saat pencarian mencocokkan token dengan cara yang sama untuk menghitung relevansi
- Menggabungkan tokenizer Word, Prefix, dan N-Gram untuk menangani kecocokan tepat, kecocokan parsial, dan toleransi typo, dengan setiap tokenizer memiliki bobot unik
- Melalui sistem bobot dan algoritme skoring berbasis SQL, sistem mengevaluasi panjang dokumen, keragaman token, kualitas rata-rata, dan faktor lainnya secara menyeluruh
- Memiliki skalabilitas dan transparansi yang tinggi, sehingga penambahan tokenizer atau tipe dokumen baru, penyesuaian bobot, dan perubahan skoring dapat dilakukan dengan leluasa
Alasan membuat mesin pencari sendiri
- Layanan eksternal seperti Elasticsearch atau Algolia memang kuat, tetapi ada beban mempelajari API yang kompleks dan mengelola infrastruktur
- Jika yang dibutuhkan hanyalah fitur pencarian yang terintegrasi dengan database yang ada dan mudah di-debug, membangunnya sendiri bisa sangat berguna
- Tujuannya adalah mesin pencari sederhana yang mengembalikan hasil relevan tanpa dependensi eksternal
Konsep inti: tokenisasi dan pencocokan
- Prinsip dasarnya adalah men-tokenisasi semua teks dan menyimpannya, lalu saat pencarian menghasilkan token dengan cara yang sama untuk dicocokkan
- Pada tahap indexing, konten dipecah menjadi unit token lalu disimpan bersama bobotnya
- Pada tahap pencarian, kueri ditokenisasi dengan cara yang sama untuk menemukan token yang cocok dan menghitung skor
- Pada tahap skoring, bobot yang tersimpan digunakan untuk menghasilkan skor relevansi
Desain skema database
- Menggunakan dua tabel:
index_tokens dan index_entries
index_tokens: menyimpan token unik dan bobot per tokenizer
index_entries: menghubungkan token dan dokumen, serta menyimpan skor akhir yang mencerminkan bobot field, dokumen, dan tokenizer
- Rumus perhitungan bobot akhir:
field_weight × tokenizer_weight × ceil(sqrt(token_length))
- Indeks disiapkan untuk pencarian dokumen, pencarian token, kueri per field, dan pemfilteran bobot
Sistem tokenisasi
- WordTokenizer: memisahkan berdasarkan kata, menghapus kata pendek, untuk kecocokan tepat (bobot 20)
- PrefixTokenizer: membuat prefiks kata, untuk autocomplete dan kecocokan parsial (bobot 5)
- NGramsTokenizer: membuat kombinasi karakter dengan panjang tetap, untuk menangani typo dan kecocokan parsial (bobot 1)
- Semua tokenizer sama-sama melakukan konversi huruf kecil, penghapusan karakter khusus, dan normalisasi spasi
Sistem bobot
- Bobot field: mencerminkan tingkat kepentingan seperti judul, isi, dan kata kunci
- Bobot tokenizer: urutannya Word > Prefix > N-Gram
- Bobot dokumen: dihitung dengan menggabungkan dua faktor di atas dan panjang token
- Fungsi
ceil(sqrt()) digunakan untuk mengurangi pengaruh token panjang, dan jika perlu dapat diubah ke fungsi logaritmik atau linear
Layanan indexing
- Hanya dokumen yang mengimplementasikan
IndexableDocumentInterface yang bisa diindeks
- Saat dokumen dibuat atau diubah, indexing dijalankan melalui event listener atau perintah (
app:index-document, app:reindex-documents)
- Prosedurnya:
- Menghapus indeks lama lalu membuat token baru
- Menjalankan semua tokenizer untuk setiap field
- Memeriksa keberadaan token lalu membuatnya jika belum ada (
findOrCreateToken)
- Melakukan batch insert ke
index_entries dengan bobot yang sudah dihitung
- Strukturnya dirancang untuk mencegah duplikasi, meningkatkan performa, dan menangani pembaruan
Layanan pencarian
- Kueri diproses dengan tokenizer yang sama untuk mendapatkan set token yang identik dengan saat indexing
- Token dideduplikasi lalu diurutkan berdasarkan panjangnya (token lebih panjang didahulukan), dengan batas maksimum 300 token
- Melalui kueri SQL, token dan dokumen di-join, lalu skor relevansi dihitung dan diurutkan
- Hasil dikembalikan dalam bentuk
SearchResult(documentId, score)
Algoritme skoring
- Skor dasar:
SUM(sd.weight)
- Koreksi keragaman token:
LOG(1 + COUNT(DISTINCT token_id))
- Koreksi bobot rata-rata:
LOG(1 + AVG(weight))
- Penalti panjang dokumen:
1 / (1 + LOG(1 + token_count))
- Normalisasi: dibagi dengan skor maksimum agar berada dalam rentang 0~1
- Melalui filter bobot token minimum (
st2.weight >= ?), sistem menghapus kecocokan berbobot rendah yang tidak bermakna
Pemrosesan hasil
- Hasil pencarian dikembalikan sebagai ID dokumen dan skor, lalu diubah menjadi dokumen aktual melalui repository
- Dengan fungsi
FIELD(), urutan hasil pencarian tetap dipertahankan saat mengambil dokumen
Skalabilitas sistem
- Tokenizer baru dapat ditambahkan dengan mengimplementasikan
TokenizerInterface
- Tipe dokumen baru dapat didaftarkan dengan mengimplementasikan
IndexableDocumentInterface
- Logika bobot atau skoring dapat disesuaikan hanya dengan memodifikasi SQL
Kesimpulan
- Struktur ini adalah mesin pencari sederhana tetapi benar-benar berfungsi, dan memberikan performa yang memadai tanpa infrastruktur eksternal
- Keunggulannya adalah logika yang jelas, kontrol penuh, dan debugging yang mudah
- Ini menekankan bahwa dibanding sistem yang kompleks, kode yang bisa dipahami dan dikendalikan langsung sering kali lebih berharga
Belum ada komentar.