PostgreSQL
Persist RAG indexes and documents with PostgreSQL and execute search through pgvector.
Runiq.AI.Rag.PostgreSql stores logical indexes, documents, chunks, embeddings, JSONB metadata, and ingestion state. Vector search executes in PostgreSQL, allowing the application to keep its retrieval data across process restarts.
PostgreSQL configuration does not silently fall back to in-memory storage. Missing configuration fails during registration, and connection or migration failures remain provider failures that the application must handle.
Production deployments should provision the
vectorandpg_trgmextensions and run reviewed migrations before the application starts. KeepInitializeSchemaandCreateVectorExtensiondisabled at runtime unless a controlled local or test environment explicitly owns schema initialization. Creating extensions requires database privileges that many runtime roles should not have.
Install the integration
The database needs the vector and pg_trgm extensions. vector backs pgvector similarity search. pg_trgm supports lexical search alongside the provider's text-search indexes, so PostgreSQL can serve lexical and hybrid retrieval in addition to durable vector storage.
Register the provider
Read the connection string from host configuration. This example assumes the schema and extensions were provisioned during deployment:
Select UsePostgreSqlVectorStore() in the named index configuration from Defining indexes. Register the PostgreSQL provider after any default in-memory provider setup: the last provider registration wins. A missing connection string fails at registration; a connection failure does not silently switch to in-memory storage.
Local database setup
From a checkout of the Runiq.AI repository, start the supplied development database:
Its development connection string is:
These credentials belong to the local sample. Supply them through ConnectionStrings:Rag for that environment. The volume retains data across container restarts.
For local schema initialization, change the provider options to:
The database role must have permission to install the required extensions. In production, provision extensions and apply reviewed migrations during deployment, then keep runtime schema initialization disabled. Initialization is opt-in, transactional, idempotent, and non-destructive.
Customize document persistence
The managed ingestion path handles document writes for the configured index. For custom ingestion code, IPostgreSqlRagDocumentStore.UpsertDocumentAsync writes a document aggregate. The following fragment assumes the caller already prepared contentHash and chunkVectors for the logical index:
| Document state | Result |
|---|---|
| New document | Create the aggregate. |
| Existing document with the same hash | Return Skipped without rewriting its chunks. |
| Existing document with a changed hash | Replace the chunk set and update state in one transaction. |
| Invalid vector dimensions or a failed transaction | Reject the write without leaving a partial replacement. |
Writes use an advisory lock scoped to the index and document. DeleteDocumentAsync(indexName, documentId) is index-scoped and idempotent; it returns Deleted or NotFound, with related chunks and ingestion state removed through foreign-key cascades.
Understand database search
Metadata equality filters run in SQL before the candidate limit. Results sort by distance, then document and chunk identity. PostgreSQL reports cosine and Euclidean distances as lower-is-better raw values and dot product as higher-is-better.
Exact scan is the default because one table can contain indexes with different vector dimensions and metrics. At scale, evaluate dimension- and metric-specific partial HNSW indexes against real workloads. IPostgreSqlRagHealthCheck reports connectivity, extensions, schema, migration version, and index-table readability.
Lexical search uses provider-managed text-search and trigram structures for exact terms, punctuation-sensitive identifiers, and phrase intent. Hybrid retrieval runs both PostgreSQL vector search and lexical search, then fuses the ranked candidates in the retrieval layer; it is not just vector persistence with a database behind it.
Reference: PostgreSQL package guide.