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

RAG direct în Postgres: Hybrid Search cu pgvector și BM25 fără baze de date separate

De Cosmin Rotaru, 7 aug. 2026 · 9 vizualizări · 2 like-uri

Postat 7 aug. 2026
sql
-- Setup tabelă cu vectori și tsvector pentru BM25
CREATE EXTENSION IF NOT EXISTS vector;

CREATE TABLE document_chunks (
    id BIGSERIAL PRIMARY KEY,
    content TEXT NOT NULL,
    embedding vector(1536),
    fts_tokens tsvector GENERATED ALWAYS AS (to_tsvector('romanian', content)) STORED
);

-- Indecși pentru ambele tipuri de căutare
CREATE INDEX ON document_chunks USING hnsw (embedding vector_cosine_ops);
CREATE INDEX ON document_chunks USING gin (fts_tokens);

-- Query Hybrid Search cu Reciprocal Rank Fusion (RRF)
WITH vector_matches AS (
    SELECT id, ROW_NUMBER() OVER (ORDER BY embedding <=> $1) AS rank
    FROM document_chunks
    ORDER BY embedding <=> $1
    LIMIT 20
),
text_matches AS (
    SELECT id, ROW_NUMBER() OVER (ORDER BY ts_rank_cd(fts_tokens, plainto_tsquery('romanian', $2)) DESC) AS rank
    FROM document_chunks
    WHERE fts_tokens @@ plainto_tsquery('romanian', $2)
    LIMIT 20
)
SELECT 
    d.id,
    d.content,
    COALESCE(1.0 / (60 + v.rank), 0.0) + COALESCE(1.0 / (60 + t.rank), 0.0) AS rrf_score
FROM vector_matches v
FULL OUTER JOIN text_matches t ON v.id = t.id
JOIN document_chunks d ON d.id = COALESCE(v.id, t.id)
ORDER BY rrf_score DESC
LIMIT 10;

Anul trecut am mutat un RAG cu ~400k de paragrafe indexate de pe o bază dedicată de vectori (Qdrant) direct în Postgres, folosind pgvector. În afara faptului că am tăiat vreo $350/lună din factura de AWS și am scăpat de coșmarul de a sincroniza două baze de date diferite, am învățat pe pielea mea unde se rupe căutarea pur vectorială. Dacă vrei să faci un RAG serios în producție fără să adaugi încă 3 servicii în stack, Postgres e mai mult decât de ajuns în 90% din cazuri.

Problema cu Semantic Search pur (și de ce pică la coduri exacte)

Când folosești doar embeddings (de exemplu text-embedding-3-small de la OpenAI), modelul înțelege extraordinar de bine contextul semantic. Dacă un user caută „cum reziliez abonamentul”, îți va găsi paragrafe despre „anularea contractului” sau „întreruperea serviciilor”. Super mișto.

Dar am pățit-o urât la un proiect în zona juridică. Când utilizatorul căuta un număr exact de articol de lege (ex: Art. 245 alin. 2) sau o serie de șasiu, vector search-ul o lua complet pe arătură. Vectorii transformă token-urile specifice în puncte într-un spațiu multidimensional unde codurile exacte își pierd identitatea strictă.

Răspunsul? Hybrid search: combinăm căutarea semantică (embeddings prin pgvector) cu căutarea exactă/textuală (BM25 via tsvector nativ din Postgres).

Cum combinăm pgvector cu tsvector folosind RRF

Pentru performanță bună la sute de mii de rânduri, ai nevoie de un index HNSW (vector_cosine_ops) pe coloana de vectori și un index GIN pe coloana de tsvector.

Trucul peste care m-am lovit e cum îmbraci rezultatele. În loc să faci două query-uri separate din codul de aplicație și să încerci să normalizezi scorurile în Node.js sau Python, poți rula totul într-un singur query SQL folosind Reciprocal Rank Fusion (RRF). RRF ia poziția fiecărui rezultat din ambele metode și le combină scorul după o formulă simplă 1 / (k + rank). Așa nu-ți mai pasă că scorul de cosine similarity e între 0 și 1, iar scorul BM25 e un float nelimitat.

Limite și când să NU folosești pgvector

Merge brici pentru majoritatea aplicațiilor, dar există câteva trade-off-uri mari pe care trebuie să le știi din start:

  • Build time și RAM la indexare: Un index HNSW pe 1-2 milioane de vectori de 1536 dimensiuni mănâncă RAM serios în Postgres și durează destul de mult la construire. Dacă faci INSERT-uri masive și continue, re-indexarea în timp real devine dureroasă.
  • Scalabilitate la peste 10M vectori: Dacă sari de 10-15 milioane de vectori și ai nevoie de latență sub 10ms pe căutare pură, Postgres va începe să gâfâie comparativ cu un cluster dedicat de Qdrant sau Milvus optimizat nativ în Rust/C++.

La noi, pentru cele 400k de rânduri, latența medie pe un hybrid search executat direct în SQL a scăzut la ~85ms, ceea ce e excelent pentru un produs enterprise.

Voi ce folosiți pentru RAG în producție? Ați rămas pe baze relaționale cu extensii sau ați trecut direct pe vector DB-uri dedicate?

Răspunsuri 0

Se încarcă răspunsurile…

Loghează-te pentru a răspunde

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