eduardweb.
RAG & EmbeddingsAvansat#rag#postgres#pgvector#sql#embeddings

RAG cu pgvector în Postgres: de ce semantic search pur e o capcană și cum îl combini cu BM25

De Ana Ionescu, 12 oct. 2026 · 5 vizualizări · 2 like-uri

Postat acum 4 zile
sql
WITH semantic_search AS (
  SELECT id, ROW_NUMBER() OVER (ORDER BY embedding <=> $1) as rank
  FROM documents
  ORDER BY embedding <=> $1
  LIMIT 50
),
keyword_search AS (
  SELECT id, ROW_NUMBER() OVER (ORDER BY ts_rank_cd(content_tsv, plainto_tsquery('romanian', $2)) DESC) as rank
  FROM documents
  WHERE content_tsv @@ plainto_tsquery('romanian', $2)
  LIMIT 50
)
SELECT 
  COALESCE(s.id, k.id) AS id,
  (COALESCE(1.0 / (60 + s.rank), 0.0) + 
   COALESCE(1.0 / (60 + k.rank), 0.0)) AS rrf_score
FROM semantic_search s
FULL OUTER JOIN keyword_search k ON s.id = k.id
ORDER BY rrf_score DESC
LIMIT 10;

Toată lumea sare direct pe Pinecone sau Qdrant la primul proiect serios de RAG. La un proiect recent cu 400k fragmente de documentație tehnică am făcut exact pasul invers: am aruncat serviciul extern de vectori și am băgat totul în Postgres cu extensia pgvector. Am scăpat de un pipeline fragil de sync între baza de date principală și vector store, economisind vreo 180$ pe lună dintr-un foc.

Dar am dat repede cu capul de pragul de sus. Dacă te bazezi doar pe embeddings, o să plângi când userii caută chestii exacte.

Problema cu semantic search pur

Modelele de embeddings (fie că vorbim de text-embedding-3-small sau alternative locale gen BGE) sunt spectaculoase când cauți concepte generale. Dacă întrebi „cum optimizez o conexiune lentă”, vector search-ul îți găsește imediat paragrafe relevante despre pooling și timeout-uri.

Problema apare în secunda doi când userul caută un string tehnic precis: un ID de eroare de tipul ERR_PG_CONN_TIMEOUT_503 sau numele unei variabile exacte. Modelul de embeddings comprimă totul într-un spațiu dens și pierde tokenul exact. Răspunsul generat de LLM devine o halucinație penibilă, deși tu ai chunk-ul perfect în baza de date.

Soluția clasică este Hybrid Search: combinăm căutarea densă (vectori) cu cea rară (lexicală / BM25). Postgres are deja Full-Text Search nativ via tsvector și tsquery, care se comportă destul de aproape de BM25 dacă e configurat cu ponderi corecte.

Indexare: HNSW este singura opțiune viabilă

Nu folosi ivfflat. La 400k vectori de 1536 dimensiuni, ivfflat generează o grămadă de recall degradat dacă nu re-indexezi constant pe măsură ce adaugi date. Treci direct pe hnsw:

Parametrii critici la indexare sunt m (câte conexiuni bidirecționale pe nod) și ef_construction (mărimea listei dinamice de candidați la build). Eu am mers pe m = 16 și ef_construction = 64. Atenție mare la maintenance_work_mem: dacă nu-i aloci măcar 2-4GB temporar în timpul creării indexului, procesul o să dureze ore întregi sau o să facă swap pe disc.

Combinarea scorurilor prin Reciprocal Rank Fusion (RRF)

Cea mai mare greșeală e să încerci să normalizezi scorul cosine (1 - distance) cu scorul ts_rank_cd prin ponderi aditive arbitrare (gen 0.7 * vec + 0.3 * text). Scările de magnitudine sunt complet diferite și se schimbă în funcție de lungimea documentului.

Formula mult mai fiabilă este Reciprocal Rank Fusion (RRF). În loc să compari scoruri absolute, compari rangul (poziția) din fiecare listă de rezultate:

RRF_Score = 1 / (k + rank_vector) + 1 / (k + rank_text), unde k este o constantă (de regulă 60).

Trade-off-uri fără menajamente

Postgres duce lejer volumul ăsta, dar nu e gratuit. Indexul HNSW trăiește ideal în RAM. Pentru cei 400k vectori la 1536 dimensiuni, indexul a mâncat cam 3.2 GB de memorie. Dacă baza ta are 4GB RAM în total, o să ai I/O spikes violente.

Dacă treci de 3-5 milioane de vectori și ai zeci de query-uri concurente pe secundă, da, o soluție dedicată precum Qdrant sau Vespa o să bată Postgres la throughput și latență. Până la pragul ăla însă, complexitatea operațională adăugată de un alt serviciu dedicat rareori se justifică.

Voi ați rămas cu vectorii în baza primară sau ați simțit nevoia să-i separați într-un engine dedicat?

Răspunsuri 0

Se încarcă răspunsurile…

Loghează-te pentru a răspunde

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