Best Practices for PostgreSQL Vector Search
Last verified 13 Jul 2026
DigitalOcean Managed PostgreSQL for vector search uses the same managed PostgreSQL engine available under Managed Databases, with the pgvector and pgvectorscale extensions for storing and querying vector embeddings alongside relational data.
PostgreSQL vector search uses the pgvector extension to store embeddings and compare them by similarity. After you load embeddings into a table, choose a distance operator, index type, and tuning strategy based on your data size, query patterns, recall requirements, and latency goals.
These choices affect search quality, query speed, index build time, and memory usage.
Use this guide to choose between exact search, HNSW, IVFFlat, filters, partial indexes, reranking, and query-time tuning. For larger architectures that need pgvectorscale, disk-resident search, hybrid retrieval, RAG, or read scaling, see Best Practices for Advanced PostgreSQL Vector Workloads.
Use Vector Search for Semantic Matching
Use vector search when users search by meaning instead of exact words.
Vector search is a good fit for:
- Natural-language questions.
- Semantic document search.
- Recommendations.
- Similarity matching.
- Retrieval for RAG applications.
- Queries where users may describe the same idea with different words.
Vector search is less effective when queries depend on exact tokens, such as product codes, commands, filenames, IDs, or error messages. For those workloads, combine vector search with PostgreSQL full-text search using hybrid search.
Choose a Distance Operator
A distance operator controls how PostgreSQL compares a query vector to the vectors stored in your table.
Choose the operator that matches your embedding model, and use it consistently in your queries and indexes.
Common pgvector distance operators include:
| Distance Type | Operator | Use Case |
|---|---|---|
| Cosine distance | <=> |
Most text embedding models |
| Negative inner product | <#> |
Normalized vectors or models trained for inner product |
| L2 distance | <-> |
Euclidean distance, often for image or general embeddings |
| L1 distance | <+> |
Manhattan distance, less common |
Cosine distance is a good default for most text embedding workloads. It measures the angle between vectors, which works well when vector direction matters more than magnitude.
The distance operator, index operator class, and ORDER BY clause must match. For example, use <=> with vector_cosine_ops.
Choose the Simplest Search Pattern First
Start with the simplest search pattern that meets your recall, latency, and scale requirements.
PostgreSQL vector search generally fits into three standard pgvector patterns:
- Exact search: Scans matching rows and computes vector distance exactly. Use it for small datasets, small filtered candidate sets, or validation tests.
- HNSW search: Uses a graph index for fast approximate nearest-neighbor search. Use HNSW as the default for most small-to-medium production workloads.
- IVFFlat search: Uses list-based partitioning for faster index builds and lower memory usage when you can accept lower recall.
Avoid starting with the most complex option. Use exact search or HNSW first, measure performance, and move to IVFFlat only when build speed, ingest cost, or memory usage matters more than recall.
If standard pgvector indexes no longer meet your scale or memory requirements, evaluate pgvectorscale in Best Practices for Advanced PostgreSQL Vector Workloads.
Use Exact Search for Small Tables
Without a vector index, PostgreSQL performs an exact nearest-neighbor scan. It compares the query vector against every matching row, then returns the closest matches.
Exact search is simple and returns the most accurate results, but it gets slower as the table grows.
Use exact search when:
- Your table is small.
- Filters reduce the candidate set to a small number of rows.
- You need a baseline to compare approximate index results against.
- You are testing a new embedding model or ranking strategy.
For larger datasets, use an approximate nearest-neighbor index.
Use Approximate Indexes for Larger Tables
Approximate nearest-neighbor indexes trade a small amount of recall for faster search. In pgvector, the two main approximate index types are HNSW and IVFFlat.
Use an approximate index when exact scans are too slow or when your table is large enough that comparing every vector for every query becomes expensive.
Choose HNSW for Most Workloads
HNSW, or Hierarchical Navigable Small World, builds a graph of vectors. Queries traverse the graph to find nearby vectors instead of scanning the whole table.
Use HNSW for most PostgreSQL vector search workloads. It usually provides high recall and fast query performance, but it uses more memory and takes longer to build than IVFFlat.
HNSW is a good fit when:
- You need strong recall.
- You need low query latency.
- You can allocate enough memory for the index.
- You do not rebuild the index frequently.
HNSW does not require a training step, but for large imports, bulk-load your data before creating the index. Building the index after loading data is usually faster than maintaining the index during many individual inserts.
Use IVFFlat for Faster Builds or Lower Memory
IVFFlat partitions the vector space into lists. At query time, PostgreSQL searches only the most relevant lists.
Use IVFFlat when you need faster index builds or lower memory usage and can accept lower recall unless you increase query-time probes.
IVFFlat is a good fit when:
- You rebuild indexes often.
- You have high ingest volume.
- You want lower memory usage than HNSW.
- You can tune
listsandprobesfor your workload.
For IVFFlat, load data before creating the index. IVFFlat chooses list centroids from the data available when the index is built. If your data distribution changes significantly, rebuild the index.
Match Operator Classes to Distance Operators
When you create a vector index, choose the operator class that matches your distance operator:
- Use
<=>withvector_cosine_opsfor cosine distance. - Use
<#>withvector_ip_opsfor inner product distance. - Use
<->withvector_l2_opsfor L2 distance. - Use
<+>withvector_l1_opsfor L1 distance.
If the operator class and query operator do not match, PostgreSQL may not use the vector index.
Tune HNSW Parameters
HNSW has index-time and query-time parameters that control recall, latency, memory usage, and build time.
In general, tune HNSW parameters based on the tradeoff you want to make:
- Increase
mwhen you need better graph connectivity and can accept higher memory usage and slower indexing. - Increase
ef_constructionwhen you need a higher-quality graph and can accept slower index builds. - Increase
ef_searchwhen you need higher recall and can accept higher query latency.
Start with the default values, then test with representative queries. Increase one parameter at a time so you can measure which change improves results.
Tune IVFFlat Parameters
IVFFlat uses lists at index build time and probes at query time.
In general, tune IVFFlat parameters based on the tradeoff you want to make:
- Increase
listswhen you have more rows and want finer partitions. - Increase
probeswhen you need higher recall and can accept higher query latency.
A common starting point for lists is rows / 1000 for fewer than one million rows, or sqrt(rows) for one million rows or more. A common starting point for probes is sqrt(lists).
Test these values with your own data. The best settings depend on vector dimensions, table size, filters, and acceptable latency.
Combine Vector Search with SQL Filters
One advantage of PostgreSQL vector search is that you can combine similarity search with ordinary SQL filters.
Use filters for fields like tenant ID, document type, creation date, language, status, or access permissions. For example, a query can search for the nearest vectors within one tenant or within recently created documents.
Add regular PostgreSQL indexes, such as B-tree or partial indexes, for columns you filter on frequently. This helps PostgreSQL narrow the candidate set before or during vector search.
Use Partial Indexes for Hot Segments
Use partial vector indexes when a specific tenant, category, or segment receives heavy search traffic.
A partial index only includes rows that match a condition. This can make the index smaller, faster to search, and cheaper to maintain.
Partial indexes are useful for multi-tenant workloads where one tenant is much larger or more active than others.
Rerank for Higher Precision
For higher precision, retrieve a larger candidate set first, then rerank those candidates with another scoring method.
This is useful when approximate search is fast enough for candidate retrieval but you need more precise final ordering. For example, you can retrieve the top 50 candidates with vector search, then rerank the final 10 with a different distance operator, a cross-encoder, business logic, or access-control rules.
Maintain Vector Indexes
Keep PostgreSQL statistics and vector indexes current as your data changes.
Run ANALYZE after large inserts so the query planner has current table statistics. Rebuild indexes if you change embedding models, change vector dimensions, or see degraded recall after major data distribution changes.
Vector indexes can use significant memory, especially HNSW indexes. If query latency increases because the index no longer fits comfortably in memory, resize the cluster or consider a lower-dimensional embedding model.
Troubleshoot Slow or Low-Quality Results
If vector search is slow or results look incorrect, check the common causes first:
- Confirm the vector index is being used with
EXPLAIN (ANALYZE, BUFFERS). - Make sure the distance operator matches the index operator class.
- Make sure the query uses
ORDER BY embedding <operator> $1with aLIMIT. - Increase
hnsw.ef_searchorivfflat.probesif recall is too low. - Check memory pressure if queries become slow under load.
- Rebuild the index if the embedding model or data distribution changed.