{"id":119462,"date":"2026-07-06T13:43:33","date_gmt":"2026-07-06T08:13:33","guid":{"rendered":"https:\/\/www.guvi.in\/blog\/?p=119462"},"modified":"2026-07-06T13:43:35","modified_gmt":"2026-07-06T08:13:35","slug":"pgvector-tutorial-vector-search-inside-postgresql","status":"publish","type":"post","link":"https:\/\/www.guvi.in\/blog\/pgvector-tutorial-vector-search-inside-postgresql\/","title":{"rendered":"pgvector Tutorial: Vector Search Inside PostgreSQL"},"content":{"rendered":"\n<p>Most vector database tutorials tell you to spin up an entirely new database just for embeddings. If you already run PostgreSQL in production, there is a much simpler path. This pgvector tutorial shows you how to add vector similarity search directly to the database you already have, with no new infrastructure, no new connection string, and no data sync between systems. pgvector brings semantic search inside PostgreSQL where it belongs.&nbsp;<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>TL;DR Summary<\/strong><\/h2>\n\n\n\n<ul>\n<li>pgvector is a free, open-source PostgreSQL extension that adds vector similarity search directly to your existing database.<\/li>\n\n\n\n<li>If you already use PostgreSQL, pgvector means you do not need a separate vector database at all.<\/li>\n\n\n\n<li>Install it with a single Docker image, enable with <strong>CREATE EXTENSION vector<\/strong>, and you are ready.<\/li>\n\n\n\n<li>pgvector supports cosine distance (<strong>&lt;=&gt;<\/strong>), L2 distance (<strong>&lt;-&gt;<\/strong>), and inner product (<strong>&lt;#&gt;<\/strong>) with HNSW and IVFFlat indexing.<\/li>\n\n\n\n<li>It is used in RAG pipelines, semantic search, and recommendation systems at Supabase, Azure, and AWS RDS.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>What is pgvector?<\/strong><\/h2>\n\n\n\n<p>pgvector is an open-source PostgreSQL extension that adds a <strong>vector<\/strong> data type and similarity search operators to standard PostgreSQL. It enables the storage, querying, and indexing of high-dimensional vectors directly in your existing database.<\/p>\n\n\n\n<p>The big advantage over dedicated vector databases is that your vectors and your application data live in the same table. You can join them, filter with WHERE clauses, and keep them in sync inside a single transaction with no separate write path.<\/p>\n\n\n\n<p>pgvector is one of the most practical ways to add AI-powered search to an existing product without adding new infrastructure. HCL\u00a0 GUVI&#8217;s<a href=\"https:\/\/www.guvi.in\/zen-class\/ai-software-development-course\/?utm_source=blog&amp;utm_medium=hyperlink&amp;utm_campaign=pgvector-tutorial\" target=\"_blank\" rel=\"noreferrer noopener\"> AI Software Development Course<\/a> is IITM Pravartak certified and covers the hands-on AI engineering skills that make integrating tools like pgvector into real applications straightforward.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>pgvector Tutorial: Installation<\/strong><\/h2>\n\n\n\n<p>The fastest way to get PostgreSQL with pgvector is to use the official Docker image, which ships with the extension pre-installed:<\/p>\n\n\n\n<p><strong>docker run &#8211;name pgvector-db -e POSTGRES_PASSWORD=secret -p 5432:5432 -d pgvector\/pgvector:pg17<\/strong><\/p>\n\n\n\n<p>This pulls a PostgreSQL 17 image with pgvector v0.8.3 already compiled and ready. No build step required.<\/p>\n\n\n\n<p>Install the Python packages you need:<\/p>\n\n\n\n<p><strong>pip install psycopg pgvector openai<\/strong><\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Enabling pgvector in Your Database<\/strong><\/h2>\n\n\n\n<p>Connect to your database and run:<\/p>\n\n\n\n<p><strong>Sql<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>CREATE EXTENSION IF NOT EXISTS vector;<\/code><\/pre>\n\n\n\n<p>That is the entire installation step at the database level. From this point, you can use the <strong>vector<\/strong> type in any table definition.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Creating a Table with a Vector Column<\/strong><\/h2>\n\n\n\n<p><strong>Sql<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>CREATE TABLE documents (\n    id BIGSERIAL PRIMARY KEY,\n    content TEXT NOT NULL,\n    embedding vector(1536)\n);\n<\/code><\/pre>\n\n\n\n<p>The number inside <strong>vector(1536)<\/strong> must match the dimensions of your embedding model. For OpenAI&#8217;s <strong>text-embedding-3-small<\/strong>, that is 1536. For sentence-transformers MiniLM, it is 384. Choose your embedding model before writing any schema, because the vector dimension is baked into the column definition and changing it later means re-embedding your entire dataset.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Inserting Vectors with Python<\/strong><\/h2>\n\n\n\n<p><strong>Python<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import psycopg\nfrom pgvector.psycopg import register_vector\nfrom openai import OpenAI\n\nopenai_client = OpenAI()\nconn = psycopg.connect(\"postgresql:\/\/postgres:secret@localhost:5432\/postgres\")\nregister_vector(conn)\n\ndef get_embedding(text):\n    return openai_client.embeddings.create(\n        model=\"text-embedding-3-small\", input=text\n    ).data&#91;0].embedding\n\nwith conn.cursor() as cur:\n    content = \"pgvector adds vector search to PostgreSQL\"\n    embedding = get_embedding(content)\n    cur.execute(\n        \"INSERT INTO documents (content, embedding) VALUES (%s, %s)\",\n        (content, embedding)\n    )\nconn.commit()\n<\/code><\/pre>\n\n\n\n<p>Calling <strong>register_vector(conn)<\/strong> tells psycopg how to handle the pgvector type automatically. Without it, you get type errors on insert.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Running a Similarity Search<\/strong><\/h2>\n\n\n\n<p>The <strong>&lt;=&gt;<\/strong> operator calculates cosine distance. Ordering by it ascending returns the most similar documents first.<\/p>\n\n\n\n<p><strong>Sql<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>SELECT content, 1 - (embedding &lt;=&gt; %s) AS similarity\nFROM documents\nORDER BY embedding &lt;=&gt; %s\nLIMIT 5;\n<\/code><\/pre>\n\n\n\n<p>The <strong>&lt;=&gt;<\/strong> operator returns cosine distance where 0 is identical and 2 is opposite, so <strong>1 &#8211; distance<\/strong> gives you cosine similarity as a score between 0 and 1.<\/p>\n\n\n\n<p>Pass your query embedding as the parameter using the same Python setup as above.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Adding an HNSW Index for Performance<\/strong><\/h2>\n\n\n\n<p>Without an index, every similarity query performs a full table scan comparing the query vector against every row. That is fine at ten thousand rows. At hundreds of thousands, it gets slow.<\/p>\n\n\n\n<p>HNSW builds a multi-layer graph where each vector is connected to its neighbors, giving much better query performance than IVFFlat in terms of the speed-recall tradeoff.<\/p>\n\n\n\n<p>Create it with:<\/p>\n\n\n\n<p><strong>Sql<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>CREATE INDEX ON documents USING hnsw (embedding vector_cosine_ops);<\/code><\/pre>\n\n\n\n<p>Ensure the operator class in your index matches the operator in your queries, because a mismatch produces no error, only a silent regression to full-table scans. If you query with <strong>&lt;=&gt;<\/strong>, index with <strong>vector_cosine_ops<\/strong>. If you query with <strong>&lt;-&gt;<\/strong>, use <strong>vector_l2_ops<\/strong>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>When to Use pgvector vs a Dedicated Vector Database<\/strong><\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table><tbody><tr><td><strong>Scenario<\/strong><\/td><td><strong>Best Choice<\/strong><\/td><\/tr><tr><td>Already using PostgreSQL, under 10M vectors<\/td><td>pgvector<\/td><\/tr><tr><td>Need auto-vectorization at import time<\/td><td>Weaviate<\/td><\/tr><tr><td>Need sub-millisecond latency at 100M+ vectors<\/td><td>Qdrant<\/td><\/tr><tr><td>Need hybrid search without extra setup<\/td><td>pgvector or Weaviate<\/td><\/tr><tr><td>Multi-tenant SaaS with strict data isolation<\/td><td>pgvector (one schema per tenant)<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<div style=\"background-color: #099f4e; border: 3px solid #110053; border-radius: 12px; padding: 18px 22px; color: #FFFFFF; font-size: 18px; font-family: Montserrat, Helvetica, sans-serif; line-height: 1.6; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); max-width: 750px; margin: 22px auto;\">\n  <h3 style=\"margin-top: 0; font-size: 22px; font-weight: 700; color: #ffffff;\">\ud83d\udca1 Did You Know?<\/h3>\n  <ul style=\"padding-left: 20px; margin: 10px 0;\">\n    <li>Each vector in pgvector takes 4 bytes per dimension plus 8 bytes of overhead. A 1536-dimension embedding therefore uses about 6.1 KB per row. A table of one million documents with 1536-dimension embeddings uses roughly 6 GB of storage, entirely within standard PostgreSQL capacity.<\/li>\n  <\/ul>\n<\/div>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Common Mistakes to Avoid<\/strong><\/h2>\n\n\n\n<ul>\n<li><strong>Forgetting to call register_vector() in Python.<\/strong> Without this call, psycopg does not know how to serialise Python lists into the pgvector type and raises a type error on every insert. Call it once immediately after opening your connection.<\/li>\n\n\n\n<li><strong>Indexing before you have enough data.<\/strong> The IVFFlat index requires a training step and was likely created with too little data for the number of lists. Drop the index until the table has more data. For HNSW, you can create the index on an empty table, but it performs best once there are at least a few thousand rows.<\/li>\n\n\n\n<li><strong>Mismatching distance operators and index operator classes.<\/strong> Using <strong>&lt;=&gt;<\/strong> in queries but <strong>vector_l2_ops<\/strong> in the index causes PostgreSQL to silently fall back to a full table scan. Always match the operator class in your <strong>CREATE INDEX<\/strong> to the distance operator in your queries.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Conclusion<\/strong><\/h2>\n\n\n\n<p>This pgvector tutorial covered the complete beginner path: running PostgreSQL with pgvector in Docker, enabling the extension, creating a table with a vector column, inserting embeddings from Python, running cosine similarity search, and adding an HNSW index for production performance. If you already use PostgreSQL, pgvector is the fastest, lowest-overhead way to add semantic search to your application in 2026.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>FAQs<\/strong><\/h2>\n\n\n<div id=\"rank-math-faq\" class=\"rank-math-block\">\n<div class=\"rank-math-list \">\n<div id=\"faq-question-1782728664444\" class=\"rank-math-list-item\">\n<h3 class=\"rank-math-question \"><strong>1. What is pgvector?<\/strong><\/h3>\n<div class=\"rank-math-answer \">\n\n<p>pgvector is an open-source PostgreSQL extension that adds a vector data type and similarity search operators, enabling semantic search and RAG directly inside your existing PostgreSQL database.<\/p>\n\n<\/div>\n<\/div>\n<div id=\"faq-question-1782728682926\" class=\"rank-math-list-item\">\n<h3 class=\"rank-math-question \"><strong>2. Is pgvector free?<\/strong>\u00a0<\/h3>\n<div class=\"rank-math-answer \">\n\n<p>Yes. pgvector is open-source under the PostgreSQL License. It is also available pre-installed on managed services including AWS RDS, Azure Database for PostgreSQL, and Supabase.<\/p>\n\n<\/div>\n<\/div>\n<div id=\"faq-question-1782728700258\" class=\"rank-math-list-item\">\n<h3 class=\"rank-math-question \"><strong>3. What distance operators does pgvector support?<\/strong>\u00a0<\/h3>\n<div class=\"rank-math-answer \">\n\n<p>pgvector supports cosine distance (<strong>&lt;=><\/strong>), L2 Euclidean distance (<strong>&lt;-><\/strong>), and negative inner product (<strong>&lt;#><\/strong>). Cosine distance is the most common choice for text embeddings generated by LLMs.<\/p>\n\n<\/div>\n<\/div>\n<div id=\"faq-question-1782728721871\" class=\"rank-math-list-item\">\n<h3 class=\"rank-math-question \"><strong>4. What is the difference between HNSW and IVFFlat in pgvector?<\/strong><\/h3>\n<div class=\"rank-math-answer \">\n\n<p>HNSW builds a graph-based index with better query performance and no training step but uses more memory. IVFFlat partitions vectors into clusters and requires training data before the index is created. HNSW is the recommended default for most use cases.<\/p>\n\n<\/div>\n<\/div>\n<div id=\"faq-question-1782728739078\" class=\"rank-math-list-item\">\n<h3 class=\"rank-math-question \"><strong>5. Should I use pgvector or a dedicated vector database like Qdrant?<\/strong>\u00a0<\/h3>\n<div class=\"rank-math-answer \">\n\n<p>If you already run PostgreSQL and have fewer than 10 million vectors, pgvector gives you semantic search with no new infrastructure. For very large datasets or use cases requiring sub-millisecond latency at massive scale, a dedicated vector database like Qdrant or Weaviate may perform better.<\/p>\n\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n\n\n<p><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Most vector database tutorials tell you to spin up an entirely new database just for embeddings. If you already run PostgreSQL in production, there is a much simpler path. This pgvector tutorial shows you how to add vector similarity search directly to the database you already have, with no new infrastructure, no new connection string, [&hellip;]<\/p>\n","protected":false},"author":65,"featured_media":121110,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[325,959],"tags":[],"views":"32","authorinfo":{"name":"Jebasta","url":"https:\/\/www.guvi.in\/blog\/author\/jebasta\/"},"thumbnailURL":"https:\/\/www.guvi.in\/blog\/wp-content\/uploads\/2026\/06\/pgvector-300x116.webp","_links":{"self":[{"href":"https:\/\/www.guvi.in\/blog\/wp-json\/wp\/v2\/posts\/119462"}],"collection":[{"href":"https:\/\/www.guvi.in\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.guvi.in\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.guvi.in\/blog\/wp-json\/wp\/v2\/users\/65"}],"replies":[{"embeddable":true,"href":"https:\/\/www.guvi.in\/blog\/wp-json\/wp\/v2\/comments?post=119462"}],"version-history":[{"count":3,"href":"https:\/\/www.guvi.in\/blog\/wp-json\/wp\/v2\/posts\/119462\/revisions"}],"predecessor-version":[{"id":121115,"href":"https:\/\/www.guvi.in\/blog\/wp-json\/wp\/v2\/posts\/119462\/revisions\/121115"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.guvi.in\/blog\/wp-json\/wp\/v2\/media\/121110"}],"wp:attachment":[{"href":"https:\/\/www.guvi.in\/blog\/wp-json\/wp\/v2\/media?parent=119462"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.guvi.in\/blog\/wp-json\/wp\/v2\/categories?post=119462"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.guvi.in\/blog\/wp-json\/wp\/v2\/tags?post=119462"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}