vchord 1.2.0: First Release Under PGSTY
vchord 1.2.0 is the first release maintained by PGSTY in pgsty/vchord under the AGPL-3.0 license. It supports direct in-place upgrades from vchord 0.2.0 through 1.1.1 (ALTER EXTENSION vchord UPDATE TO '1.2.0' without reindexing), fixing query-correctness bugs in vchordrq and vchordg, VACUUM vertex visibility and catalog statistics, internal and external index-build checks, quantization edge cases, and privilege checks for prewarming and query sampling.
- GitHub Release:
pgsty/vchord 1.2.0· Source Repository - Extension Catalog: pgext.cloud (
vchord) · Pigsty Extension Page (pigsty.io) - Binary Packages: RPM / DEB Repository Setup · SBOM & License Inventory
Changes in 1.2.0
| Area | Change |
|---|---|
Multi-vector MaxSim (@#) |
Under partial vchordrq.probes with maxsim_refine = 0 and maxsim_threshold = 0, documents absent from a query token’s candidate set now receive the worst distance in that candidate set instead of -Infinity, preventing missing candidates from being falsely ranked first. |
| Sphere filters & constructors | vchordg sphere operators (<<->>, <<=>>, <<#>>) compare radii in the operator’s distance units rather than squared distance and set xs_recheck = true. All six sphere(center, real) constructors bind return types at extension creation time rather than via caller search_path. |
read_stream reranking |
On PostgreSQL 17/18, vchordrq.io_rerank = 'read_stream' keeps heap buffer guards aligned with candidate index items when concurrent VACUUM shortens a page’s visible item batch. |
VACUUM & catalog stats |
vchordg excludes deleted vertices after VACUUM while keeping a deleted entry point traversable; CREATE INDEX, REINDEX, and VACUUM on vchordrq and vchordg report actual tuple and page counts instead of resetting reltuples to 0; ANALYZE alone no longer triggers index cleanup. |
| Planner & parallel builds | vchordg honors partial index WHERE predicates and adds disable_cost for non-vector scans (such as SELECT COUNT(*)); vchordrq cost estimation accounts for LIMIT, filter selectivity, and probes depth; table options like parallel_workers no longer block parallel index builds. |
| Internal & external builds | Internal k-means sampling reads only MVCC-visible rows and checks maintenance_work_mem when lists > samples; external centroid builds validate tree depth (2..=9), reject cycles and unreachable nodes, and synthesize a root when >= 2 top-level centroids have parent IS NULL. |
| Quantization types | rabitq4 and rabitq8 enforce 1..=65535 dimensions and code ranges (0..=15 / 0..=255); quantize_to_rabitq* rejects non-finite overflow while input functions accept legacy NaN/Inf metadata for pg_restore; halfvec distances accumulate in f32; quantized L2 clamps negative rounding residuals to 0 before sqrt. |
| Recall & privileges | vchordrq_evaluate_query_recall requires exact_search => true for @#, derives multi-level default accu_probes, and restores vchordrq.* GUCs; vchordrq_prewarm, vchordg_prewarm, and query sampling enforce table ownership or SELECT privileges. |
| Build & platform support | Upgraded to pgrx 0.19.3 for PostgreSQL 14–18 on Linux x86_64 and aarch64, with full in-place upgrade scripts from 0.2.0 through 1.1.1. |
Multi-vector MaxSim (@#), sphere filters, and read_stream reranking
- MaxSim (
@#) missing-value estimates under partial probes: VectorChord’s@#operator negates MaxSim similarity so smaller (more negative) values rank first in ascendingORDER BY. Under partial probes, a candidate document can appear in the probed lists for some query vectors while being absent from others. Previously, when bothvchordrq.maxsim_refine = 0andvchordrq.maxsim_threshold = 0, missing query-token hits defaulted to-Infinity(f32::NEG_INFINITY), which falsely ranked unprobed candidates first. In1.2.0, when neither refinement nor a threshold supplies an estimate, missing hits use the worst distance observed in that query token’s candidate set. vchordgsphere similarity filters (<<->>,<<=>>,<<#>>): Internally,vchordgtracks squared L2 and cosine distances during graph traversal. Previously, sphere range scans compared the user-supplied radius directly against the internal squared distance and returned index tuples without requesting a heap recheck.vchordgnow converts the radius into the operator’s distance units before pruning and setsxs_recheck = trueon returned items so PostgreSQL rechecks the exact sphere predicate on the heap tuple.search_path-safesphere(...)constructors: The sixsphere(vector, real),sphere(halfvec, real),sphere(rabitq8, real), and multi-vector array constructors are now defined as SQL-languageROW($1, $2)::sphere_*expressions whose composite return types are bound atCREATE EXTENSION/ALTER EXTENSIONtime. Callingsphere(...)works regardless of the caller’ssearch_path.- PostgreSQL 17/18
read_streamreranking: Whenvchordrq.io_rerank = 'read_stream'is active on PostgreSQL 17 or 18 and concurrentVACUUMremoves heap tuples between index scan and heap fetch, theread_streambatch consumer keeps its buffer pin guards aligned with the remaining candidate tuples rather than advancing out of sync.
Index maintenance, VACUUM, and planner cost estimation
vchordgVACUUMvisibility and entry-point reachability: AfterVACUUMmarks deleted heap TIDs in avchordgindex, graph searches exclude deleted vertices from returned results while still traversing through the graph’s entry point if that entry point was deleted, keeping surviving vertices reachable without aREINDEX.vchordgpartial indexes andCOUNT(*)protection:vchordgnow supports partial index predicates (CREATE INDEX ... USING vchordg (...) WHERE ...) and adds PostgreSQL’sdisable_costpenalty whenever a scan has no vector ordering or filter clauses, preventing the planner from choosing a graph index for plainSELECT COUNT(*)queries.- Accurate
reltuples/relpagesstatistics andANALYZEno-op: Previously,ambuildandamvacuumcleanupreturned zeroedIndexBuildResult/IndexVacuumInfocounters, which causedCREATE INDEX,REINDEX, andVACUUMto overwritepg_class.reltupleswith0. Bothvchordrqandvchordgnow report actual indexed tuple and page counts. WhenANALYZEruns (analyze_only = true),amvacuumcleanupreturns immediately without running index compaction or page cleanup. - Parallel index build planning: Setting table storage options such as
ALTER TABLE items SET (parallel_workers = 4)previously causedamoptionsvalidation to reject the table’s reloptions when planning parallel index builds. Bothvchordrqandvchordgnow validate only index-level reloptions so parallel worker planning proceeds normally. vchordrqcost model:amcostestimateforvchordrqnow incorporates the query’sLIMIT, filter selectivity, and the effectiveprobesbudget when estimating filtered vector scans, and addsdisable_costwhen a multi-levelvchordrq.probessetting does not match the number of levels in the index’slistshierarchy.
Internal and external index build validation
- MVCC-visible k-means sampling and memory checks: Internal k-means clustering checks
HeapTupleSatisfiesVisibilitybefore sampling a heap tuple so dead or uncommitted tuples do not skew centroids. The initial sampling buffer is sized from the estimated live row count (capped atmax(lists) * sampling_factor), and centroid buffer allocations are checked againstmaintenance_work_memeven whenlistsexceeds the number of sampled rows. - External centroid tree validation: When building a
vchordrqindex from an external centroid table ([build.external]),vchordvalidates theid/parenthierarchy before partitioning vectors:- The tree depth must be between
2and9levels (including the root), matching the1..=8listslevels supported byvchordrq. - Cyclic
parentchains and disconnected nodes are rejected with a descriptive error. - If two or more top-level centroids have
parent IS NULL,vchordrqautomatically synthesizes a root node above them so flat or multi-root centroid tables do not need a manual dummy root row.
- The tree depth must be between
- Strict
probesandalphavalidation: BothSET vchordrq.probesand the per-indexprobesstorage option validate every comma-separated integer (1..=4294967295, permitting surrounding whitespace and at most one trailing comma) and reject invalid strings immediately. Forvchordg, thealphabuild option must contain1..=8non-decreasing floats in[1.0, 2.0)whose first element is1.0.
Quantization types (rabitq4, rabitq8, and halfvec)
- Dimensions and code ranges:
rabitq4andrabitq8enforce1..=65535dimensions (typmodvalidation and input parsing) and verify that every quantized code lies in0..=15(rabitq4) or0..=255(rabitq8), rejecting trailing tokens or malformed brackets. - Overflow rejection with
pg_dump/pg_restorecompatibility:quantize_to_rabitq4andquantize_to_rabitq8reject input vectors whose norm or dot product overflowsf32(NaN/Infinity), and produce finite metadata for zero or constant vectors. At the same time, therabitq4andrabitq8text and binary input functions still accept legacyNaN/Infmetadata fields so existing backups created under1.1.xrestore without error. f32accumulation forhalfvecand clamped L2 roots:halfvecdistance operators (<->,<=>,<#>) now accumulate inf32rather thanf16so large or high-dimensionalhalfvecvalues do not overflow toInfinity. Quantized L2 distance functions clamp tiny negative floating-point rounding residuals to0.0before callingsqrt, eliminating spuriousNaNresults on near-identical vectors.
Recall evaluation and prewarm privileges
vchordrq_evaluate_query_recallimprovements:- Multi-vector MaxSim (
@#) queries now requireexact_search => true(attempting approximate ground-truth probing with@#raises an error explaining whyexact_search => trueis required). - When
accu_probesis omitted,vchordrq_evaluate_query_recallgenerates a65535entry for each level of a multi-levellistsindex (for example,'65535,65535'on a 2-level index) instead of a single'65535'value that would fail level-count validation. - Temporary changes to
vchordrq.probes,vchordrq.epsilon, andvchordrq.enable_scaninsidevchordrq_evaluate_query_recallare restored to their prior values and GUC sources (RESETwhen previously unset) even if the query errors out.
- Multi-vector MaxSim (
- Prewarm and query-sampling privilege checks:
vchordrq_prewarm(regclass, integer)andvchordg_prewarm(regclass)verify that the calling role owns the underlying table or holds table-levelSELECTprivilege before warming index pages into shared buffers.- Enabling or configuring query sampling (
vchordrq.query_sampling_*) checks that the role owns the table, holds table-levelSELECT, or is a member ofpg_read_all_stats. Similarly, readingvchordrq_sampled_queriesfilters out recorded queries from indexes whose underlying tables the current role is not allowed to read.
Known issues
Package QA for 1.2.0 found two cases where results can come back out of distance order. Both are tracked in pgsty/vchord#17 for a later release. Recall is not affected.
vchordgcan returnORDER BY ... LIMITrows out of distance order, because it keeps searching while it returns rows. A largervchordg.ef_searchmakes this rarer but does not rule it out.- On
halfveccolumns, rows whose distances differ by about 0.0001 or less can be swapped. This affectsvchordrqwithhalfvec_cosine_opsandvchordgwith anyhalfvecoperator class.
When strict order matters, re-sort the candidates in an outer query; see Graph Index: Result order. The GitHub release lists the other known issues.
Upgrading to 1.2.0 and packages
vchord 1.2.0 is binary- and on-disk-compatible with indexes created in 0.2.0 through 1.1.1. Install the 1.2.0 package for your PostgreSQL version, restart PostgreSQL so the updated vchord.so is loaded via shared_preload_libraries, and run:
Direct upgrade scripts are shipped for every historical version from 0.2.0 through 1.1.1, and the shared library retains C entry-point compatibility wrappers (_vchord_0_3_* and _vchord_0_4_*) so databases still running pre-1.0.0 SQL catalog definitions can execute ALTER EXTENSION vchord UPDATE TO '1.2.0' without error.
Native RPM and DEB packages (1.2.0-1PIGSTY / 1.2.0-1PGSTY) for PostgreSQL 14–18 across 16 Linux distributions on x86_64 and aarch64 are available on the Download page. See the Upgrade & Migration Guide, Indexing Guide, Quantization Types Guide, and SBOM page for complete details.