Membuat mesin pencari sederhana yang benar-benar berfungsi
(karboosx.net)- 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_tokensdanindex_entriesindex_tokens: menyimpan token unik dan bobot per tokenizerindex_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
IndexableDocumentInterfaceyang 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_entriesdengan 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
1 komentar
Komentar Hacker News
Ide dasar pencarian itu sederhana dan merupakan ranah masalah yang menarik
Tetapi yang benar-benar sulit adalah menangani data dalam jumlah besar dan memproses kueri yang ambigu
Pendekatan berbasis DBMS masih oke untuk situs web kecil, tetapi pada skala Wikipedia bahasa Inggris akan cepat mencapai batasnya
Untuk pemula, SeIRP e-book adalah sumber gratis yang bagus
Yang особенно rumit adalah tidak adanya jawaban yang jelas benar
Google bahkan kadang menampilkan iklan sebagai ‘hasil yang paling relevan’, jadi Marginalia Search adalah contoh pembanding yang bagus
Saya jadi penasaran apakah Anda pernah merujuk ke makalah TREC
Mesin pencari harus terus bertarung melawan pemain yang bersifat adversarial yang mengejar pendapatan iklan
Ini menjadi permainan kucing dan tikus tanpa akhir dengan terus mengubah metrik kualitas agar mereka tidak bisa mengeksploitasinya
Dengan patokan 5 detik per kueri dan sekitar 12 kueri per menit, saya ingin tahu korpus sebesar apa yang bisa dicari
Misalnya, sulit menentukan apakah artikel wiki Gilligan’s Island atau blog penggemar adalah hasil yang lebih “baik”
Ditambah lagi dengan manipulasi peringkat atau keyword stuffing, ini menjadi tantangan yang jauh lebih rumit daripada sekadar masalah skalabilitas
Pencarian memang sangat sulit
Bahkan perusahaan seperti Apple, Microsoft, dan OpenAI yang penuh sumber daya dan kemampuan teknis pun kualitas fitur pencariannya rendah
Ini bukan sekadar masalah teknis
Untuk meningkatkan kualitas pencarian, perlu penyetelan parameter peringkat secara rinci, tetapi pekerjaan seperti ini sulit direncanakan lewat sistem manajemen seperti sprint atau Jira
Pada akhirnya, ini adalah bidang yang membutuhkan kepercayaan dan otonomi bagi developer
Mereka menginvestasikan miliaran ke model AI, tetapi webapp atau pencarian dianggap sekunder, sehingga hasilnya menjadi seperti itu
Sekitar 10 tahun lalu saya pernah bekerja dengan rekan yang sedang menempuh doktoral di bidang desain mesin pencari
Dia berbicara dengan sangat antusias tentang integrasi pencarian dan database, dan saya belajar banyak darinya
Suatu hari saya ingin benar-benar mendalami bagian dalam Apache Solr dan Lucene
Dulu, karena belum ada solusi pencarian open source, semuanya harus dibuat sendiri
Pelajaran dari pengalaman itu adalah: “jangan membuat mesin pencari sendiri”
Selama bertahun-tahun banyak orang telah mencurahkan tenaga untuk masalah ini, dan jika membuat sendiri Anda akan jatuh ke neraka maintenance tanpa akhir
Begitu mulai muncul permintaan seperti “tolong tambahkan koreksi typo” atau “tahun depan mari masukkan juga sistem klasifikasi”, habislah sudah
Saya dulu sangat menikmati kuliah pembuatan mesin pencari yang dibawakan Profesor David Evans dari University of Virginia
Membangun sendiri “mesin pencari klasik” adalah proyek yang sangat menyenangkan
Bisa merujuk ke tautan kuliah dan playlist YouTube
Saya kesal karena mesin pencari yang sering saya pakai mengabaikan singkatan atau kata 2~3 huruf
Saat mencari kata pendek seperti “mp3” atau “PHP”, sangat merepotkan kalau itu dihapus
Setelah membaca Programming Collective Intelligence karya Toby Segaran, saya mendapat inspirasi dari berbagai ide seperti pencarian, rekomendasi, dan classifier
Tulisannya menarik
Saya jadi penasaran seberapa tinggi tingkat optimasi tokenizer yang digunakan oleh mesin pencari populer
Saya penasaran seberapa skalabel sistem ini akan bekerja
Elasticsearch menunjukkan performa yang cukup mengesankan bahkan saat melewati skala yang direkomendasikan
Mesin pencari teks sederhana tidak terlalu sulit dibuat
Tetapi membuat mesin pencari yang bagus adalah cerita yang sama sekali berbeda
Tidak cukup hanya mengimplementasikan algoritme seperti BM25
Sebagian besar perusahaan yang pernah saya bantu sebagai konsultan memakai solusi buatan sendiri lalu akhirnya pindah ke Elasticsearch atau Opensearch
Implementasi sendiri memang sederhana di awal, tetapi seiring waktu menjadi rumit karena masalah ranking dan penurunan performa
Gejala seperti “lambat” atau “hasilnya aneh” terus berulang
Elasticsearch sudah menyelesaikan masalah seperti ini sejak 10 tahun lalu, dan sekarang jauh lebih matang
Ada yang bilang “konfigurasinya sulit”, tetapi sekarang sebagian besar sudah terkonfigurasi otomatis, dan layanan terkelola juga banyak
Bahkan lebih mudah ditangani daripada Postgres
Pada akhirnya yang penting adalah optimasi index mapping
Ada juga orang yang berkata “fitur canggih seperti itu tidak perlu”, tetapi pada kenyataannya kualitas pencarian berhubungan langsung dengan kelangsungan bisnis
Jika benar-benar menginginkan pencarian yang layak, pada akhirnya kompleksitas seperti ini memang harus diterima
Alternatif baru seperti SeekStorm, yang belakangan sering disebut di HN, juga tampak menarik, tetapi saya belum melihat contoh penggunaan produksi yang nyata
Terutama tips untuk mematikan dynamic mapping dan mencegah pengindeksan field yang tidak perlu sangat berguna
Setahu saya ini adalah proyek yang lebih tua daripada Lucene