Furkan Türkoğlu
TR EN
Tüm yazılar

Tek başına vektör araması neden yetmiyor: PostgreSQL FTS, pgvector ve RRF

Furkan Türkoğlu 2 dk okuma

Yoğun Vektörlerin Kör Noktası

GitHub depoları veya hukuki mevzuat metinleri üzerinde bilgi getirimi (retrieval) kurarken yalnızca yoğun gömme (dense embedding) vektörlerine güvenmek ilk prototipte hızlı sonuç verir; ancak gerçek kullanıcı sorguları geldiğinde sessizce başarısız olur. Kullanıcı RFC 8785, pg_trgm veya Madde 41/A gibi keskin bir tanımlayıcı aradığında kosinüs benzerliği bu dar terimi genel “veri doğrulama” bağlamının içinde eritir ve alakasız ama anlamsal olarak komşu dokümanları üst sıraya taşır.

Harici bir vektör veritabanı ekleyip iki ayrı sistem arasında ağ senkronizasyonu yönetmek yerine, aynı PostgreSQL örneği içinde sözcüksel tam metin aramasını (tsvector) ve pgvector HNSW indeksini birleştirip Reciprocal Rank Fusion (RRF) ile sıralamak hem operasyonel yüzeyi küçültür hem de deterministik sonuç üretir.

Neden Skor Normalizasyonu Değil de RRF?

ts_rank_cd çıktısı üst sınırı belirsiz bir sıklık skoru üretirken pgvector kosinüs uzaklığı [0, 2] aralığında çalışır. Bu iki ham skoru ağırlıklı toplamla (0.7 * cos + 0.3 * bm25) birleştirmeye çalışmak her veri dağılımı değiştiğinde kırılan keyfi katsayılar yaratır. RRF ise ham skor büyüklüklerini tamamen yok sayıp yalnızca sıralama konumunu (rank) kullanır.

Tek Sorguda Hibrit Birleştirme

Her iki arama kolunu ortak tablo ifadeleriyle (CTE) sınırlandırıp FULL OUTER JOIN üzerinden birleştiren üretim sorgusu:

WITH semantic AS (
    SELECT id,
           ROW_NUMBER() OVER (ORDER BY embedding <=> $1::vector) AS r_sem
    FROM repositories
    ORDER BY embedding <=> $1::vector
    LIMIT 60
),
lexical AS (
    SELECT id,
           ROW_NUMBER() OVER (
               ORDER BY ts_rank_cd(search_tsv, websearch_to_tsquery('simple', $2)) DESC
           ) AS r_lex
    FROM repositories
    WHERE search_tsv @@ websearch_to_tsquery('simple', $2)
    LIMIT 60
)
SELECT COALESCE(s.id, l.id) AS repo_id,
       COALESCE(1.0 / (60 + s.r_sem), 0.0) +
       COALESCE(1.0 / (60 + l.r_lex), 0.0) AS rrf_score
FROM semantic s
FULL OUTER JOIN lexical l ON s.id = l.id
ORDER BY rrf_score DESC
LIMIT 20;

Mimari Ödünleşimler

Buradaki k = 60 sabiti, tek bir arama kolunda 1. sıraya yerleşen gürültülü bir eşleşmenin toplam skoru tek başına domine etmesini engeller; her iki arama kolunda da ilk 10’a giren kanıtlı adaylar doğal olarak en üste çıkar.

Bu yapının iki açık maliyeti vardır:

  1. Bellek Bütçesi: HNSW grafiği shared_buffers dışına taştığında diskten rastgele okuma gecikmesi hızla artar; bu nedenle vektör boyutu ve m / ef_construction parametreleri veri hacmine göre sınırlandırılmalıdır.
  2. İkinci Aşama Yeniden Sıralama: RRF ilk 20–50 adayı yüksek geri çağırma (recall) ile toplar; ancak kesin sıralama hassasiyeti gerektiğinde bu daraltılmış aday kümesi üzerinde çapraz kodlayıcı (cross-encoder reranker) çalıştırmak en yüksek doğruluk kazancını sağlar.