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

RAG direct în Postgres: De ce pgvector și hybrid search bat soluțiile dedicate (uneori)

De Paul Ene, 31 mai 2026 · 4 vizualizări · 3 like-uri

Postat 31 mai 2026
sql
WITH semantic_search AS (
    SELECT id, 1 - (embedding <=> :query_embedding::vector) as similarity
    FROM medical_documents
    ORDER BY embedding <=> :query_embedding::vector
    LIMIT 30
),
text_search AS (
    SELECT id, ts_rank_cd(to_tsvector('romanian', content), query) as text_rank
    FROM medical_documents, plainto_tsquery('romanian', :query_text) query
    WHERE to_tsvector('romanian', content) @@ query
    ORDER BY text_rank DESC
    LIMIT 30
)
SELECT
    COALESCE(s.id, t.id) as document_id,
    (COALESCE(s.similarity, 0) * 0.7) + (COALESCE(t.text_rank, 0) * 0.3) as hybrid_score
FROM semantic_search s
FULL OUTER JOIN text_search t ON s.id = t.id
ORDER BY hybrid_score DESC
LIMIT 10;

Am tot văzut hype-ul cu baze de date vectoriale dedicate gen Pinecone sau Qdrant pentru RAG. În practică, dacă ai deja datele în Postgres, să adaugi încă o bază de date doar pentru embeddings e de multe ori o bătaie de cap administrativă inutilă. Am trecut recent prin asta la un proiect cu vreo 150.000 de documente medicale și am decis să mergem all-in pe Postgres cu pgvector.

Setup-ul și de ce HNSW te poate pune în cap

Când ai zeci de mii de vectori de dimensiune mare (noi am folosit modelul text-embedding-3-small de la OpenAI, cu 1536 de dimensiuni), o simplă căutare exactă prin cosine similarity (operatorul <=>) devine extrem de lentă. Devine practic un sequential scan pe toată tabela. Ca să rezolvăm asta, am trecut la un index HNSW (Hierarchical Navigable Small World).

Aici vine primul trade-off sincer pe care trebuie să-l înțelegi: indexarea HNSW mănâncă memorie RAM de rupe la build time. La prima încercare de reindexare pe o instanță de test cu 4GB RAM, baza de date ne-a dat crash instant (Out of Memory). Am fost nevoiți să urcăm instanța la 16GB RAM și să ajustăm maintenance_work_mem serios doar ca să putem construi indexul fără downtime. Partea bună? După ce s-a construit, timpul de query a scăzut de la 450ms la sub 12ms.

De ce căutarea semantică simplă dă rateuri

Embeddings-urile sunt geniale pentru a înțelege contextul ("durere de cap" se va potrivi cu "migrenă"), dar sunt incredibil de proaste la potriviri exacte. Dacă utilizatorul caută un cod de diagnostic specific, cum ar fi „ICD-10-M54”, un model vectorial s-ar putea să returneze alte documente despre dureri de spate, ignorând fix fișa medicală care conținea acel cod exact.

Soluția pe care am implementat-o este căutarea hibridă. Combinăm similarity search-ul semantic oferit de pgvector cu full-text search-ul clasic din Postgres (tsvector și tsquery), folosind o schemă simplă de ponderare.

Query-ul de hybrid search care rulează în producție

Mai jos e query-ul pe care l-am optimizat. Folosește două Common Table Expressions (CTE) pentru a rula în paralel căutarea semantică și cea clasică pe text, apoi le combină rezultatele folosind o formulă de scor ponderat.

În query-ul de mai jos, am dat o pondere de 70% căutării vectoriale și 30% căutării clasice. Raportul ăsta l-am reglat empiric, după ce am rulat vreo 200 de query-uri de test introduse manual de medici.

Postgres s-a dovedit a fi incredibil de capabil. Totuși, dacă ai de gestionat zeci de milioane de documente sau ai nevoie de filtrare dinamică extrem de complexă în timp ce parcurgi graful HNSW, s-ar putea să lovești limitările de memorie ale Postgres-ului mult mai repede decât cu o soluție dedicată.

Voi ce folosiți pentru RAG în producție? Ați rămas pe Postgres sau ați făcut pasul spre baze de date vectoriale dedicate?

Răspunsuri 0

Se încarcă răspunsurile…

Loghează-te pentru a răspunde

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