Interface reference v0

Lists every supported part of the ivfplus interface: operator classes, index options, session parameters, functions, write behavior, and fixed limits. Anything else the extension installs is internal and may change or disappear without a version bump.

Access method and operator classes

ivfplus is the only access method.

OpclassTypeOperatorMetricDefaultScan path
vector_l2_opsvector<->L2yesRaBitQ quantized + rerank
vector_ip_opsvector<#>inner productnoRaBitQ quantized + rerank
vector_cosine_opsvector<=>cosinenoRaBitQ quantized + rerank
halfvec_l2_opshalfvec<->L2noRaBitQ quantized + rerank
halfvec_ip_opshalfvec<#>inner productnoRaBitQ quantized + rerank
halfvec_cosine_opshalfvec<=>cosinenoRaBitQ quantized + rerank

halfvec is indexed exactly like vector, same codes, same rerank copy, so the two differ only in the width of the heap value, and every dimensionality the type allows is quantized. Its centroids are stored as f16, which is why a halfvec index reaches 4000 dimensions where vector stops at 2000; see Limits and fixed constants.

Only vector_l2_ops is a default, so a bare USING ivfplus (col) works for vector and fails for halfvec.

Not indexed: <+> (L1), <%> (Jaccard), and sparsevec in any form — these fall back to an exact sequential scan, same as pgvector's ivfflat.

bit is the one place ivfplus indexes less than pgvector's ivfflat. There's no residual to quantize on a bit vector, so an ivfplus bit index would have been the same exact scan ivfflat already gives you, and the opclass was removed rather than kept as a synonym. Index bit vectors with USING ivfflat (col bit_hamming_ops) instead — the type, <~>, and hamming_distance() are pgvector's and are unaffected.

Index options

Set at build time with CREATE INDEX ... WITH (...):

OptionTypeDefaultRangeApplies toAvailable
listsint1001–32768all opclassesalways
store_vectorsbooloff—all opclassesIndex-rerank-policy builds only

Both are read only at build time. ALTER INDEX ... SET (...) takes an AccessExclusiveLock and changes nothing until the next REINDEX.

CREATE INDEX ON items USING ivfplus (embedding vector_l2_ops)
WITH (lists = 1000);

One edge case: store_vectors doesn't exist on stock Postgres — WITH (store_vectors = ...) fails with unrecognized parameter, and the index behaves as though it were on.

Session parameters

All PGC_USERSET, settable per session, transaction, role, or database by any user. The ivfplus. prefix is reserved, so typos are rejected.

ParameterTypeDefaultRange
ivfplus.probesint11–32768
ivfplus.iterative_scanenumoffoff, relaxed_order
ivfplus.max_probesint327681–32768

ivfplus.iterative_scan never refills, and that's a trap. Elsewhere the name means "scan probes lists, and if the query still wants rows, fetch more, up to max_probes." Here it only widens the initial probe set to max_probes, which at its default of 32768 clamps to every list in the index. You get a full upfront collection, and the 200-row pool still caps output. Never enable it without also lowering max_probes:

SET ivfplus.iterative_scan = relaxed_order;
SET ivfplus.max_probes = 128; -- otherwise this is a full-index scan

On a two-level index (lists >= 1800), both modes share a further ceiling: the descent only ranks leaves under the coarse groups retained by probes (outer_probes is derived from probes, not max_probes), so raising max_probes past that fan-out's leaf count adds nothing — raise probes to widen the descent instead.

Functions

FunctionReturnsPurpose
check_ivfplus_index_health(idx regclass)record (6 cols)How far the index has drifted from freshly built
edb_vectorplus_rerank_policy_available()booleanWhether this build has the executor rerank-policy API (Index-rerank-policy builds)
edb_vectorplus_rerank_stats(reset boolean DEFAULT false)record (3 cols)Per-process rerank counters, Index-rerank-policy builds only
ivfplus_metapage_info(idx regclass)record (15 cols)The shape an index was actually built with

check_ivfplus_index_health columns:

ColumnTypeMeaning
index_healthreal(1 - pct_pending) × (1 - pct_dead_lanes); NOTICE below 0.8
pct_pendingrealFraction of live rows still in the unpacked append region
pct_dead_lanesrealFraction of frozen lanes tombstoned by VACUUM
n_frozen_livebigintLive rows in the packed region
n_dead_lanesbigintTombstoned lanes awaiting REINDEX
n_pendingbigintRows in the append region

