Skip to content

This is the multi-page printable view of this section. .

Return to the regular view of this page.

Administration

Migrating from pgvecto.rs or pgvector, horizontal read scalability, and Kubernetes deployment.

1 - Migration

Most of VectorChord’s vector types are supported by pgvector.

outline: [2,3]

VectorChord Migration Overview

From PGVecto.rs

Most of VectorChord’s vector types are supported by pgvector. Therefore, migrating from PGVecto.rs to VectorChord is a simple inversion of migrating from pgvector to PGVecto.rs.

Tip

We provide migration image modelzai/pgvectors-vchord for pgvecto.rs to VectorChord and vice versa.

Prerequisites

Let’s suppose that you have a table with vector data, and the extension PGVecto.rs installed. A simple example would be:

CREATE TABLE table_vector (id bigserial PRIMARY KEY, vec_f32 vectors.vector(3));
CREATE TABLE table_float16 (id bigserial PRIMARY KEY, vec_f16 vectors.vecf16(3));
CREATE TABLE table_binary (id bigserial PRIMARY KEY, vec_bits vectors.bvector(3));

In addition, some rows will be inserted into the table, as follows:

INSERT INTO table_vector (vec_f32) VALUES ('[0.1, 0.1, 0.1]'), ('[0.2, 0.2, 0.2]');
INSERT INTO table_float16 (vec_f16) VALUES ('[0.1, 0.1, 0.1]'), ('[-0.2, 0.2, 0.2]');
INSERT INTO table_binary (vec_bits) VALUES ('[0, 0, 0]'), ('[0, 0, 1]');

In most cases, there will also be one or more vector indexes:

CREATE INDEX index_vector ON table_vector USING vectors (vec_f32 vectors.vector_l2_ops) WITH (options = "[indexing.hnsw]");
-- CREATE INDEX index_float16 ON table_float16 USING vectors (vec_f16 vectors.vecf16_cos_ops) WITH (options = "[indexing.hnsw]");
-- CREATE INDEX index_binary ON table_binary USING vectors (vec_bits vectors.bvector_hamming_ops) WITH (options = "[indexing.hnsw]");

This is a typical scenario when PGVecto.rs is used. If the following requirements are met, the migration can be initiated.

  • Please note that your pgvector and VectorChord extension is not installed in the vectors schema. This means that you can install pgvector and PGVecto.rs at the same time.

Steps to Migration

1. Install VectorChord

Please follow the instructions provided to install VectorChord and ensure that the extension is loaded by PostgreSQL.

CREATE EXTENSION vchord CASCADE;
Note

This will install vectorchord and pgvector at public schema, if other schema is picked, please substitute public with your schema at the migration command.

Details

The two extensions VectorChord and PGVecto.rs are now installed on PostgreSQL, but inside different schemas. To validate it, please run \dx in psql. You will see that there are 3 extensions:

  • vectors for PGVecto.rs at schema vectors
  • vector for pgvector at schema public
  • vchord for VectorChord at schema public
postgres=# \dx
                                                 List of installed extensions
  Name   | Version |   Schema   |                                         Description                                          
---------+---------+------------+----------------------------------------------------------------------------------------------
 plpgsql | 1.0     | pg_catalog | PL/pgSQL procedural language
 vchord  | 0.1.0   | public     | vchord: Vector database plugin for Postgres, written in Rust, specifically designed for LLM
 vector  | 0.8.0   | public     | vector data type and ivfflat and hnsw access methods
 vectors | 0.4.0   | vectors    | vectors: Vector database plugin for Postgres, written in Rust, specifically designed for LLM

2. Check existing indexes

For indexes created at vector columns, please retrieve the definition and record it in a convenient location. This information is necessary for the recovery of the indexes at a later stage.

postgres=# \di
                       List of relations
 Schema |        Name        | Type  |  Owner   |     Table     
--------+--------------------+-------+----------+---------------
 public | index_float16      | index | postgres | table_float16
 public | index_vector       | index | postgres | table_vector
 public | table_binary_pkey  | index | postgres | table_binary
 public | table_float16_pkey | index | postgres | table_float16
 public | table_vector_pkey  | index | postgres | table_vector

