Prev Next

Database / pgvector basics Interview Questions

1. What is pgvector and what problem does it solve for developers? 2. What are vector embeddings and why are they central to pgvector's use? 3. How do you install and enable pgvector in PostgreSQL? 4. What data types does pgvector provide and how do you define vector columns? 5. What distance operators does pgvector provide and when do you use each? 6. How do you insert and update vector data in pgvector? 7. How do you perform a basic nearest-neighbour search with pgvector? 8. What is the difference between exact and approximate nearest-neighbour search in pgvector? 9. What is the HNSW index in pgvector and how do you create and tune it? 10. What is the IVFFlat index in pgvector and how does it compare to HNSW? 11. How do you use pgvector with Python and psycopg2? 12. How do you use pgvector with SQLAlchemy and Python ORMs? 13. How do you combine vector similarity search with SQL filters (hybrid search) in pgvector? 14. How do you bulk-load vectors efficiently into pgvector? 15. What operator classes must you use when creating pgvector indexes and why do they matter? 16. How does pgvector fit into a RAG (Retrieval-Augmented Generation) pipeline? 17. What is cosine distance vs cosine similarity and which does pgvector return? 18. How do you check and monitor pgvector index creation progress? 19. What is the pgvector maximum supported dimensions limit and how do you handle high-dimensional vectors? 20. How do you use pgvector with LangChain for building AI applications? 21. How does pgvector handle NULL values in vector columns? 22. What are partial indexes in pgvector and when should you use them? 23. How do you perform vector arithmetic and other vector functions in pgvector? 24. How does pgvector compare to dedicated vector databases like Pinecone, Weaviate, and Qdrant? 25. What is the difference between L1 and L2 distance in pgvector? 26. How do you store and query vector embeddings with pgvector in a real schema design? 27. What is halfvec and when should you use it to reduce storage costs? 28. How do you handle vector dimensionality mismatches in pgvector? 29. How do you use pgvector with Django? 30. What are common performance tuning techniques for pgvector at scale? 31. How do you implement semantic search with pgvector and a similarity threshold? 32. How do you use pgvector with asyncpg or asyncio in Python? 33. What is vector quantisation and how does pgvector support binary quantisation? 34. How does pgvector integrate with managed PostgreSQL services? 35. How do you use the inner product operator <#> with pgvector and when is it appropriate? 36. How do you combine pgvector with full-text search (hybrid keyword + semantic search)? 37. What PostgreSQL configuration parameters affect pgvector performance? 38. How do you implement recommendation systems using pgvector? 39. How do you use EXPLAIN and EXPLAIN ANALYZE to debug pgvector queries? 40. What are best practices for a production pgvector deployment?
Could not find what you were looking for? send us the question and we would be happy to answer your question.

1. What is pgvector and what problem does it solve for developers?

pgvector is an open-source PostgreSQL extension that adds native vector storage and similarity search capabilities to PostgreSQL. It allows developers to store high-dimensional vector embeddings generated by machine learning models alongside conventional relational data, and query them using efficient distance-based operators - all within the same database they already use.

The problem it solves: modern AI applications need to store and search vector embeddings (numerical representations of text, images, audio, etc.) to enable semantic search, recommendations, and RAG pipelines. Without pgvector, teams had to choose between:

  • A dedicated vector database (Pinecone, Weaviate, Qdrant) - separate system, additional infrastructure cost, data synchronisation challenges
  • Custom application-level search - slow, no indexing, hard to maintain

pgvector eliminates this tradeoff by extending PostgreSQL with vector types and operators. Teams get vector search alongside full ACID compliance, transactions, JOINs, replication, point-in-time recovery, and all other PostgreSQL features - with no new database system to operate.

pgvector at a glance
PropertyDetail
TypePostgreSQL extension
LicenseOpen source (PostgreSQL License)
Current versionv0.8.4 (as of mid-2026)
RequiresPostgreSQL 11+
Extension namevector (not pgvector)
GitHubgithub.com/pgvector/pgvector
What PostgreSQL extension name do you use in CREATE EXTENSION to enable pgvector?
What is the primary architectural benefit of using pgvector over a dedicated vector database?

2. What are vector embeddings and why are they central to pgvector's use?

A vector embedding is a list of floating-point numbers produced by a machine learning model that encodes the semantic meaning of some input data. The defining property is that semantically similar inputs produce numerically similar vectors - meaning you can measure the 'closeness' of two concepts by computing the distance between their vectors.

Embedding examples
Input dataModelDimensionsApplication
Text (sentences, paragraphs)OpenAI text-embedding-3-small1,536Semantic search, RAG
TextOpenAI text-embedding-3-large3,072Higher-quality RAG
TextGoogle text-embedding-004768Google AI search
ImagesCLIP, ResNet512-2048Image similarity search
User behaviourCollaborative filtering modelVariesRecommendations
# Example: generating embeddings with OpenAI
from openai import OpenAI
client = OpenAI()

texts = [
    "I love programming in Python.",
    "Python is my favourite coding language.",
    "I enjoy outdoor hiking on weekends.",
]

response = client.embeddings.create(
    model="text-embedding-3-small",
    input=texts,
)
embeddings = [item.embedding for item in response.data]
# embeddings[0] and embeddings[1] will be numerically close
# embeddings[0] and embeddings[2] will be numerically far apart
print(f"Dimension: {len(embeddings[0])}")  # 1536
print(f"Type: {type(embeddings[0][0])}")  # float

Embeddings make semantic search possible: instead of matching keywords, you match meaning. A user querying 'something warm for high-altitude hiking' can find a product described as 'insulated mountain jacket' even though not a single word overlaps - because both phrases produce similar vectors.

What property of vector embeddings makes semantic similarity search possible?
How many dimensions does OpenAI's text-embedding-3-small model produce per text input?

3. How do you install and enable pgvector in PostgreSQL?

pgvector installation has two steps: installing the extension binary on the server, and enabling it in each database where you want to use it. The extension name used in SQL is vector (not pgvector).

Installation methods
MethodCommand
Ubuntu/Debian (PostgreSQL APT repo)sudo apt install postgresql-17-pgvector
RHEL/CentOS (Yum repo)sudo yum install pgvector_17
macOS (Homebrew)brew install pgvector
DockerUse pgvector/pgvector Docker image
Condaconda install -c conda-forge pgvector
From sourcegit clone + make + make install
Managed (AWS RDS, Supabase, Neon, Azure)Enable via console or allowlist
-- Step 1: Enable in the database (run once per database)
CREATE EXTENSION vector;

-- Verify it is installed:
SELECT * FROM pg_extension WHERE extname = 'vector';

-- Upgrade an existing installation:
ALTER EXTENSION vector UPDATE;

-- On managed services (e.g. Azure), allowlist it first:
-- Then CREATE EXTENSION vector; in the database

-- Build from source (Linux):
-- git clone --branch v0.8.4 https://github.com/pgvector/pgvector.git
-- cd pgvector && make && sudo make install
-- Then in psql: CREATE EXTENSION vector;

Important naming note: although the project is universally called pgvector, the PostgreSQL extension name is vector. Always use CREATE EXTENSION vector (not pgvector). This distinction matters on managed services like Azure where you must allowlist the name vector.

What is the correct SQL command to enable pgvector in a PostgreSQL database?
What command verifies that the vector extension is installed in your PostgreSQL database?

4. What data types does pgvector provide and how do you define vector columns?

pgvector introduces several new data types to PostgreSQL for storing vector data. The primary type is vector, with additional types for half-precision and binary vectors added in later releases.

pgvector data types
TypeStoragePrecisionMax dimensionsUse case
vector(n)4 bytes per dimension32-bit float (single precision)16,000Standard embeddings (OpenAI, Cohere, etc.)
halfvec(n)2 bytes per dimension16-bit float (half precision)16,000Reduced storage; slight precision trade-off
bit(n)~1 bit per dimensionBinary (0 or 1)64,000Binary quantised embeddings; very compact
sparsevec(n)Only non-zero values stored32-bit float1,000,000Sparse vectors (most dimensions are 0)
-- Basic vector column (fixed dimensions)
CREATE TABLE documents (
    id         BIGSERIAL PRIMARY KEY,
    content    TEXT,
    embedding  VECTOR(1536)    -- 1536-dim for text-embedding-3-small
);

-- Half-precision to save storage (2x space reduction)
CREATE TABLE documents_small (
    id         BIGSERIAL PRIMARY KEY,
    embedding  HALFVEC(1536)
);

-- Binary vectors (very compact; special distance operators)
CREATE TABLE binary_items (
    id         BIGSERIAL PRIMARY KEY,
    embedding  BIT(1536)
);

-- Variable dimensions (no dimension constraint)
CREATE TABLE flexible (
    id         BIGSERIAL PRIMARY KEY,
    embedding  VECTOR         -- accepts any dimension
);
-- NOTE: indexes on variable-dim columns require expression/partial indexes

How many bytes does each dimension occupy in a standard vector(n) column?
What is the primary advantage of using halfvec(n) instead of vector(n)?

5. What distance operators does pgvector provide and when do you use each?

pgvector defines several SQL operators for computing distance or similarity between vectors. The operator you choose affects both the mathematical semantics and which index types can accelerate the query.

