eduardweb.
RAG & EmbeddingsAvansat#rag#ai#postgres#pgvector#search

RAG cu pgvector și BM25 în Postgres: Căutare hibridă în producție fără baze de date exotice

De Liliana Ghiță, 24 iul. 2026 · 12 vizualizări · 3 like-uri

Postat 24 iul. 2026
sql
WITH vector_matches AS (
  SELECT id, ROW_NUMBER() OVER (ORDER BY embedding <=> $1) as rank
  FROM documents
  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 documents
  WHERE fts_tokens @@ plainto_tsquery('romanian', $2)
  ORDER BY rank
  LIMIT 20
)
SELECT 
  COALESCE(v.id, t.id) AS id,
  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
ORDER BY rrf_score DESC
LIMIT 10;

Am scos recent Qdrant dintr-un proiect cu peste 150.000 de documente tehnice și am mutat totul în Postgres folosind extensia pgvector. Economisim aproape 220$ pe lună pe infrastructură, iar latency-ul la căutare a rămas constant sub 40ms. Îți arăt mai jos cum am configurat hybrid search (semantic + BM25) și de ce nu merită să complici arhitectura cu un vector database separat până nu treci de cel puțin un milion de înregistrări.

Problema cu Similarity Search simplu

Semantic search-ul bazat exclusiv pe embeddings (cum ar fi text-embedding-3-small de la OpenAI) e genial când userul întreabă "cum îmi resetez parola". Înțelege intenția. Dar problemele mari apar când userul caută un cod de eroare exact, cum ar fi ERR_DB_TIMEOUT_502, o serie de produs sau o sintaxă specifică de cod.

Vectorii sunt de cele mai multe ori "orbi" la potriviri exacte de caractere. Am pățit-o pe pielea mea la un client pe un sistem de RAG intern: vector search-ul returna articole generale despre baze de date când echipa de suport căuta un cod de eroare exact din log-uri. Soluția pe care am aplicat-o a fost căutarea hibridă: combinăm cosine similarity din pgvector cu Full-Text Search-ul nativ din Postgres (tsvector / BM25).

Indexare și interogare hibridă cu RRF

În Postgres nu trebuie să reinventezi roata. Pui un index HNSW pe coloana de vectori pentru căutarea semantică și un index GIN pe coloana de tsvector pentru căutarea clasică pe cuvinte cheie.

Cea mai curată metodă de a combina rezultatele fără să te chinui să normalizezi scoruri complet diferite este Reciprocal Rank Fusion (RRF). Fiecare algoritm dă o listă de rezultate ordonate, iar noi calculăm un scor final bazat pe poziția documentului în ambele topuri. Formula clasică folosește o constantă (de obicei k = 60) pentru a nivela influența primelor poziții.

Trade-off-uri reale: unde se rupe filmul

PGVector e excelent, dar nu e un glonte de argint. Am dat de câteva praguri dureroase când am urcat spre 500.000 de vectori cu 1536 de dimensiuni:

  1. Consumul masiv de RAM: Indexul HNSW trebuie să stea complet în memorie pentru performanță maximă. Pe o instanță de RDS de 8GB RAM, am luat Out Of Memory în timpul re-indexării. A trebuit să ajustăm atent maintenance_work_mem și parametrii m și ef_construction.
  2. Viteza de inserare: Insert-urile individuale pe un tabel cu un index HNSW mare devin sesizabil mai lente comparativ cu o bază dedicată optimizată pe scriere (gen Milvus sau Qdrant).

Dacă ai sub 1 milion de bucăți de text și o bază de date cu cel puțin 16GB RAM, nu ai niciun motiv rațional să adaugi o altă bază de date în stack. Îți complici pipeline-ul de sync și deploiement-ul degeaba.

Voi ce folosiți în producție pentru RAG? Ați rămăs pe Postgres sau ați fost nevoiți să migrați pe vector DB-uri dedicate când a crescut volumul?

Răspunsuri 0

Se încarcă răspunsurile…

Loghează-te pentru a răspunde

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