3. Migrate vector columns

To migrate from a PGVecto.rs vector column, the first step is to cast column datatypes between extensions.

Here provides the conversion path for each vector type of VectorChord:

  • vector(PGVecto.rs) -> real[] -> vector(pgvector)
  • vecf16 -> vector(PGVecto.rs) -> real[] -> halfvec
  • bvector -> vector(PGVecto.rs) -> text[] -> real[] -> bit
  • svector -> text -> index++ -> text -> sparsevec

For sparse vector type svector, we need additional helper function to finish the casting, see the appendix.

The migration will be completed in one command and no additional memory is required. However, you need to delete the index on the vector column in advance.

Warning

This step will cause some downtime, due to:

  • a long ACCESS EXCLUSIVE lock by ALTER TABLE ALTER COLUMN
-- The name of index can be found in step 2
DROP INDEX IF EXISTS index_vector;

-- From vector type
ALTER TABLE table_vector ALTER COLUMN vec_f32 TYPE public.vector(3) USING vec_f32::real[]::public.vector;

-- From halfvec type
ALTER TABLE table_float16 ALTER COLUMN vec_f16 TYPE public.halfvec(3) USING vec_f16::vectors.vector::real[]::public.halfvec;

-- For bit type
ALTER TABLE table_binary ALTER COLUMN vec_bits TYPE bit(3) USING array_to_string(vec_bits::vectors.vector::real[], '')::bit(3);

4. Recreate index

The next step is to recreate index on vector columns for PGVecto.rs.

CREATE INDEX ON index_vector USING vchordrq (vec_f32 vector_l2_ops) WITH (options = $$
residual_quantization = true
[build.internal]
lists = [4096]
spherical_centroids = false
$$);

For more information on creating index at VectorChord, see indexing.

5. Clean the environment

You may now safely remove the PGVecto.rs extension if it is no longer required.

DROP extension vectors;

Appendix: cast of sparse vector

We have discussed most types, but not the sparse vector. It is a little more complicated than other types. Suppose we have a table with a sparse vector column:

CREATE TABLE table_sparse (id bigserial PRIMARY KEY, vec_sparse vectors.svector(3));
INSERT INTO table_sparse (vec_sparse) VALUES ('{0:-0.1, 1:0.1, 2:0.1}/3'), ('{0:-0.4, 1:-0.4, 2:-0.4}/3');

Sparse vectors have a different text representation between PGVecto.rs and pgvector: The index starts from 0 in PGVecto.rs and from 1 in pgvector.

For example, a typical sparse vector [-1, 0, 1, 0, 2] has a text representation of:

  • {0:-1, 2:1, 4:2}/5 in PGVecto.rs
  • {1:-1, 3:1, 5:2}/5 in pgvector

So we need a helper function to handle this change by processing the string:

CREATE OR REPLACE FUNCTION migrate_sparse(input_text text) RETURNS text AS $$
DECLARE
    output_text text := '';
    cache_index_number text := '';
    number_started boolean := false;
BEGIN
    FOR i IN 1..length(input_text) LOOP
        IF substring(input_text from i for 1) = ':' THEN
            output_text := output_text || (cache_index_number::int + 1)::text || ':';
            number_started := false;
        ELSIF substring(input_text from i for 1) ~ '[,{]' THEN
            cache_index_number := '';
            number_started := true;
            output_text := output_text || substring(input_text from i for 1);
        ELSIF substring(input_text from i for 1) ~ '[0-9]' THEN
            IF number_started THEN
                cache_index_number := cache_index_number || substring(input_text from i for 1);
            ELSE
                output_text := output_text || substring(input_text from i for 1);
            END IF;
        ELSE
            output_text := output_text || substring(input_text from i for 1);
        END IF;
    END LOOP;
    RETURN output_text;
END;
$$ LANGUAGE plpgsql;

After that, the migration can continue smoothly:

