eduardweb.
RAG & EmbeddingsAvansat#rag#postgres#pgvector#sql#hybrid-search

De ce am mutat RAG-ul în Postgres cu pgvector și cum am făcut hybrid search

De Dan Ciobanu, 30 mai 2026 · 5 vizualizări · 3 like-uri

Postat 30 mai 2026
sql
WITH vector_search AS (
  SELECT id, row_number() OVER (ORDER BY embedding <=> $1) as rank
  FROM documents
  ORDER BY embedding <=> $1
  LIMIT 50
),
text_search AS (
  SELECT id, row_number() OVER (ORDER BY ts_rank_cd(text_tsv, query) DESC) as rank
  FROM documents, to_tsquery('romanian', $2) query
  WHERE text_tsv @@ query
  ORDER BY ts_rank_cd(text_tsv, query) DESC
  LIMIT 50
)
SELECT
  coalesce(v.id, t.id) as document_id,
  (coalesce(1.0 / (60 + v.rank), 0.0) + coalesce(1.0 / (60 + t.rank), 0.0)) as rrf_score
FROM vector_search v
FULL OUTER JOIN text_search t ON v.id = t.id
ORDER BY rrf_score DESC
LIMIT 10;

Dacă faci RAG, probabil primul instinct a fost să arunci o bază vectorială dedicată în stack, gen Pinecone sau Qdrant. Am făcut și eu asta la un proiect cu peste 120.000 de documente medicale, până când m-am săturat de latența de rețea și de sincronizarea infernală a datelor între DB-ul principal și cel de vectori. Mutarea pe Postgres cu extensia pgvector ne-a salvat de multă bătaie de cap și a redus costurile de infrastructură cu 35%.

De ce vectorii simpli nu sunt suficienți

Embeddings-urile (generate de exemplu cu text-embedding-3-small de la OpenAI) sunt excelente pentru a înțelege contextul semantic. Dacă userul caută „probleme cu somnul”, modelul va găsi documente despre „insomnie”. Dar ce te faci când caută un cod de eroare exact, cum ar fi „ERR_902”, sau un nume specific de medicament?

Vectorii sunt foarte slabi la potriviri exacte (exact matches). Aici intervine căutarea hibridă (Hybrid Search). Combinăm puterea semantică a vectorilor cu algoritmul clasic BM25, implementat nativ în Postgres prin tsvector și tsquery.

Implementarea: pgvector + tsvector + RRF

Pentru a combina rezultatele din cele două lumi, cea mai curată metodă este Reciprocal Rank Fusion (RRF). În loc să încerci să normalizezi scorurile de similaritate cosmică (care vin din distanța cosine) cu scorurile BM25 (care sunt scoruri logaritmice, teoretic nelimitate), RRF pur și simplu acordă puncte în funcție de poziția (rank-ul) documentului în fiecare dintre cele două liste de rezultate.

Dar atenție la indici. La peste 10.000 de înregistrări, o căutare vectorială fără index devine incredibil de lentă deoarece face sequential scan pe toată tabela. Pentru asta folosim un index HNSW (Hierarchical Navigable Small World). Acesta creează un graf de proximitate între vectori, permițând căutări extrem de rapide cu o precizie de peste 95%.

Trade-off-ul de care te lovești în producție

Nimic nu e gratis pe lumea asta. HNSW este un algoritm extrem de rapid, dar mănâncă memorie RAM la micul dejun. Pentru setul nostru de 120k vectori de 1536 de dimensiuni, indexul HNSW trebuie să stea complet în RAM ca să ai latențe sub 15ms.

Am pățit să rulăm query-ul pe o instanță de Postgres mai ieftină și, pentru că indexul nu încăpea în shared_buffers, Postgres făcea swap pe disc. Rezultatul? Latența a sărit instant de la 12ms la 1.8 secunde. Dacă nu ai buget să scalezi RAM-ul pe Postgres, mai bine rămâi la indexul IVFFlat (care e mai lent, dar mult mai mic ca dimensiune) sau folosești o bază dedicată.

O altă problemă e reconstrucția indexului. Când faci update-uri masive de date, indexul HNSW se poate degrada, iar build-ul lui durează considerabil și folosește mult CPU. Noi am rezolvat asta rulând update-urile în batch-uri noaptea.

În codul de mai jos aveți o interogare SQL curată care face exact asta: o căutare hibridă folosind CTE-uri și combină scorurile prin formula RRF. Valoarea de 60 este o constantă standard în literatura RRF care previne ca rezultatele de pe primele poziții să aibă o influență disproporționat de mare.

Voi cum gestionați RAG-ul în producție? Ați mers pe varianta simplă cu Postgres sau complexitatea hybrid search-ului v-a forțat să alegeți un tool dedicat precum Qdrant sau Elasticsearch?

Răspunsuri 0

Se încarcă răspunsurile…

Loghează-te pentru a răspunde

Doar membrii comunității pot lăsa comentarii.