Kartik Gautam

Available for hire

Engineer & Product Enthusiast

githublinkedintwitter


all blogs

PostgreSQL with pgvector: A Practical Guide to Vector Storage

6 min read

PostgreSQL with pgvector: A Practical Guide to Vector Storage

In the era of AI and machine learning, vector storage has become increasingly important. While specialized vector databases like Pinecone and Weaviate are gaining popularity, PostgreSQL with the pgvector extension offers a compelling alternative that combines familiarity with powerful vector capabilities.

Why Choose PostgreSQL for Vector Storage?

Cost and Complexity Benefits

  • Single Database: No need to manage multiple database systems
  • Familiar Tooling: Use existing PostgreSQL knowledge and tools
  • Lower Infrastructure Costs: One database to maintain and scale
  • Simplified Operations: Reduced operational complexity

Performance Considerations

  • Query Performance: Up to 100,000 vectors with sub-100ms latency
  • Indexing Options: Multiple index types for different use cases
  • Memory Efficiency: Optimized for both speed and resource usage
  • Scalability: Can handle millions of vectors with proper configuration

Getting Started with pgvector

Installation and Setup

-- Enable the extension CREATE EXTENSION vector; -- Create a table with vector support CREATE TABLE items ( id SERIAL PRIMARY KEY, name TEXT, description TEXT, embedding vector(1536) -- OpenAI embeddings dimension ); -- Create an index for similarity search CREATE INDEX items_embedding_idx ON items USING ivfflat (embedding vector_cosine_ops) WITH (lists = 100);

Basic Operations

-- Insert a vector INSERT INTO items (name, description, embedding) VALUES ( 'Product A', 'A great product', '[0.1, 0.2, 0.3, ...]'::vector ); -- Find similar items SELECT name, description, 1 - (embedding <=> '[0.1, 0.2, 0.3, ...]'::vector) as similarity FROM items ORDER BY embedding <=> '[0.1, 0.2, 0.3, ...]'::vector LIMIT 5;

Advanced Usage Patterns

1. Hybrid Search

-- Combine vector similarity with traditional filters SELECT name, description, 1 - (embedding <=> query_vector) as similarity FROM items WHERE category = 'electronics' AND price < 100 ORDER BY embedding <=> query_vector LIMIT 10;

2. Batch Operations

import psycopg2 import numpy as np def batch_insert_vectors(vectors, batch_size=1000): conn = psycopg2.connect("dbname=mydb") cur = conn.cursor() for i in range(0, len(vectors), batch_size): batch = vectors[i:i + batch_size] values = [(v.tolist(),) for v in batch] cur.executemany( "INSERT INTO items (embedding) VALUES (%s::vector)", values ) conn.commit() cur.close() conn.close()

3. Performance Optimization

-- Create a specialized index for your use case CREATE INDEX items_embedding_hnsw_idx ON items USING hnsw (embedding vector_cosine_ops) WITH (m = 16, ef_construction = 64); -- Analyze table for better query planning ANALYZE items; -- Set work_mem for better performance SET work_mem = '256MB';

Real-World Applications

1. Semantic Search

from sentence_transformers import SentenceTransformer import psycopg2 class SemanticSearch: def __init__(self): self.model = SentenceTransformer('all-MiniLM-L6-v2') self.conn = psycopg2.connect("dbname=mydb") def search(self, query, limit=5): # Generate embedding for query query_embedding = self.model.encode(query) # Search database cur = self.conn.cursor() cur.execute(""" SELECT name, description, 1 - (embedding <=> %s::vector) as similarity FROM items ORDER BY embedding <=> %s::vector LIMIT %s """, (query_embedding, query_embedding, limit)) return cur.fetchall()

2. Recommendation System

class ProductRecommender: def __init__(self): self.conn = psycopg2.connect("dbname=mydb") def get_recommendations(self, product_id, limit=5): cur = self.conn.cursor() # Get product embedding cur.execute(""" SELECT embedding FROM items WHERE id = %s """, (product_id,)) product_embedding = cur.fetchone()[0] # Find similar products cur.execute(""" SELECT id, name, description, 1 - (embedding <=> %s::vector) as similarity FROM items WHERE id != %s ORDER BY embedding <=> %s::vector LIMIT %s """, (product_embedding, product_id, product_embedding, limit)) return cur.fetchall()

Performance Tuning

1. Index Selection

  • IVFFlat: Good for approximate search with large datasets
  • HNSW: Better for high-dimensional vectors and faster queries
  • Exact: For small datasets or when accuracy is critical

2. Memory Configuration

-- Optimize for vector operations ALTER SYSTEM SET shared_buffers = '4GB'; ALTER SYSTEM SET work_mem = '256MB'; ALTER SYSTEM SET maintenance_work_mem = '1GB';

3. Query Optimization

-- Use EXPLAIN ANALYZE to understand query performance EXPLAIN ANALYZE SELECT name, description, 1 - (embedding <=> query_vector) as similarity FROM items ORDER BY embedding <=> query_vector LIMIT 5; -- Add appropriate indexes CREATE INDEX items_embedding_hnsw_idx ON items USING hnsw (embedding vector_cosine_ops) WITH (m = 16, ef_construction = 64);

Monitoring and Maintenance

1. Performance Metrics

-- Check index usage SELECT schemaname, tablename, indexname, idx_scan, idx_tup_read, idx_tup_fetch FROM pg_stat_user_indexes WHERE tablename = 'items'; -- Monitor table size SELECT pg_size_pretty(pg_total_relation_size('items'));

2. Regular Maintenance

-- Update statistics ANALYZE items; -- Reindex if needed REINDEX INDEX items_embedding_idx; -- Vacuum to reclaim space VACUUM ANALYZE items;

Best Practices

  1. Index Selection

    • Use IVFFlat for large datasets
    • Use HNSW for high-dimensional vectors
    • Consider exact search for small datasets
  2. Batch Operations

    • Use prepared statements
    • Implement proper error handling
    • Monitor memory usage
  3. Query Optimization

    • Use appropriate index types
    • Monitor query performance
    • Regular maintenance
  4. Resource Management

    • Monitor memory usage
    • Regular vacuum and analyze
    • Proper connection pooling

Conclusion

PostgreSQL with pgvector provides a powerful, cost-effective solution for vector storage needs. While specialized vector databases have their place, pgvector offers a compelling alternative that combines familiarity with powerful vector capabilities. The key to success lies in proper configuration, regular maintenance, and understanding your specific use case requirements.

"The best solution isn't always the newest one—sometimes it's the one that fits seamlessly into your existing infrastructure while meeting your needs effectively."