AI Layer

pgvector

Use pgvector to store embeddings and run vector similarity search in PostgreSQL, then choose indexes and validate retrieval inside an existing data platform.

Published by How to Train Your Data

Last updated: 2026-10-26

Sources

What pgvector is

pgvector is a PostgreSQL extension for storing vectors and searching by distance. The project documentation lists exact and approximate nearest-neighbor search, multiple vector representations, and distance operators. That makes it useful when your application already depends on Postgres and the retrieval workload can share its data and operational boundary. The first question is not whether every vector workload belongs in Postgres; it is whether a single database simplifies the data path without making latency, memory, or maintenance unacceptable. Start with a representative query and measure it in the environment you plan to operate.

In a retrieval pipeline, the embedding model turns text into a vector, ingestion writes the vector and its metadata, and a query vector retrieves candidate rows. pgvector supplies the database type, operators, and index methods; it does not choose chunks, produce embeddings, manage prompt context, or determine whether an answer is correct. Keep those concerns explicit. Store the source identifier, content version, and model identifier with each vector so an engineer can explain which source material a result represents and decide when to rebuild it.

Postgres also gives the vector record a relational context. A retrieved chunk can be joined to a document table, filtered by tenant or status, and updated through normal database workflows. The convenience comes with shared resource planning: vector reads compete with other database operations and an index has a storage and build cost. Treat the extension as part of your existing database service plan, and check the provider-specific Postgres version and extension availability before designing around it.

Where it fits in a RAG pipeline

Use pgvector at the retrieval store boundary when the system needs nearest-neighbor search over embeddings and Postgres is already an acceptable place to keep the associated records. A small implementation can keep document IDs, text, metadata, and vectors together, then use SQL to retrieve candidates. This is especially convenient when application filters and joins are part of retrieval. It does not replace a document ingestion process: extraction, deduplication, access control, chunking, embedding generation, and deletion still need owners and repeatable jobs.

A vector index is an approximation and tuning choice, not a correctness guarantee. The official README describes HNSW as a multilayer graph with a speed-recall tradeoff against IVFFlat, while IVFFlat clusters vectors into lists and searches a subset. The documentation says IVFFlat should be created after data is present and describes how probes and list counts affect recall and speed. Those are starting points for experiments, not universal production settings. Compare indexed results with exact nearest-neighbor results on a labeled sample and monitor the retrieval behavior that matters to users.

For a data engineer, the surrounding pipeline contract matters as much as the SQL. Define what changes trigger a re-embedding, how stale rows are removed, whether metadata filters run before or after approximate search, and how an index is rebuilt. A model change can put new and old vectors into different spaces. Record model and preprocessing versions, plan a backfill, and avoid mixing incompatible embeddings in a single search space unless the application explicitly separates them.

When to pick it over a separate vector service

Choose pgvector for an evaluation when keeping relational records and vector search in one Postgres service reduces moving parts and the expected workload fits its resource envelope. It is a strong candidate when SQL joins, transactions, existing access controls, and familiar backup procedures are central to the application. A separate service may be worth evaluating when vector indexing has distinct scaling, availability, or operational requirements. Decide by testing representative data, filters, update rates, concurrency, and recovery expectations; this page makes no benchmark claim for either option.

Do not begin with an index type debate. First establish a baseline: a fixed document set, a fixed query set, expected relevant records, and an exact-search result. Then compare HNSW and IVFFlat under the same query and filter workload. Record build time, database footprint, query latency, and recall on the sample. Keep the experiment reproducible and rerun it after changing embedding models, query distribution, Postgres version, or data volume. If a quality difference is not measured, avoid attributing it to the index.

A practical boundary is to let Postgres own durable records and use a specialized retrieval system only when requirements call for it. If a second system is introduced, document how changes flow to it, what happens on partial failure, how access control is kept aligned, and how deletion propagates. Every extra index creates a consistency path. The benefit must justify that path for the team that will own refresh, monitoring, and incident response.

Operational gotchas

The extension must be installed and enabled in the target database. A Python package alone does not install the server-side extension. Verify the hosted service documentation and database version, then run the documented SQL enablement step in a non-production database. Test restore and migration procedures as well: a backup is useful only if the extension and its objects return correctly in the intended recovery environment.

Filtering can affect approximate-search results. Compare the results after application filters with the unfiltered baseline and inspect whether the candidate set still contains useful records. Keep tenant and authorization checks in the trusted application/database boundary; retrieved text must not be treated as permission evidence. Also define query limits and safe parameterization so an input cannot turn into arbitrary SQL.

Index maintenance and vector lifecycle deserve explicit runbooks. The project documentation notes tradeoffs in index construction and query settings, and calls out vacuum and reindex operations. Test inserts, updates, deletes, and rebuilds with the expected data shape before rollout. Log query and embedding versions without exposing private document text. When quality regresses, inspect source freshness, chunk boundaries, filters, model versions, and index settings separately instead of changing several at once.

Learning map

Stage 1: Understand the retrieval contract

Define what is embedded, filtered, retrieved, and returned to the application.

Stage 2: Learn Postgres vector search

Use the documented extension and compare exact and indexed queries.

Stage 3: Operate the derived data

Plan refresh, access control, index rebuild, and recovery.

Get hands-on: step by step

  1. Check the server and extension

    Read the <a href="https://github.com/pgvector/pgvector/tree/v0.8.7" rel="noopener noreferrer">pgvector v0.8.7 installation guide</a> and the target provider instructions. In a disposable database, enable the extension as documented, then record the PostgreSQL and extension versions. Do not assume a client library installs server components.

    CREATE EXTENSION vector;
  2. Create a tiny fixture

    Use the table and vector type examples from the <a href="https://github.com/pgvector/pgvector/blob/v0.8.7/README.md" rel="noopener noreferrer">tagged README</a>. Insert a handful of synthetic vectors with distinct IDs and a text label. Keep this fixture small enough to inspect manually.

    CREATE TABLE items (id bigserial PRIMARY KEY, content text, embedding vector(3));
  3. Run exact nearest-neighbor search

    Use the distance operator examples in the <a href="https://github.com/pgvector/pgvector/blob/v0.8.7/README.md" rel="noopener noreferrer">pgvector README</a>. Predict the nearest row from the fixture before executing the query, then compare the ordering and distance with your hand calculation.

    SELECT id, content FROM items ORDER BY embedding <-> '[1,2,3]' LIMIT 2;
  4. Compare an HNSW index

    Follow the HNSW section in the <a href="https://github.com/pgvector/pgvector/blob/v0.8.7/README.md" rel="noopener noreferrer">versioned index guide</a>. Run the same query before and after creating the index. A tiny table may not use the index, so inspect the plan and avoid drawing performance conclusions from a toy dataset.

    CREATE INDEX ON items USING hnsw (embedding vector_l2_ops);
  5. Test IVFFlat with loaded data

    The <a href="https://github.com/pgvector/pgvector/blob/v0.8.7/README.md" rel="noopener noreferrer">IVFFlat documentation</a> says to build after data exists and explains probes. Create a larger local fixture if needed, compare results with exact search, and write down the quality and speed tradeoff observed on your machine.

    CREATE INDEX ON items USING ivfflat (embedding vector_l2_ops) WITH (lists = 10);
  6. Write the refresh runbook

    Document the embedding model ID, source version field, update and delete rules, index rebuild method, authorization filters, and a recovery check. Confirm each operational command against the target PostgreSQL and provider documentation before using real data.

Key resources

Related topics

More in ai-layer

Stay current on AI data engineering

New resources and perspective on building AI-ready data systems, a few times a month. No spam.