Deployment guide

VectorChord

Overview

VectorChord (the vchord extension) adds the vchordrq vector index to PostgreSQL. Instead of walking a graph, vchordrq splits your embeddings into lists (partitions) and compresses them with RaBitQ quantisation. It scans only the lists closest to the query, then reranks the best candidates with exact distances.

VectorChord uses the vector type and distance operators (<->, <=>, <#>) from pgvector, so existing pgvector tables and queries work unchanged. pgvector's own HNSW and IVFFlat indexes remain available next to it.

This SecureAnyCloud image runs one PostgreSQL 18 server per virtual machine on Ubuntu 24.04 on Azure, with VectorChord preloaded. It is not a managed service or a cluster, and it has no web interface. You work with it in SQL through psql or any PostgreSQL driver.

pgvector is included and required. VectorChord declares requires = 'vector', so pgvector is installed and enabled alongside it.

What you get:

  • PostgreSQL 18.6 from the official PGDG repository, running under systemd as postgresql@18-main.service
  • VectorChord 1.1.1 preloaded (shared_preload_libraries = 'vchord') and pgvector 0.8.7, both enabled in the default vectordb database
  • A demo items table of twenty thousand 128-dimensional vectors with a real vchordrq index, so you can run a similarity search the moment the VM boots
  • vectorchord-benchmark, which builds a pgvector HNSW index and a VectorChord vchordrq index on the same data on your VM and reports build time, size, recall and latency for each
  • A per-VM database cluster, a per-VM TLS certificate and two per-VM passwords (the postgres superuser and the vectorapp application role), generated on first boot into a root-only file
  • Loopback-only networking by default: the only port reachable from the network is TCP 22
  • Unattended security upgrades left enabled

Licensing. VectorChord is offered upstream under a choice of the GNU Affero General Public License v3 (AGPL-3.0-only) or the Elastic License 2.0. SecureAnyCloud distributes it in this image under the AGPL v3 only. The image ships the AGPL text, a notice recording that election, and the complete corresponding source of the installed build (the upstream release commit) in /usr/share/doc/postgresql-18-vchord/. SecureAnyCloud does not modify VectorChord. PostgreSQL and pgvector are under the PostgreSQL License.

VectorChord is a trademark of its owner. PostgreSQL is a registered trademark of the PostgreSQL Community Association. SecureAnyCloud is not affiliated with, sponsored by or endorsed by either. This image packages the open-source software for use on Azure.

Prerequisites

You need an active Azure subscription, an SSH public key, a VNet with a subnet in the target region, and a subscription to this listing on Azure Marketplace.

Configure a network security group (NSG) that allows TCP 22 from your administration network. Nothing else needs to be opened, because PostgreSQL listens on loopback in the shipped image.

Recommended VM size: Standard_B2ms (2 vCPU, 8 GB RAM). On first boot, the PostgreSQL memory settings (shared_buffers, effective_cache_size, maintenance_work_mem) are sized from the VM's RAM, so a larger size is used automatically. For production workloads with millions of vectors, choose a memory-rich size such as Standard_E4s_v5 and a Premium SSD.

Deploy the virtual machine

Create the VM from the image. Open only SSH, and only to your own network:

# Accept the Marketplace terms once per subscription (required before the first CLI deploy)
az vm image terms accept --urn sac1647283583153:vectorchord-ubuntu-24-04:default:latest
az vm create \
  --resource-group my-rg \
  --name vectorchord-1 \
  --image sac1647283583153:vectorchord-ubuntu-24-04:default:latest \
  --size Standard_B2ms \
  --admin-username azureuser \
  --generate-ssh-keys \
  --public-ip-sku Standard

First boot creates the database cluster, generates the passwords and builds the demo index. It takes under a minute.

Confirm first boot finished

SSH in as azureuser. Confirm that the first-boot unit and PostgreSQL are both active, and that the ready marker exists:

systemctl is-active vectorchord-firstboot.service postgresql@18-main.service
ls -l /var/lib/sac/data/vectorchord-ready

Both units report active. The ready marker is written only after the passwords are set, and PostgreSQL refuses to start with any network rule until the marker exists.

Retrieve your per-VM credentials

Read the root-only credentials file written on first boot:

sudo cat /root/vectorchord-credentials.txt

The file is 0600 root:root. It holds the postgres superuser password, the vectorapp application role password and the database name (vectordb). These values are unique to your VM. The passwords were sent to PostgreSQL only as SCRAM-SHA-256 verifiers, so they do not appear in any log.

Connect to the database

The quickest local connection uses peer authentication and needs no password. Connect as the superuser:

sudo -u postgres psql -d vectordb -c "SELECT version();"

To connect as the application role, store its password in your own ~/.pgpass, so it never appears on a command line or in your shell history. Then connect over TLS:

sudo awk -F= '/^vectorapp\.password=/{print "127.0.0.1:5432:vectordb:vectorapp:"$2}' /root/vectorchord-credentials.txt > ~/.pgpass && chmod 600 ~/.pgpass
psql "host=127.0.0.1 dbname=vectordb user=vectorapp sslmode=require" -w -c "SELECT current_user, s.ssl, s.version FROM pg_stat_ssl s WHERE s.pid = pg_backend_pid();"

The result shows vectorapp, t and TLSv1.3. The session is encrypted and authenticated with the per-VM password.

Verify the extensions

Check the installed extensions and the preload setting:

sudo -u postgres psql -d vectordb -c "SELECT extname, extversion FROM pg_extension WHERE extname IN ('vchord','vector') ORDER BY extname;"
sudo -u postgres psql -d vectordb -c "SHOW shared_preload_libraries;"

In the output, vchord is VectorChord, vector is pgvector, and VectorChord is loaded at server start.

Run a similarity search on the demo table

The vectordb database ships an items table with twenty thousand 128-dimensional vectors and a vchordrq index named items_embedding_idx. Inspect them:

psql "host=127.0.0.1 dbname=vectordb user=vectorapp sslmode=require" -w -c "SELECT count(*) AS rows, pg_size_pretty(pg_relation_size('items_embedding_idx')) AS index_size, (SELECT amname FROM pg_am a JOIN pg_class c ON c.relam = a.oid WHERE c.relname = 'items_embedding_idx') AS access_method FROM items;"

Find the five nearest neighbours of item 42 by Euclidean distance:

psql "host=127.0.0.1 dbname=vectordb user=vectorapp sslmode=require" -w -c "SELECT id, category, round((embedding <-> (SELECT embedding FROM items WHERE id = 42))::numeric, 4) AS distance FROM items ORDER BY embedding <-> (SELECT embedding FROM items WHERE id = 42) LIMIT 5;"

Item 42 comes back first at distance zero, followed by its nearest neighbours from the same cluster.

Confirm that the search uses the vchordrq index:

psql "host=127.0.0.1 dbname=vectordb user=vectorapp sslmode=require" -w -c "EXPLAIN (COSTS OFF) SELECT id FROM items ORDER BY embedding <-> (SELECT embedding FROM items WHERE id = 42) LIMIT 5;"

The plan shows Index Scan using items_embedding_idx.

The probes setting

A vchordrq index built with lists answers a query by scanning the vchordrq.probes lists closest to it. More probes means higher recall and more work per query.

A query against a partitioned index fails with need 1 probes, but 0 probes provided if no probes value is set. This image therefore sets a default of 10 for the vectordb database:

psql "host=127.0.0.1 dbname=vectordb user=vectorapp sslmode=require" -w -c "SHOW vchordrq.probes;"

Set it for a new database with ALTER DATABASE yourdb SET vchordrq.probes = '10';, or per session with SET vchordrq.probes = '30';. A good starting point is three to ten percent of the number of lists. Then measure.

Index your own embeddings

Create a table with a vector column sized to your embedding model, load your data, then build the index. For cosine similarity, which most text embedding models use, upstream recommends residual quantisation and spherical centroids:

CREATE TABLE documents ( id bigserial PRIMARY KEY, content text, embedding vector(768) ); -- load your rows, then: CREATE INDEX ON documents USING vchordrq (embedding vector_cosine_ops) WITH (options = $$ residual_quantization = true [build.internal] lists = [1000] spherical_centroids = true build_threads = 2 $$); SET vchordrq.probes = '30'; SELECT id, content FROM documents ORDER BY embedding <=> '[...]' LIMIT 10;

Choose lists from the number of rows. Upstream suggests roughly four to eight times the square root of the row count. Below about one hundred thousand rows, you can leave lists out entirely.

Build the index after you load the data, because the partitions are learned from the rows present at build time.

To measure recall for a real query, compare the index result with an exact search:

SELECT vchordrq_evaluate_query_recall(query => $$ SELECT ctid FROM documents ORDER BY embedding <=> '[...]' LIMIT 10 $$, exact_search => true);

Benchmark: vchordrq against pgvector HNSW

vectorchord-benchmark generates a deterministic dataset of embedding-like vectors (768 dimensions, L2 normalised, cosine distance). It computes the exact top ten neighbours of each query by sequential scan. It then builds a pgvector HNSW index and a VectorChord vchordrq index on the same table, with the same memory and parallel worker settings, and measures each at several search settings. Latency is measured inside the server, so it excludes network and client overhead.

Run a quick version on your VM. It takes about a minute:

sudo vectorchord-benchmark --rows 20000 --dim 768 --queries 50

Measured result. Standard_B2ms (2 vCPU, 8 GB RAM) launched from this image, 100,000 rows of 768 dimensions, 200 queries, top 10.

Index build time and size:

  • pgvector HNSW (m 16, ef_construction 64): 129.9 s build time, 391 MB index size
  • VectorChord vchordrq (1,264 lists): 23.4 s build time, 445 MB index size

Search results (recall@10, p50 and p95 latency, queries per second):

  • HNSW, ef_search 40: recall 0.861, p50 1.46 ms, p95 2.94 ms, 574 queries per second
  • HNSW, ef_search 100: recall 0.969, p50 1.94 ms, p95 4.65 ms, 416 queries per second
  • HNSW, ef_search 200: recall 0.984, p50 2.95 ms, p95 5.53 ms, 300 queries per second
  • vchordrq, probes 37: recall 1.000, p50 1.05 ms, p95 1.51 ms, 849 queries per second
  • vchordrq, probes 126: recall 1.000, p50 1.92 ms, p95 2.51 ms, 480 queries per second
  • vchordrq, probes 316: recall 1.000, p50 3.64 ms, p95 4.16 ms, 265 queries per second
  • vchordrq, probes 632: recall 1.000, p50 6.57 ms, p95 7.30 ms, 149 queries per second

How to read the results. The clear and repeatable difference is index build time. vchordrq built about five and a half times faster than HNSW here (23 seconds against 130), because it learns partitions with k-means instead of inserting every vector into a graph. Its index was about 14 percent larger.

On this dataset, vchordrq also returned the exact top ten at a lower latency than HNSW reached at its best recall. That part depends on how well your data clusters. On a smaller, less clustered run during the build (20,000 rows on a 4 GB VM), the two were much closer. On data with little cluster structure, HNSW holds its recall better while vchordrq needs more probes.

The B-series sizes are burstable, so a long benchmark on a VM that has used its CPU credits runs slower. Synthetic data is only a stand-in, so run the benchmark on a copy of your own embeddings before you choose.

The full-size run takes several minutes:

sudo vectorchord-benchmark --rows 100000 --dim 768 --queries 200

Security posture

The database is not reachable from the network. PostgreSQL listens on loopback only, so the only port listening off the box is TCP 22:

sudo ss -tlnH | awk '{print $4}' | sort
sudo -u postgres psql -c "SHOW listen_addresses;"

Every TCP connection requires TLS and a password. Ask PostgreSQL for the authentication rules it applies:

sudo -u postgres psql -c "SELECT type, database, user_name, address, auth_method FROM pg_hba_file_rules ORDER BY rule_number;"

There is one local rule for peer authentication over the Unix socket, and two hostssl rules for loopback with scram-sha-256. There is no plaintext host rule.

Fail closed before first boot completes. The image contains no database cluster. First boot creates one and starts PostgreSQL with Unix socket access only. It sets the passwords, and only then adds the TLS loopback rules and writes the ready marker. A start guard on postgresql@.service refuses to start a cluster that listens beyond loopback, or that has any TCP rule, while the ready marker is missing.

SSH. Root has no login path. The image honours your choice of key or password authentication in the portal:

sudo sshd -T | grep -E '^(permitrootlogin|clientaliveinterval) '

Kernel module baseline. The image fences the IPsec and RxRPC modules associated with the Dirty Frag vulnerabilities (CVE-2026-43284, CVE-2026-43500). This appliance terminates no IPsec, so the fence has no cost:

sudo modprobe -n -v esp4

The last line reads install /bin/false.

Connect from your workstation over an SSH tunnel

The recommended way to reach the database from your own machine is an SSH tunnel, which needs no change to the VM. Open the tunnel:

ssh -N -L 5432:127.0.0.1:5432 azureuser@<vm-public-ip>

Then, on your workstation, connect to localhost as if the database were local:

psql "host=localhost port=5432 dbname=vectordb user=vectorapp sslmode=require"

Optionally expose PostgreSQL to your application subnet

Opening the database is a deliberate change. Scope it to your own subnet, never to the internet. TLS with a per-VM certificate is already on.

First, allow your subnet in the authentication rules and listen on all interfaces. Then restart PostgreSQL:

echo "hostssl all all 10.0.1.0/24 scram-sha-256" | sudo tee -a /etc/postgresql/18/main/pg_hba.conf
echo "listen_addresses = '*'" | sudo tee /etc/postgresql/18/main/conf.d/99-local.conf
sudo chown postgres:postgres /etc/postgresql/18/main/conf.d/99-local.conf
sudo systemctl restart postgresql

Next, open TCP 5432 to the same subnet in the VM's network security group:

az network nsg rule create \
  --resource-group my-rg \
  --nsg-name my-nsg \
  --name allow-postgres-from-app-subnet \
  --priority 200 \
  --source-address-prefixes 10.0.1.0/24 \
  --destination-port-ranges 5432 \
  --access Allow --protocol Tcp

Clients must now present the per-VM password over TLS. The shipped certificate is self-signed, so use sslmode=require. Alternatively, replace /etc/ssl/certs/ssl-cert-snakeoil.pem and its key with a certificate from your own CA and use sslmode=verify-full.

Rotating the credentials

Change a password interactively with \password. It hashes the password in psql before sending, so the new password never reaches the server logs:

sudo -u postgres psql -d vectordb \password vectorapp

Then update /root/vectorchord-credentials.txt and your ~/.pgpass to match.

License and source

The license election, the AGPL text and the source of the installed VectorChord build are in the image:

ls /usr/share/doc/postgresql-18-vchord/
grep -E 'ONLY|commit' /usr/share/doc/postgresql-18-vchord/LICENSE-ELECTION

Maintenance

Security updates. Unattended upgrades remain enabled for Ubuntu security updates. PostgreSQL, pgvector and VectorChord come from the PGDG repository and the VectorChord release, so update them deliberately. Run sudo apt-get update && sudo apt-get install --only-upgrade postgresql-18 postgresql-18-pgvector, then restart PostgreSQL.

Upgrading VectorChord. Install the newer postgresql-18-vchord package from the upstream release, then run ALTER EXTENSION vchord UPDATE; in each database.

Rebuilding an index. vchordrq learns its partitions at build time. After the data has grown or changed substantially, run REINDEX INDEX CONCURRENTLY your_index; to re-learn them.

Backups. Indexes are part of the database, so ordinary PostgreSQL backups (pg_dump, pg_basebackup or an Azure disk snapshot) capture them along with your data.

Support

SecureAnyCloud provides 24/7 support for this image. Raise a request at secureanycloud.com with the VM size, the region and the output of sudo -u postgres psql -d vectordb -c "SELECT extname, extversion FROM pg_extension;".

Deploy on Azure

Launch VectorChord on PostgreSQL 18 on Ubuntu 24.04 on Azure with 24/7 support from SecureAnyCloud.

View this image

Need Help?

Our support team is available 24/7.

sales@secureanycloud.com

Deploy on Azure