- Mevcut veritabanını kullanarak harici servis olmadan çalışan bir arama motoru mimarisi kuruyor; odak noktası tokenization, ağırlıklandırma ve skorlamadır
- Temel fikir, tüm metni token’lara ayırıp saklamak ve arama sırasında aynı yöntemle token’ları eşleştirerek ilgililiği hesaplamaktır
- Word, Prefix, N-Gram tokenizer kombinasyonu ile tam eşleşme, kısmi eşleşme ve yazım hatası toleransı birlikte ele alınır; her tokenizer’ın kendine özgü bir ağırlığı vardır
- Ağırlık sistemi ve SQL tabanlı skorlama algoritması ile belge uzunluğu, token çeşitliliği ve ortalama kalite birlikte değerlendirilir
- Ölçeklenebilirlik ve şeffaflık yüksektir; yeni tokenizer ya da belge türü eklemek, ağırlıkları ayarlamak ve skorlama mantığını değiştirmek serbesttir
Neden kendi arama motorunu yapmak gerekir?
- Elasticsearch veya Algolia gibi harici servisler güçlüdür, ancak karmaşık API’leri öğrenme ve altyapı yönetimi yükü getirir
- Sadece mevcut veritabanıyla entegre çalışan ve hata ayıklaması kolay bir arama özelliği gerektiğinde, bunu doğrudan inşa etmek faydalıdır
- Amaç, harici bağımlılık olmadan yüksek ilgililikte sonuçlar döndüren basit bir arama motoru oluşturmaktır
Temel kavram: tokenization ve eşleştirme
- Temel ilke, tüm metni tokenize edip saklamak ve arama sırasında aynı şekilde token üretip eşleştirmektir
- İndeksleme aşamasında içerik token düzeyinde ayrılır ve ağırlıklarıyla birlikte saklanır
- Arama aşamasında sorgu da aynı şekilde tokenize edilir, eşleşen token’lar bulunur ve puan hesaplanır
- Skorlama aşamasında saklanan ağırlıklar kullanılarak ilgililik skoru üretilir
Veritabanı şeması tasarımı
- İki tablo kullanılır:
index_tokens ve index_entries
index_tokens: benzersiz token’ları ve tokenizer bazlı ağırlıkları saklar
index_entries: token’ları belgelerle ilişkilendirir ve alan, belge ve tokenizer ağırlıklarını yansıtan son skoru saklar
- Nihai ağırlık hesaplama formülü:
field_weight × tokenizer_weight × ceil(sqrt(token_length))
- İndeksler; belge sorgulama, token sorgulama, alan bazlı sorgular ve ağırlık filtreleme için tanımlanır
Tokenization sistemi
- WordTokenizer: kelime bazlı ayırma, kısa kelimeleri kaldırma, tam eşleşme için (ağırlık 20)
- PrefixTokenizer: kelime önekleri üretir, otomatik tamamlama ve kısmi eşleşme için (ağırlık 5)
- NGramsTokenizer: sabit uzunluklu karakter kombinasyonları üretir, yazım hataları ve kısmi eşleşmeler için (ağırlık 1)
- Tüm tokenizer’lar ortak olarak küçük harfe çevirme, özel karakterleri temizleme ve boşlukları normalize etme işlemlerini yapar
Ağırlık sistemi
- Alan ağırlığı: başlık, gövde, anahtar kelime gibi alanların önemini yansıtır
- Tokenizer ağırlığı: Word > Prefix > N-Gram sırasındadır
- Belge ağırlığı: bu iki unsur ile token uzunluğunun birleşiminden hesaplanır
ceil(sqrt()) fonksiyonu, uzun token’ların etkisini yumuşatmak için kullanılır; gerekirse logaritmik ya da doğrusal fonksiyonlarla ayarlanabilir
İndeksleme servisi
- Yalnızca
IndexableDocumentInterface uygulayan belgeler indekslenebilir
- Belge oluşturma veya güncelleme sırasında event listener ya da komutlarla (
app:index-document, app:reindex-documents) indeksleme yapılır
- Süreç:
- Eski indeks kaldırılır, ardından yeni token’lar üretilir
- Her alan için tüm tokenizer’lar çalıştırılır
- Token’ın var olup olmadığı kontrol edilir ve gerekirse oluşturulur (
findOrCreateToken)
- Hesaplanan ağırlıklarla
index_entries tablosuna toplu ekleme (batch insert) yapılır
- Yapı; tekrarları önleme, performansı artırma ve güncellemeleri karşılama amacı taşır
Arama servisi
- Sorgu, aynı tokenizer’larla işlenerek indekslemeyle aynı token kümesi elde edilir
- Tekrarlanan token’lar kaldırılır, uzunluğa göre sıralanır (uzun token önce gelir) ve en fazla 300 token ile sınırlandırılır
- SQL sorgusu üzerinden token ve belge join edilerek ilgililik skoru hesaplanır ve sıralama yapılır
- Sonuçlar
SearchResult(documentId, score) biçiminde döner
Skorlama algoritması
- Temel skor:
SUM(sd.weight)
- Token çeşitliliği düzeltmesi:
LOG(1 + COUNT(DISTINCT token_id))
- Ortalama ağırlık düzeltmesi:
LOG(1 + AVG(weight))
- Belge uzunluğu cezası:
1 / (1 + LOG(1 + token_count))
- Normalizasyon: en yüksek skora bölünerek 0~1 aralığına getirilir
- Minimum token ağırlığı filtresi (
st2.weight >= ?) ile anlamsız, düşük ağırlıklı eşleşmeler elenir
Sonuç işleme
- Arama sonuçları belge ID’si ve skor olarak döner; repository üzerinden gerçek belgelere dönüştürülür
FIELD() fonksiyonu kullanılarak arama sonucu sırası korunarak belge sorgulaması yapılır
Sistemin genişletilebilirliği
- Yeni tokenizer’lar
TokenizerInterface uygulanarak eklenebilir
- Yeni belge türleri
IndexableDocumentInterface uygulanarak kaydedilebilir
- Ağırlıklar veya skorlama mantığı yalnızca SQL değiştirilerek ayarlanabilir
Sonuç
- Bu yapı, basit ama gerçekten çalışan bir arama motorudur ve harici altyapı olmadan da yeterli performans sunar
- Açık mantık, tam kontrol ve kolay hata ayıklama en büyük avantajlarıdır
- Karmaşık sistemlerden ziyade doğrudan anlayıp kontrol edebileceğiniz kodun daha değerli olduğunu vurgular
1 yorum
Hacker News görüşleri
Aramanın temel fikri basit ve ilgi çekici bir problem alanı.
Ama büyük miktarda veriyle uğraşmak ve belirsiz sorguları işlemek asıl zor kısım.
DBMS tabanlı yaklaşım küçük web siteleri düzeyinde idare eder, ancak İngilizce Vikipedi ölçeğinde hızla sınırlarına dayanır.
Başlangıç için SeIRP e-book iyi bir ücretsiz kaynak.
Net bir doğru cevabın olmaması bunu özellikle zorlaştırıyor.
Google bazen reklamları da ‘en alakalı sonuç’ olarak gösteriyor; bu yüzden Marginalia Search iyi bir karşıt örnek.
Acaba hiç TREC makalelerine baktınız mı diye merak ettim.
Arama motorları, reklam geliri peşindeki hasım aktörlerle sürekli mücadele etmek zorunda.
Kalite metriklerini sürekli değiştirip onların bunu istismar edememesini sağlamak, bitmeyen bir kedi-fare oyununa dönüşüyor.
Sorgu başına 5 saniye ve dakikada 12 sorgu civarı bir hızla ne büyüklükte bir korpusta arama yapılabileceğini bilmek isterdim.
Örneğin Gilligan’s Island wiki sayfası ile bir hayran blogu arasında hangisinin daha “iyi” sonuç olduğunu belirlemek zor.
Buna bir de sıralama manipülasyonu veya anahtar kelime doldurma eklenince, bu sorun ölçeklenebilirlikten çok daha karmaşık bir meydan okumaya dönüşüyor.
Arama gerçekten çok zor bir iş.
Apple, Microsoft, OpenAI gibi kaynak ve teknik güç açısından çok güçlü şirketlerin bile arama kalitesi düşük.
Bu sadece teknik bir sorun değil.
Arama kalitesini artırmak için sıralama parametrelerini ince ayarlamak gerekiyor, ama böyle işler sprint veya Jira benzeri yönetim çerçeveleriyle planlanması zor şeyler.
Sonuçta bu alan geliştiriciye güven ve özerklik gerektiriyor.
Yapay zeka modellerine milyarlar yatırıyorlar, ama web uygulaması ya da arama ikincil kaldığı için sonuç böyle oluyor.
Yaklaşık 10 yıl önce arama motoru tasarımı üzerine doktora yapan bir meslektaşımla çalışma fırsatım olmuştu.
Arama ile veritabanı entegrasyonu hakkında büyük bir tutkuyla konuşuyordu ve ondan çok şey öğrendim.
Bir gün Apache Solr ve Lucene’in iç yapısını derinlemesine incelemek istiyorum.
Eskiden açık kaynak arama çözümleri yoktu, bu yüzden kendin yapmak zorundaydın.
O deneyimden çıkan ders şu oldu: “Kendi arama motorunu yapma.”
Yıllarca sayısız insan bu problem üzerinde çalıştı; kendin yaparsan bitmeyen bir bakım cehennemine giriyorsun.
“Yazım düzeltme özelliği ekleyelim”, “seneye sınıflandırma da koyalım” gibi talepler gelmeye başlayınca sonu gelmiyor.
Bir zamanlar Virginia University’den Profesör David Evans’ın verdiği arama motoru geliştirme dersinden gerçekten çok keyif almıştım.
“Klasik bir arama motorunu” doğrudan inşa etmek çok eğlenceli bir projeydi.
Ders bağlantısı ve YouTube oynatma listesi incelenebilir.
Sık kullandığım arama motorlarının 2-3 harfli kısaltmaları veya kelimeleri yok saymasına sinir oluyorum.
“mp3” veya “PHP” gibi kısa sözcükleri ararken bunların atılması gerçekten çok can sıkıcı.
Toby Segaran’ın Programming Collective Intelligence kitabını okuyup arama, öneri sistemleri ve sınıflandırıcılar gibi çeşitli fikirlerden ilham almıştım.
İlginç bir yazıydı.
Popüler arama motorlarının kullandığı tokenizer optimizasyonunun seviyesi ne kadar yüksek, merak ettim.
Bu sistemin ne kadar ölçeklenebilir çalışacağını merak ediyorum.
Elasticsearch, önerilen ölçeğin ötesinde bile oldukça etkileyici performans gösteriyor.
Basit bir metin arama motoru yapmak zor değildir.
Ama iyi bir arama motoru yapmak tamamen başka bir mesele.
Sadece BM25 gibi bir algoritmayı uygulamak yeterli değil.
Danışmanlık verdiğim şirketlerin çoğu kendi çözümlerini kullanıyordu ama sonunda Elasticsearch veya Opensearch’e geçti.
Kendi uygulaman ilk başta basit görünür, ama zamanla sıralama sorunları ve performans düşüşü yüzünden karmaşıklaşır.
“Yavaş”, “saçma sonuçlar veriyor” gibi belirtiler tekrar tekrar ortaya çıkar.
Elasticsearch bu tür sorunları zaten 10 yılı aşkın süredir çözüyor ve bugün çok daha ileri durumda.
“Yapılandırması zor” deniyor ama artık çoğu şey otomatik yapılandırılıyor ve pek çok yönetilen hizmet de var.
Postgres’ten bile daha kolay yönetilebilir.
Sonuçta önemli olan indeks eşleme optimizasyonu.
“Böyle gelişmiş özelliklere gerek yok” diyenler oluyor, ama gerçekte arama kalitesi işletmenin hayatta kalmasıyla doğrudan bağlantılı.
Düzgün bir arama istiyorsan sonunda bu karmaşıklığı kabullenmek zorundasın.
Son dönemde HN’de sık anılan SeekStorm gibi yükselen alternatifler de ilginç görünüyor, ama henüz gerçek üretim örneklerini görmedim.
Özellikle dynamic mapping’i kapatıp gereksiz alanların indekslenmesini engelleme ipucu faydalıydı.
Bildiğim kadarıyla Lucene’den daha eski bir proje.