ALTER TABLE table_sparse ALTER COLUMN vec_sparse TYPE public.sparsevec USING migrate_sparse(vec_sparse::text)::public.sparsevec;

2 - Scalability

High queries per second (QPS) vector search is essential for real-time applications such as recommendation engines, image recognition, and natural language proc

Background

High queries per second (QPS) vector search is essential for real-time applications such as recommendation engines, image recognition, and natural language processing. These applications demand rapid, efficient searches to deliver immediate results, which is critical for maintaining user engagement and operational effectiveness. By leveraging PostgreSQL for vector search, you not only benefit from its mature, robust relational capabilities and advanced indexing but also avoid the overhead of managing a separate, specialized vector database. This unified approach simplifies integration and reduces system complexity while still meeting the performance needs of modern applications.

Architecture

Using VectorChord on a multi-node cluster is more difficult than on a single machine. As with other services, a reliable distributed vector search usually has the following requirements:

  • Usability: Declarative configuration management

  • Elasticity: Automatic scaling for nodes based on QPS requirements

  • Load Balancing: Requests are handled effectively across different replica instances

  • High Availability: Having multiple copies of the database and automatically recovering from a replica in case of failure

  • Durability: Secure, persistent storage to ensure data integrity and prevent loss in the event of failures

Therefore, we deploy VectorChord services on AWS EKS with OpenTofu and CloudNativePG to manage the infrastructure that provides the above capabilities. Here are some modules to build the example clusters:

  • OpenTofu: Open source alternative of terraform, used for infrastructure as code (IaC) on AWS EKS

  • CloudNativePG: Manage the full lifecycle of a highly available PostgreSQL database cluster with a primary/standby architecture

  • PGBouncer: Lightweight connection pooler for PostgreSQL, provided by module Pooler of CloudNativePG

Architecture

Similar to DiskANN, VectorChord can fully utilize disk resources and still maintain competitive performance when memory is insufficient. Besides, VectorChord can also cache indexes in memory to achieve higher query performance.

For more on the technical details of VectorChord, please see our previous blog.

To reach the QPS limit, we should assume that the memory can fully hold the dataset and select the AWS machine type based on this standard.

In the following table, we provide the appropriate machine which can hold everything in memory for different sizes of datasets:

dataset dim / size machine / memory
LAION-5m 768 / 5,000,000 r7i.xlarge / 32GB
LAION-100m 768 / 100,000,000 r7i.16xlarge / 512GB

Results

As outlined in our documentation, VectorChord accepts two key parameters: nprob and epsilon. Here, nprob defines the number of candidate vectors to scan during a query, while epsilon serves as the threshold for determining whether to rerank results. Both parameters play a critical role in influencing both QPS and recall, allowing users to fine-tune the balance between search speed and accuracy.

  • Lower nprob will result in higher QPS and lower recall.

  • Lower epsilon will result in higher but more unstable QPS and lower recall.

If these parameters are fixed, the precision of each experiment will remain consistent. However, the QPS will still vary depending on the CPU and I/O performance of the underlying hardware. To ensure the precision stays above 0.9, we configured the parameters based on earlier experiments conducted with smaller datasets.

dataset nprob / epsilon recall
LAION-5m 20 / 1.0 0.9324
LAION-100m 20 / 1.0 0.9042

We deployed an r7i.4xlarge instance as the client within the same Availability Zone to query the VectorChord clusters. The clusters were tested with multiple workers ranging from 16 to 128, simulating the concurrency levels typically applied to the database in real-world scenarios. This setup allows us to evaluate how VectorChord performs under varying workloads and concurrency conditions.

Tip

With 16 vCPUs, r7i.4xlarge can effectively reduce CPU contention from the client side when the concurrency requests reach up to 128.

In line with common pgbouncer practices, we employed two types of instances: a primary instance for data ingestion and multiple replica instances for read-only workloads. Pgbouncer served as the load balancer, efficiently distributing queries across these backend instances. For the benchmark, we directed our query endpoint to the read-only (ro) pooler, while the read-write (rw) primary instance remained idle during the test. This setup ensures optimal resource utilization and scalability for read-heavy workloads.

