[{"data":1,"prerenderedAt":27},["ShallowReactive",2],{"$fHNl9KFof7KNhi_yxxEtDKFhBBX_1fEEcPcItNEP74AY":3},{"slug":4,"title":5,"description":6,"date":7,"tags":8,"draft":11,"readingMinutes":12,"html":13,"related":14},"pgvector","pgvector in Production: The Settings I'd Check First","pgvector in October 2026: version 0.8.7, HNSW vs IVFFlat, the 2,000-dimension index limit, halfvec, and why filtered queries return fewer rows than you asked for.","2026-10-27",[9,4,10],"postgres","ai",false,4,"\u003Cp>pgvector is the Postgres extension that stores embeddings in a normal table and finds the nearest ones with an index. If your app already runs on Postgres, it&#39;s the vector database I&#39;d reach for first, before Pinecone or anything else with its own bill and its own copy of your data. It works. It also has three or four defaults that quietly give you wrong-looking results, and none of them show up until you have real data.\u003C/p>\n\u003Cp>Everything here was checked against the \u003Ca href=\"https://github.com/pgvector/pgvector\">pgvector README and changelog\u003C/a> on October 27, 2026. The current release is \u003Cstrong>0.8.7\u003C/strong> (October 1). 0.9.0 is in progress on the main branch and drops Postgres 13 and 14, so if you&#39;re still on 14, that&#39;s your nudge.\u003C/p>\n\u003Ch2>The column and the dimension limit\u003C/h2>\n\u003Cp>You add a column like \u003Ccode>embedding vector(1536)\u003C/code> and store one row per chunk of text. Easy part.\u003C/p>\n\u003Cp>The catch is the index, not the column. A \u003Ccode>vector\u003C/code> column can hold up to 16,000 dimensions, but HNSW and IVFFlat indexes only cover \u003Ccode>vector\u003C/code> up to \u003Cstrong>2,000 dimensions\u003C/strong>. OpenAI&#39;s \u003Ccode>text-embedding-3-large\u003C/code> returns 3,072. So the most obvious &quot;use the best model&quot; choice gives you a column you can&#39;t index.\u003C/p>\n\u003Cp>Two ways out. Store it as \u003Ccode>halfvec(3072)\u003C/code>, which indexes up to 4,000 dimensions at half precision. Or ask the API for fewer dimensions: the v3 OpenAI embedding models take a \u003Ccode>dimensions\u003C/code> parameter, and 1,024 or 1,536 is plenty for most search. I&#39;d do the second one first. Smaller vectors are cheaper everywhere, not just in the index.\u003C/p>\n\u003Cp>The storage maths is worth doing once. Per the README, a \u003Ccode>vector\u003C/code> takes \u003Ccode>4 * dimensions + 8\u003C/code> bytes and a \u003Ccode>halfvec\u003C/code> takes \u003Ccode>2 * dimensions + 8\u003C/code>. A million rows at 1,536 dimensions is about 6.2 GB for the column alone as \u003Ccode>vector\u003C/code>, about 3.1 GB as \u003Ccode>halfvec\u003C/code>, before the index. On a managed Postgres that charges for disk and RAM, that&#39;s a real line on the bill.\u003C/p>\n\u003Ch2>HNSW or IVFFlat\u003C/h2>\n\u003Cp>HNSW. I don&#39;t think this is close for most apps.\u003C/p>\n\u003Cp>IVFFlat builds faster and uses less memory, but you have to build it after the data is there (it clusters what exists), pick a \u003Ccode>lists\u003C/code> count (\u003Ccode>rows / 1000\u003C/code> up to a million rows, \u003Ccode>sqrt(rows)\u003C/code> after that, per the README), and set \u003Ccode>probes\u003C/code> at query time. Get those wrong and recall drops without any error. HNSW you can create on an empty table, it keeps working as rows come in, and the defaults (\u003Ccode>m = 16\u003C/code>, \u003Ccode>ef_construction = 64\u003C/code>) are fine to start.\u003C/p>\n\u003Cp>The one HNSW thing to know: building it on a big table is slow and memory-hungry. If the graph doesn&#39;t fit in \u003Ccode>maintenance_work_mem\u003C/code>, Postgres tells you, in a NOTICE that&#39;s easy to miss:\u003C/p>\n\u003Cpre>\u003Ccode>NOTICE:  hnsw graph no longer fits into maintenance_work_mem after 100000 tuples\n\u003C/code>\u003C/pre>\n\u003Cp>Raise \u003Ccode>maintenance_work_mem\u003C/code> for that session before you build. Not so high that the server runs out of memory, which the README warns about too, and which on a small managed instance is not a lot of headroom.\u003C/p>\n\u003Cimg src=\"/blog/pgvector/pgvector-filtered-query-funnel.webp\" alt=\"Funnel of a filtered pgvector query: HNSW returns 40 candidates, a 10% tenant filter leaves about 4, LIMIT 10 gets 4 rows unless iterative scans are on\" width=\"1600\" height=\"900\" loading=\"lazy\">\u003Ch2>The filtering trap\u003C/h2>\n\u003Cp>This is the one I&#39;d put at the top of any pgvector checklist.\u003C/p>\n\u003Cp>With an approximate index, Postgres scans the index first and applies your \u003Ccode>WHERE\u003C/code> clause after. HNSW&#39;s default search width, \u003Ccode>hnsw.ef_search\u003C/code>, is 40. So a query like &quot;the 10 closest documents where \u003Ccode>tenant_id = 42\u003C/code>&quot; finds 40 candidates, throws away the ones from other tenants, and returns what&#39;s left. The README&#39;s own example: if your filter matches 10% of rows, you get about 4 results on average. You asked for 10. Nothing errors. Your RAG answer just has less context and nobody knows why.\u003C/p>\n\u003Cp>Since 0.8.0 there&#39;s a proper fix:\u003C/p>\n\u003Cpre>\u003Ccode class=\"language-sql\">SET hnsw.iterative_scan = strict_order;\n\u003C/code>\u003C/pre>\n\u003Cp>With iterative scans on, pgvector keeps scanning more of the index until it has enough rows or hits \u003Ccode>hnsw.max_scan_tuples\u003C/code> (20,000 by default). \u003Ccode>relaxed_order\u003C/code> is faster and can return results slightly out of order, which is usually fine for retrieval. If you filter on a handful of values, a \u003Ca href=\"https://www.postgresql.org/docs/current/indexes-partial.html\">partial index\u003C/a> per value is often better still, and for many tenants, partitioning.\u003C/p>\n\u003Cp>I&#39;d turn iterative scans on in any app that filters vector search by user, team or document set, which is most of them.\u003C/p>\n\u003Ch2>Where to run it\u003C/h2>\n\u003Cp>Every major managed Postgres has it now. Supabase enables it from the dashboard (\u003Ca href=\"https://supabase.com/docs/guides/database/extensions/pgvector\">their pgvector guide\u003C/a>), Neon and RDS support it, and on your own server it&#39;s a package install. The thing to check is the version: managed providers lag the releases, and 0.8.0 is the minimum I&#39;d accept because of iterative scans.\u003C/p>\n\u003Cp>What I wouldn&#39;t do is run a separate vector database alongside Postgres for an app with a few million vectors or fewer. You&#39;d be syncing two copies of the same rows, handling deletes twice, and paying for both. pgvector keeps the embedding next to the row it describes, inside the same transaction, behind the same row level security. That&#39;s the whole reason to use it.\u003C/p>\n\u003Ch2>The short list\u003C/h2>\n\u003Cp>If I were reviewing someone&#39;s pgvector setup tomorrow, I&#39;d check: extension version (0.8.0 or later), dimensions under 2,000 or \u003Ccode>halfvec\u003C/code>, HNSW rather than IVFFlat unless there&#39;s a reason, \u003Ccode>maintenance_work_mem\u003C/code> raised for index builds, and \u003Ccode>hnsw.iterative_scan\u003C/code> on wherever there&#39;s a \u003Ccode>WHERE\u003C/code> clause. Then I&#39;d run the app&#39;s real queries with \u003Ccode>EXPLAIN ANALYZE\u003C/code> and count the rows that come back.\u003C/p>\n\u003Cp>If you want a second pair of eyes on yours, or a Postgres-backed search built from scratch, \u003Ca href=\"https://calendly.com/director-norsky/meeting\">book a free call\u003C/a>.\u003C/p>\n",[15,19,23],{"slug":16,"title":17,"description":18},"mysql-vs-postgres","MySQL vs Postgres: What Actually Bites You in 2026","MySQL vs Postgres in November 2026: MySQL 9.7 LTS and calendar versions, Postgres 19 RC, and the collation and migration surprises that bite when you switch.",{"slug":20,"title":21,"description":22},"pgbouncer","PgBouncer: What Transaction Mode Breaks, and the Fix","PgBouncer in transaction mode lets 1,000 clients share 20 Postgres connections. What it breaks (SET, LISTEN, advisory locks), and what 1.24 fixed.",{"slug":24,"title":25,"description":26},"supabase-cli","Supabase CLI: Migrations, Types and the Docker Catch","The Supabase CLI in late 2026: how to install it, why local dev needs a container runtime, and the migration commands that keep production in sync.",1794181975356]