Retour à l'accueil

pgvector vs pgvectorscale vs VectorChord : comparaison

Analyse comparative des extensions PostgreSQL pour la recherche vectorielle : pgvector, pgvectorscale et VectorChord. Description des algorithmes HNSW, StreamingDiskANN, RaBitQ. Infrastructure Docker, chargement en masse 1M enregistrements, préparation des benchmarks.

Bataille des index vectoriels PostgreSQL : qui est le plus rapide ?
Advertisement 728x90

pgvector vs pgvectorscale vs VectorChord : Comparatif des performances pour la recherche vectorielle dans PostgreSQL

La recherche vectorielle transforme le texte en embeddings numériques et trouve des documents sémantiquement similaires grâce à des métriques de proximité. Les mots ayant des sens proches se regroupent dans un espace à N dimensions. Un exemple classique : Roi - Homme + Femme ≈ Reine illustre les analogies vectorielles.

Métriques clés :

  • Similarité cosinus : angle entre les vecteurs ; les vecteurs normalisés donnent des résultats équivalents à la distance euclidienne.
  • Distance euclidienne : distance en ligne droite entre deux points.

Les extensions PostgreSQL implémentent la recherche ANN (Approximate Nearest Neighbor) via IVFFlat, HNSW et des algorithmes spécialisés.

Google AdInline article slot

Principales extensions de recherche vectorielle

pgvector

Extension open-source en C avec le type vector. Prend en charge la similarité cosinus, la distance L2 et le produit scalaire. Types d'index :

  • IVFFlat : clustering de vecteurs.
  • HNSW : graphe hiérarchique, recherche descendante par niveaux.

Nécessite un chargement complet de l'index en RAM.

pgvectorscale

Couche basée sur Rust au-dessus de pgvector (utilisant PGRX). Propose l'index StreamingDiskANN (adaptation de DiskANN) qui stocke les données sur SSD, réduisant ainsi l'utilisation de la RAM. Utilise la compression SBQ (Statistical Binary Quantization).

Google AdInline article slot

Gains de performance annoncés : 16x plus de débit sur 50 millions de vecteurs de dimension 768.

pgvecto.rs

Écrit entièrement en Rust, prend en charge les formats binaire, FP16 et INT8, avec des dimensions allant jusqu’à 65 535. L'algorithme VBASE est optimisé pour les requêtes hybrides (vecteurs + filtres + JOINs).

VectorChord

Le successeur actif de pgvecto.rs par TensorChord. Utilise RaBitQ (Randomized Binary Quantization) combiné à un réordonnancement. Stocke 26 fois plus de données au même coût.

Google AdInline article slot

Toutes utilisent le type standard vector, assurant une compatibilité totale lors des migrations.

Configuration de l'environnement de test

Déploiement de trois bases de données via Docker Compose :

  • pgvectorscale (timescale/timescaledb-ha:pg18) :
services:
  postgres:
    image: timescale/timescaledb-ha:pg18
    ports:
      - "5432:5432"
    volumes:
      - pgdata:/home/postgres/pgdata
      - ./db_init_pgv:/docker-entrypoint-initdb.d

Initialisation :

CREATE EXTENSION IF NOT EXISTS vector;
CREATE EXTENSION IF NOT EXISTS vectorscale;
  • VectorChord (tensorchord/vchord-postgres:pg18-v1.1.1) :
CREATE EXTENSION IF NOT EXISTS vchord CASCADE;

Fichier Makefile pour l'orchestration :

COMPOSE := docker compose
PGV_FILE := docker-compose-pgv.yml
PGVS_FILE := docker-compose-pgvs.yml
VC_FILE := docker-compose-vc.yml

up-pgv:
	$(COMPOSE) -f $(PGV_FILE) up --build -d
up-pgvs:
	$(COMPOSE) -f $(PGVS_FILE) up --build -d
up-vc:
	$(COMPOSE) -f $(VC_FILE) up --build -d
up: up-pgv up-pgvs up-vc

Les noms de services et les ports doivent être uniques.

Extension du dépôt pour les opérations en masse

La méthode bulk_create dans BaseRepository :

async def bulk_create(self, schemas: list[CreateSchemaType]) -> list[ModelType]:
    instances = [
        self.model(**self._normalize_embedding_format(schema.model_dump()))
        for schema in schemas
    ]
    self.session.add_all(instances)
    await self.session.flush()
    return instances

Schéma BulkCreateRequest :

class BulkCreateRequest(BaseModel):
    documents: Annotated[
        list[DocumentCreate],
        Field(..., description='Liste des documents à créer', min_length=1, max_length=500)
    ]

Point d'entrée :

@router.post('/bulk', response_model=list[DocumentResponse], status_code=status.HTTP_201_CREATED)
async def bulk_create_documents(request: BulkCreateRequest):
    async with transaction() as session:
        repo = DocumentRepository(session)
        created_docs = await repo.bulk_create(request.documents)
        return created_docs

Préparation au benchmark

Jeux de données : ag_news depuis Hugging Face (titres → embeddings, catégorie → métadonnées). Objectif : 1 million d’enregistrements.

Dépendances :

uv add --dev datasets ollama aiohttp tqdm

Composants du test :

  • Chargement par lots depuis les jeux de données
  • Génération d’embeddings via ollama
  • Insertion massive dans la base
  • Mesure de latence pour les requêtes de recherche

La structure de classe de test permet une comparaison parallèle entre toutes les extensions.

Points clés

  • Compatibilité : Toutes les extensions utilisent le type vector ; le code est portable.
  • Évolutivité : pgvectorscale et VectorChord fonctionnent avec le stockage disque ; pgvector nécessite de la RAM.
  • Compression : RaBitQ (densité 26x), SBQ pour les grands jeux de données.
  • Recherche hybride : VBASE dans pgvecto.rs supporte les filtres + JOINs.
  • Performance : StreamingDiskANN offre une amélioration de 16x du débit sur 50 millions de vecteurs et plus.

— Editorial Team

Advertisement 728x90

Lire ensuite