For the 5 million dataset, we scaled the number of instances from 5 to 15 to achieve 10,000 QPS. In contrast, for the 100 million dataset, only 2 instances were sufficient to meet the performance standard. The results of these experiments are illustrated in the accompanying figure, demonstrating the scalability and efficiency of VectorChord across different dataset sizes.

Result

Cost

In summary, this is the minimum hardware cost if you need 10000 QPS on such datasets:

dataset minimal hardware for 10000+ QPS number of vcpu estimate cost per month
LAION-5m r7i.xlarge × 7 4 × 7 = 28 vcpu $0.2646 × 7 × 720 = $1334
LAION-100m r7i.16xlarge × 2 64 × 2 = 128 vcpu $4.2336 × 2 × 720 = $6096
Tip

The cost of the primary instance is excluded from this calculation, as it can be shared and utilized with other PostgreSQL connection poolers, further optimizing resource allocation and reducing overall expenses.

This experiment empowers you to design and scale your own cluster to achieve the ideal level of VectorChord search performance, whether in the public cloud or a private data center. Even in scenarios where performance is the top priority, VectorChord delivers exceptionally competitive pricing, making it a cost-effective solution for high-performance vector search needs.

Summary

Whether you’re launching a startup on a budget-friendly VPS or scaling a large cluster for uncompromised performance, VectorChord opens up unlimited possibilities. While 10,000 QPS is the focus of this post, it’s far from the limit of what VectorChord can achieve.

With its powerful indexing algorithm, VectorChord not only surpasses standard PostgreSQL solutions like pgvector by efficiently utilizing memory and high-speed SSDs, but also outperforms specialized vector databases in handling massive data volumes and high QPS demands. At the same cost, it delivers superior performance while eliminating the complexity of maintaining a separate vector search system.

If you’re eager to explore large-scale vector search solutions, join our Discord community or reach out to us at [email protected]. Let’s unlock the full potential of your data together!

3 - Kubernetes

If you want to use vectorchord in PostgreSQL which is running in Kubernetes, we provide a tutorial to help you. In this tutorial, we will use CloudNative-PG to

If you want to use vectorchord in PostgreSQL which is running in Kubernetes, we provide a tutorial to help you. In this tutorial, we will use CloudNative-PG to deploy PostgreSQL cluster in Kubernetes.

Requirements

Name Version
helm >= 2.4.1
kubectl >= 1.14
Kubernetes >= 1.24.0
Kubernetes (Image Volume Extensions) >= 1.31.0

Install CloudNative-PG

We use CloudNative-PG (cnpg) to deploy PostgreSQL cluster in Kubernetes. You can install it by helm.

$ sudo helm repo add cnpg https://cloudnative-pg.github.io/charts
$ sudo helm upgrade --install cnpg   --namespace cnpg-system   --create-namespace   cnpg/cloudnative-pg
$ helm list --all-namespaces
NAME                    NAMESPACE       REVISION        UPDATED                                 STATUS          CHART                         APP VERSION
cnpg                    cnpg-system     1               2024-12-30 16:37:55.9520917 +0800 CST   deployed        cloudnative-pg-0.23.0               1.25.0   
$ kubectl get pod -n cnpg-system   
NAME                                   READY   STATUS    RESTARTS   AGE
cnpg-cloudnative-pg-54f97dbcdf-jm97n   1/1     Running   0          1m

PostgreSQL image with vectorchord

In the repository, we already provide a docker image with vectorchord installed. You can find it in ghcr.io/tensorchord/cloudnative-vectorchord. If you want to build your own image, you can use the following Dockerfile as a reference.

ARG CNPG_TAG

FROM ghcr.io/cloudnative-pg/postgresql:$CNPG_TAG-bookworm

ARG CNPG_TAG
ARG VECTORCHORD_TAG
ARG TARGETARCH

