When working with vector search, dimension limits matter.
Many embedding models fit comfortably inside 768, 1,024, or 1,536 dimensions. But some use cases need much wider vectors.
That raises an important question:
- Can the database store the vector, and can it actually index it?
That distinction matters.
In pgvector, the vector type can support up to 16000 dimensions. However, PostgreSQL pgvector’s upstream hnsw index path for the vector type supports only up to 2000 indexed dimensions.
YugabyteDB’s ybhnsw index path can index a vector(16000) column.
Let’s prove it.
vector(16000), but its upstream hnsw index path for vector tops out at 2000 dimensions. YugabyteDB’s ybhnsw index path can index a vector(16000).hnsw
and
ybhnsw.
For a deeper look at why HNSW indexes are so fast in YugabyteDB, including how the index is distributed and colocated with the table data at the tablet level, see
Why HNSW Indexes Are So Fast in YugabyteDB
.What We Are Testing
This is a boundary test.
We are not benchmarking search latency, recall, or index build speed. We are simply proving the indexed dimension limit.
| Database | Index Method | Test | Expected Result |
|---|---|---|---|
| PostgreSQL + pgvector |
hnsw
|
vector(2000)
|
Index succeeds |
| PostgreSQL + pgvector |
hnsw
|
vector(2001)
|
Index fails |
| YugabyteDB |
ybhnsw
|
vector(16000)
|
Index succeeds |
| YugabyteDB | N/A |
vector(16001)
|
Column definition fails |
Demo 1: PostgreSQL pgvector HNSW Limit
Run the following in PostgreSQL with pgvector installed:
SELECT version();
CREATE EXTENSION IF NOT EXISTS vector;
SELECT extversion
FROM pg_extension
WHERE extname = 'vector';
DROP TABLE IF EXISTS tip_pg_hnsw_2000;
DROP TABLE IF EXISTS tip_pg_hnsw_2001;
CREATE OR REPLACE FUNCTION make_demo_vector(dim integer, seed integer DEFAULT 0)
RETURNS vector
LANGUAGE sql
IMMUTABLE
AS $$
SELECT array_agg((((g.i + seed) % 97)::real / 97::real) ORDER BY g.i)::vector
FROM generate_series(1, dim) AS g(i);
$$;
Create and index a vector(2000) column:
CREATE TABLE tip_pg_hnsw_2000 (
id bigserial PRIMARY KEY,
embedding vector(2000)
);
INSERT INTO tip_pg_hnsw_2000 (embedding)
SELECT make_demo_vector(2000, g.i)
FROM generate_series(1, 5) AS g(i);
DO $$
BEGIN
EXECUTE '
CREATE INDEX tip_pg_hnsw_2000_idx
ON tip_pg_hnsw_2000
USING hnsw (embedding vector_l2_ops)
';
RAISE NOTICE 'PASS: PostgreSQL pgvector indexed vector(2000) with hnsw.';
EXCEPTION WHEN OTHERS THEN
RAISE EXCEPTION 'FAIL: PostgreSQL pgvector should index vector(2000), but failed with: %', SQLERRM;
END $$;
SELECT indexname, indexdef
FROM pg_indexes
WHERE tablename = 'tip_pg_hnsw_2000';
Expected result:
NOTICE: PASS: PostgreSQL pgvector indexed vector(2000) with hnsw.
Now try one dimension higher: vector(2001).
CREATE TABLE tip_pg_hnsw_2001 (
id bigserial PRIMARY KEY,
embedding vector(2001)
);
INSERT INTO tip_pg_hnsw_2001 (embedding)
SELECT make_demo_vector(2001, g.i)
FROM generate_series(1, 5) AS g(i);
DO $$
BEGIN
EXECUTE '
CREATE INDEX tip_pg_hnsw_2001_idx
ON tip_pg_hnsw_2001
USING hnsw (embedding vector_l2_ops)
';
RAISE EXCEPTION 'FAIL: PostgreSQL pgvector unexpectedly indexed vector(2001) with hnsw.';
EXCEPTION WHEN OTHERS THEN
IF SQLERRM ILIKE '%dimension%' OR SQLERRM ILIKE '%2000%' THEN
RAISE NOTICE 'PASS: PostgreSQL pgvector rejected hnsw on vector(2001): %', SQLERRM;
ELSE
RAISE;
END IF;
END $$;
Expected result:
NOTICE: PASS: PostgreSQL pgvector rejected hnsw on vector(2001): ...
- ●
vector(2000)+ hnsw = succeeds - ●
vector(2001)+ hnsw = fails
Demo 2: YugabyteDB ybhnsw Limit
Now run the YugabyteDB version of the test:
\set ON_ERROR_STOP on
SELECT version();
CREATE EXTENSION IF NOT EXISTS vector;
SELECT extversion
FROM pg_extension
WHERE extname = 'vector';
DROP TABLE IF EXISTS tip_yb_ybhnsw_16000;
DROP TABLE IF EXISTS tip_yb_ybhnsw_16001;
CREATE OR REPLACE FUNCTION make_demo_vector(dim integer, seed integer DEFAULT 0)
RETURNS vector
LANGUAGE sql
IMMUTABLE
AS $$
SELECT array_agg((((g.i + seed) % 97)::real / 97::real) ORDER BY g.i)::vector
FROM generate_series(1, dim) AS g(i);
$$;
Create and index a vector(16000) column with ybhnsw.
CREATE TABLE tip_yb_ybhnsw_16000 (
id bigserial PRIMARY KEY,
embedding vector(16000)
);
INSERT INTO tip_yb_ybhnsw_16000 (embedding)
SELECT make_demo_vector(16000, g.i)
FROM generate_series(1, 5) AS g(i);
DO $$
BEGIN
EXECUTE '
CREATE INDEX NONCONCURRENTLY tip_yb_ybhnsw_16000_idx
ON tip_yb_ybhnsw_16000
USING ybhnsw (embedding vector_l2_ops)
';
RAISE NOTICE 'PASS: YugabyteDB indexed vector(16000) with ybhnsw.';
EXCEPTION WHEN OTHERS THEN
RAISE EXCEPTION 'FAIL: YugabyteDB should index vector(16000) with ybhnsw, but failed with: %', SQLERRM;
END $$;
SELECT indexname, indexdef
FROM pg_indexes
WHERE tablename = 'tip_yb_ybhnsw_16000';
Expected result:
NOTICE: PASS: YugabyteDB indexed vector(16000) with ybhnsw.
You can also run a simple nearest-neighbor query against the table:
SELECT id
FROM tip_yb_ybhnsw_16000
ORDER BY embedding <-> make_demo_vector(16000, 3)
LIMIT 3;
Now test one dimension higher: vector(16001).
DO $$
BEGIN
EXECUTE '
CREATE TABLE tip_yb_ybhnsw_16001 (
id bigserial PRIMARY KEY,
embedding vector(16001)
)
';
RAISE EXCEPTION 'FAIL: YugabyteDB unexpectedly allowed vector(16001).';
EXCEPTION WHEN OTHERS THEN
IF SQLERRM ILIKE '%dimension%' OR SQLERRM ILIKE '%16000%' THEN
RAISE NOTICE 'PASS: YugabyteDB rejected vector(16001): %', SQLERRM;
ELSE
RAISE;
END IF;
END $$;
Expected result:
NOTICE: PASS: YugabyteDB rejected vector(16001): dimensions for type vector cannot exceed 16000
- ●
vector(16000)+ ybhnsw = succeeds - ●
vector(16001)= fails
Why This Matters
The difference is not just academic.
If your embedding model produces vectors wider than 2,000 dimensions, PostgreSQL pgvector may be able to store the vector, but the standard hnsw index path for the vector type cannot index it.
That means you may need to use workarounds such as:
- ● reducing dimensions
- ● using
halfvec - ● binary quantization
- ● indexing subvectors
- ● storing the vector without an ANN index
With YugabyteDB’s ybhnsw, you can index a vector(16000) column directly.
Cleanup
DROP TABLE IF EXISTS tip_pg_hnsw_2000;
DROP TABLE IF EXISTS tip_pg_hnsw_2001;
DROP TABLE IF EXISTS tip_yb_ybhnsw_16000;
DROP TABLE IF EXISTS tip_yb_ybhnsw_16001;
DROP FUNCTION IF EXISTS make_demo_vector(integer, integer);
What to Remember
PostgreSQL pgvector and YugabyteDB can both expose familiar pgvector-style SQL, but their HNSW indexing limits are not the same.
For the vector type:
- ● PostgreSQL pgvector hnsw: 2,000 indexed dimensions
- ● YugabyteDB ybhnsw: 16,000 indexed dimensions
So the practical takeaway is simple:
- If your application needs HNSW indexing on vectors wider than 2,000 dimensions, YugabyteDB’s
ybhnswgives you a much larger indexed-dimension ceiling.
Have Fun!
Every Wednesday, we get a company-sponsored DoorDash credit, and one of my favorite spots is Frank’s North Hills Home Base. They have a loyal following… and for good reason. The buffalo chicken pizza keeps people coming back!
Since I’m moving to Dallas, I’m guessing they won’t deliver quite that far, so this may have been my last visit for a while. 🍕😢
