On 27 October 2026 Pinecone open-sourced pg_pinecone, a Postgres extension that runs the search engine Pinecone uses at scale inside the Postgres you already operate. You keep your tables and your pgvector queries; what changes is the index behind ORDER BY embedding <=> $1. By default it never calls Pinecone, and when one server isn’t enough, one SQL call moves a table’s search to a Pinecone index while Postgres keeps every row.
On 3 November 2026 I read the pg_pinecone docs, the GitHub repo (README, reference, runbook, changelog, benchmark reports) and Pinecone’s announcement post. Every command and SQL statement below comes from those pages, except the recall harness in step 2, which I flag as mine. Docker wasn’t running on the machine I wrote this on, so I executed none of it: this is a recipe checked against the docs, not a lab report.
1. The Problem: The Index Outgrows the Server Before the Data Does
pgvector’s HNSW is a graph, and graphs want RAM. In Pinecone’s own test on 1 million 768-dimension vectors, HNSW took 4.1 GB with full vectors and 2.0 GB on halfvec, against 136 MB for pg_pinecone’s native index (vendor-reported; Techzine’s coverage repeats the figures). Under a 4 GB memory cap, the full-precision HNSW index got 2.8 to 3.8 times slower at recall 0.95 to 0.99 in the vendor’s over-RAM report, while halfvec HNSW and native didn’t slow down.
The second pain is filters. pgvector applies a WHERE after the index scan, so with the default hnsw.ef_search of 40, a filter that matches 10% of rows leaves about 4 results on average. Iterative scans (pgvector 0.8.0+) help, and they’re what my earlier pgvector posts recommend. pg_pinecone takes a different route: it pushes one equality filter, such as a tenant ID, into the scan itself.
Latest release
0.11.0
Published on GitHub on 26 Oct 2026, announced on 27 Oct
License
Apache-2.0
Written in Rust with pgrx 0.19.2
Postgres
17 and 18
Self-managed only: needs superuser and pgvector
GitHub
~55 stars
pinecone-io/pg_pinecone on 3 Nov 2026, created 26 Oct
Maturity check: a 0.x project that moves fast
0.10.0 (17 October) renamed the project from pinegres with no upgrade path and changed the native on-disk format, so 0.9 native indexes stay offline until REINDEX. 0.11.0 (26 October) made native the default engine of pinecone.create_index(); until you run ALTER EXTENSION pg_pinecone UPDATE after upgrading, a call without engine still builds HNSW. Pin the version and read the changelog before every upgrade.
2. The Minimum Concepts
Six ideas cover everything the tutorial touches. All of them come from the docs and the SQL reference for 0.11.0.
Three engines behind one index
The engine is a setting on the table’s one pg_pinecone index: pgvector (its own HNSW or IVFFlat), native (the default) or remote. Queries don’t change, and Postgres keeps every row on all three, so every switch can be reversed by rebuilding from the table.
IVF + RaBitQ, then an exact rescore
Native clusters vectors into lists (IVF) and stores each as a RaBitQ code of about one bit per dimension (116 bytes per record at 768-d). A query probes the nearest lists, keeps the best candidates by estimated distance and rescores them exactly against the full vectors in the heap. Think compressed IVFFlat, not HNSW.
Calibrated defaults
Each CREATE INDEX or REINDEX picks the cheapest probe count and rescore pool that reach target_recall (default 0.95, recall@10 on a sample of table rows) and prints the choice in a NOTICE. Inserts don’t recalibrate; centroids only move on REINDEX.
Flat below 10,000 rows
Under 10,000 rows a native index is flat: an exact scan of every row, correct but slower as the table grows. Build it after the bulk load, or REINDEX later.
One filter column, two scan modes
filter_column pushes WHERE column = value into the scan. At or below 3% estimated selectivity, mode L scores every matching row exactly; above it, mode H probes up to half the lists and checks the key per block. Other conditions run on each batch the index returns.
Remote: Pinecone searches, Postgres keeps rows
Committed writes are queued in Postgres and copied to Pinecone by a background worker, usually within seconds, which is why the extension must be preloaded. There’s no read-your-writes, and a search Pinecone rejects fails at once instead of retrying.
Where it fits next to my other Postgres posts
In the pgvector vs Qdrant guide, heavily filtered search at scale was the main reason to add a second system. Native is a third option: keep Postgres, change only the index. pg_pinecone also ships full-text search, which I leave out; the hybrid search tutorial covers the lexical leg inside Postgres.
3. Step 1: Run pg_pinecone in Docker and Build a First Index
The quickest path is the official image: pgvector/pgvector plus the extension, one tag per Postgres major. These are the quickstart’s commands, which pin 0.11.0-pg17.
1. Pull, start and connect (from the quickstart)
# The image is pgvector/pgvector plus pg_pinecone (amd64 and arm64).
# Pin the version: "latest" currently points at the pg18 build.
docker pull pineconeteam/pg_pinecone:0.11.0-pg17
# shared_preload_libraries isn't needed for the native engine, but the remote
# engine and full-text search need it: setting it now saves a restart later
docker run -d --name pg_pinecone \
-e POSTGRES_PASSWORD=secret \
pineconeteam/pg_pinecone:0.11.0-pg17 \
-c shared_preload_libraries=pg_pinecone
# No port is published: connect through the container as the postgres superuser
docker exec -it pg_pinecone psql -U postgresCheck your CPU before you pull
On x86_64, the image and the Linux archives need an x86-64-v3 CPU (Intel Haswell or newer), and the archives need glibc 2.39+, such as Ubuntu 24.04 or Debian 13. Debian 12, Ubuntu 22.04, RHEL 9 and older CPUs build from source with Rust and cargo-pgrx 0.19.2.
2. Create both extensions and record the versions
-- pgvector first: pg_pinecone builds on its vector type and distance operators
CREATE EXTENSION vector;
CREATE EXTENSION pg_pinecone;
-- The two versions the support page asks for in every bug report
SELECT extversion FROM pg_extension WHERE extname = 'pg_pinecone';
SELECT version();3. The docs quickstart: a table, a native index, a query and its plan
-- The docs quickstart: hand-made 3-d vectors (food, nature, places)
CREATE TABLE docs (
id bigint PRIMARY KEY,
body text,
embedding vector(3)
);
INSERT INTO docs (id, body, embedding) VALUES
(1, 'The Eiffel Tower was completed in 1889 for the World''s Fair in Paris.', '[0.1, 0.1, 0.9]'),
(2, 'Photosynthesis converts sunlight, water, and carbon dioxide into glucose.', '[0.05, 0.9, 0.1]'),
(3, 'The Great Barrier Reef is the largest coral reef system in the world.', '[0.1, 0.8, 0.3]'),
(4, 'Espresso is brewed by forcing hot water through finely ground coffee.', '[0.95, 0.05, 0.05]'),
(5, 'Mount Everest sits on the border between Nepal and China.', '[0.05, 0.4, 0.8]'),
(6, 'Honeybees communicate the location of flowers with a waggle dance.', '[0.2, 0.85, 0.05]'),
(7, 'A croissant is a flaky pastry made from laminated, buttered dough.', '[0.95, 0.05, 0.1]'),
(8, 'The Amazon rainforest produces a large share of the world''s oxygen.', '[0.05, 0.9, 0.3]');
-- native is the default engine since 0.11.0; naming it is explicit, not required
SELECT pinecone.create_index('docs', 'embedding', engine => 'native');
-- Eight rows: force the index so you can watch it work (tests only)
SET enable_seqscan = off;
SELECT id, body
FROM docs
ORDER BY embedding <=> '[1, 0, 0]'
LIMIT 2;
-- 4 | Espresso is brewed by forcing hot water through finely ground coffee.
-- 7 | A croissant is a flaky pastry made from laminated, buttered dough.
-- Which native path served that scan: flat, h or l (expect flat: under 10,000 rows)
SELECT pinecone.last_scan_mode();
EXPLAIN (COSTS OFF)
SELECT id
FROM docs
ORDER BY embedding <=> (SELECT embedding FROM docs WHERE id = 4)
LIMIT 2;
-- The plan should include: Index Scan using docs_embedding_pinecone_idxWhat this first index does and doesn’t prove
With eight rows the index is flat and the planner would rather read every row, hence enable_seqscan = off. This proves the extension loads, builds and answers a plain pgvector query through docs_embedding_pinecone_idx. It says nothing about speed or recall; that takes your own data.
4. Step 2: Native vs pgvector HNSW on Your Own Embeddings
This is the comparison that matters, and only your embeddings and real queries can run it. The plan follows the docs’ switch-from-pgvector guide: measure HNSW, build native next to it, drop HNSW, measure again. Like Pinecone’s harness, it never lets two indexes compete for the same query.
1. The table, a set of real queries and the HNSW baseline
-- Your corpus. STORAGE PLAIN keeps 768-d vectors in the heap row instead of
-- TOAST, which the native engine's exact rescore reads for every candidate
CREATE TABLE items (
id bigint PRIMARY KEY,
tenant_id bigint,
body text,
embedding vector(768) STORAGE PLAIN
);
-- Real query embeddings from the same model, kept apart from the corpus
CREATE TABLE eval_queries (id serial PRIMARY KEY, embedding vector(768));
-- Load both with your usual pipeline, THEN build the pgvector baseline
CREATE INDEX items_hnsw ON items USING hnsw (embedding vector_cosine_ops);
SET hnsw.ef_search = 100;
SELECT pg_size_pretty(pg_relation_size('items_hnsw'));2. Recall@10 and latency: exact pass once, approximate pass per index
-- recall@10 of whichever index serves items right now, against exact answers.
-- My glue, plain Postgres. The exact pass forces sequential scans, the way
-- Pinecone's bench harness builds its baseline. Run it once.
SET enable_indexscan = off;
SET enable_bitmapscan = off;
SET pinecone.am_enabled = off;
CREATE TEMP TABLE exact AS
SELECT q.id AS qid, n.id
FROM eval_queries q
CROSS JOIN LATERAL (
SELECT i.id FROM items i ORDER BY i.embedding <=> q.embedding LIMIT 10
) n;
RESET enable_indexscan;
RESET enable_bitmapscan;
RESET pinecone.am_enabled;
-- The approximate pass: rerun after every index change. \timing gives the
-- total, divide by the number of queries (check the plan with EXPLAIN first)
\timing on
DROP TABLE IF EXISTS approx;
CREATE TEMP TABLE approx AS
SELECT q.id AS qid, n.id
FROM eval_queries q
CROSS JOIN LATERAL (
SELECT i.id FROM items i ORDER BY i.embedding <=> q.embedding LIMIT 10
) n;
SELECT round(count(*) / (10.0 * (SELECT count(*) FROM eval_queries)), 3) AS recall_at_10
FROM approx JOIN exact USING (qid, id);This harness is mine, so check it
The runbook describes the method in prose: recall@10 of real queries against exact answers, which you get with SET pinecone.am_enabled = off. The SQL is my plain-Postgres version, plus the planner settings the vendor’s harness uses for its exact baseline. I haven’t run it, so EXPLAIN one inner query first and confirm it’s an Index Scan on the index you mean to test.
3. Switch to native, inspect it, and know the way back
-- 1. Build the native index next to HNSW. Cosine is create_index's default
-- metric, matching vector_cosine_ops (use metric => 'l2' or 'ip' otherwise).
-- Reads keep using HNSW meanwhile; writes wait for the build.
SELECT pinecone.create_index('items', 'embedding', engine => 'native');
-- NOTICE: pg_pinecone: calibrated for recall@10 0.95: probing ... lists and rescoring ... rows
-- 2. Drop the pgvector index: queries move to the native one
DROP INDEX items_hnsw;
-- 3. What the build chose, and what it weighs
SELECT * FROM pinecone.index_calibration('items_embedding_pinecone_idx');
SELECT pg_size_pretty(pg_relation_size('items_embedding_pinecone_idx'));
-- 4. Rerun the approximate pass and compare recall_at_10 and \timing
-- Going back is two statements, from the same guide
DROP INDEX items_embedding_pinecone_idx;
CREATE INDEX items_hnsw ON items USING hnsw (embedding vector_cosine_ops);4. Tune it if the recall isn’t there
-- Session overrides for a sweep; both win over the index's calibrated defaults
SET pinecone.scan_fraction = 0.05; -- share of IVF lists probed (nprobe / n_lists)
SET pinecone.rescore_multiplier = 5; -- rescore pool = 5 x 100 rows
-- LIMIT 100 workloads: pin a 1,000-row pool, then rebuild (probes recalibrate)
ALTER INDEX items_embedding_pinecone_idx SET (rescore_multiplier = 10);
REINDEX INDEX items_embedding_pinecone_idx;
-- Or calibrate on real queries (a table of 50+ distinct ones, never eval_queries)
ALTER INDEX items_embedding_pinecone_idx SET (calibration_queries = 'public.calib_queries');
REINDEX INDEX items_embedding_pinecone_idx;The two knobs, and when to pin them
scan_fraction (lists probed) and rescore_multiplier (rows rescored, in hundreds) are session overrides for sweeps. Calibration targets recall@10 and often settles on the 100-row pool minimum, which shortchanges LIMIT 100: Pinecone measured recall@100 of 0.754 to 0.799 at defaults, 0.88 to 0.98 with a 1,000-row pool. If your queries are questions and your rows passages, calibrate on real queries.
Don’t benchmark this on random vectors
IVF needs cluster structure, and uniform random vectors have none. On 300,000 of them, the reference says calibration ended up probing all 548 lists with a 2,000-row pool, for recall 0.928. Real embeddings cluster; random ones make native look like an exhaustive scan.
5. Step 3: Filtered Search Without Losing Rows
Multi-tenant search is where native earns its place. You declare one filter column at index creation, and equality on it is applied while the index scans, so a selective filter doesn’t starve the result list.
1. Rebuild the index with a filter column and query one tenant
-- One pg_pinecone index per table: rebuild it with the tenant as filter column
-- (smallint, integer, bigint, text or varchar)
DROP INDEX items_embedding_pinecone_idx;
SELECT pinecone.create_index('items', 'embedding', engine => 'native', filter_column => 'tenant_id');
-- "=" on the filter column runs inside the scan, not after it
SELECT id, body FROM items
WHERE tenant_id = 42
ORDER BY embedding <=> (SELECT embedding FROM eval_queries WHERE id = 1)
LIMIT 10;
SELECT pinecone.last_scan_mode(); -- l: key at or below 3% of rows, h: aboveMode L or mode H: the planner’s estimate decides
At or below 3% estimated matching rows, native uses mode L: it reads every matching row and scores it exactly, so recall is 1.0. Above that, mode H probes IVF lists and tests the key per block, skipping blocks with no match. pinecone.last_scan_mode() shows which path your session’s last scan took.
2. Conditions the index can’t push, and how to read the scan
-- Any other condition (a range, IN, ILIKE, a second column) filters each batch the
-- index returns. On native, pinecone.iterative_scan = auto (the default) keeps
-- scanning until LIMIT is filled; rows after the first batch arrive in batch
-- order, so re-sort them when exact order matters:
WITH relaxed AS MATERIALIZED (
SELECT id, embedding <=> (SELECT embedding FROM eval_queries WHERE id = 1) AS distance
FROM items
WHERE tenant_id = 42 AND body ILIKE '%invoice%'
ORDER BY distance
LIMIT 10
)
SELECT * FROM relaxed ORDER BY distance + 0;
-- Did the scan stop at pinecone.max_scan_tuples (20,000 rows by default)?
SELECT batches, rescored, capped FROM pinecone.last_scan_stats();
-- On the pgvector engine, pgvector's own switch applies instead (0.8.0+)
SET hnsw.iterative_scan = relaxed_order;The planner doesn’t price the size of a pushed key
The reference says the cost estimate gives a 1% and a 10% key the same cost, so it can pick mode L where a sequential scan is faster. Pinecone’s 10M-row run shows what mode L can cost: 669 ms for a 1% filter against 391 ms for a filtered sequential scan. Check slow tenants with EXPLAIN ANALYZE; SET pinecone.am_enabled = off steers a query away from the index.
6. Step 4: The Remote Engine (Search in Pinecone, Rows in Postgres)
Remote is for a table that has outgrown one Postgres server. pg_pinecone creates a serverless index in your Pinecone project and copies the vectors in the background, writing to both until the copy finishes. The commands come from the switch-to-remote guide and the storage engines page.
Switch, watch the copy, query, and switch back
-- Prerequisites: shared_preload_libraries = 'pg_pinecone' (the docker run in
-- step 1 sets it), a single-column primary key, and an API key in the database
SELECT pinecone.set_api_key('YOUR_API_KEY');
-- Creates a serverless index (aws us-east-1 unless you set pinecone.am_cloud and
-- pinecone.am_region first) and copies the vectors in the background.
-- Queries keep using the native index until the copy finishes.
SELECT pinecone.set_engine('items', 'remote');
-- mode is 'promoting' during the copy, 'remote' with backlog 0 once Pinecone serves
SELECT table_name, mode, index_name, backlog FROM pinecone.status();
-- The same query, unchanged: the filter column travels as a metadata filter
SELECT id, body FROM items
WHERE tenant_id = 42
ORDER BY embedding <=> (SELECT embedding FROM eval_queries WHERE id = 1)
LIMIT 10;
-- Back to native: a blocking local rebuild, then the Pinecone index is deleted
SELECT pinecone.set_engine('items', 'native');
SELECT table_name, mode FROM pinecone.status(); -- done when items no longer appearsWhat you give up on remote
A query right after a write may miss it: writes usually reach Pinecone within seconds, minutes under heavy load. Rate-limited or failed searches aren’t retried, so add backoff in your app. The copy takes up to about an hour per million rows, and switching back blocks reads and writes while native rebuilds. It’s a standard index in your Pinecone project, so it shows up on that bill.
Remote makes sense when
- • The table no longer fits one server, but rows, joins and transactions still belong in Postgres.
- • Each tenant needs its own namespace: a NOT NULL partition_column makes every query name its tenant.
- • You already pay for Pinecone and want to drop a sync job you maintain.
Stay on native or pgvector when
- • You need read-your-writes, such as search right after an upload.
- • Data can’t leave your network, or a network call per query is unacceptable.
- • The table fits one server: native’s index was 1.3 GB at 10M × 768 in Pinecone’s run, against 41 GB for HNSW.
7. Native vs Remote vs pgvector
The three engines side by side, from the docs and the SQL reference for 0.11.0. Sizes are Pinecone’s 1M × 768 measurements.
| Engine | Where search runs | Filters | Consistency | Best for | Watch out for |
|---|---|---|---|---|---|
| native (default) | Inside Postgres: IVF + RaBitQ, exact rescore from the heap; no account or network calls | One equality filter column inside the scan; other conditions per batch | Transactional, like any Postgres index | Filtered, multi-tenant search on RAM-tight servers (136 MB index) | 0.x; no CONCURRENTLY builds; flat under 10,000 rows; 2,038-dim cap; REINDEX to move centroids |
| remote | Pinecone serverless index; rows stay in Postgres; needs an API key and preload | Filter column as a metadata filter; a partition column gives each tenant a namespace | Eventual (seconds); no read-your-writes; reads not retried | A table that has outgrown one server | Pinecone bill, network latency, us-east-1 default, blocking switch back |
| pgvector | pgvector’s own HNSW or IVFFlat, in Postgres | After the index scan; hnsw.iterative_scan (0.8.0+) scans further | Transactional | Tables on HNSW that don’t filter; CONCURRENTLY builds | The graph wants RAM: 4.1 GB full precision, 2.0 GB on halfvec |
pg_pinecone manages one index per table, so the engine is a per-table choice, and every switch keeps the rows.
8. Pinecone’s Benchmarks, Read Carefully
Vendor numbers, but published with harness and configs, which is more than most launches give you. Unfiltered rows come from the halfvec report, filtered ones from the filtered report. Out-of-region queries land far from the rows that match the filter; in-region (tenant-style) ones land near them.
| Metric (1M × 768) | pg_pinecone native | HNSW on halfvec | HNSW on full vectors |
|---|---|---|---|
| Index size | 136 MB | 2.0 GB | 4.1 GB |
| Heap + index | 4.3 GB (keeps full vectors) | 3.7 GB on a halfvec column, 6.2 GB as an expression index | 8.2 GB |
| Build time | 45 s | 55–62 s | 79 s |
| p50 at recall@10 0.95 | 1.27 ms | 1.71 ms | 2.67 ms |
| p50 at recall@10 0.99 | 2.28 ms | 4.65 ms | 6.72 ms |
| 1% filter, out-of-region | 0.981 @ 8.3 ms | 0.924 @ 55.7 ms | 0.926 @ 57.8 ms |
| 10% filter, out-of-region | 1.000 @ 12.3 ms | 0.922 @ 5.1 ms | 0.920 @ 5.9 ms |
| Tenant-style, in-region | 1.000 @ 8.5–12.8 ms | 0.983–0.989 @ 3.2 ms | 0.981–0.987 @ 3.7–4.5 ms |
The conditions behind the table
One AWS r6id.4xlarge (16 vCPUs, 128 GB), wiki_dpr 1M × 768, L2, k = 10, 500 held-out queries, one client, pgvector 0.8.7. Unfiltered: PostgreSQL 17.11, HNSW built with m = 16 and pg_pinecone 0.10.0 built from main before release (16 Oct). Filtered: PostgreSQL 18.6, the 0.10.0 release, ef_search 160 with relaxed_order (22 Oct). The announcement says 5.9 ms for HNSW at 10% and its chart 5.1 ms: the report has both, for full vectors and halfvec.
What I take from it
- • A small index isn’t a small footprint: native rescores against full vectors, so the 4.1 GB heap stays (4.3 GB total, more than halfvec HNSW on a halfvec column). What shrinks is what must sit in RAM.
- • Filters are where the gap is real, and it cuts both ways: at 10% HNSW was faster but stopped near 0.92 recall, and on tenant-style queries HNSW beat native on latency at 0.98+ recall.
- • Scale changes the path: at 10M on 0.11.0, a 1% filter fell into mode L and lost to a sequential scan, while HNSW sat near 0.85 recall.
- • halfvec closes much of the unfiltered gap at no measurable recall cost on this dataset. Try it before switching engines.
- • One vendor, box and dataset, L2 only, with fsync and jit off. I found no independent reproduction yet.
9. Common Errors (and the Fix for Each)
What I’d expect to hit in week one, each traced to the docs, the reference or the runbook.
A flat index, or no index at all
Symptom: On a small table the plan shows Seq Scan, or last_scan_mode() says flat: under 10,000 rows native scans every row.
Fix: Build after the bulk load or REINDEX once the table grows. enable_seqscan = off is for tests only.
Every rescored row pays a TOAST fetch
Symptom: Native is slower than expected, and CREATE INDEX printed a NOTICE about STORAGE PLAIN: pgvector stores a 768-d vector (3,080 bytes) out of line.
Fix: ALTER TABLE items ALTER COLUMN embedding SET STORAGE PLAIN, then VACUUM FULL items (ACCESS EXCLUSIVE lock). UPDATE … SET embedding = embedding won’t move existing rows.
Wrong metric after the switch
Symptom: You replaced an L2 or inner-product HNSW index and queries stopped using an index: create_index() defaults to cosine.
Fix: Pass metric => 'l2' (vector_l2_ops) or 'ip' (vector_ip_ops), and keep the query operator matching.
Calibration says 0.97, your eval says 0.92
Symptom: The build calibrates on table rows, and question embeddings don’t look like passages.
Fix: Point calibration_queries at 200 to 1,000 real queries (not your eval set) and REINDEX, or raise target_recall.
LIMIT 100 returns worse neighbours than LIMIT 10
Symptom: Calibration targets recall@10, and the pool doesn’t follow the LIMIT.
Fix: ALTER INDEX … SET (rescore_multiplier = 10), then REINDEX.
Writes stall during builds and maintenance
Symptom: Native rejects CREATE INDEX CONCURRENTLY and REINDEX CONCURRENTLY by design: builds block writes, and REINDEX also blocks reads through the index.
Fix: Use a maintenance window, watch pinecone.index_drift(), and leave pinecone.auto_maintain off unless an unattended stall is acceptable.
Your embeddings are too wide
Symptom: CREATE INDEX fails on 3,072-d vectors: native caps at 2,038 dimensions on 8 KB pages, per a boundary test in the source.
Fix: Use a 768- to 1,536-d model or shortened embeddings, or pgvector HNSW on halfvec (up to 4,000 dimensions).
It won’t install on your managed Postgres
Symptom: CREATE EXTENSION pg_pinecone fails: it needs superuser and files in the server’s extension directory.
Fix: The docs scope it to self-managed Postgres 17 and 18, and none of my sources names a managed provider that ships it.
My rule of thumb
Measure before you switch: recall@10 and latency of real queries, with real filters, on HNSW and then on native. Switch only the tables where native wins on your numbers, and keep the HNSW CREATE INDEX in your migrations so going back is two statements.
Conclusion
pg_pinecone is a credible alternative to HNSW inside Postgres: an IVF + RaBitQ index about 30 times smaller than full-precision HNSW in Pinecone’s test, which pushes one equality filter into the scan and leaves your queries alone. The remote engine is a reasonable escape hatch, with a documented way back.
It’s also eight days old as a public project, with a default that changed last week and an on-disk format that changed the week before. My take: try it this week on a copy of your data, with the harness from step 2 and your real filters, and ship it only where it beats HNSW on your numbers, on self-managed Postgres, with a maintenance window for REINDEX.
Verification stamp
Docs: the pg_pinecone pages on docs.pinecone.io, read on 3 Nov 2026. Repo: pinecone-io/pg_pinecone at v0.11.0 and main, including the reference, runbook, changelog and bench/vector reports. Image tags checked on Docker Hub; pgvector README at v0.8.7. Every benchmark is Pinecone’s. I executed nothing: Docker wasn’t running on this machine.
Sources
- Pinecone: Introducing pg_pinecone (27 Oct 2026)
- pg_pinecone docs: overview
- pg_pinecone docs: quickstart
- pg_pinecone docs: storage engines
- pg_pinecone docs: switch from pgvector to native
- pg_pinecone docs: switch from native to remote
- pinecone-io/pg_pinecone on GitHub
- pg_pinecone SQL reference
- pg_pinecone operator runbook
- pg_pinecone changelog
- Benchmark: halfvec, full-vector HNSW and native, 1M × 768
- Benchmark: 1% and 10% filters
- Benchmark: 4 GB memory cap
- Benchmark: wiki_dpr 10M on 0.11.0
- Docker Hub: pineconeteam/pg_pinecone
- pgvector README
- Techzine: Pinecone brings its own vector search technology to PostgreSQL