# drop to root to install packages
USER root
ADD https://github.com/tensorchord/VectorChord/releases/download/$VECTORCHORD_TAG/postgresql-${CNPG_TAG%.*}-vchord_${VECTORCHORD_TAG#"v"}-1_$TARGETARCH.deb /tmp/vchord.deb
RUN apt-get install -y /tmp/vchord.deb && rm -f /tmp/vchord.deb

USER postgres

Create PostgreSQL cluster

We provide a sample yaml file to create a PostgreSQL cluster in Kubernetes. You can modify it according to your needs. You need pay attention to the following points:

  • Set shared_preload_libraries to load vchord shared library.
  • Execute CREATE EXTENSION IF NOT EXISTS vchord CASCADE; to create vchord extension.
apiVersion: v1
kind: Secret
metadata:
  name: tensorchord
type: kubernetes.io/basic-auth
data:
  password: dGVuc29yY2hvcmQ= # tensorchord
  username: dGVuc29yY2hvcmQ= # tensorchord
---
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
  name: vchord
spec:
  instances: 1
  bootstrap:
    initdb:
      database: tensorchord
      postInitApplicationSQL:
        - CREATE EXTENSION IF NOT EXISTS vchord CASCADE;
      owner: tensorchord
      secret:
        name: tensorchord
      dataChecksums: true
      encoding: 'UTF8'
  storage:
    size: 1Gi
  imageName: ghcr.io/tensorchord/cloudnative-vectorchord:17
  postgresql:
    shared_preload_libraries: # load vchord shared library
    - "vchord"

You can install cnpg kubectl plugin to manage your PostgreSQL cluster. Now we can check the status of the cluster.

$ sudo kubectl get pod
vchord-1                                                 1/1     Running     0             42s

$ kubectl cnpg status vchord
Cluster Summary
Name:                vchord
Namespace:           default
System ID:           7527554386501582867
PostgreSQL Image:    ghcr.io/tensorchord/cloudnative-vectorchord:17
Primary instance:    vchord-1
Primary start time:  2025-07-16 05:55:51 +0000 UTC (uptime 53s)
Status:              Cluster in healthy state
Instances:           1
Ready instances:     1
Current Write LSN:   0/204FB50 (Timeline: 1 - WAL File: 000000010000000000000002)

Certificates Status
Certificate Name    Expiration Date                Days Left Until Expiration
----------------    ---------------                --------------------------
vchord-ca           2025-10-14 05:50:08 +0000 UTC  90.00
vchord-replication  2025-10-14 05:50:08 +0000 UTC  90.00
vchord-server       2025-10-14 05:50:08 +0000 UTC  90.00

Continuous Backup status
Not configured

Physical backups
No running physical backups found

Streaming Replication status
Not configured

Unmanaged Replication Slot Status
No unmanaged replication slots found

Managed roles status
No roles managed

Tablespaces status
No managed tablespaces

Instances status
Name      Database Size  Current LSN  Replication role  Status  QoS         Manager Version  Node
----      -------------  -----------  ----------------  ------  ---         ---------------  ----
vchord-1                 0/204FB50    Primary           OK      BestEffort  1.25.0           ip-10-0-2-29.ec2.internal

Notice: This article just provides a minimal configuration, not include logical replication, streaming logical replication, backup and restore, etc. We will provide those in another article. You can also refer to CloudNative-PG for more Cluster configuration.

Connect to PostgreSQL cluster

You can use kubectl port-forward to connect to PostgreSQL cluster.

$ kubectl port-forward services/vchord-rw 5432:5432
$ psql -h 127.0.0.1 -d tensorchord -U tensorchord
tensorchord=> \dx
                                                 List of installed extensions
  Name   | Version |   Schema   |                                         Description                                         
---------+---------+------------+---------------------------------------------------------------------------------------------
 plpgsql | 1.0     | pg_catalog | PL/pgSQL procedural language
 vchord  | 1.1.1   | public     | vchord: Vector database plugin for Postgres, written in Rust, specifically designed for LLM
 vector  | 0.8.1   | public     | vector data type and ivfflat and hnsw access methods
(3 rows)

The vchord extension is installed successfully. You can try quick-start without installation.