ivfplus_metapage_info answers what an index was built with, which is otherwise unrecoverable — build-time options aren't stored in a readable form, and REINDEX can change the answer. These columns are supported:

ColumnTypeMeaning
versionintOn-disk format version
dimensionsintIndexed dimensionality
leaf_listsintThe lists actually built
inner_listsintCoarse groups; 0 on a flat index
heightint1 flat, 2 two-level
metricint1 L2, 2 inner product, 3 cosine
flagsintBitmask: 1 residual codes, 2 f16 rerank copy present, 4 rotation applied, 8 f16 centroids
pack_widthintFast-scan block width
k_lists, k_probesrealHierarchy fan-out constants stamped at build; 0 on a flat index
rotation_stagesintRotation stages applied

The remaining four columns — inner_centroids_start, leaf_headers_start, insert_page, centroid_codes_start — are page numbers with no meaning outside the implementation. Every column here is tied to version and may change when it does.

The extension is relocatable, so schema-qualify these calls if the extension is installed in a schema not on your search_path. Any other function you find in \dx+ edb_vectorplus is internal.

Writes, VACUUM, and REINDEX

k-means clustering only happens at CREATE INDEX/REINDEX time. After that, chosen centroids are fixed for the lifetime of the index, so for best performance it's best to periodically REINDEX high-churn tables.

check_ivfplus_index_health() reports the drift: index_health = (1 - pct_pending) × (1 - pct_dead_lanes). It's 1.0 on a fresh build, with a NOTICE recommending REINDEX INDEX CONCURRENTLY below 0.8.

OperationEffect on the index
INSERT after buildInserts unpacked in the append region and are read by a slower bit-sliced scan
UPDATEDelete plus insert, so the row moves to the append region
DELETE / VACUUMTombstones the row's lane; the space isn't reclaimed and the lane isn't reused
REINDEXThe only operation that repacks the append region rows and reclaims dead lanes

Limits and fixed constants

Compiled in; none is a runtime knob.

ConstantValue
Max dimensions2000 vector · 4000 halfvec
lists / probes range1–32768
Rerank pool200 rows per query
Two-level hierarchylists >= 1800
Hierarchy fan-outK_lists 1.0, K_probes 2.0
RaBitQ error-bound widthε₀ 1.9
Fast-scan block width32
On-disk format version1 (ivfplus_metapage_info(...).version)

The packing window is the one entry with an action attached. A vector index gets the packed fast-scan layout only when dim % 4 == 0 and the packed block fits one page, at the default 8 kB BLCKSZ, dim <= 1780. Outside it, the index silently runs the slower unpacked path and health reports 1.0 forever, so nothing surfaces it: vector(1536) is packed, vector(1537) and vector(1792) aren't. Pad or truncate to a multiple of 4 at or below 1780.

Index-rerank-policy builds

postgres-im-rerank is an EDB-internal Postgres fork exposing an executor index rerank policy — most installs run on stock Postgres and won't have this build; check with SELECT edb_vectorplus_rerank_policy_available(); if you're unsure. The fork lets the access method hand the executor candidates in approximate order, and the executor recomputes exact distances from the full-precision heap tuple and uses an access-method-provided policy function to decide what to return. edb_vectorplus uses this to drop the in-index f16 copy and use heap vectors for reranking.

Detection is automatic in the Makefile. At runtime, SELECT edb_vectorplus_rerank_policy_available(); shows whether the server supports the index rerank policy.

Stock Postgrespostgres-im-rerank
store_vectors optiondoesn't existpresent, defaults to off
In-index f16 copyyes, by defaultno
Index bytes/row at dim = 768~1674~128
Where rerank happensin-index, against f16in the executor, against the heap
Exact at full probeto f16 precisionexactly, at f32
edb_vectorplus_rerank_policy_available()falsetrue
edb_vectorplus_rerank_stats()not installedinstalled

store_vectors = on on a fork build restores the behavior edb_vectorplus has on a server that doesn't support the index rerank policy.

edb_vectorplus_rerank_stats(reset) returns (decisions, fetch_more, return_top), how often the policy callback fired and how it decided; passing true resets them after reading. A store_vectors = on index never invokes the policy, so its counters stay at zero.

The counters live in backend-process memory only. Treat them as a diagnostic tool: they start at zero when the backend starts, accumulate across every query and every ivfplus index that one process touches, and are gone when the connection ends — a new connection always begins at zero. They're also not per-index.