Contents
- SQL API Reference
- Compaction
- Zone map
- Monitoring
- Trigger
- Scan pruning
- Configuration (GUCs)
- Vector search
- sorted_hnsw Index AM
- Training permissions
- svec_ann_train(source_query, nlist, m, n_iter, max_samples)
- svec_pq_train_residual(source_query, m, ivf_cb_id, n_iter, max_samples)
- svec_ann_scan(tbl, query, nprobe, lim, rerank_topk, cb_id, ivf_cb_id, pq_column)
- svec_ann_search(tbl, query, nprobe, lim, rerank_topk, cb_id)
- svec_ivf_assign(vec, cb_id)
- svec_ivf_probe(vec, nprobe, cb_id)
- svec_pq_encode(vec, cb_id) / svec_pq_encode_residual(vec, centroid_id, pq_cb_id, ivf_cb_id)
- GraphRAG
- sorted_heap_graph_register(rel, entity_column, relation_column, target_column, embedding_column, payload_column)
- sorted_heap_graph_config(rel)
- sorted_heap_graph_unregister(rel)
- sorted_heap_graph_rag_stats()
- sorted_heap_graph_rag_reset_stats()
- sorted_heap_graph_route_last_stats()
- sorted_heap_graph_rag(rel, query, relation_path, ann_k, top_k, score_mode, limit_rows)
- sorted_heap_graph_rag_segmented(rels, query, relation_path, ann_k, top_k, score_mode, limit_rows)
- sorted_heap_graph_segment_meta_register(rel, segment_group, relation_family, segment_labels)
- sorted_heap_graph_segment_meta_config(rel)
- sorted_heap_graph_segment_meta_unregister(rel)
- sorted_heap_graph_segment_register(route_name, rel, route_min, route_max, segment_group, relation_family)
- sorted_heap_graph_segment_config(route_name, segment_groups, relation_family, segment_labels)
- sorted_heap_graph_segment_catalog(route_name, segment_groups, relation_family, segment_labels)
- sorted_heap_graph_segment_resolve(route_name, route_value, fanout_limit, segment_groups, relation_family, segment_labels)
- sorted_heap_graph_segment_unregister(route_name, rel)
- sorted_heap_graph_route_policy_register(route_name, policy_name, segment_groups)
- sorted_heap_graph_route_policy_config(route_name, policy_name)
- sorted_heap_graph_route_policy_groups(route_name, policy_name)
- sorted_heap_graph_route_policy_unregister(route_name, policy_name)
- sorted_heap_graph_route_profile_register(route_name, profile_name, policy_name, segment_groups, relation_family, fanout_limit, segment_labels)
- sorted_heap_graph_route_profile_config(route_name, profile_name)
- sorted_heap_graph_route_profile_resolve(route_name, profile_name)
- sorted_heap_graph_route_profile_unregister(route_name, profile_name)
- sorted_heap_graph_route_default_register(route_name, profile_name)
- sorted_heap_graph_route_default_config(route_name)
- sorted_heap_graph_route_default_resolve(route_name)
- sorted_heap_graph_route_profile_catalog(route_name, profile_name)
- sorted_heap_graph_route_catalog(route_name)
- sorted_heap_graph_route_default_unregister(route_name)
- sorted_heap_graph_rag_routed(route_name, route_value, query, relation_path, ann_k, top_k, score_mode, limit_rows, fanout_limit, segment_groups, relation_family, segment_labels)
- sorted_heap_graph_rag_routed_policy(route_name, route_value, policy_name, query, relation_path, ann_k, top_k, score_mode, limit_rows, fanout_limit, relation_family, segment_labels)
- sorted_heap_graph_rag_routed_profile(route_name, route_value, profile_name, query, relation_path, ann_k, top_k, score_mode, limit_rows)
- sorted_heap_graph_rag_routed_default(route_name, route_value, query, relation_path, ann_k, top_k, score_mode, limit_rows)
- sorted_heap_graph_route(route_name, query, relation_path, route_key, route_value, profile_name, policy_name, ann_k, top_k, score_mode, limit_rows, fanout_limit, segment_groups, relation_family, segment_labels)
- sorted_heap_graph_route_plan(route_name, route_key, route_value, profile_name, policy_name, fanout_limit, segment_groups, relation_family, segment_labels)
- sorted_heap_graph_exact_register(route_name, route_key, rel, priority, segment_group, relation_family)
- sorted_heap_graph_exact_config(route_name, route_key, segment_groups, relation_family, segment_labels)
- sorted_heap_graph_exact_catalog(route_name, route_key, segment_groups, relation_family, segment_labels)
- sorted_heap_graph_exact_resolve(route_name, route_key, fanout_limit, segment_groups, relation_family, segment_labels)
- sorted_heap_graph_exact_unregister(route_name, route_key, rel)
- sorted_heap_graph_rag_routed_exact(route_name, route_key, query, relation_path, ann_k, top_k, score_mode, limit_rows, fanout_limit, segment_groups, relation_family, segment_labels)
- sorted_heap_graph_rag_routed_exact_policy(route_name, route_key, policy_name, query, relation_path, ann_k, top_k, score_mode, limit_rows, fanout_limit, relation_family, segment_labels)
- sorted_heap_graph_rag_routed_exact_profile(route_name, route_key, profile_name, query, relation_path, ann_k, top_k, score_mode, limit_rows)
- sorted_heap_graph_rag_routed_exact_default(route_name, route_key, query, relation_path, ann_k, top_k, score_mode, limit_rows)
- Lower-level GraphRAG building blocks (beta)
- sorted_heap_expand_ids(rel, seed_ids, relation_filter, limit_rows)
- sorted_heap_expand_rerank(rel, seed_ids, query, top_k, relation_filter, limit_rows)
- sorted_heap_expand_twohop_rerank(rel, seed_ids, query, top_k, hop1_relation_filter, hop2_relation_filter, limit_rows)
- sorted_heap_expand_twohop_path_rerank(rel, seed_ids, query, top_k, hop1_relation_filter, hop2_relation_filter, limit_rows)
- sorted_heap_graph_rag_scan(rel, query, ann_k, top_k, relation_filter, limit_rows)
- sorted_heap_graph_rag_twohop_scan(rel, query, ann_k, top_k, hop1_relation_filter, hop2_relation_filter, limit_rows)
- sorted_heap_graph_rag_twohop_path_scan(rel, query, ann_k, top_k, hop1_relation_filter, hop2_relation_filter, limit_rows)
- sorted_heap_expand_multihop_rerank(rel, seed_ids, query, top_k, relation_path, limit_rows)
- sorted_heap_expand_multihop_path_rerank(rel, seed_ids, query, top_k, relation_path, limit_rows)
- sorted_heap_graph_rag_multihop_scan(rel, query, ann_k, top_k, relation_path, limit_rows)
- sorted_heap_graph_rag_multihop_path_scan(rel, query, ann_k, top_k, relation_path, limit_rows)
- Routed GraphRAG: operator recipe
layout: default title: SQL API
nav_order: 5
SQL API Reference
Compaction
sorted_heap_compact(regclass)
Rewrites the table in globally sorted PK order and rebuilds the zone map.
Acquires AccessExclusiveLock.
SELECT sorted_heap_compact('events'::regclass);
sorted_heap_compact_online(regclass)
Non-blocking compaction using trigger-based change capture. Concurrent reads and writes continue during the operation.
CALL sorted_heap_compact_online('events'::regclass);
sorted_heap_merge(regclass)
Incremental merge: detects the already-sorted prefix and only re-sorts the
unsorted tail. 50–90% faster than full compact when data is partially sorted.
Acquires AccessExclusiveLock.
SELECT sorted_heap_merge('events'::regclass);
sorted_heap_merge_online(regclass)
Non-blocking variant of merge with the same three-phase approach as
sorted_heap_compact_online.
CALL sorted_heap_merge_online('events'::regclass);
sorted_heap_bulk_load_ordered(regclass, text, text, boolean default false, name[] default NULL)
Explicit trusted-operator bulk ingestion helper. It inserts rows from
source_sql into a concrete sorted_heap or clustered_heap table after
ordering the source by order_by.
SELECT sorted_heap_bulk_load_ordered(
'events'::regclass,
'SELECT id, payload FROM staging_events',
'id',
analyze_after := true,
key_columns := ARRAY['id']::name[]);
This is an ingestion-locality helper, not a merge/compaction input. source_sql
and order_by are dynamic SQL fragments and should be supplied only by trusted
operators or migration code. The source query must return the target table
columns in target order.
The helper records an observational append-run witness. Inspect it with:
SELECT *
FROM sorted_heap_append_run_status('events'::regclass);
SELECT *
FROM sorted_heap_append_run_plan('events'::regclass);
is_current = false means the relation filenode changed or the witness was
otherwise marked invalid. Current releases do not consume these witnesses for
merge/compaction. The witness includes the inserted heap block range, row count,
rows per touched heap block, and first/last source rows under the requested
ordering. If key_columns is supplied, the witness also includes normalized
bigint[] first/last keys for one or two integer key columns.
Use sorted_heap_append_run_invalidate(...) to mark witnesses invalid and
sorted_heap_append_run_cleanup(...) to delete invalid, relfilenode-stale, or
relation-orphaned rows.
Partition-scoped maintenance
For declarative partitioned tables, use the explicit parent helpers instead of calling the concrete-table functions on the parent. They recurse to concrete leaves and operate one leaf at a time.
SELECT *
FROM sorted_heap_partition_status('events_parent'::regclass);
SELECT *
FROM sorted_heap_partition_index_status('events_parent'::regclass);
SELECT *
FROM sorted_heap_compact_partitions('events_parent'::regclass);
SELECT *
FROM sorted_heap_merge_partitions('events_parent'::regclass);
SELECT *
FROM sorted_heap_rebuild_zonemap_partitions('events_parent'::regclass);
Before running a maintenance operation on a large partition tree, use the read-only plan helper:
SELECT *
FROM sorted_heap_partition_maintenance_plan(
'events_parent'::regclass,
operation := 'compact');
The plan returns one row per concrete leaf with:
status = 'would_run'for supported sorted_heap leaves with a primary key;status = 'blocked'for unsupported leaves or sorted_heap leaves without a primary key;lock_mode, currentlyAccessExclusiveLockfor the concrete operation;tablespace_oid,tablespace_name, andtablespace_location, so operators can join the rewrite estimate to their tablespace or filesystem monitoring;relation_size_bytesandestimated_temp_bytes. Forcompactandmerge, the temp estimate is the current leaf size; forrebuild_zonemap, heap rewrite temp is0.
By default, maintenance helpers fail before doing work if any leaf is not a
sorted_heap table or if a sorted_heap leaf has no primary key. Pass false
as the second argument to explicitly skip unsupported leaves:
SELECT *
FROM sorted_heap_compact_partitions('events_parent'::regclass, false);
Locking and disk-space model:
sorted_heap_compact_partitions(...)callssorted_heap_compact(...)on each leaf, so each processed leaf takesAccessExclusiveLockwhile it is rewritten.sorted_heap_merge_partitions(...)callssorted_heap_merge(...)on each leaf and has the same lock class.- The rewrite needs temporary disk headroom for the leaf being processed, not for the whole logical parent at once.
- PostgreSQL does not expose a portable SQL-level free-space metric; use the reported tablespace fields with OS or platform monitoring when checking available bytes.
- These helpers are not atomic across all leaves. If a later leaf fails, already processed leaves remain processed.
- See Huge-Table Compaction Operating Model for the detailed rewrite/free-space contract.
sorted_heap_partition_index_status(parent) returns one row per leaf index
for a partitioned parent or concrete sorted_heap table. It reports the index AM,
valid/ready/live flags, primary/unique flags, and convenience booleans for
sorted_hnsw and btree indexes. Leaves without indexes are still represented
with NULL index fields, so the output can be used as a health checklist.
Manual lock-behavior smoke:
make test-partition-lock
Partition-aware vector search
Use sorted_hnsw_partition_search(...) when vector search should run across a
partitioned parent or an explicit subset of leaves:
SELECT leaf_relid, distance, row_data
FROM sorted_hnsw_partition_search(
'documents_parent'::regclass,
'embedding',
'[0.1,0.2,0.3,...]',
top_k := 10,
local_k := 32,
leaf_relids := ARRAY['documents_2026_05'::regclass]);
Contract:
- each selected leaf runs local
ORDER BY embedding <=> query LIMIT local_k; - the helper unions local candidate pools and globally reranks by exact distance;
local_kmust be at leasttop_k, otherwise a dense leaf could contribute more true global winners than the local pool preserves;local_kmust be no larger thansorted_hnsw.ef_search, so each local scan remains inside the planner-integratedsorted_hnswcontract;- each selected sorted_heap leaf must have a valid, ready, non-expression,
non-partial leaf-local
sorted_hnswindex onvector_column; the helper fails closed instead of silently falling back to exact sort; - every explicit
leaf_relidsentry must be a concrete leaf underparent; wrong-parent relations are rejected instead of being ignored; leaf_relidsis optional. When omitted, all supported sorted_heap leaves under the parent participate.
The query argument is text and is cast to the actual vector type of each leaf
column (svec or hsvec). The result is intentionally generic:
row_data jsonb carries the source row because partitioned parents can have
arbitrary table shapes.
Use sorted_hnsw_partition_search_status(...) when the caller needs explicit
underfill metadata for the same routed search contract:
SELECT *
FROM sorted_hnsw_partition_search_status(
'documents_parent'::regclass,
'embedding',
'[0.1,0.2,0.3,...]',
top_k := 10,
local_k := 32,
leaf_relids := ARRAY['documents_2026_05'::regclass]);
Returned fields:
requested_top_k: requested final result counteffective_local_k: per-leaf candidate budget after applying the defaultselected_leaves: supported sorted_heap leaves participating in the callreturned_rows: rows returned by the routed searchunderfilled: true whenreturned_rows < requested_top_kfallback:none,underfilled_no_fallback, orexact_filteredwhenexact_fallback := truewas requested and the ANN candidate pool underfilled before the exact selected-leaf pass
By default, the status helper is diagnostic and does not change the row-returning
search API. If callers explicitly pass exact_fallback := true to
sorted_hnsw_partition_search(...) or
sorted_hnsw_partition_search_status(...), an underfilled ANN candidate pool is
replaced with an exact rerank over the same selected leaves. This fallback is
bounded by shard selection, not by arbitrary base-table WHERE clauses.
Zone map
sorted_heap_zonemap_stats(regclass)
Returns a text summary of the zone map: format version, number of entries, validity flags, and overflow page chain.
SELECT sorted_heap_zonemap_stats('events'::regclass);
Example output:
v6 nentries=1946 flags=valid,sorted overflow_pages=7
sorted_heap_zonemap_may_match_int8(regclass, bigint, bigint)
Returns false only when valid zone-map metadata proves that the first-key
int8 range cannot match any tracked heap page. It returns true when the
range may match, and also fail-opens to true when metadata is stale or the
table/key shape is unsupported.
This is a metadata-only empty-result probe. It does not return rows and does not bypass heap visibility or executor quals.
SELECT sorted_heap_zonemap_may_match_int8('events'::regclass, 100, 200);
sorted_heap_rebuild_zonemap(regclass)
Forces a full zone map rebuild by scanning all tuples. Useful after bulk operations that invalidate the zone map.
SELECT sorted_heap_rebuild_zonemap('events'::regclass);
Monitoring
sorted_heap_scan_stats()
Returns scan statistics as a record: total scans, blocks scanned, blocks pruned, and stats source (shared memory or per-backend).
SELECT * FROM sorted_heap_scan_stats();
total_scans | blocks_scanned | blocks_pruned | source
-------------+----------------+---------------+---------
142 | 284 | 276012 | shmem
These counters are not keyed by relation. For partitioned deployments, use
sorted_heap_partition_status(...) and
sorted_heap_partition_index_status(...) for per-leaf state; do not interpret
sorted_heap_scan_stats() as parent-level runtime telemetry. The future
runtime-observability contract is tracked in
Parent Runtime Observability.
sorted_heap_scan_stats_by_relation()
Returns scan statistics keyed by concrete relation:
SELECT *
FROM sorted_heap_scan_stats_by_relation();
Returned fields:
relid: concrete relation that executedSortedHeapScanrelname: current relation name, orNULLif the relation was dropped after the counter was recordedtotal_scans: number ofSortedHeapScanexecutions for this relationblocks_scanned: heap blocks visited by those scansblocks_pruned: heap blocks skipped by zone-map pruningsource:sharedwhenpg_sorted_heapis loaded throughshared_preload_libraries, otherwiselocal
This is the first relation-aware runtime surface. Without preload it is
backend-local and useful for same-backend diagnostics. With
shared_preload_libraries = 'pg_sorted_heap', it is cluster-wide shared
telemetry. In both modes it is reset by sorted_heap_reset_stats().
The shared relation table tracks up to 4,096 concrete relations per reset
window; aggregate sorted_heap_scan_stats() counters continue to count all
scans even if that relation table is exhausted.
sorted_heap_partition_scan_stats(parent)
Returns scan statistics for sorted_heap leaves under a partitioned parent or concrete sorted_heap table:
SELECT *
FROM sorted_heap_partition_scan_stats('events_parent'::regclass);
The helper joins sorted_heap_partition_status(parent) to
sorted_heap_scan_stats_by_relation(). It returns one row per sorted_heap leaf,
with zero counters for leaves that have not executed SortedHeapScan in the
current stats window. source follows sorted_heap_scan_stats_by_relation():
shared with preload, otherwise local.
sorted_heap_restore_plan(parent default NULL)
Returns a post-restore maintenance checklist for sorted_heap relations:
SELECT *
FROM sorted_heap_restore_plan();
SELECT *
FROM sorted_heap_restore_plan('events_parent'::regclass);
When called without an argument, it scans all concrete sorted_heap relations in the database. With a concrete sorted_heap table or partitioned parent argument, it reports only that table or its sorted_heap leaves.
Returned fields include:
zone_map_valid,zone_map_sorted, andsorted_prefix_pagessorted_hnsw_indexesneeds_compact: true when compact/merge is needed to restore zone-map pruningpost_restore_hnsw_rebuild_recommended: true when sorted_hnsw indexes exist; afterpg_restore, rebuild them because heap TIDs changedrecommended_action:none,compact_or_merge,rebuild_sorted_hnsw_after_pg_restore, orcompact_or_merge_and_rebuild_sorted_hnsw
sorted_heap_reset_stats()
Resets the scan statistics counters.
SELECT sorted_heap_reset_stats();
Trigger
sorted_heap_compact_trigger()
A trigger function used internally by the online compact/merge procedures to capture changes during non-blocking operations. Not intended for direct use.
Scan pruning
IN / ANY support
SortedHeapScan prunes blocks for IN and = ANY(array) queries on the
leading primary key column. Each block’s zone map entry is checked against
the sorted list of target values using O(log K) binary search — blocks that
contain no matching values are skipped entirely.
Both literal arrays and parameterized arrays (generic prepared statements) are supported, including LATERAL/NestLoop runtime parameters.
-- Literal IN-list — pruned at plan time
SELECT * FROM events WHERE id IN (100, 200, 300);
-- Literal ANY — same pruning
SELECT * FROM events WHERE id = ANY(ARRAY[100, 200, 300]);
-- Generic prepared statement — pruned at execution time
PREPARE q(int[]) AS SELECT * FROM events WHERE id = ANY($1);
SET plan_cache_mode = force_generic_plan;
EXECUTE q(ARRAY[100, 200, 300]);
-- LATERAL join with runtime array — pruned per outer row
SELECT o.i, s.cnt
FROM (SELECT g AS i, make_arr(g) AS arr FROM generate_series(1,10) g) o
CROSS JOIN LATERAL (
SELECT count(*) AS cnt FROM events WHERE id = ANY(o.arr)
) s;
The scan computes a bounding box (min/max of the array values) to limit the block range, then applies per-block IN-value filtering within that range.
For LATERAL/NestLoop joins, runtime array parameters (PARAM_EXEC) are
resolved at first rescan when outer values become available.
Configuration (GUCs)
sorted_heap.enable_scan_pruning
| Property | Value |
|---|---|
| Type | boolean |
| Default | on |
| Context | user (SET) |
Enables or disables zone map scan pruning for sorted_heap tables. When disabled, queries fall back to sequential scan.
SET sorted_heap.enable_scan_pruning = off;
sorted_heap.vacuum_rebuild_zonemap
| Property | Value |
|---|---|
| Type | boolean |
| Default | on |
| Context | user (SET) |
When enabled, VACUUM automatically rebuilds an invalid zone map, re-enabling scan pruning without a manual compact step.
SET sorted_heap.vacuum_rebuild_zonemap = off;
sorted_heap.lazy_update
| Property | Value |
|---|---|
| Type | boolean |
| Default | off |
| Context | user (SET, SET LOCAL, ALTER SYSTEM) |
When enabled, the first UPDATE on a covered page invalidates the zone map on disk. All subsequent UPDATEs skip zone map maintenance entirely. The planner falls back to Index Scan. INSERT always uses eager maintenance regardless. Compact or merge restores zone map pruning.
-- Per session
SET sorted_heap.lazy_update = on;
-- Per transaction
SET LOCAL sorted_heap.lazy_update = on;
-- Globally
ALTER SYSTEM SET sorted_heap.lazy_update = on;
SELECT pg_reload_conf();
sorted_heap.hnsw_cache_l0
| Property | Value |
|---|---|
| Type | boolean |
| Default | off |
| Context | user (SET) |
Enables session-local cache for HNSW sidecar tables. On first
svec_hnsw_scan call, L0 is loaded via sequential scan (~95ms build,
~100 MB for 100K nodes). Upper levels (L1–L4) cached separately (~6 MB).
Cache is evicted on DDL (relcache invalidation).
SET sorted_heap.hnsw_cache_l0 = on;
sorted_heap.hnsw_ef_patience
| Property | Value |
|---|---|
| Type | integer |
| Default | 0 (disabled) |
| Context | user (SET) |
Patience-based early termination for L0 beam search. Applies to both
svec_hnsw_scan() and sorted_hnsw ordered index scans. When set to N > 0,
the search stops after N consecutive expansions that don’t improve the result
set. ef_search becomes the maximum budget.
SET sorted_heap.hnsw_ef_patience = 20;
sorted_hnsw.ef_search
| Property | Value |
|---|---|
| Type | integer |
| Default | 96 |
| Context | user (SET) |
Beam width for planner-integrated sorted_hnsw ordered index scans.
Higher values increase candidate exploration and usually improve recall at the
cost of latency.
SET sorted_hnsw.ef_search = 128;
sorted_hnsw.sq8
| Property | Value |
|---|---|
| Type | boolean |
| Default | on |
| Context | user (SET) |
Controls SQ8 quantization in sorted_hnsw L0 storage and scan distance
evaluation. Leave this enabled for the current release path unless you are
doing low-level experiments.
SET sorted_hnsw.sq8 = on;
sorted_hnsw.build_sq8
| Property | Value |
|---|---|
| Type | boolean |
| Default | off |
| Context | user (SET) |
Builds sorted_hnsw from SQ8-compressed build vectors instead of keeping a
full float32 build slab resident. This lowers build-time memory materially for
large indexes at the cost of an extra heap scan and possible graph-quality
loss on some corpora.
The narrow verified point so far:
- local
1M x 64Dmultidepth build (m=16,ef_construction=64) build_indexes:48.606 s -> 46.541 s- depth-5 unified GraphRAG stayed
87.5% / 100.0%
The memory saving is by construction:
- float32 build slab:
4 * N * Dbytes - SQ8 build slab:
1 * N * Dbytes
So, for example:
10M x 64D: about2.56 GiB -> 0.64 GiB10M x 384D: about15.36 GiB -> 3.84 GiB
SET sorted_hnsw.build_sq8 = on;
sorted_hnsw.shared_cache
| Property | Value |
|---|---|
| Type | boolean |
| Default | on |
| Context | user (SET) |
Enables the preloaded shared decoded scan cache for ordered sorted_hnsw
index scans. Effective only when pg_sorted_heap is loaded via
shared_preload_libraries; otherwise scans fall back to backend-local cache
builds. When active, fresh backends can attach to a shared immutable snapshot
keyed by {relid, relfilenode, cache_gen} instead of rebuilding the decoded
graph privately.
SET sorted_hnsw.shared_cache = on;
sorted_hnsw_scan_stats()
Backend-local scan diagnostics for planner-integrated sorted_hnsw ordered
index scans. Use this while tuning or benchmarking to distinguish normal ANN
latency from the fail-closed exact heap fallback used when ANN/top-up rerank
underfills the configured sorted_hnsw.ef_search budget.
SELECT sorted_hnsw_reset_stats();
SELECT id
FROM items
ORDER BY embedding <=> '[0.1,0.2,0.3,...]'::svec
LIMIT 10;
SELECT sorted_hnsw_scan_stats();
The text output includes cumulative calls, l0_searches,
topup_searches, exact_fallbacks, and last-scan fields such as ef,
nodes, l0_candidates, initial_results, topup_results,
fallback_results, final_results, and exact_fallback.
sorted_heap.ann_timing
| Property | Value |
|---|---|
| Type | boolean |
| Default | off |
| Context | user (SET) |
Enables per-query timing breakdown for svec_ann_scan, svec_graph_scan,
and svec_hnsw_scan. Output is emitted at DEBUG1 log level.
SET sorted_heap.ann_timing = on;
SET client_min_messages = debug1;
Vector search
See the Vector Search guide for a full tutorial.
sorted_hnsw Index AM
The stable ANN path in this release is the planner-integrated HNSW access
method. It supports both svec and hsvec.
CREATE TABLE items (
id bigserial PRIMARY KEY,
embedding svec(384),
body text
);
CREATE INDEX items_embedding_idx
ON items USING sorted_hnsw (embedding)
WITH (m = 16, ef_construction = 200);
SET sorted_hnsw.ef_search = 32;
SELECT id, body
FROM items
ORDER BY embedding <=> '[0.1,0.2,0.3,...]'::svec
LIMIT 10;
Compact-storage variant:
CREATE TABLE items_compact (
id bigserial PRIMARY KEY,
embedding hsvec(384),
body text
);
CREATE INDEX items_compact_embedding_idx
ON items_compact USING sorted_hnsw (embedding hsvec_cosine_ops)
WITH (m = 16, ef_construction = 200);
Current contract:
- planner-integrated ordered scan for base-relation
ORDER BY embedding <=> query LIMIT k - not chosen when there is no
LIMIT, whenLIMIT > sorted_hnsw.ef_search, or when extra base-table quals would make the path under-return candidates - exact rerank happens inside the index scan
sorted_hnsw.shared_cache = onis most useful whenshared_preload_libraries = 'pg_sorted_heap'
Training permissions
All training functions (svec_ann_train, svec_pq_train, svec_pq_train_residual,
svec_ivf_train) create internal metadata tables in the extension schema on first
call. The calling role must have CREATE privilege on the extension schema, or be
the extension owner / superuser.
svec_ann_train(source_query, nlist, m, n_iter, max_samples)
Trains both IVF centroids and raw PQ codebook from a SQL query returning
svec vectors. Returns (ivf_cb_id, pq_cb_id).
SELECT * FROM svec_ann_train(
'SELECT embedding FROM my_table',
nlist := 64, m := 192);
svec_pq_train_residual(source_query, m, ivf_cb_id, n_iter, max_samples)
Trains a residual PQ codebook on (vector − nearest IVF centroid) residuals.
Higher recall than raw PQ at no additional storage cost.
SELECT svec_pq_train_residual(
'SELECT embedding FROM my_table',
m := 192, ivf_cb_id := 1);
svec_ann_scan(tbl, query, nprobe, lim, rerank_topk, cb_id, ivf_cb_id, pq_column)
C-level IVF-PQ scan — fastest path. Performs IVF probe, PQ ADC, top-K selection, and optional exact cosine reranking in a single C function call.
-- PQ-only (fastest)
SELECT * FROM svec_ann_scan('my_table', query_vec,
nprobe := 3, lim := 10, cb_id := 2, ivf_cb_id := 1);
-- With exact reranking
SELECT * FROM svec_ann_scan('my_table', query_vec,
nprobe := 10, lim := 10, rerank_topk := 200,
cb_id := 2, ivf_cb_id := 1);
| Parameter | Default | Description |
|---|---|---|
| tbl | — | Table name (regclass) |
| query | — | Query vector (svec) |
| nprobe | 10 | Number of IVF partitions to probe |
| lim | 10 | Number of results to return |
| rerank_topk | 0 | If > 0, rerank this many PQ candidates with exact cosine |
| cb_id | 1 | PQ codebook ID |
| ivf_cb_id | 0 | IVF codebook ID (> 0 enables residual PQ mode) |
| pq_column | ‘pq_code’ | Name of the PQ code column |
svec_ann_search(tbl, query, nprobe, lim, rerank_topk, cb_id)
SQL-level IVF-PQ search. Same interface as svec_ann_scan but implemented in
PL/pgSQL. Useful for debugging and when svec_ann_scan is not available.
svec_ivf_assign(vec, cb_id)
Returns the nearest IVF centroid ID for a vector. Used in generated columns to assign rows to partitions.
svec_ivf_probe(vec, nprobe, cb_id)
Returns an array of the nprobe nearest IVF centroid IDs. Used in WHERE
clauses to filter candidates.
svec_pq_encode(vec, cb_id) / svec_pq_encode_residual(vec, centroid_id, pq_cb_id, ivf_cb_id)
Encode a vector as an M-byte PQ code. The residual variant encodes
(vec − centroid) for use with residual PQ codebooks.
GraphRAG
The stable 0.13 GraphRAG surface is intentionally narrow: fact-shaped
retrieval over a sorted_heap table clustered by
(entity_id, relation_id, target_id), or by an equivalent registered alias
mapping.
Stable API:
sorted_heap_graph_rag(...)sorted_heap_graph_register(...)sorted_heap_graph_config(...)sorted_heap_graph_unregister(...)sorted_heap_graph_rag_stats()sorted_heap_graph_rag_reset_stats()
Beta API:
sorted_heap_expand_ids(...)sorted_heap_expand_rerank(...)sorted_heap_expand_twohop_rerank(...)sorted_heap_expand_twohop_path_rerank(...)sorted_heap_expand_multihop_rerank(...)sorted_heap_expand_multihop_path_rerank(...)sorted_heap_graph_rag_scan(...)sorted_heap_graph_rag_twohop_scan(...)sorted_heap_graph_rag_twohop_path_scan(...)sorted_heap_graph_rag_multihop_scan(...)sorted_heap_graph_rag_multihop_path_scan(...)
The stable contract covers fact-shaped retrieval with explicit per-hop relation sequences. Broader code-corpus snippet/symbol/lexical retrieval recipes remain benchmark-side reference logic, not SQL-stable product surface.
Recommended schema shape:
CREATE TABLE facts (
entity_id int4,
relation_id int2,
target_id int4,
embedding svec(384),
payload text,
PRIMARY KEY (entity_id, relation_id, target_id)
) USING sorted_heap;
CREATE INDEX facts_embedding_idx
ON facts USING sorted_hnsw (embedding)
WITH (m = 24, ef_construction = 200);
Canonical fact columns work out of the box. If a fact table uses different names, register the mapping once and then keep using the same GraphRAG API.
sorted_heap_graph_register(rel, entity_column, relation_column, target_column, embedding_column, payload_column)
Registers a non-canonical fact-table schema for GraphRAG helpers and wrappers.
All mapped columns must still be type-compatible with the fact-graph contract:
int4 / int2 / int4 / svec / text.
SELECT sorted_heap_graph_register(
'facts_alias'::regclass,
entity_column := 'src_id',
relation_column := 'edge_type',
target_column := 'dst_id',
embedding_column := 'vec',
payload_column := 'body'
);
sorted_heap_graph_config(rel)
Returns the current GraphRAG mapping for a relation, including whether it was explicitly registered or is still using the canonical default names.
sorted_heap_graph_unregister(rel)
Removes a previously registered mapping and restores the canonical-name defaults for that relation.
sorted_heap_graph_rag_stats()
Returns backend-local statistics for the last GraphRAG helper or wrapper call. This is intended for stage-level observability while tuning seed breadth, expansion width, and rerank cost.
Returned fields:
calls: number of top-level GraphRAG calls seen by the current backendapi: concrete GraphRAG execution path for the last callseed_count: ANN or explicit seed countexpanded_rows: rows collected during graph expansionreranked_rows: rows considered by the exact rerank stagereturned_rows: rows emitted to the callerann_ms: ANN seed stage time in millisecondsexpand_ms: graph expansion time in millisecondsrerank_ms: final rerank / emit time in millisecondstotal_ms: aggregate of the recorded stages
api reports the top-level C execution path, not necessarily the SQL wrapper
name. For example, a unified sorted_heap_graph_rag(...) call with
relation_path := ARRAY[1, 2], score_mode := 'path' will report
sorted_heap_graph_rag_twohop_path_scan.
These stats are backend-local and aggregate the last top-level GraphRAG call.
For segmented/routed calls, the aggregate row is the sum of the recorded shard
rows from sorted_heap_graph_route_last_stats().
SELECT * FROM sorted_heap_graph_rag_stats();
sorted_heap_graph_rag_reset_stats()
Resets the backend-local GraphRAG stats counters.
SELECT sorted_heap_graph_rag_reset_stats();
sorted_heap_graph_route_last_stats()
Returns backend-local per-shard stats for the last
sorted_heap_graph_rag_segmented(...) or routed GraphRAG call.
Returned fields:
call_id: backend-local route-trace idapi: route wrapper that started the tracesource_rel: concrete shard relationseed_count,expanded_rows,reranked_rows,returned_rows: shard-local stage countersann_ms,expand_ms,rerank_ms,total_ms: shard-local timing counters
The row set is diagnostic last-call telemetry, not persistent accounting. It is
cleared by sorted_heap_graph_rag_reset_stats(). The current implementation
keeps at most 256 shard rows for the last call; aggregate
sorted_heap_graph_rag_stats() totals still include all executed shards.
SELECT *
FROM sorted_heap_graph_route_last_stats();
sorted_heap_graph_rag(rel, query, relation_path, ann_k, top_k, score_mode, limit_rows)
Preferred fact-shaped GraphRAG entry point.
relation_path := ARRAY[1]- one-hop expansion
- ANN seed on
entity_id - exact rerank on the endpoint facts
score_mode := 'endpoint'andscore_mode := 'path'are intentionally equivalent here because there is only one hop
relation_path := ARRAY[1, 2], score_mode := 'endpoint'- two-hop expansion
- rerank by the final-hop endpoint only
relation_path := ARRAY[1, 2], score_mode := 'path'- two-hop expansion
- path-aware rerank using hop-1 and hop-2 evidence together
relation_path := ARRAY[1, 2, 3, ...]- multi-hop expansion
- each array element is the relation filter for that hop
score_mode := 'endpoint'ranks only the final hopscore_mode := 'path'accumulates distance across the whole path
Current constraints:
relation_pathmust be a non-empty one-dimensionalint4[]- supported
score_modevalues areendpointandpath limit_rows := 0means unlimited expansion/rerank work- positive
limit_rowsvalues cap rows collected or scanned inside the current GraphRAG helper stages; this is a work bound, not a final result count override - canonical fact columns (
entity_id,relation_id,target_id,embedding,payload) need no extra setup - non-canonical schemas must be registered first with
sorted_heap_graph_register(...) - current regression and benchmark coverage verifies the generic path-aware
contract through synthetic depth
5
SET sorted_hnsw.ef_search = 128;
SELECT *
FROM sorted_heap_graph_rag(
'facts'::regclass,
'[0.1,0.2,0.3,...]'::svec,
relation_path := ARRAY[1, 2],
ann_k := 64,
top_k := 10,
score_mode := 'path'
);
sorted_heap_graph_rag_segmented(rels, query, relation_path, ann_k, top_k, score_mode, limit_rows)
Beta segmented GraphRAG wrapper.
relsis the candidate shard list to search- each shard is queried via
sorted_heap_graph_rag(...) - shard-local rows are merged globally by
(distance, entity_id, relation_id, target_id) - the call records backend-local shard rows visible through
sorted_heap_graph_route_last_stats() limit_rowskeeps the same per-shard work-cap semantics assorted_heap_graph_rag(...); it does not replacetop_kas the final merge limit- this does not solve routing for you; the caller still chooses which shard subset to query
This is the first SQL-level segmented reference path for large-scale fact-shaped GraphRAG. It is useful when routing/pruning already exists in the application or in metadata tables, and you want to move shard fanout/merge out of benchmark code and into SQL.
Partitioned-parent contract:
sorted_heap_graph_rag(...)is a concrete-relation API. Do not use a declarative partition parent as an implicit global graph.- For partitioned or tenant-sharded facts, register each concrete leaf/shard
with
sorted_heap_graph_register(...)and callsorted_heap_graph_route(...)orsorted_heap_graph_rag_segmented(...)over the selected leaves. - Fanout is explicit.
sorted_heap_graph_route_plan(...)shows the concrete shard list before execution. - The merge step is global over the routed shard-local result sets:
global_top_k = top_k(sort(union(shard_local_top_k))). It is not local top-k concatenation. fanout_limitnarrows the selected shard list before GraphRAG execution. If correctness requires every possibly relevant shard, keepfanout_limit := 0or use a profile that includes all required shards.- ANN seed retrieval,
ann_k, andlimit_rowsremain per-shard work bounds; they do not become a transparent cross-partition planner path.
SELECT source_rel, entity_id, relation_id, target_id, payload, distance
FROM sorted_heap_graph_rag_segmented(
ARRAY['facts_2025q1'::regclass, 'facts_2025q2'::regclass],
'[0.1,0.2,0.3,...]'::svec,
relation_path := ARRAY[1, 2],
ann_k := 64,
top_k := 10,
score_mode := 'path'
);
sorted_heap_graph_segment_meta_register(rel, segment_group, relation_family, segment_labels)
Registers shared per-shard routing metadata.
relis one concrete shard relationsegment_groupis optional shared shard labeling such ashotorsealedrelation_familyis optional shared shard labeling such asclaims,citations, orrightsegment_labelsis optional shared multi-valued shard labeling such asARRAY['sealed','archive']- this is mainly for reducing repeated registry data when several route rows point at the same shard
- if a routed row also stores
segment_grouporrelation_family, the row-local value wins over the shared metadata
sorted_heap_graph_segment_meta_config(rel)
Lists current shared per-shard metadata rows.
sorted_heap_graph_segment_meta_unregister(rel)
Deletes one shared per-shard metadata row, or all rows when rel is NULL.
sorted_heap_graph_segment_register(route_name, rel, route_min, route_max, segment_group, relation_family)
Registers a shard in the beta segment-routing registry.
route_namegroups shards into one logical routed graphroute_min/route_maxdefine an inclusiveint8rangesegment_groupis an optional shard label such ashot,sealed, or a lifecycle tierrelation_familyis an optional second routing label such asclaims,citations, orright- either label may be left
NULLand resolved later fromsorted_heap_graph_segment_meta_registry - overlapping ranges are allowed
sorted_heap_graph_segment_config(route_name, segment_groups, relation_family, segment_labels)
Lists the current registered shard ranges for one route group, ordered by group, range, and relation.
segment_groups := NULLmeans “all groups”- non-
NULLtext[]filters to matching shard labels only relation_family := NULLmeans “all families”- non-
NULLrelation_familynarrows rows to one family value segment_labels := NULLmeans “all label sets”- non-
NULLsegment_labelsrequires the effective shared shard labels to contain all supplied labels - effective labels come from the route row first, then from
sorted_heap_graph_segment_meta_registryfor the same shard when the route-local label isNULL - when
segment_groupsis non-NULL, its array order also becomes the preferred group order in the result set
sorted_heap_graph_segment_catalog(route_name, segment_groups, relation_family, segment_labels)
Lists range-routed shard rows with both raw and effective metadata.
Returned fields include:
- route-local values:
route_segment_grouproute_relation_family
- shared shard metadata:
shared_segment_groupshared_relation_familyshared_segment_labels
- effective resolved values:
effective_segment_groupeffective_relation_familyeffective_segment_labels
- source markers:
segment_group_sourcerelation_family_sourcesegment_labels_source
Source markers are one of:
routesharedunset
This is an introspection helper only. It does not affect shard routing or GraphRAG scoring.
sorted_heap_graph_segment_resolve(route_name, route_value, fanout_limit, segment_groups, relation_family, segment_labels)
Resolves candidate shards for a route value.
- matches rows where
route_value BETWEEN route_min AND route_max - optionally filters to
segment_group = ANY(segment_groups) - optionally filters to
relation_family = <supplied family> - optionally filters to shards whose shared
segment_labelscontain all supplied labels - effective labels come from the route row first, then from
sorted_heap_graph_segment_meta_registryfor the same shard when the route-local label isNULL - when
segment_groupsis non-NULL, its array order is preferred before the usual narrower-range ordering - orders narrower ranges first
fanout_limit := 0means “all matching shards”
sorted_heap_graph_segment_unregister(route_name, rel)
Deletes one shard from a route group, or all shards for that route group when
rel is NULL.
sorted_heap_graph_route_policy_register(route_name, policy_name, segment_groups)
Registers a named shard-group preference policy.
route_namescopes the policy to one routed graphpolicy_nameis an application-facing label such asprefer_hotsegment_groupsis a non-emptytext[]- array order is preserved and becomes the preference order used by the policy-backed wrappers
sorted_heap_graph_route_policy_config(route_name, policy_name)
Lists registered shard-group policies for one route group.
sorted_heap_graph_route_policy_groups(route_name, policy_name)
Returns the stored segment_groups text[] for one named policy.
- raises an error if the policy does not exist
sorted_heap_graph_route_policy_unregister(route_name, policy_name)
Deletes one named policy, or all policies under that route group when
policy_name is NULL.
sorted_heap_graph_route_profile_register(route_name, profile_name, policy_name, segment_groups, relation_family, fanout_limit, segment_labels)
Registers a named routed profile that bundles the current beta routing knobs.
route_namescopes the profile to one routed graphprofile_nameis an application-facing name such assealed_claimspolicy_namemay beNULL; when non-NULLit must name a policy already registered under the same routesegment_groupsmay beNULL; when non-NULLit must be a non-empty one-dimensionaltext[]policy_nameandsegment_groupscannot both be non-NULLrelation_familymay beNULLfanout_limitmust be>= 0segment_labelsmay beNULL; when non-NULLit must be a non-empty one-dimensionaltext[]
sorted_heap_graph_route_profile_config(route_name, profile_name)
Lists registered routed profiles for one route group.
sorted_heap_graph_route_profile_resolve(route_name, profile_name)
Resolves one routed profile into:
policy_namesegment_groupsrelation_familyfanout_limitsegment_labels
This is mainly useful for inspection/testing. Query execution should normally go through the profile-backed wrappers below.
sorted_heap_graph_route_profile_unregister(route_name, profile_name)
Deletes one routed profile, or all profiles under that route group when
profile_name is NULL.
sorted_heap_graph_route_default_register(route_name, profile_name)
Registers the default routed profile for one route.
route_namescopes the default to one routed graphprofile_namemust already exist insorted_heap_graph_route_profile_registryunder the same route- later registrations replace the previous default for that route
sorted_heap_graph_route_default_config(route_name)
Lists current default-profile bindings.
sorted_heap_graph_route_default_resolve(route_name)
Returns the profile_name currently marked as default for one route.
This is mainly useful for inspection/testing. Query execution should normally go through the default-backed wrappers below.
sorted_heap_graph_route_profile_catalog(route_name, profile_name)
Lists routed profiles with both raw and effective shard-group metadata.
Returned fields include:
- profile-local values:
policy_nameinline_segment_groups
- policy-backed values:
policy_segment_groups
- effective resolved values:
effective_segment_groupsrelation_familyfanout_limitsegment_labels
- source/default markers:
segment_groups_sourceis_default
segment_groups_source is one of:
inlinepolicyunset
This is an introspection helper only. It does not affect shard routing or GraphRAG scoring.
sorted_heap_graph_route_catalog(route_name)
Lists one summary row per route across the current segmented/routed control plane.
Returned fields include:
- control-plane counts:
range_shard_countexact_binding_countpolicy_countprofile_count
- effective default-profile state:
default_profile_namedefault_effective_segment_groupsdefault_segment_groups_sourcedefault_relation_familydefault_fanout_limitdefault_segment_labels
This is the top-level operator summary:
- range shards are counted from
sorted_heap_graph_segment_registry - exact bindings are counted from
sorted_heap_graph_exact_registry - policies and profiles are counted from their corresponding registries
- default-profile fields come from the effective profile/default catalog layer
This is an introspection helper only. It does not affect shard routing or GraphRAG scoring.
sorted_heap_graph_route_default_unregister(route_name)
Deletes one default-profile binding, or all bindings when route_name is
NULL.
sorted_heap_graph_rag_routed(route_name, route_value, query, relation_path, ann_k, top_k, score_mode, limit_rows, fanout_limit, segment_groups, relation_family, segment_labels)
Beta routed GraphRAG wrapper.
- resolves candidate shards from
sorted_heap_graph_segment_registry - optionally narrows those shards by
segment_group - optionally narrows those shards by
relation_family - optionally narrows those shards by shared
segment_labels - when
segment_groupsis non-NULL, its array order is the shard preference order beforefanout_limitis applied - delegates to
sorted_heap_graph_rag_segmented(...) - preserves the same GraphRAG scoring contract after routing
This is the first metadata-driven routing surface for segmented GraphRAG. It does not try to infer a route from the vector query itself; the caller supplies the route value.
SELECT sorted_heap_graph_segment_register('tenant_facts', 'facts_hot'::regclass, 1, 1000, 'hot', 'claims');
SELECT sorted_heap_graph_segment_register('tenant_facts', 'facts_cold'::regclass, 1, 1000, 'sealed', 'claims');
SELECT source_rel, entity_id, relation_id, target_id, payload, distance
FROM sorted_heap_graph_rag_routed(
'tenant_facts',
812,
'[0.1,0.2,0.3,...]'::svec,
relation_path := ARRAY[1, 2],
ann_k := 64,
top_k := 10,
score_mode := 'path',
segment_groups := ARRAY['hot'],
relation_family := 'claims'
);
sorted_heap_graph_rag_routed_policy(route_name, route_value, policy_name, query, relation_path, ann_k, top_k, score_mode, limit_rows, fanout_limit, relation_family, segment_labels)
Beta routed GraphRAG wrapper with registry-backed shard-group policy lookup.
- resolves
segment_groupsfromsorted_heap_graph_route_policy_registry - delegates to
sorted_heap_graph_rag_routed(...) - can still add a per-query
relation_familyfilter on top of the stored shard-group order - can also add a per-query
segment_labelsfilter on top of the stored shard-group order - keeps the same routing and GraphRAG scoring semantics
SELECT sorted_heap_graph_route_policy_register(
'tenant_facts',
'prefer_hot',
ARRAY['hot', 'sealed']
);
SELECT source_rel, entity_id, relation_id, target_id, payload, distance
FROM sorted_heap_graph_rag_routed_policy(
'tenant_facts',
812,
'prefer_hot',
'[0.1,0.2,0.3,...]'::svec,
relation_path := ARRAY[1, 2],
ann_k := 64,
top_k := 10,
score_mode := 'path',
fanout_limit := 1,
relation_family := 'claims'
);
sorted_heap_graph_rag_routed_profile(route_name, route_value, profile_name, query, relation_path, ann_k, top_k, score_mode, limit_rows)
Beta routed GraphRAG wrapper with registry-backed profile lookup.
- resolves
policy_name,relation_family,fanout_limit, and optionalsegment_labelsfromsorted_heap_graph_route_profile_registry - delegates to
sorted_heap_graph_rag_routed(...)with inlinesegment_groupswhen the profile stores them directly - otherwise delegates to
sorted_heap_graph_rag_routed_policy(...)when the profile names a policy - otherwise delegates to
sorted_heap_graph_rag_routed(...) - keeps the same routing and GraphRAG scoring semantics
SELECT sorted_heap_graph_route_profile_register(
'tenant_facts',
'sealed_claims',
NULL,
ARRAY['sealed', 'hot'],
'claims',
1
);
SELECT source_rel, entity_id, relation_id, target_id, payload, distance
FROM sorted_heap_graph_rag_routed_profile(
'tenant_facts',
812,
'sealed_claims',
'[0.1,0.2,0.3,...]'::svec,
relation_path := ARRAY[1, 2],
ann_k := 64,
top_k := 10,
score_mode := 'path'
);
sorted_heap_graph_rag_routed_default(route_name, route_value, query, relation_path, ann_k, top_k, score_mode, limit_rows)
Beta routed GraphRAG wrapper with registry-backed default-profile lookup.
- resolves
profile_namefromsorted_heap_graph_route_default_registry - delegates to
sorted_heap_graph_rag_routed_profile(...) - keeps the same routing and GraphRAG scoring semantics
SELECT sorted_heap_graph_route_default_register(
'tenant_facts',
'sealed_claims'
);
SELECT source_rel, entity_id, relation_id, target_id, payload, distance
FROM sorted_heap_graph_rag_routed_default(
'tenant_facts',
812,
'[0.1,0.2,0.3,...]'::svec,
relation_path := ARRAY[1, 2],
ann_k := 64,
top_k := 10,
score_mode := 'path'
);
sorted_heap_graph_route(route_name, query, relation_path, route_key, route_value, profile_name, policy_name, ann_k, top_k, score_mode, limit_rows, fanout_limit, segment_groups, relation_family, segment_labels)
Unified routed GraphRAG entry point over the existing beta exact/range wrappers.
Resolution order:
- exactly one of
route_keyorroute_value - at most one of
profile_nameorpolicy_name - explicit
profile_name - explicit
policy_name - explicit call-site routing overrides
fanout_limitsegment_groupsrelation_familysegment_labels
- route default profile, if one exists
- base exact/range routed wrapper
Important constraints:
profile_namecannot be combined with call-site routing overridespolicy_namecannot be combined withsegment_groups- defaults never override explicit call-site routing knobs
- this wrapper reuses the existing routed GraphRAG paths; it does not define a new GraphRAG scoring model
- this is the recommended parent/shard fanout API for GraphRAG; declarative partition parents should be resolved to concrete leaves first rather than passed as implicit global GraphRAG relations
SELECT source_rel, entity_id, relation_id, target_id, payload, distance
FROM sorted_heap_graph_route(
'tenant_facts',
'[0.1,0.2,0.3,...]'::svec,
relation_path := ARRAY[1, 2],
route_key := 'kb_alpha',
ann_k := 64,
top_k := 10,
score_mode := 'path'
);
sorted_heap_graph_route_plan(route_name, route_key, route_value, profile_name, policy_name, fanout_limit, segment_groups, relation_family, segment_labels)
Explain helper for sorted_heap_graph_route(...).
Returns:
route_kindresolution_pathused_profile_nameused_policy_nameused_defaulteffective_fanout_limiteffective_segment_groupseffective_relation_familyeffective_segment_labelscandidate_shards
This function uses the same routing precedence as sorted_heap_graph_route(...)
but does not execute GraphRAG.
SELECT *
FROM sorted_heap_graph_route_plan(
'tenant_facts',
route_key := 'kb_alpha'
);
sorted_heap_graph_exact_register(route_name, route_key, rel, priority, segment_group, relation_family)
Registers an exact-key shard mapping in the beta exact-routing registry.
route_namegroups shards into one logical routed graphroute_keyis an exact text key such as a tenant id or knowledge-base id- multiple shards may share the same key
priorityorders those shards when one key fans out to several shardssegment_groupis an optional shard label such ashotorsealedrelation_familyis an optional second routing label such asclaims,citations, orright- either label may be left
NULLand resolved later fromsorted_heap_graph_segment_meta_registry
sorted_heap_graph_exact_config(route_name, route_key, segment_groups, relation_family, segment_labels)
Lists the current exact-key shard mappings ordered by
(route_name, route_key, priority desc, segment_group, relation_family, rel).
- when
segment_groupsis non-NULL, its array order becomes the preferred group order before per-shard priority relation_family := NULLmeans “all families”- non-
NULLrelation_familynarrows rows to one family value segment_labels := NULLmeans “all label sets”- non-
NULLsegment_labelsrequires the effective shared shard labels to contain all supplied labels - effective labels come from the exact-route row first, then from
sorted_heap_graph_segment_meta_registryfor the same shard when the route-local label isNULL
sorted_heap_graph_exact_catalog(route_name, route_key, segment_groups, relation_family, segment_labels)
Lists exact-key routed shard rows with both raw and effective metadata.
Returned fields include:
- route-local values:
route_segment_grouproute_relation_family
- shared shard metadata:
shared_segment_groupshared_relation_familyshared_segment_labels
- effective resolved values:
effective_segment_groupeffective_relation_familyeffective_segment_labels
- source markers:
segment_group_sourcerelation_family_sourcesegment_labels_source
Source markers are one of:
routesharedunset
This is an introspection helper only. It does not affect shard routing or GraphRAG scoring.
sorted_heap_graph_exact_resolve(route_name, route_key, fanout_limit, segment_groups, relation_family, segment_labels)
Resolves candidate shards for an exact route key.
- matches rows where
route_key = <supplied key> - optionally filters to
segment_group = ANY(segment_groups) - optionally filters to
relation_family = <supplied family> - optionally filters to shards whose shared
segment_labelscontain all supplied labels - effective labels come from the exact-route row first, then from
sorted_heap_graph_segment_meta_registryfor the same shard when the route-local label isNULL - when
segment_groupsis non-NULL, its array order is preferred before the usualpriority DESCordering - orders by
priority DESC, rel fanout_limit := 0means “all matching shards”
sorted_heap_graph_exact_unregister(route_name, route_key, rel)
Deletes exact-key shard mappings.
route_key := NULLdeletes every key under that route grouprel := NULLdeletes all matching shard rows for the selected route/key
sorted_heap_graph_rag_routed_exact(route_name, route_key, query, relation_path, ann_k, top_k, score_mode, limit_rows, fanout_limit, segment_groups, relation_family, segment_labels)
Beta exact-key routed GraphRAG wrapper.
- resolves candidate shards from
sorted_heap_graph_exact_registry - optionally narrows those shards by
segment_group - optionally narrows those shards by
relation_family - optionally narrows those shards by shared
segment_labels - when
segment_groupsis non-NULL, its array order is the shard preference order beforefanout_limitis applied - delegates to
sorted_heap_graph_rag_segmented(...) - keeps the same GraphRAG scoring contract after routing
This is the stronger fit for tenant-id / knowledge-base-id routing than the range-based wrapper.
SELECT sorted_heap_graph_exact_register('tenant_facts', 'kb_alpha', 'facts_hot'::regclass, 100, 'hot', 'claims');
SELECT sorted_heap_graph_exact_register('tenant_facts', 'kb_alpha', 'facts_cold'::regclass, 50, 'sealed', 'claims');
SELECT source_rel, entity_id, relation_id, target_id, payload, distance
FROM sorted_heap_graph_rag_routed_exact(
'tenant_facts',
'kb_alpha',
'[0.1,0.2,0.3,...]'::svec,
relation_path := ARRAY[1, 2],
ann_k := 64,
top_k := 10,
score_mode := 'path',
segment_groups := ARRAY['hot'],
relation_family := 'claims'
);
sorted_heap_graph_rag_routed_exact_policy(route_name, route_key, policy_name, query, relation_path, ann_k, top_k, score_mode, limit_rows, fanout_limit, relation_family, segment_labels)
Beta exact-key routed GraphRAG wrapper with registry-backed shard-group policy lookup.
- resolves
segment_groupsfromsorted_heap_graph_route_policy_registry - delegates to
sorted_heap_graph_rag_routed_exact(...) - can still add a per-query
relation_familyfilter on top of the stored shard-group order - can also add a per-query
segment_labelsfilter on top of the stored shard-group order - keeps the same routing and GraphRAG scoring semantics
SELECT sorted_heap_graph_route_policy_register(
'tenant_facts',
'prefer_hot',
ARRAY['hot', 'sealed']
);
SELECT source_rel, entity_id, relation_id, target_id, payload, distance
FROM sorted_heap_graph_rag_routed_exact_policy(
'tenant_facts',
'kb_alpha',
'prefer_hot',
'[0.1,0.2,0.3,...]'::svec,
relation_path := ARRAY[1, 2],
ann_k := 64,
top_k := 10,
score_mode := 'path',
fanout_limit := 1,
relation_family := 'claims'
);
sorted_heap_graph_rag_routed_exact_profile(route_name, route_key, profile_name, query, relation_path, ann_k, top_k, score_mode, limit_rows)
Beta exact-key routed GraphRAG wrapper with registry-backed profile lookup.
- resolves
policy_name,relation_family,fanout_limit, and optionalsegment_labelsfromsorted_heap_graph_route_profile_registry - delegates to
sorted_heap_graph_rag_routed_exact(...)with inlinesegment_groupswhen the profile stores them directly - otherwise delegates to
sorted_heap_graph_rag_routed_exact_policy(...)when the profile names a policy - otherwise delegates to
sorted_heap_graph_rag_routed_exact(...) - keeps the same routing and GraphRAG scoring semantics
SELECT sorted_heap_graph_route_profile_register(
'tenant_facts',
'sealed_claims',
NULL,
ARRAY['sealed', 'hot'],
'claims',
1
);
SELECT source_rel, entity_id, relation_id, target_id, payload, distance
FROM sorted_heap_graph_rag_routed_exact_profile(
'tenant_facts',
'kb_alpha',
'sealed_claims',
'[0.1,0.2,0.3,...]'::svec,
relation_path := ARRAY[1, 2],
ann_k := 64,
top_k := 10,
score_mode := 'path'
);
sorted_heap_graph_rag_routed_exact_default(route_name, route_key, query, relation_path, ann_k, top_k, score_mode, limit_rows)
Beta exact-key routed GraphRAG wrapper with registry-backed default-profile lookup.
- resolves
profile_namefromsorted_heap_graph_route_default_registry - delegates to
sorted_heap_graph_rag_routed_exact_profile(...) - keeps the same routing and GraphRAG scoring semantics
SELECT sorted_heap_graph_route_default_register(
'tenant_facts',
'sealed_claims'
);
SELECT source_rel, entity_id, relation_id, target_id, payload, distance
FROM sorted_heap_graph_rag_routed_exact_default(
'tenant_facts',
'kb_alpha',
'[0.1,0.2,0.3,...]'::svec,
relation_path := ARRAY[1, 2],
ann_k := 64,
top_k := 10,
score_mode := 'path'
);
Lower-level GraphRAG building blocks (beta)
sorted_heap_expand_ids(rel, seed_ids, relation_filter, limit_rows)
Expands known entity seeds into fact rows without reranking.
limit_rows := 0means unlimited- positive
limit_rowsstops expansion after that many matching rows have been collected
SELECT *
FROM sorted_heap_expand_ids(
'facts'::regclass,
ARRAY[101, 202],
relation_filter := 1
);
sorted_heap_expand_rerank(rel, seed_ids, query, top_k, relation_filter, limit_rows)
One-hop expansion followed by exact rerank on the expanded candidates.
limit_rowscaps the expansion and rerank scan work before top-k selectiontop_kstill controls the final output size
SELECT *
FROM sorted_heap_expand_rerank(
'facts'::regclass,
ARRAY[101, 202],
'[0.1,0.2,0.3,...]'::svec,
top_k := 10,
relation_filter := 1
);
sorted_heap_expand_twohop_rerank(rel, seed_ids, query, top_k, hop1_relation_filter, hop2_relation_filter, limit_rows)
Two-hop expansion with rerank on the final candidate set.
limit_rowscaps work in the hop-expansion and final rerank scan stages
sorted_heap_expand_twohop_path_rerank(rel, seed_ids, query, top_k, hop1_relation_filter, hop2_relation_filter, limit_rows)
Two-hop expansion with path-aware rerank using hop-1 and hop-2 evidence together. This is the stronger current contract for fact-shaped multihop retrieval.
limit_rowscaps work in the hop-expansion and final path-rerank scan stages
SELECT *
FROM sorted_heap_expand_twohop_path_rerank(
'facts'::regclass,
ARRAY[101, 202],
'[0.1,0.2,0.3,...]'::svec,
top_k := 10,
hop1_relation_filter := 1,
hop2_relation_filter := 2
);
sorted_heap_graph_rag_scan(rel, query, ann_k, top_k, relation_filter, limit_rows)
Lower-level one-hop wrapper retained for backward compatibility and
target-seeded graph shapes. This wrapper seeds one-hop expansion from
ANN-selected target_id values, so it is not the preferred fact-graph
contract.
limit_rowskeeps the same work-cap semantics as the lower-level helpers
sorted_heap_graph_rag_twohop_scan(rel, query, ann_k, top_k, hop1_relation_filter, hop2_relation_filter, limit_rows)
Lower-level endpoint-scored two-hop wrapper. sorted_heap_graph_rag(...)
with relation_path := ARRAY[hop1, hop2], score_mode := 'endpoint'
is the preferred higher-level syntax.
limit_rowskeeps the same work-cap semantics as the underlying helper path
sorted_heap_graph_rag_twohop_path_scan(rel, query, ann_k, top_k, hop1_relation_filter, hop2_relation_filter, limit_rows)
Lower-level path-aware two-hop wrapper. sorted_heap_graph_rag(...) with
relation_path := ARRAY[hop1, hop2], score_mode := 'path' is the preferred
higher-level syntax.
limit_rowskeeps the same work-cap semantics as the underlying helper path
sorted_heap_expand_multihop_rerank(rel, seed_ids, query, top_k, relation_path, limit_rows)
Lower-level endpoint-scored multi-hop helper. relation_path is the explicit
per-hop relation sequence.
limit_rowscaps work at each hop expansion plus the final rerank scan
sorted_heap_expand_multihop_path_rerank(rel, seed_ids, query, top_k, relation_path, limit_rows)
Lower-level path-aware multi-hop helper. This accumulates distance across the
full explicit relation_path.
limit_rowscaps work at each hop expansion plus the final path-rerank scan
sorted_heap_graph_rag_multihop_scan(rel, query, ann_k, top_k, relation_path, limit_rows)
Lower-level ANN-seeded multi-hop wrapper for endpoint-scored retrieval.
limit_rowskeeps the same work-cap semantics as the underlying helper path
sorted_heap_graph_rag_multihop_path_scan(rel, query, ann_k, top_k, relation_path, limit_rows)
Lower-level ANN-seeded multi-hop wrapper for path-aware retrieval.
limit_rowskeeps the same work-cap semantics as the underlying helper path
SET sorted_hnsw.ef_search = 128;
SELECT *
FROM sorted_heap_graph_rag(
'facts'::regclass,
'[0.1,0.2,0.3,...]'::svec,
relation_path := ARRAY[1, 2],
ann_k := 64,
top_k := 10,
score_mode := 'path'
);
Routed GraphRAG: operator recipe
This section describes the recommended app-facing flow for multi-shard
GraphRAG with routing. Use sorted_heap_graph_route(...) for queries and
sorted_heap_graph_route_plan(...) for inspection. The lower-level
_routed, _routed_exact, _routed_policy, _routed_profile, and
_routed_default wrappers remain lower-level beta building blocks.
What to call in an app
| Task | Function |
|---|---|
| Query (app code) | sorted_heap_graph_route(...) |
| Inspect routing (operator/debug) | sorted_heap_graph_route_plan(...) |
| Setup: register shard metadata | sorted_heap_graph_register(...) per shard |
| Setup: register exact routes | sorted_heap_graph_exact_register(...) |
| Setup: register range routes | sorted_heap_graph_segment_register(...) |
| Setup: optional profile/default | sorted_heap_graph_route_profile_register(...) + sorted_heap_graph_route_default_register(...) |
Exact-key routing (tenant / knowledge-base)
Setup (once per deployment):
-- 1. Create shard tables with HNSW indexes
CREATE TABLE facts_shard_a (...) USING sorted_heap;
CREATE TABLE facts_shard_b (...) USING sorted_heap;
-- load data, compact, create sorted_hnsw indexes, ANALYZE
-- 2. Register graph schema on each shard
SELECT sorted_heap_graph_register('facts_shard_a'::regclass,
entity_column := 'entity_id', relation_column := 'relation_id',
target_column := 'target_id', embedding_column := 'embedding',
payload_column := 'payload');
SELECT sorted_heap_graph_register('facts_shard_b'::regclass, ...);
-- 3. Map tenant keys to shards
SELECT sorted_heap_graph_exact_register('tenants', 'acme',
'facts_shard_a'::regclass, 100);
SELECT sorted_heap_graph_exact_register('tenants', 'globex',
'facts_shard_b'::regclass, 100);
Query (app code):
SELECT entity_id, relation_id, target_id, payload, distance
FROM sorted_heap_graph_route(
'tenants', -- route name
query_embedding, -- svec
ARRAY[1, 2], -- relation path (2-hop)
route_key := 'acme', -- tenant key
ann_k := 64,
top_k := 10,
score_mode := 'path'
);
Inspect (operator):
SELECT * FROM sorted_heap_graph_route_plan(
'tenants', route_key := 'acme');
-- route_kind | resolution_path | candidate_shards
-- exact | base | {facts_shard_a}
Range routing (time-window / ID-range)
Setup:
-- Map ID ranges to shards
SELECT sorted_heap_graph_segment_register('id_range',
'facts_shard_a'::regclass, 1, 100000);
SELECT sorted_heap_graph_segment_register('id_range',
'facts_shard_b'::regclass, 100001, 200000);
Query:
SELECT entity_id, relation_id, target_id, payload, distance
FROM sorted_heap_graph_route(
'id_range',
query_embedding,
ARRAY[1, 2],
route_value := 42000, -- routes to shard_a
ann_k := 64,
top_k := 10
);
Adding a default profile
Profiles bundle routing knobs so app code doesn’t need to pass them:
-- Register a profile that filters by segment group and relation family
SELECT sorted_heap_graph_route_profile_register(
'tenants', 'production',
NULL, -- no policy
ARRAY['active'], -- segment_groups filter
'core', -- relation_family
0, -- fanout_limit
ARRAY['verified'] -- segment_labels
);
-- Set as the default for this route
SELECT sorted_heap_graph_route_default_register('tenants', 'production');
Now sorted_heap_graph_route('tenants', ..., route_key := 'acme') will
automatically use the ‘production’ profile (step 6 in the resolution
order) unless the caller explicitly provides a different profile, policy,
or routing overrides.
Lower-level building blocks (not recommended for app code)
The following functions are internal dispatch targets used by
sorted_heap_graph_route(...). They remain available for advanced use
cases but are not the recommended app entry point:
sorted_heap_graph_rag_routed(...)/sorted_heap_graph_rag_routed_exact(...)sorted_heap_graph_rag_routed_policy(...)/sorted_heap_graph_rag_routed_exact_policy(...)sorted_heap_graph_rag_routed_profile(...)/sorted_heap_graph_rag_routed_exact_profile(...)sorted_heap_graph_rag_routed_default(...)/sorted_heap_graph_rag_routed_exact_default(...)sorted_heap_graph_rag_segmented(...)