pgvector distance operators
OperatorNameFormulaBest for
<->L2 (Euclidean) distancesqrt(sum((a-b)^2))General purpose; when vector magnitude carries meaning
<#>Negative inner product-sum(a*b)Recommendation systems; normalised vectors
<=>Cosine distance1 - (a.b / |a||b|)Text embeddings; magnitude-independent similarity
<+>L1 (Manhattan) distancesum(|a-b|)Robust to outliers; sparse-ish vectors
<~>Hamming distanceCount of differing bitsBinary vectors (bit type only)
<%>Jaccard distance1 - |A intersect B| / |A union B|Binary vectors; set-like similarity
-- L2 distance: find 5 nearest neighbours to '[3,1,2]'
SELECT id, content, embedding <-> '[3,1,2]' AS distance
FROM documents
ORDER BY distance
LIMIT 5;

-- Cosine distance: best for text embeddings
SELECT id, content, embedding <=> '[3,1,2]' AS cosine_dist
FROM documents
ORDER BY cosine_dist
LIMIT 5;

-- Inner product: NOTE <#> returns NEGATIVE inner product
-- Negate the result to get actual similarity score:
SELECT id, -(embedding <#> '[3,1,2]') AS inner_product_sim
FROM documents
ORDER BY embedding <#> '[3,1,2]'  -- ASC = most similar (most negative)
LIMIT 5;

-- Get actual distance value (not just ordering):
SELECT id, embedding <-> '[3,1,2]' AS l2_dist,
            embedding <=> '[3,1,2]' AS cos_dist
FROM documents
LIMIT 10;

Why does pgvector use the <#> operator for inner product and return a NEGATIVE value?
Which pgvector operator is most commonly recommended for text embedding similarity search?

6. How do you insert and update vector data in pgvector?

Vector values are inserted as SQL string literals in the format '[v1,v2,...,vn]' - a JSON array-like notation enclosed in single quotes. PostgreSQL automatically casts these to the vector type.

-- Insert a single row with a vector literal
INSERT INTO documents (content, embedding)
VALUES ('Hello world', '[0.1, 0.2, 0.3, ...]');

-- Insert multiple rows in one statement
INSERT INTO documents (id, content, embedding) VALUES
    (1, 'Python tutorial', '[0.11, 0.22, 0.33]'),
    (2, 'JavaScript guide', '[0.44, 0.55, 0.66]'),
    (3, 'Hiking tips', '[0.77, 0.88, 0.99]');

-- Insert from a Python list (psycopg2)
# import psycopg2
# embedding = [0.1, 0.2, 0.3]   # list of floats from embedding model
# cur.execute(
#     "INSERT INTO documents (content, embedding) VALUES (%s, %s)",
#     (text, embedding)   # psycopg2 converts list to vector literal
# )

-- Update an existing embedding:
UPDATE documents
SET embedding = '[0.11, 0.21, 0.31]'
WHERE id = 1;

-- Upsert (insert or update on conflict):
INSERT INTO documents (id, content, embedding)
VALUES (1, 'Updated Python tutorial', '[0.12, 0.23, 0.34]')
ON CONFLICT (id) DO UPDATE SET
    content   = EXCLUDED.content,
    embedding = EXCLUDED.embedding;

-- Cast a text literal to vector explicitly:
SELECT '[1,2,3]'::vector;   -- explicit cast notation
SELECT CAST('[1,2,3]' AS vector(3));

What format is used to represent a vector value as an SQL literal in pgvector?
What SQL clause makes an INSERT statement behave as 'insert or update' with pgvector?

7. How do you perform a basic nearest-neighbour search with pgvector?

The fundamental pgvector query pattern combines a distance operator in the ORDER BY clause with LIMIT to retrieve the k nearest neighbours to a query vector. Without an index, this performs an exact sequential scan of all rows.

-- K-Nearest Neighbour (KNN) query: find 5 most similar documents
-- Replace '[...]' with the actual query embedding from your ML model
SELECT id, content, embedding <-> '[0.1,0.2,0.3]' AS distance
FROM documents
ORDER BY distance          -- ORDER BY the distance expression
LIMIT 5;                   -- return only the top-5 nearest

-- Using cosine distance (common for text embeddings):
SELECT id, content, 1 - (embedding <=> '[0.1,0.2,0.3]') AS similarity
FROM documents
ORDER BY embedding <=> '[0.1,0.2,0.3]'
LIMIT 10;

-- Self-similarity: find items similar to a known row (not itself)
SELECT b.id, b.content, a.embedding <-> b.embedding AS distance
FROM documents a
CROSS JOIN documents b
WHERE a.id = 42 AND b.id != 42
ORDER BY distance
LIMIT 5;

-- Or using a subquery:
SELECT id, content,
       embedding <-> (SELECT embedding FROM documents WHERE id = 42) AS distance
FROM documents
WHERE id != 42
ORDER BY distance
LIMIT 5;

-- With a parameter placeholder (Python + psycopg2):
-- cur.execute(
--     'SELECT id, content, embedding <-> %s AS dist FROM documents ORDER BY dist LIMIT 5',
--     (query_embedding,)
-- )

In a pgvector KNN query, where must the distance operator expression appear?
What does a smaller value returned by the <-> (L2 distance) operator indicate?

8. What is the difference between exact and approximate nearest-neighbour search in pgvector?

pgvector supports two search modes with very different performance and accuracy characteristics. Choosing the right one depends on dataset size and whether you need perfect recall.

Exact vs Approximate search
AspectExact (sequential scan)Approximate (ANN with index)
MethodComputes distance to EVERY rowSearches a smart subset of rows
Recall100% - always finds the true nearest neighboursLess than 100% - may miss some true neighbours
Speed at scaleSlow O(n) per querySub-linear; fast even at millions of rows
ConfigurationNo index neededRequires HNSW or IVFFlat index
Good for< ~100k rows or when 100% recall neededMillions of rows; speed more important than perfect recall
Index requiredNoYes (HNSW or IVFFlat)
-- EXACT search (no index): scans every row
-- Accurate but slow at scale
SELECT id, embedding <-> '[3,1,2]' AS dist
FROM items
ORDER BY dist
LIMIT 5;

-- APPROXIMATE search (with HNSW index): fast, high recall
-- Create the index:
CREATE INDEX ON items USING hnsw (embedding vector_l2_ops);

-- Same query syntax - PostgreSQL uses the index automatically:
SELECT id, embedding <-> '[3,1,2]' AS dist
FROM items
ORDER BY dist
LIMIT 5;
-- Now uses HNSW index for fast approximate search

-- Check which index was used:
EXPLAIN SELECT id FROM items ORDER BY embedding <-> '[1,2,3]' LIMIT 5;
-- Look for 'Index Scan using items_embedding_idx' in the plan

-- Force exact search even when index exists:
SET enable_indexscan = off;
SELECT id FROM items ORDER BY embedding <-> '[1,2,3]' LIMIT 5;
RESET enable_indexscan;

At what approximate dataset size does the pgvector documentation suggest considering an ANN index instead of exact search?
What is 'recall' in the context of approximate nearest-neighbour search?

9. What is the HNSW index in pgvector and how do you create and tune it?

HNSW (Hierarchical Navigable Small World) is the recommended index type for most pgvector workloads. It builds a multilayer graph structure where each layer is a navigable small world graph, enabling very fast approximate nearest-neighbour search with excellent recall.

-- Create an HNSW index (choose operator class to match your distance metric)

-- For L2 (Euclidean) distance <->:
CREATE INDEX ON items USING hnsw (embedding vector_l2_ops);

-- For cosine distance <=>:
CREATE INDEX ON items USING hnsw (embedding vector_cosine_ops);

-- For inner product <#>:
CREATE INDEX ON items USING hnsw (embedding vector_ip_ops);

-- For L1 distance <+>:
CREATE INDEX ON items USING hnsw (embedding vector_l1_ops);

-- HNSW with custom build parameters:
CREATE INDEX ON items USING hnsw (embedding vector_cosine_ops)
WITH (
    m = 16,             -- max connections per node per layer (default 16)
    ef_construction = 64  -- candidate list size during build (default 64)
);

-- Tune QUERY performance at query time:
SET hnsw.ef_search = 100;  -- larger = better recall, slower query (default 40)
SELECT id FROM items ORDER BY embedding <=> '[...]' LIMIT 5;
RESET hnsw.ef_search;

-- Speed up index BUILD with parallel workers:
SET max_parallel_maintenance_workers = 7;  -- match your CPU cores
SET maintenance_work_mem = '2GB';
CREATE INDEX ON items USING hnsw (embedding vector_cosine_ops);

HNSW build parameters
ParameterDefaultEffect of increasing
m16More connections = better recall, larger index, slower build
ef_construction64Larger candidate list during build = better recall, much slower build
HNSW query parameter
ParameterDefaultEffect of increasing
hnsw.ef_search40Larger candidate set during search = better recall, slightly slower query
Which HNSW operator class must you use when creating an index for cosine distance queries (<=>)?
What does increasing hnsw.ef_search at query time do?

10. What is the IVFFlat index in pgvector and how does it compare to HNSW?

IVFFlat (Inverted File Flat) is pgvector's other index type. It clusters vectors into lists using k-means, then searches only the closest lists to the query vector. It was the original pgvector index type but is now generally considered secondary to HNSW for most workloads.

-- Create an IVFFlat index
-- IMPORTANT: table must have data before creating the index
-- (k-means clustering needs existing vectors to learn from)

-- For L2 distance:
CREATE INDEX ON items USING ivfflat (embedding vector_l2_ops)
WITH (lists = 100);  -- number of clusters/lists

-- For cosine distance:
CREATE INDEX ON items USING ivfflat (embedding vector_cosine_ops)
WITH (lists = 100);

-- Guideline for lists parameter:
-- rows <= 1,000,000: lists = sqrt(rows)  e.g. sqrt(100000) ~ 316
-- rows >  1,000,000: lists = rows / 1000  e.g. 2000000/1000 = 2000

-- Tune QUERY recall at query time:
SET ivfflat.probes = 10;  -- number of lists to search (default 1)
-- Higher probes = better recall, slower queries
-- probes = lists gives exact search (defeats purpose of index)

SELECT id FROM items ORDER BY embedding <-> '[...]' LIMIT 5;
RESET ivfflat.probes;

HNSW vs IVFFlat comparison
AspectHNSWIVFFlat
Build timeSlower (builds complex graph)Faster (simpler k-means clustering)
Build memoryMore memory requiredLess memory required
Query speedGenerally fasterGenerally slower at same recall
RecallBetter recall at same speedNeeds more probes to match HNSW recall
Requires data firstNo (can build on empty table)Yes (needs vectors to cluster)
Recommended forMost workloadsMemory-constrained or faster build needed
Query tuninghnsw.ef_searchivfflat.probes
Why must an IVFFlat index be created AFTER inserting data into the table?
What is the recommended formula for choosing the 'lists' parameter in an IVFFlat index for a table with up to 1 million rows?

11. How do you use pgvector with Python and psycopg2?

The standard Python path for pgvector uses psycopg2 (or psycopg3) as the PostgreSQL driver, with the pgvector Python package providing type adapters that automatically convert Python lists to vector literals and back.

# Install dependencies
# pip install pgvector psycopg2-binary openai

import psycopg2
from pgvector.psycopg2 import register_vector
from openai import OpenAI

# Connect and register the vector type adapter
conn = psycopg2.connect("postgresql://user:pass@localhost/mydb")
register_vector(conn)  # allows Python lists <-> vector column
cur = conn.cursor()

# Create the schema
cur.execute("""
    CREATE TABLE IF NOT EXISTS documents (
        id        BIGSERIAL PRIMARY KEY,
        content   TEXT NOT NULL,
        embedding VECTOR(1536)
    )
""")
conn.commit()

# Generate embedding and insert
oai = OpenAI()
text = "How does pgvector work?"
response = oai.embeddings.create(model="text-embedding-3-small", input=text)
embedding = response.data[0].embedding  # list of 1536 floats

cur.execute(
    "INSERT INTO documents (content, embedding) VALUES (%s, %s)",
    (text, embedding)   # psycopg2 + register_vector handles the conversion
)
conn.commit()

# Query: find 5 most similar documents
query_text = "What is the pgvector extension?"
query_embedding = oai.embeddings.create(
    model="text-embedding-3-small", input=query_text
).data[0].embedding

cur.execute(
    "SELECT id, content, embedding <=> %s AS distance"
    " FROM documents ORDER BY distance LIMIT 5",
    (query_embedding,)
)
results = cur.fetchall()
for row in results:
    print(f"id={row[0]}, dist={row[2]:.4f}: {row[1]}")

cur.close()
conn.close()

What does the register_vector() function do in the pgvector Python package?
What Python package provides the register_vector() adapter for psycopg2?

12. How do you use pgvector with SQLAlchemy and Python ORMs?

SQLAlchemy is the most popular Python ORM and supports pgvector through the pgvector package, which provides a SQLAlchemy column type and custom operators. This allows defining vector columns declaratively and using Pythonic query expressions.

# pip install pgvector sqlalchemy psycopg2-binary

from sqlalchemy import create_engine, Column, Integer, Text, select
from sqlalchemy.orm import declarative_base, Session
from pgvector.sqlalchemy import Vector

engine = create_engine("postgresql+psycopg2://user:pass@localhost/mydb")
Base = declarative_base()

# Define the model with a vector column
class Document(Base):
    __tablename__ = "documents"
    id        = Column(Integer, primary_key=True)
    content   = Column(Text)
    embedding = Column(Vector(1536))  # pgvector Vector type

Base.metadata.create_all(engine)

# Insert a document
with Session(engine) as session:
    doc = Document(content="Python tutorial", embedding=[0.1, 0.2, ...])
    session.add(doc)
    session.commit()

# Query: find 5 nearest neighbours using cosine distance
from pgvector.sqlalchemy import cosine_distance

query_vector = [0.1, 0.2, ...]
with Session(engine) as session:
    results = session.scalars(
        select(Document)
        .order_by(cosine_distance(Document.embedding, query_vector))
        .limit(5)
    ).all()
    for doc in results:
        print(doc.content)

# Create HNSW index via SQLAlchemy:
from sqlalchemy import Index, text
Index(
    "ix_documents_embedding_hnsw",
    Document.embedding,
    postgresql_using="hnsw",
    postgresql_ops={"embedding": "vector_cosine_ops"},
    postgresql_with={"m": 16, "ef_construction": 64},
)

What SQLAlchemy column type does the pgvector package provide for vector columns?
How do you express a cosine distance ORDER BY clause in SQLAlchemy with pgvector?

13. How do you combine vector similarity search with SQL filters (hybrid search) in pgvector?

One of pgvector's key advantages over standalone vector databases is the ability to combine vector similarity with arbitrary SQL predicates in a single query. This is called hybrid search or filtered vector search.

-- Filtered vector search: find similar documents in a specific category
SELECT id, content, embedding <-> '[...]' AS distance
FROM documents
WHERE category = 'technical'          -- metadata filter
  AND created_at > NOW() - INTERVAL '30 days'  -- date filter
ORDER BY distance
LIMIT 5;

-- Filter by user ownership:
SELECT id, title, embedding <=> '[...]' AS sim_dist
FROM articles
WHERE user_id = 42
  AND is_published = TRUE
ORDER BY sim_dist
LIMIT 10;

-- JOIN with another table:
SELECT d.id, d.content, d.embedding <-> '[...]' AS distance,
       c.name AS category_name
FROM documents d
JOIN categories c ON d.category_id = c.id
WHERE c.name IN ('AI', 'Machine Learning')
ORDER BY distance
LIMIT 5;

-- Distance threshold (only return results within a distance)
SELECT id, content, embedding <-> '[...]' AS distance
FROM documents
WHERE embedding <-> '[...]' < 0.5   -- only close vectors
ORDER BY distance
LIMIT 20;

-- NOTE: ANN indexes (HNSW/IVFFlat) may have reduced recall with filters
-- Workaround: increase ef_search / probes before the query
SET hnsw.ef_search = 100;
SELECT id FROM documents
WHERE category = 'technical'
ORDER BY embedding <-> '[...]' LIMIT 5;
RESET hnsw.ef_search;

Why does adding a WHERE clause filter to a pgvector ANN query potentially reduce recall?
What is one recommended approach to improve recall when using pgvector ANN search with metadata filters?

14. How do you bulk-load vectors efficiently into pgvector?

Inserting vectors one row at a time with individual INSERT statements is the slowest possible approach. For large datasets (thousands to millions of rows), bulk loading strategies dramatically reduce ingestion time.

-- Method 1: Multi-row INSERT (batch inserts)
INSERT INTO documents (content, embedding) VALUES
    ('Document 1', '[0.1,0.2,0.3]'),
    ('Document 2', '[0.4,0.5,0.6]'),
    ('Document 3', '[0.7,0.8,0.9]');
-- Send batches of 100-1000 rows per INSERT statement

-- Method 2: COPY command (fastest for very large datasets)
COPY documents (content, embedding)
FROM '/path/to/data.csv'
WITH (FORMAT TEXT, DELIMITER '\t');
-- CSV format: text<TAB>[v1,v2,v3,...]

-- Method 3: psycopg2 executemany or copy_from (Python)
# cur.executemany(
#     "INSERT INTO documents (content, embedding) VALUES (%s, %s)",
#     [(text1, emb1), (text2, emb2), ...]
# )

-- Method 4: Disable index during load, rebuild after
-- (Much faster for large bulk loads)

-- Step 1: Create table WITHOUT index
CREATE TABLE documents (
    id BIGSERIAL PRIMARY KEY,
    embedding VECTOR(1536)
);

-- Step 2: Bulk load all data
COPY documents FROM '/data/embeddings.csv';

-- Step 3: Increase memory and workers for faster index build
SET maintenance_work_mem = '4GB';
SET max_parallel_maintenance_workers = 8;

-- Step 4: Build the index AFTER loading all data
CREATE INDEX ON documents USING hnsw (embedding vector_cosine_ops)
WITH (m=16, ef_construction=64);

Why is it recommended to create the HNSW index AFTER bulk-loading data rather than before?
Which PostgreSQL command is typically fastest for bulk-loading large amounts of vector data?

15. What operator classes must you use when creating pgvector indexes and why do they matter?

When creating an HNSW or IVFFlat index on a vector column, you must specify an operator class that tells PostgreSQL which distance metric the index is optimised for. The operator class in the index must match the distance operator used in queries, otherwise the planner cannot use the index.

pgvector operator classes
Operator classDistance operatorDistance metric
vector_l2_ops<->L2 / Euclidean distance
vector_cosine_ops<=>Cosine distance
vector_ip_ops<#>Inner product (negative)
vector_l1_ops<+>L1 / Manhattan distance
halfvec_l2_ops<->L2 distance (halfvec columns)
halfvec_cosine_ops<=>Cosine distance (halfvec columns)
bit_hamming_ops<~>Hamming distance (bit columns)
bit_jaccard_ops<%>Jaccard distance (bit columns)
-- CORRECT: operator class matches the query operator
CREATE INDEX ON items USING hnsw (embedding vector_cosine_ops);
SELECT * FROM items ORDER BY embedding <=> '[...]' LIMIT 5;
-- ^ PostgreSQL uses the index above

-- WRONG: mismatched operator class and query operator
CREATE INDEX ON items USING hnsw (embedding vector_cosine_ops);
SELECT * FROM items ORDER BY embedding <-> '[...]' LIMIT 5;
-- ^ PostgreSQL CANNOT use the index (different metric)
-- This falls back to a slow sequential scan!

-- Multiple indexes for different metrics on the same column:
CREATE INDEX ON items USING hnsw (embedding vector_l2_ops);
CREATE INDEX ON items USING hnsw (embedding vector_cosine_ops);
-- Now queries using either <-> or <=> can use an index

-- Check index usage with EXPLAIN:
EXPLAIN SELECT * FROM items ORDER BY embedding <=> '[1,2,3]' LIMIT 5;
-- Should show: Index Scan using items_embedding_idx

What happens if the operator class in your HNSW index (e.g. vector_cosine_ops) does not match the distance operator in your query (e.g. <->)?
Which operator class should you use when creating an index for queries using the <=> (cosine distance) operator?

16. How does pgvector fit into a RAG (Retrieval-Augmented Generation) pipeline?

RAG (Retrieval-Augmented Generation) is a technique that improves LLM responses by retrieving relevant documents from a knowledge base and including them as context in the prompt. pgvector serves as the vector store component, storing document embeddings and enabling semantic retrieval.

RAG pipeline stages with pgvector
StageWhat happenspgvector role
1. IngestSplit documents into chunks; embed each chunkStore chunks + embeddings in vector table
2. RetrieveEmbed user query; find similar chunksKNN query returns top-k relevant chunks
3. GenerateInject retrieved chunks into LLM promptNo role - LLM (OpenAI, Gemini, etc.) generates answer
import psycopg2
from pgvector.psycopg2 import register_vector
from openai import OpenAI

conn = psycopg2.connect("postgresql://user:pass@localhost/mydb")
register_vector(conn)
cur = conn.cursor()
oai = OpenAI()

# STAGE 1: INGEST - embed and store documents
documents = [
    "pgvector is a PostgreSQL extension for vector search.",
    "HNSW indexes provide fast approximate nearest neighbour search.",
    "Cosine distance is commonly used for text embeddings.",
]
for text in documents:
    emb = oai.embeddings.create(
        model="text-embedding-3-small", input=text
    ).data[0].embedding
    cur.execute(
        "INSERT INTO documents (content, embedding) VALUES (%s, %s)",
        (text, emb)
    )
conn.commit()

# STAGE 2: RETRIEVE - semantic search for user query
user_question = "What kind of index should I use for fast search?"
q_emb = oai.embeddings.create(
    model="text-embedding-3-small", input=user_question
).data[0].embedding

cur.execute(
    "SELECT content FROM documents ORDER BY embedding <=> %s LIMIT 3",
    (q_emb,)
)
context_chunks = [r[0] for r in cur.fetchall()]

# STAGE 3: GENERATE - pass context to LLM
context = "\n".join(context_chunks)
completion = oai.chat.completions.create(
    model="gpt-4o-mini",
    messages=[
        {"role": "system", "content": f"Answer using this context:\n{context}"},
        {"role": "user",   "content": user_question}
    ]
)
print(completion.choices[0].message.content)

In a RAG pipeline, what is the role of pgvector?
What must be done to a user's query before using it to retrieve documents in a RAG pipeline?

17. What is cosine distance vs cosine similarity and which does pgvector return?

Cosine similarity and cosine distance are closely related but measure different things. pgvector's <=> operator returns cosine distance, not similarity. Understanding the relationship prevents confusion when interpreting results.

Cosine similarity vs distance
MetricFormulaRangeInterpretation
Cosine similaritycos(theta) = (a.b) / (|a||b|)[-1, 1] or [0, 1] for normalisedHigher = more similar (1 = identical direction)
Cosine distance1 - cosine_similarity[0, 2] or [0, 1] for normalisedLower = more similar (0 = identical direction)
-- pgvector's <=> returns COSINE DISTANCE (not similarity)
-- Lower value = more similar
SELECT id, content,
       embedding <=> '[0.1,0.2,0.3]' AS cosine_distance
FROM documents
ORDER BY cosine_distance          -- ASC: smallest distance first = most similar
LIMIT 5;

-- Convert to cosine SIMILARITY:
SELECT id, content,
       1 - (embedding <=> '[0.1,0.2,0.3]') AS cosine_similarity
FROM documents
ORDER BY embedding <=> '[0.1,0.2,0.3]'  -- still ORDER BY distance
LIMIT 5;

-- For normalised vectors (most embedding models output unit vectors):
-- cosine_similarity = 1 - cosine_distance
-- Also: cosine_similarity ~= inner_product for unit vectors
-- So for OpenAI text-embedding-3-small (outputs unit vectors):
-- <=> and <#> (after negation) give equivalent rankings

-- Verify a vector is normalised (magnitude = 1.0):
SELECT id, |/( embedding <-> '[0,0,0]'^2 ) AS magnitude
-- or more simply:
SELECT id, sqrt(embedding <.> embedding) AS magnitude  -- inner product of self

What range of values does pgvector's <=> cosine distance operator return for normalised vectors?
How do you convert pgvector's cosine distance to cosine similarity?

18. How do you check and monitor pgvector index creation progress?

Building HNSW or IVFFlat indexes on large tables can take significant time (minutes to hours). PostgreSQL provides the pg_stat_progress_create_index view to monitor progress in real time.

-- Monitor index build progress:
SELECT
    phase,
    round(100.0 * blocks_done / NULLIF(blocks_total, 0), 1) AS pct_complete,
    blocks_done,
    blocks_total,
    tuples_done,
    tuples_total
FROM pg_stat_progress_create_index;

-- Typical phases for HNSW index:
-- 'initializing'
-- 'loading tuples'
-- 'done'

-- Typical phases for IVFFlat index:
-- 'initializing'
-- 'performing k-means'
-- 'assigning tuples'
-- 'loading tuples'

-- List existing indexes on a table:
SELECT indexname, indexdef
FROM pg_indexes
WHERE tablename = 'documents';

-- Check index size:
SELECT pg_size_pretty(pg_indexes_size('documents'));

-- Speed up index builds with more memory and parallel workers:
SET maintenance_work_mem = '4GB';            -- more memory = faster build
SET max_parallel_maintenance_workers = 7;    -- use multiple CPU cores
-- Then create the index:

Performance tip: setting maintenance_work_mem higher is particularly important for HNSW index builds. The HNSW graph is constructed in memory, so more memory directly reduces build time. PostgreSQL's default of 64MB is far too low for large vector tables.

Which PostgreSQL system view lets you monitor pgvector index build progress in real time?
Why is setting maintenance_work_mem to a higher value before creating an HNSW index important?

19. What is the pgvector maximum supported dimensions limit and how do you handle high-dimensional vectors?

pgvector has dimension limits that vary by data type. These limits are generous for current embedding models but may be a consideration for future or custom embeddings.

Dimension limits by type
TypeMax dimensionsNotes
vector(n)16,000Standard; covers OpenAI (1536/3072), most models
halfvec(n)16,000Half-precision; same dim limit, half storage
bit(n)64,000Binary quantised; very high dimension support
sparsevec(n)1,000,000Sparse vectors; only non-zero values stored
-- Check current dimension limit at runtime:
SELECT current_setting('vector.max_dimensions');

-- If you need > 16,000 dimensions (rare in practice):
-- Option 1: Use sparsevec if vector is sparse
CREATE TABLE items (
    id        BIGSERIAL PRIMARY KEY,
    embedding SPARSEVEC(100000)  -- up to 1M dims
);

-- Option 2: Dimensionality reduction before storage
-- Use PCA or UMAP to reduce from e.g. 65536-dim to 2048-dim
-- then store as vector(2048)

-- Option 3: Split vector across columns (workaround, ugly)
-- Not recommended but possible for extreme edge cases

-- Real-world dimension reference:
-- text-embedding-3-small: 1,536 dims (well within limit)
-- text-embedding-3-large: 3,072 dims (well within limit)
-- CLIP image embeddings: 512-768 dims (well within limit)
-- Custom deep models: may go up to 4096 dims
-- All well within the 16,000 dim limit for vector(n)

What is the maximum number of dimensions supported by the standard vector(n) type in pgvector?
Which pgvector type would you use to efficiently store vectors where most dimensions are zero?

20. How do you use pgvector with LangChain for building AI applications?

LangChain provides a PGVector vector store implementation that wraps pgvector, making it easy to use pgvector as the backend for LangChain-based RAG applications, agents, and chatbots.

# pip install langchain langchain-postgres langchain-openai

from langchain_postgres import PGVector
from langchain_openai import OpenAIEmbeddings
from langchain_core.documents import Document

# Connection string:
CONNECTION = "postgresql+psycopg://user:pass@localhost/mydb"

# Initialise the vector store (creates table and extension if needed)
vectorstore = PGVector(
    connection=CONNECTION,
    collection_name="documents",
    embeddings=OpenAIEmbeddings(model="text-embedding-3-small"),
    use_jsonb=True,           # store metadata in JSONB column
)

# Add documents
docs = [
    Document(page_content="pgvector enables vector search in PostgreSQL",
             metadata={"source": "docs", "category": "database"}),
    Document(page_content="HNSW is the recommended index for most use cases",
             metadata={"source": "docs", "category": "indexing"}),
]
vectorstore.add_documents(docs)

# Similarity search:
results = vectorstore.similarity_search(
    "What index should I use for fast search?",
    k=3,
)
for doc in results:
    print(doc.page_content, doc.metadata)

# Search with score:
results_with_score = vectorstore.similarity_search_with_score(
    "fast nearest neighbour search",
    k=3,
)
for doc, score in results_with_score:
    print(f"score={score:.4f}: {doc.page_content}")

# Use as a LangChain retriever:
retriever = vectorstore.as_retriever(search_kwargs={"k": 5})

What Python package provides the LangChain integration for pgvector?
What does use_jsonb=True do in the LangChain PGVector initialisation?

21. How does pgvector handle NULL values in vector columns?

pgvector follows standard PostgreSQL NULL semantics. Vector columns can contain NULL values, and NULL vectors are excluded from distance calculations and index scans. This is useful for records where an embedding has not yet been generated.

-- Create table allowing NULLs (default behaviour)
CREATE TABLE documents (
    id        BIGSERIAL PRIMARY KEY,
    content   TEXT,
    embedding VECTOR(1536)   -- nullable by default
);

-- Insert without embedding (embedding is NULL)
INSERT INTO documents (content) VALUES ('Article not yet embedded');

-- NULL rows are automatically excluded from similarity queries
-- (they cannot have a meaningful distance)
SELECT id, content FROM documents
ORDER BY embedding <-> '[0.1,0.2,...]'  -- NULLs won't appear in results
LIMIT 5;

-- Find rows missing embeddings (need to be processed):
SELECT id, content FROM documents WHERE embedding IS NULL;

-- Count unembedded documents:
SELECT COUNT(*) FROM documents WHERE embedding IS NULL;

-- Prevent NULLs if all rows must have embeddings:
CREATE TABLE documents_required (
    id        BIGSERIAL PRIMARY KEY,
    content   TEXT,
    embedding VECTOR(1536) NOT NULL  -- enforce non-null
);

-- Partial index to cover only non-NULL rows
-- (useful for variable-dimension or partially-embedded tables):
CREATE INDEX ON documents USING hnsw (embedding vector_cosine_ops)
WHERE embedding IS NOT NULL;

How does pgvector handle rows where the embedding column contains NULL in a similarity search query?
What SQL pattern identifies documents that have not yet had their embeddings generated?

22. What are partial indexes in pgvector and when should you use them?

A partial index is a pgvector (or PostgreSQL) index that covers only a subset of rows, defined by a WHERE clause at index creation. This is useful for filtering common values efficiently or indexing only rows that have embeddings.

-- Partial index: only index documents in a specific category
CREATE INDEX ON documents USING hnsw (embedding vector_cosine_ops)
WHERE category = 'technical';  -- index only 'technical' rows

-- Queries using the same filter benefit from the smaller index:
SELECT id, content FROM documents
WHERE category = 'technical'                  -- matches the partial index
ORDER BY embedding <=> '[...]' LIMIT 5;      -- uses the partial index
-- Much faster: index is smaller and focused

-- Partial index for published articles only:
CREATE INDEX ON articles USING hnsw (embedding vector_cosine_ops)
WHERE is_published = TRUE;

-- Partial index to exclude NULL embeddings:
CREATE INDEX ON documents USING hnsw (embedding vector_cosine_ops)
WHERE embedding IS NOT NULL;
-- Useful if some rows have no embedding yet

-- Multiple partial indexes for different categories:
CREATE INDEX idx_tech  ON docs USING hnsw (embedding vector_cosine_ops) WHERE category = 'tech';
CREATE INDEX idx_legal ON docs USING hnsw (embedding vector_cosine_ops) WHERE category = 'legal';
-- Each index is smaller and faster for its category

-- PostgreSQL uses a partial index only when the query WHERE clause
-- includes the index's WHERE condition

What is the key performance benefit of a partial index in pgvector?
For a pgvector partial index with WHERE category = 'technical' to be used, what must the query include?

23. How do you perform vector arithmetic and other vector functions in pgvector?

pgvector exposes several SQL functions and operators for vector arithmetic - useful for computing centroids, adding noise, normalising vectors, or combining them mathematically before storage or search.

-- Vector arithmetic operators:
-- + : element-wise addition
-- - : element-wise subtraction
-- * : element-wise multiplication

SELECT '[1,2,3]'::vector + '[4,5,6]'::vector;  -- [5,7,9]
SELECT '[4,5,6]'::vector - '[1,2,3]'::vector;  -- [3,3,3]
SELECT '[1,2,3]'::vector * '[2,2,2]'::vector;  -- [2,4,6]

-- Compute the L2 norm (magnitude) of a vector:
SELECT l2_norm('[1,2,2]'::vector);  -- sqrt(1+4+4) = 3.0

-- Normalise a vector (make unit length):
SELECT l2_normalize('[1,2,2]'::vector);
-- Result: [0.333, 0.667, 0.667] (each / 3.0)

-- Compute the average (centroid) of multiple vectors:
SELECT avg(embedding) FROM documents WHERE category = 'technical';
-- Returns the centroid vector of all 'technical' embeddings
-- Useful for finding the 'centre' of a cluster

-- Compute the sum of all vectors:
SELECT sum(embedding) FROM documents;

-- Round-trip: store normalised version of an embedding
UPDATE documents
SET embedding = l2_normalize(embedding)
WHERE id = 1;

-- Convert between types:
SELECT embedding::halfvec(1536) FROM documents WHERE id = 1;
SELECT embedding::vector(1536)  FROM documents WHERE id = 1;

What does the l2_normalize() function return for a vector?
What does SELECT avg(embedding) FROM documents return in pgvector?

24. How does pgvector compare to dedicated vector databases like Pinecone, Weaviate, and Qdrant?

pgvector vs dedicated vector databases is one of the most common architectural decisions for AI applications. The right choice depends on scale, existing infrastructure, and feature requirements.

pgvector vs dedicated vector databases
FactorpgvectorDedicated (Pinecone/Weaviate/Qdrant)
Data co-locationSame DB as relational data - easy JOINsSeparate system - sync required
ACID transactionsFull PostgreSQL ACIDVaries; often eventual consistency
Operational complexityOne system to operateAdditional system to manage and scale
Performance at scaleGood; ~100M vectors feasible with tuningOptimised for billions of vectors
FilteringNative SQL WHERE clausesSpecialised metadata filtering APIs
Recall tuningef_search, probes parametersManaged, less exposed to user
CostPostgreSQL hosting cost onlyAdditional vector DB subscription
MaturityPostgreSQL is battle-testedNewer; some have production track record
Multi-tenancySchema/RLS-basedOften built-in
Hybrid searchBM25 + vector via pg_bm25/ParadeDBOften built-in

Decision guide:

  • Use pgvector when you already use PostgreSQL, need transactional consistency, have <100M vectors, and want to minimise infrastructure complexity
  • Use a dedicated vector database when you have hundreds of millions or billions of vectors, need managed scaling, or require specialised features not available in pgvector
What is the primary architectural advantage of pgvector over a dedicated vector database like Pinecone?
At what approximate scale do teams typically start considering dedicated vector databases over pgvector?

25. What is the difference between L1 and L2 distance in pgvector?

pgvector supports both L1 (Manhattan) distance and L2 (Euclidean) distance. They measure different things and have different properties that make each better suited for certain data types.

L1 vs L2 distance comparison
PropertyL1 (Manhattan) <+>L2 (Euclidean) <->
Formulasum(|a_i - b_i|)sqrt(sum((a_i - b_i)^2))
Visual metaphorCity block distance (grid navigation)Straight-line distance
Sensitivity to outliersLess sensitive (linear)More sensitive (squares differences)
High-dimensional behaviourMore robust in high dimensionsCurse of dimensionality is stronger
Typical useSparse vectors; robust comparisonsGeneral purpose; most common default
pgvector operator<+><->
Index operator classvector_l1_opsvector_l2_ops
-- L2 distance (most common):
SELECT id, embedding <-> '[1,2,3]' AS l2_dist
FROM items ORDER BY l2_dist LIMIT 5;

-- L1 (Manhattan) distance:
SELECT id, embedding <+> '[1,2,3]' AS l1_dist
FROM items ORDER BY l1_dist LIMIT 5;

-- Comparison on same data:
SELECT
    id,
    embedding <-> '[1,2,3]' AS l2_distance,
    embedding <+> '[1,2,3]' AS l1_distance,
    embedding <=> '[1,2,3]' AS cosine_distance
FROM items
ORDER BY l2_distance
LIMIT 10;

-- L1 index:
CREATE INDEX ON items USING hnsw (embedding vector_l1_ops);

-- Quick rule of thumb:
-- Text embeddings: cosine distance <=> (magnitude-independent)
-- General purpose: L2 <->
-- Sparse or outlier-prone data: L1 <+>

Why is L1 (Manhattan) distance considered more robust to outliers than L2 (Euclidean) distance?
For text embedding similarity search, which distance metric is typically recommended and why?

26. How do you store and query vector embeddings with pgvector in a real schema design?

A well-designed schema stores embeddings close to the source data they represent, includes metadata for filtering, and separates concerns cleanly. Here are common production schema patterns.

-- Pattern 1: Embedding column on the same table as the content
CREATE TABLE articles (
    id           BIGSERIAL   PRIMARY KEY,
    title        TEXT        NOT NULL,
    body         TEXT        NOT NULL,
    author_id    BIGINT      REFERENCES users(id),
    category     TEXT,
    published_at TIMESTAMPTZ,
    is_published BOOLEAN     DEFAULT FALSE,
    embedding    VECTOR(1536)                    -- text-embedding-3-small
);

-- Pattern 2: Separate embeddings table (useful when embedding
-- the same content with multiple models or at multiple granularities)
CREATE TABLE article_chunks (
    id           BIGSERIAL   PRIMARY KEY,
    article_id   BIGINT      REFERENCES articles(id) ON DELETE CASCADE,
    chunk_index  INTEGER,    -- which chunk within the article
    chunk_text   TEXT,
    embedding    VECTOR(1536),
    model_name   TEXT        DEFAULT 'text-embedding-3-small'
);

-- Pattern 3: Multi-modal embeddings (different models on different columns)
CREATE TABLE products (
    id              BIGSERIAL   PRIMARY KEY,
    name            TEXT,
    description     TEXT,
    image_url       TEXT,
    text_embedding  VECTOR(1536),   -- text-embedding-3-small on description
    image_embedding VECTOR(512)     -- CLIP on product image
);

-- Indexes on each:
CREATE INDEX ON articles        USING hnsw (embedding vector_cosine_ops);
CREATE INDEX ON article_chunks  USING hnsw (embedding vector_cosine_ops);
CREATE INDEX ON products        USING hnsw (text_embedding  vector_cosine_ops);
CREATE INDEX ON products        USING hnsw (image_embedding vector_cosine_ops);

Why might you use a separate 'article_chunks' table instead of storing the embedding directly on the articles table?
What SQL clause should you add to article_chunks when the referenced article is deleted?

27. What is halfvec and when should you use it to reduce storage costs?

halfvec is a pgvector data type that stores each vector dimension as a 16-bit (half-precision) float instead of the standard 32-bit float. This halves storage requirements at the cost of a small precision reduction.

-- Standard vector: 4 bytes per dimension
-- halfvec:           2 bytes per dimension (50% storage reduction)

-- Create table with halfvec column:
CREATE TABLE documents_compact (
    id        BIGSERIAL   PRIMARY KEY,
    content   TEXT,
    embedding HALFVEC(1536)
);

-- For 1M documents at 1536 dimensions:
-- vector(1536):   1,000,000 * 1536 * 4 bytes = ~6 GB
-- halfvec(1536):  1,000,000 * 1536 * 2 bytes = ~3 GB
-- Saving: ~3 GB per million documents

-- Insert (same syntax as vector):
INSERT INTO documents_compact (content, embedding)
VALUES ('Example', '[0.1, 0.2, 0.3, ...]');
-- Values are automatically rounded to 16-bit precision

-- HNSW index for halfvec:
CREATE INDEX ON documents_compact
    USING hnsw (embedding halfvec_cosine_ops);  -- note: halfvec_cosine_ops

-- Convert from vector to halfvec:
SELECT embedding::halfvec(1536) FROM documents WHERE id = 1;

-- When to use halfvec:
-- Storage cost is significant (millions of documents, high dimensions)
-- Slight precision loss is acceptable (typically negligible for text)
-- When to stick with vector:
-- Highest accuracy required (scientific data, financial embeddings)
-- Precision is critical

halfvec operator classes
Operator classDistance
halfvec_l2_opsL2 distance <->
halfvec_cosine_opsCosine distance <=>
halfvec_ip_opsInner product <#>
halfvec_l1_opsL1 distance <+>
What is the storage difference between vector(1536) and halfvec(1536)?
Which operator class should you use for a halfvec column with cosine distance queries?

28. How do you handle vector dimensionality mismatches in pgvector?

pgvector enforces dimension consistency within typed columns - you cannot insert a 1024-dimensional vector into a VECTOR(1536) column. Understanding how to handle this prevents common insertion and query errors.

-- FIXED-dimension column (recommended when all vectors have same size)
CREATE TABLE docs (embedding VECTOR(1536));  -- enforces exactly 1536 dims

-- ERROR: dimension mismatch
INSERT INTO docs (embedding) VALUES ('[1,2,3]');   -- ERROR: expected 1536 dims
INSERT INTO docs (embedding) VALUES (ARRAY_FILL(0.0::float4, ARRAY[1024])::vector);
-- ERROR: expected 1536 dimensions, got 1024

-- VARIABLE-dimension column (no constraint)
CREATE TABLE docs_flex (embedding VECTOR);   -- accepts any dimension
INSERT INTO docs_flex VALUES ('[1,2,3]');     -- OK: 3 dims
INSERT INTO docs_flex VALUES ('[1,2,3,4,5]');-- OK: 5 dims

-- CAVEAT: you cannot create a standard index on variable-dim columns!
-- Because all rows in the index must have the same dimensionality
-- Use a PARTIAL INDEX or EXPRESSION INDEX on a specific dimension:
CREATE INDEX ON docs_flex USING hnsw ((embedding::vector(1536)) vector_cosine_ops)
WHERE vector_dims(embedding) = 1536;

-- Check dimensions of a stored vector:
SELECT vector_dims(embedding) FROM docs_flex LIMIT 5;

-- Find rows with unexpected dimension count:
SELECT id, vector_dims(embedding) AS dims
FROM docs_flex
WHERE vector_dims(embedding) != 1536;

What happens when you try to insert a 1024-dimensional vector into a VECTOR(1536) column?
What is the limitation of a variable-dimension VECTOR column (without a dimension constraint)?

29. How do you use pgvector with Django?

The pgvector Python package includes a Django integration that provides a VectorField model field, enabling vector storage and similarity search within Django ORM queries.

# pip install pgvector django psycopg2-binary

# settings.py - make sure django uses PostgreSQL:
DATABASES = {
    "default": {
        "ENGINE": "django.db.backends.postgresql",
        "NAME": "mydb",
        "USER": "user",
        "PASSWORD": "pass",
        "HOST": "localhost",
    }
}

# models.py
from django.db import models
from pgvector.django import VectorField, HnswIndex

class Document(models.Model):
    content   = models.TextField()
    embedding = VectorField(dimensions=1536)  # pgvector field

    class Meta:
        indexes = [
            HnswIndex(
                name="document_embedding_hnsw",
                fields=["embedding"],
                m=16,
                ef_construction=64,
                opclasses=["vector_cosine_ops"],
            )
        ]

# Migration: generates CREATE EXTENSION vector + table
# python manage.py makemigrations && python manage.py migrate

# views.py / management commands:
from pgvector.django import CosineDistance

# Insert
Document.objects.create(content="Example", embedding=[0.1, 0.2, ...])

# Query: 5 most similar documents
query_vector = get_embedding("search query")
results = Document.objects.annotate(
    distance=CosineDistance("embedding", query_vector)
).order_by("distance")[:5]

for doc in results:
    print(doc.content, doc.distance)

What Django model field type does the pgvector package provide for storing vector embeddings?
How do you specify the HNSW index in a Django model using pgvector?

30. What are common performance tuning techniques for pgvector at scale?

As vector tables grow to millions of rows, several tuning techniques help maintain good query performance and manageable index build times.

Performance tuning checklist
TechniqueWhen to applyHow
HNSW index> ~100k rows or when speed neededCREATE INDEX USING hnsw with appropriate ops class
ef_search tuningRecall too low after adding filterSET hnsw.ef_search = 100 (or higher)
Partial indexesHeavy filtering on specific valueCREATE INDEX ... WHERE category = 'X'
halfvec typeStorage cost is a concernUse HALFVEC(n) instead of VECTOR(n)
Parallel index buildSlow index creationSET max_parallel_maintenance_workers = CPU_CORES
maintenance_work_memSlow HNSW buildSET maintenance_work_mem = '4GB'
EXPLAIN ANALYZEDiagnose slow queriesEXPLAIN ANALYZE SELECT ...
Context cachingSame filter repeatedPartial index on that filter value
-- Full performance-tuned setup for large tables:

-- 1. Create table with efficient type
CREATE TABLE items (
    id        BIGSERIAL PRIMARY KEY,
    content   TEXT,
    category  TEXT,
    embedding HALFVEC(1536)   -- 2x storage savings
);

-- 2. Add composite B-tree index for filter columns:
CREATE INDEX ON items (category);   -- speeds up WHERE category = '...'

-- 3. Configure for fast HNSW build:
SET maintenance_work_mem = '4GB';
SET max_parallel_maintenance_workers = 7;

-- 4. Build HNSW index AFTER bulk loading data:
CREATE INDEX ON items USING hnsw (embedding halfvec_cosine_ops)
WITH (m = 16, ef_construction = 64);

-- 5. At query time, tune recall if needed:
SET hnsw.ef_search = 100;  -- increase if recall is insufficient

-- 6. Check query plan:
EXPLAIN (ANALYZE, BUFFERS)
SELECT id, content FROM items
WHERE category = 'tech'
ORDER BY embedding <=> '[...]'::halfvec
LIMIT 10;
-- Verify: 'Index Scan using items_embedding_idx' appears

What maintenance_work_mem setting is recommended for faster HNSW index builds on large tables?
How do you verify that a pgvector query is using an HNSW index rather than a sequential scan?

31. How do you implement semantic search with pgvector and a similarity threshold?

Returning only results above a minimum similarity threshold prevents surfacing irrelevant results when no truly similar documents exist. This is preferable to always returning the top-k regardless of quality.

-- Return results only within a distance threshold
-- (distance < threshold means similar enough)

-- Cosine distance threshold (0 = identical, 1 = orthogonal)
SELECT id, content,
       1 - (embedding <=> '[...]') AS similarity,
       embedding <=> '[...]' AS distance
FROM documents
WHERE embedding <=> '[...]' < 0.3    -- only results within distance 0.3
ORDER BY distance
LIMIT 20;

-- L2 distance threshold (depends on your vector magnitude):
SELECT id, content, embedding <-> '[...]' AS distance
FROM documents
WHERE embedding <-> '[...]' < 1.5    -- only close vectors
ORDER BY distance
LIMIT 10;

-- Dynamic threshold: always return at least 1 result,
-- but enforce threshold if more than 1 exists
WITH ranked AS (
    SELECT id, content,
           embedding <=> '[...]' AS dist,
           ROW_NUMBER() OVER (ORDER BY embedding <=> '[...]') AS rn
    FROM documents
)
SELECT id, content, dist
FROM ranked
WHERE dist < 0.3 OR rn = 1  -- always return top result
ORDER BY dist
LIMIT 10;

-- Typical cosine distance thresholds (vary by model and use case):
-- 0.0 - 0.15: very similar (nearly duplicate content)
-- 0.15 - 0.30: similar (same topic, different wording)
-- 0.30 - 0.50: somewhat related
-- > 0.50: likely unrelated (for most text embedding models)

Why use a distance threshold in pgvector queries rather than always returning LIMIT 5 results?
For text embeddings using cosine distance, what does a distance value of 0.05 indicate?

32. How do you use pgvector with asyncpg or asyncio in Python?

Modern Python web frameworks (FastAPI, Starlette, aiohttp) use async I/O. pgvector works with asyncpg (the high-performance async PostgreSQL driver) using the pgvector codec registration.

# pip install asyncpg pgvector

import asyncio
import asyncpg
from pgvector.asyncpg import register_vector
from openai import AsyncOpenAI

async def main():
    # Connect with asyncpg
    conn = await asyncpg.connect(
        "postgresql://user:pass@localhost/mydb"
    )
    # Register the vector codec (required for asyncpg)
    await register_vector(conn)

    # Create schema
    await conn.execute("""
        CREATE TABLE IF NOT EXISTS documents (
            id        BIGSERIAL PRIMARY KEY,
            content   TEXT,
            embedding VECTOR(1536)
        )
    """)

    # Generate and insert embedding
    client = AsyncOpenAI()
    text = "pgvector is a PostgreSQL extension"
    response = await client.embeddings.create(
        model="text-embedding-3-small", input=text
    )
    embedding = response.data[0].embedding

    await conn.execute(
        "INSERT INTO documents (content, embedding) VALUES ($1, $2)",
        text, embedding
    )

    # Query: async KNN search
    query_text = "vector search in PostgreSQL"
    q_emb = (await client.embeddings.create(
        model="text-embedding-3-small", input=query_text
    )).data[0].embedding

    rows = await conn.fetch(
        "SELECT id, content, embedding <=> $1 AS dist"
        " FROM documents ORDER BY dist LIMIT 5",
        q_emb
    )
    for row in rows:
        print(f"dist={row['dist']:.4f}: {row['content']}")

    await conn.close()

asyncio.run(main())

# For connection pooling with FastAPI:
# async with asyncpg.create_pool(dsn) as pool:
#     async with pool.acquire() as conn:
#         await register_vector(conn)
#         rows = await conn.fetch(...)

What function must you call after creating an asyncpg connection to enable pgvector type support?
What parameter placeholder syntax does asyncpg use for query parameters (unlike psycopg2's %s)?

33. What is vector quantisation and how does pgvector support binary quantisation?

Vector quantisation compresses full-precision vectors into more compact representations, trading some precision for dramatically reduced storage and faster distance computations. pgvector supports binary quantisation via the bit type.

-- Binary quantisation: convert float vectors to binary (0/1 per dimension)
-- Each dimension becomes 1 bit instead of 32 bits = 32x compression!

-- Store binary quantised vectors:
CREATE TABLE items_binary (
    id              BIGSERIAL   PRIMARY KEY,
    content         TEXT,
    embedding       VECTOR(1536),    -- keep original for reranking
    embedding_bin   BIT(1536)        -- binary quantised for fast coarse search
);

-- Quantise: positive values -> 1, negative/zero -> 0
UPDATE items_binary
SET embedding_bin = (
    SELECT string_agg(
        CASE WHEN val > 0 THEN '1' ELSE '0' END, ''
        ORDER BY ordinality
    )::bit(1536)
    FROM unnest(embedding::float4[]) WITH ORDINALITY AS t(val, ordinality)
);

-- Two-stage retrieval with binary quantisation:
-- Stage 1: Fast coarse search on binary vectors (Hamming distance)
CREATE INDEX ON items_binary USING hnsw (embedding_bin bit_hamming_ops);

WITH candidates AS (
    SELECT id, embedding_bin <~> '[...]'::bit(1536) AS hamming_dist
    FROM items_binary
    ORDER BY hamming_dist
    LIMIT 100   -- get 100 candidates quickly from binary index
)
-- Stage 2: Rerank top candidates using full-precision cosine distance
SELECT i.id, i.content,
       i.embedding <=> '[...]' AS cosine_dist
FROM items_binary i
JOIN candidates c ON i.id = c.id
ORDER BY cosine_dist
LIMIT 5;  -- return final top-5 after precise reranking

What is the storage compression ratio when using binary quantisation (bit type) vs standard float32 vectors (vector type)?
In a two-stage binary quantisation retrieval pipeline, what is the purpose of the second (reranking) stage?

34. How does pgvector integrate with managed PostgreSQL services?

pgvector is supported by all major managed PostgreSQL providers, though the setup process varies. This is one of pgvector's key practical advantages - you can enable vector search on your existing managed database without migrating to a new system.

Managed service support
Providerpgvector supportSetup methodNotes
SupabaseFull (purpose-built)CREATE EXTENSION vector;Free tier; vector-first platform
NeonFullCREATE EXTENSION vector;Serverless; auto-scaling; branch feature
Amazon RDSFullEnable in parameter group + CREATE EXTENSION vectorRDS Postgres 15+
Amazon AuroraFullCREATE EXTENSION vector;Aurora PostgreSQL 15.2+
Google AlloyDBFullCREATE EXTENSION vector;Higher performance than standard RDS
Azure Database for PostgreSQLFullAllowlist 'vector', then CREATE EXTENSIONFlexible Server
Heroku PostgresFullCREATE EXTENSION vector;Standard tier+
Google Cloud SQLFullCREATE EXTENSION vector;PostgreSQL 14+
AivenFullCREATE EXTENSION vector;Multi-cloud managed
-- Supabase example (Python):
from supabase import create_client

client = create_client(SUPABASE_URL, SUPABASE_KEY)

# pgvector is pre-installed; just use it:
client.rpc("match_documents", {
    "query_embedding": embedding,   # list of floats
    "match_threshold": 0.75,
    "match_count": 10
}).execute()

-- Neon example:
-- Same as regular PostgreSQL:
-- psql "postgresql://user:pass@ep-xxx.neon.tech/mydb?sslmode=require"
-- CREATE EXTENSION vector;
-- (works immediately - vector is pre-installed on Neon)

How do you enable pgvector on Supabase?
What additional step is required before running CREATE EXTENSION vector on Azure Database for PostgreSQL?

35. How do you use the inner product operator <#> with pgvector and when is it appropriate?

The <#> operator computes the negative inner product (dot product) between two vectors. It is most useful with normalised vectors (unit vectors where magnitude = 1), in which case it is mathematically equivalent to cosine similarity but computed faster.

-- <#> returns the NEGATIVE inner product
-- For unit vectors: inner_product = cosine_similarity
-- ORDER BY <#> ASC = most similar first (most negative = highest similarity)

-- Inner product similarity search:
SELECT id, content,
       -(embedding <#> '[0.1,0.2,0.3]') AS inner_product_similarity
FROM documents
ORDER BY embedding <#> '[0.1,0.2,0.3]'  -- ASC order = most similar
LIMIT 5;
-- NOTE: negate in SELECT for display, but use the raw expression in ORDER BY

-- Why negative? PostgreSQL indexes only support ASC scans
-- High inner product = more similar, but ASC would put low values first
-- Solution: return the NEGATIVE so ASC scan gives most similar first

-- When inner product == cosine similarity:
-- When all vectors are unit-normalised (L2 norm = 1.0)
-- OpenAI text-embedding-3-small/large outputs ARE unit-normalised
-- So for OpenAI embeddings, <#> and <=> produce equivalent RANKINGS

-- Verify your vectors are normalised:
SELECT id, l2_norm(embedding) AS norm FROM documents LIMIT 5;
-- Should return values very close to 1.0 for normalised vectors

-- HNSW index for inner product:
CREATE INDEX ON documents USING hnsw (embedding vector_ip_ops);

-- IVFFlat index for inner product:
CREATE INDEX ON documents USING ivfflat (embedding vector_ip_ops) WITH (lists = 100);

Why does pgvector return the NEGATIVE inner product with the <#> operator?
For which type of vectors is the inner product <#> equivalent to cosine similarity?

36. How do you combine pgvector with full-text search (hybrid keyword + semantic search)?

Combining vector semantic search with keyword full-text search (BM25/tsvector) produces better results than either alone. This hybrid search pattern handles both cases: queries that need exact keyword matches and queries that need semantic understanding.

-- Hybrid search: combine semantic similarity with full-text ranking
-- using Reciprocal Rank Fusion (RRF) to merge the two ranked lists

CREATE TABLE documents (
    id         BIGSERIAL PRIMARY KEY,
    content    TEXT NOT NULL,
    embedding  VECTOR(1536),
    content_ts TSVECTOR GENERATED ALWAYS AS
               (to_tsvector('english', content)) STORED  -- auto-updated FTS index
);
CREATE INDEX ON documents USING gin(content_ts);   -- FTS index
CREATE INDEX ON documents USING hnsw(embedding vector_cosine_ops);  -- vector index

-- Reciprocal Rank Fusion hybrid search:
WITH
semantic AS (
    SELECT id, ROW_NUMBER() OVER (ORDER BY embedding <=> '[...]') AS rank
    FROM documents
    ORDER BY embedding <=> '[...]'
    LIMIT 50
),
keyword AS (
    SELECT id, ROW_NUMBER() OVER (ORDER BY ts_rank(content_ts, query) DESC) AS rank,
           ts_rank(content_ts, query) AS ts_score
    FROM documents,
         to_tsquery('english', 'pgvector & PostgreSQL') AS query
    WHERE content_ts @@ query
    LIMIT 50
),
fused AS (
    SELECT
        COALESCE(s.id, k.id) AS id,
        COALESCE(1.0 / (60 + s.rank), 0) +
        COALESCE(1.0 / (60 + k.rank), 0) AS rrf_score
    FROM semantic s FULL OUTER JOIN keyword k USING (id)
)
SELECT d.id, d.content, f.rrf_score
FROM fused f JOIN documents d ON d.id = f.id
ORDER BY f.rrf_score DESC
LIMIT 10;

Why hybrid search outperforms either alone: semantic search handles paraphrasing and concept matching but can miss exact technical terms; keyword search is precise for exact terms but cannot understand synonyms. RRF merges both ranked lists without needing to tune a mixing weight parameter.

What does Reciprocal Rank Fusion (RRF) do in a hybrid search query?
What PostgreSQL column type is used for full-text search in the hybrid search schema?

37. What PostgreSQL configuration parameters affect pgvector performance?

Several PostgreSQL-level settings directly impact pgvector query and index performance. Tuning these appropriately for a vector workload can yield significant speedups.

Key PostgreSQL parameters for pgvector
ParameterDefaultRecommended (vector workload)Effect
maintenance_work_mem64MB2-8GBMemory for index builds - most impactful for HNSW build speed
max_parallel_maintenance_workers2CPU cores - 1Parallel workers for index build
work_mem4MB64-256MBMemory per query operation
effective_cache_size4GB~75% of RAMHelps query planner estimate index usage
shared_buffers128MB25% of RAMPostgreSQL shared memory cache
hnsw.ef_search4040-200 (based on recall needs)HNSW query-time recall/speed tradeoff
ivfflat.probes11-lists (based on recall needs)IVFFlat query-time recall/speed tradeoff
-- Permanently set in postgresql.conf or via ALTER SYSTEM:
ALTER SYSTEM SET maintenance_work_mem = '4GB';
ALTER SYSTEM SET max_parallel_maintenance_workers = 7;
ALTER SYSTEM SET work_mem = '128MB';
-- Apply config changes without full restart:
SELECT pg_reload_conf();

-- Set per-session (for index build or critical query):
SET maintenance_work_mem = '4GB';
SET max_parallel_maintenance_workers = 7;
CREATE INDEX ON items USING hnsw (embedding vector_cosine_ops);
RESET maintenance_work_mem;
RESET max_parallel_maintenance_workers;

-- Per-query tuning:
SET hnsw.ef_search = 100;   -- increase recall for this query
SELECT * FROM items ORDER BY embedding <-> '[...]' LIMIT 5;
RESET hnsw.ef_search;

-- Verify current settings:
SHOW maintenance_work_mem;
SHOW hnsw.ef_search;
SHOW ivfflat.probes;

Which PostgreSQL configuration parameter has the greatest impact on HNSW index build speed?
What is the effect of increasing work_mem for pgvector queries?

38. How do you implement recommendation systems using pgvector?

Recommendation systems find items similar to those a user has interacted with. pgvector is well-suited for this because item embeddings (trained on interaction data or content features) can be stored and queried with KNN search, with SQL filtering for business rules.

-- Schema for a product recommendation system
CREATE TABLE products (
    id          BIGSERIAL PRIMARY KEY,
    name        TEXT,
    category    TEXT,
    price       NUMERIC(10,2),
    in_stock    BOOLEAN DEFAULT TRUE,
    embedding   VECTOR(512)   -- content/collaborative filtering embedding
);

CREATE TABLE user_interactions (
    user_id     BIGINT,
    product_id  BIGINT REFERENCES products(id),
    interacted_at TIMESTAMPTZ DEFAULT NOW()
);

CREATE INDEX ON products USING hnsw (embedding vector_cosine_ops);

-- Find products similar to a specific product:
SELECT p.id, p.name, p.category,
       p.embedding <-> target.embedding AS distance
FROM products p,
     (SELECT embedding FROM products WHERE id = 42) AS target
WHERE p.id != 42
  AND p.in_stock = TRUE             -- business filter
  AND p.category = 'electronics'    -- category filter
ORDER BY distance
LIMIT 10;

-- 'Users who liked X also liked' - user-based recommendations:
-- 1. Find products a user interacted with
-- 2. Average their embeddings to get user preference vector
-- 3. Find products closest to that preference vector
WITH user_prefs AS (
    SELECT avg(p.embedding) AS preference_vector
    FROM user_interactions ui
    JOIN products p ON p.id = ui.product_id
    WHERE ui.user_id = 99
)
SELECT p.id, p.name,
       p.embedding <-> (SELECT preference_vector FROM user_prefs) AS distance
FROM products p
WHERE p.in_stock = TRUE
  AND p.id NOT IN (   -- exclude already-seen products
    SELECT product_id FROM user_interactions WHERE user_id = 99
  )
ORDER BY distance
LIMIT 10;

How does pgvector enable user preference-based recommendations?
What SQL function computes the average of multiple vector embeddings in pgvector?

39. How do you use EXPLAIN and EXPLAIN ANALYZE to debug pgvector queries?

EXPLAIN and EXPLAIN ANALYZE are essential for understanding whether pgvector queries are using indexes or falling back to slow sequential scans. Diagnosing this is often the first step in troubleshooting slow queries.

-- Basic EXPLAIN: shows the plan without running the query
EXPLAIN
SELECT id, content FROM documents
ORDER BY embedding <=> '[0.1,0.2,0.3]'
LIMIT 5;

-- Sample output (with HNSW index):
-- Limit  (cost=...)
--   ->  Index Scan using documents_embedding_idx on documents
--         Order By: (embedding <=> '[0.1,0.2,0.3]'::vector)
-- This shows the index IS being used.

-- Sample output (sequential scan - index not used):
-- Limit  (cost=...)
--   ->  Sort  (cost=...)
--         Sort Key: ((embedding <=> '[0.1,0.2,0.3]'::vector))
--         ->  Seq Scan on documents
-- This is slow - investigate why the index was not used!

-- EXPLAIN ANALYZE: actually runs the query and shows real timings
EXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT)
SELECT id FROM documents
WHERE category = 'tech'
ORDER BY embedding <=> '[...]' LIMIT 5;

-- Key metrics to look at in EXPLAIN ANALYZE:
-- 'Seq Scan' vs 'Index Scan' - index should be used for KNN
-- actual rows vs estimated rows - large discrepancy = stale stats
-- actual time - how long each node actually took
-- Buffers: hit=N miss=N - cache hit ratio

-- Force sequential scan to compare:
SET enable_indexscan = off;
EXPLAIN ANALYZE SELECT id FROM documents ORDER BY embedding <=> '[...]' LIMIT 5;
RESET enable_indexscan;

-- Update statistics if planner makes poor decisions:
ANALYZE documents;  -- refresh table statistics

In EXPLAIN output, what indicates that pgvector is using an HNSW index for the query?
What should you do if EXPLAIN shows a Seq Scan when you expected an Index Scan for a vector query?

40. What are best practices for a production pgvector deployment?

A checklist of best practices covers schema design, indexing, performance, operations, and application integration for reliable, performant pgvector deployments.

Production best practices checklist
AreaBest practice
SchemaStore embeddings in the same table as the content for easy JOINs; use ON DELETE CASCADE for chunk tables
Data typesUse halfvec instead of vector when storage matters; use sparsevec for sparse embeddings
IndexingCreate HNSW index after bulk loading data; use correct operator class matching your distance metric
Build tuningSet maintenance_work_mem high (2-8GB) and max_parallel_maintenance_workers before building indexes
Query recallTune hnsw.ef_search upward if recall is insufficient with metadata filters
FilteringUse partial indexes for frequently-filtered column values
Bulk loadLoad data first, then build indexes; use COPY for large imports
MonitoringMonitor index build with pg_stat_progress_create_index; check pg_indexes for existence
StatisticsRun ANALYZE after large data loads to keep query planner statistics fresh
ApplicationUse the pgvector package's register_vector() for proper Python type conversion
-- Production-ready setup summary:

-- 1. Enable extension
CREATE EXTENSION IF NOT EXISTS vector;

-- 2. Create schema with appropriate type
CREATE TABLE documents (
    id         BIGSERIAL   PRIMARY KEY,
    content    TEXT        NOT NULL,
    category   TEXT,
    created_at TIMESTAMPTZ DEFAULT NOW(),
    embedding  HALFVEC(1536)   -- halfvec for 2x storage savings
);

-- 3. Add metadata index for filtering
CREATE INDEX ON documents (category);

-- 4. Bulk load data (skip vector index during this phase)
-- COPY documents FROM '/data/docs.csv';

-- 5. Update statistics after load
ANALYZE documents;

-- 6. Build vector index with tuned memory/parallelism
SET maintenance_work_mem = '4GB';
SET max_parallel_maintenance_workers = 7;
CREATE INDEX ON documents
    USING hnsw (embedding halfvec_cosine_ops)
    WITH (m = 16, ef_construction = 64);
RESET maintenance_work_mem;
RESET max_parallel_maintenance_workers;

-- 7. Verify index is used:
EXPLAIN SELECT id FROM documents
ORDER BY embedding <=> '[...]'::halfvec LIMIT 5;

What is the recommended order of operations when loading a large dataset into a pgvector table?
Why should you run ANALYZE after a large data load into a pgvector table?
«
»

Comments & Discussions