Steps to setup and use PGVector with PGSQL
1. Create the database
Create the database with the additional configuration, this example assumes the database is being created with terraform. So you would just append the hcl below to pgsql-flexible_server.tf matching the correct server id.
1 | resource "azurerm_postgresql_flexible_server_configuration" "cb-psql-extensions" { |
If you needed multiple extensions, list them comma-separated: value = "vector,pg_stat_statements,uuid-ossp" ect
2. Connect to the database and enable the plugin
1 | CREATE EXTENSION IF NOT EXISTS vector; |
3. Create a test table
1 | CREATE TABLE documents ( |
4. Persist data
you can also select data with
1 | SELECT '[0.1,0.2,0.3,...]'::vector; |
Distance metrics
Cosine similarity (recommended for LLM embeddings)
1 | SELECT |
Smaller distance = more similar.
You can also calculate a similarity score:
1 | SELECT |
This produces values roughly between 0 and 1.
Euclidean (L2) distance
1 | SELECT |
Again, smaller is better.
Dot Product / Inner Product
This one catches people out.
<#> returns the negative inner product because PostgreSQL indexes sort in ascending order.
1 | SELECT |
If you want the actual dot product:
1 | SELECT |
Creating indexes
You’ll get much better performance by matching the index type to the operator you use.
Cosine
1 | CREATE INDEX documents_embedding_cos_idx |
Euclidean
1 | CREATE INDEX documents_embedding_l2_idx |
Dot Product
1 | CREATE INDEX documents_embedding_ip_idx |
Which should you use?
For modern embedding models (such as OpenAI’s text-embedding-3-large, Gemini embeddings, Voyage AI, or most sentence-transformer models), the usual recommendation is:
Cosine (<=>): Best default choice for semantic search and Retrieval-Augmented Generation (RAG).
Dot product (<#>): Best if your embeddings are already normalized to unit length. It’s often slightly faster and mathematically equivalent to cosine on normalized vectors.
Euclidean (<->): Useful for embeddings where the magnitude of the vector carries meaning, but it’s less common for text retrieval.
For a chatbot or semantic search application, I’d recommend cosine distance with an HNSW index using vector_cosine_ops. It’s the most common configuration and works well across most embedding models without requiring you to worry about whether the vectors have been normalized.