Lightweight Extension Images

Managing extensions is one of the biggest challenges when running PostgreSQL on Kubernetes. If you want to add a new extension to this image, you must rebuild the image, back up your PostgreSQL cluster, apply the new image with the added extension, and then restore the cluster. This process reflects the concept of immutable containers in Kubernetes.

Thanks to Andrew Dunstan and Matheus Alcantara, a proposal for PostgreSQL 18 introduces a new configuration option (GUC), extension_control_path, which allows users to specify additional directories for extension control files.

For Kubernetes, the ImageVolume feature allows us to mount a container image as a read-only and immutable volume inside a running pod. This enables PostgreSQL extensions packaged as independent OCI-compliant container images to be mounted inside CloudNativePG clusters at a known directory.

Based on these two features, we can create lightweight vectorchord extension image vchord-scratch.

Tip

If you want to use Image Volume Extensions, you need to meet the following requirements:

  • Use Kubernetes version 1.31.0 or above (1.33.0 is recommended), and make sure the ImageVolume feature gate is enabled.
  • Use CloudNative-PG helm chart version 0.26.0 or above.

Create PostgreSQL cluster with Image Volume Extensions

You can use the following sample yaml file to create a PostgreSQL cluster with vectorchord extension image. You can modify it according to your needs. You need pay attention to the following points:

  • Set shared_preload_libraries to load vchord shared library.
  • Execute CREATE EXTENSION IF NOT EXISTS vchord CASCADE; to create vchord extension.
  • Set extensions to specify the vchord-scratch extension image.
  • Set extension_control_path and dynamic_library_path to specify the paths of extension control files and shared library files.
apiVersion: v1
kind: Secret
metadata:
  name: tensorchord
type: kubernetes.io/basic-auth
data:
  password: dGVuc29yY2hvcmQ= # tensorchord 
  username: dGVuc29yY2hvcmQ= # tensorchord
---
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
  name: vchord 
spec:
  instances: 1 
  imageName: ghcr.io/cloudnative-pg/postgresql:18-system-bookworm
  bootstrap:
    initdb:
      database: tensorchord
      postInitApplicationSQL:
        - CREATE EXTENSION IF NOT EXISTS vchord CASCADE;
      owner: tensorchord
      secret:
        name: tensorchord
      dataChecksums: true
      encoding: 'UTF8'
  storage:
    size: 1Gi
  postgresql:
    extensions:
      - name: vchord
        image:
          reference: ghcr.io/tensorchord/vchord-scratch:pg18-v1.1.1
        dynamic_library_path:
          - /usr/lib/postgresql/18/lib/
        extension_control_path:
          - /usr/share/postgresql/18/
    shared_preload_libraries:
    - "vchord"

We can check the extension is already installed successfully.

$ kubectl port-forward services/vchord-rw 5432:5432
$ psql -h
$ psql -h 0.0.0.0 -U tensorchord -d tensorchord
Password for user tensorchord: 
psql (13.3 (Ubuntu 13.3-1.pgdg16.04+1), server 18.0 (Debian 18.0-1.pgdg12+3))
WARNING: psql major version 13, server major version 18.
         Some psql features might not work.
SSL connection (protocol: TLSv1.3, cipher: TLS_AES_256_GCM_SHA384, bits: 256, compression: off)
Type "help" for help.

tensorchord=> \dx
                                                 List of installed extensions
  Name   | Version |   Schema   |                                         Description                                         
---------+---------+------------+---------------------------------------------------------------------------------------------
 plpgsql | 1.0     | pg_catalog | PL/pgSQL procedural language
 vchord  | 1.1.1   | public     | vchord: Vector database plugin for Postgres, written in Rust, specifically designed for LLM
 vector  | 0.8.1   | public     | vector data type and ivfflat and hnsw access methods
(3 rows)
Warning

The image ghcr.io/cloudnative-pg/postgresql:18-system-bookworm already contains pgvector, which is required for vchord. However, this image may not include pgvector in future versions, but we have not seen this trend in the short term.