Zurück zur Startseite

Hybride Suche in PostgreSQL | VectorChord Setup

Der Artikel beschreibt die Einrichtung der Infrastruktur für hybride Suche mit PostgreSQL und VectorChord. Er umfasst Tabellendesign, automatische Erweiterungsinstallation und die Organisation von Datenverarbeitungspipelines. Das Material richtet sich an Entwickler, die fortschrittliche Suchsysteme erstellen.

So richten Sie hybride Suche in PostgreSQL mit VectorChord ein
Advertisement 728x90

## Hybride Suche auf PostgreSQL und VectorChord: Infrastruktur-Setup

Die hybride Suche, die Vektor- und Volltextmethoden kombiniert, wird zum Standard moderner Suchsysteme. In diesem Artikel tauchen wir in die Einrichtung der Infrastruktur basierend auf PostgreSQL und VectorChord ein – eine Lösung, die die Implementierung der hybriden Suche vereinfacht, ohne dass manuelle Installation von Erweiterungen und komplexe Konfigurationen erforderlich sind.

Umgebungseinrichtung: Docker und PostgreSQL

Zum Einstieg benötigen wir eine containerisierte Umgebung. Wir verwenden Docker Compose, um PostgreSQL mit vorinstallierten VectorChord-Erweiterungen bereitzustellen. Hinweis: In der Konfiguration geben wir die Erweiterungen nicht explizit an – sie werden bei der ersten Verbindung automatisch über VechordRegistry hinzugefügt.

Beispiel für eine docker-compose-dev.yml-Datei:

Google AdInline article slot
services:
  postgres:
    image: tensorchord/vchord-suite:pg18-latest
    environment:
      POSTGRES_DB: ${DB__NAME}
      POSTGRES_USER: ${DB__USER}
      POSTGRES_PASSWORD: ${DB__PASSWORD}
    volumes:
      - pgdata:/var/lib/postgresql
    ports:
      - "5432:5432"
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${DB__USER} -d ${DB__NAME}"]
      interval: 5s
      timeout: 5s
      retries: 5
volumes:
  pgdata:

Wichtiger Punkt: Das Image tensorchord/vchord-suite enthält bereits alle notwendigen Komponenten, aber die Erweiterungen werden erst bei der ersten Zugriff über VechordRegistry aktiviert. Dadurch entfällt die manuelle Einrichtung und die Versionskompatibilität ist gewährleistet.

Tabellendesign für hybride Suche

Die Datenstruktur basiert auf drei Tabellen. Die Basistabelle BaseTable enthält gemeinsame Felder:

from datetime import datetime, timezone
from functools import partial
import msgspec
from psycopg.types.json import Jsonb
from vechord import Table

class BaseTable(Table, kw_only=True):
    metadata: Jsonb
    created_at: datetime = msgspec.field(
        default_factory=partial(datetime.now, timezone.utc))
    updated_at: datetime = msgspec.field(
        default_factory=partial(datetime.now, timezone.utc))

Das Feld metadata speichert beliebige Daten im JSONB-Format, und die Zeitstempel werden bei der Erstellung eines Datensatzes automatisch aktualisiert. Hinweis: updated_at wird bei Änderungen des Datensatzes nicht automatisch aktualisiert – dies muss manuell in den Update-Methoden erfolgen.

Google AdInline article slot

Die Document-Tabelle:

import msgspec
from vechord.spec import PrimaryKeyUUID
from .base import BaseTable

class Document(BaseTable, kw_only=True):
    uid: PrimaryKeyUUID = msgspec.field(default_factory=PrimaryKeyUUID.factory)
    title: str
    text: str

UUID wird als Primärschlüssel verwendet. Die Chunk-Tabelle ist über einen Fremdschlüssel mit dem Dokument verknüpft:

from vechord.spec import ForeignKey, Keyword, PrimaryKeyUUID, Vector

class Chunk(BaseTable, kw_only=True):
    uid: PrimaryKeyUUID = msgspec.field(default_factory=PrimaryKeyUUID.factory)
    doc_id: Annotated[UUID, ForeignKey[Document.uid]]
    content: str
    content_tsv: Keyword
    embedding: DenseVector
    chunk_index: int

Hier ist content_tsv für die Volltextsuche (BM25) vorgesehen, und embedding speichert die Vektordarstellung des Chunks. Der Fremdschlüssel doc_id implementiert automatisch eine Kaskadendekete.

Google AdInline article slot

Wichtig: Der Versuch, uid in die Basisklasse zu verschieben, führt zu einem Fehler, da ForeignKey das Feld in BaseTable sucht, anstatt in der spezifischen Tabelle. Daher wird der Primärschlüssel in jeder Tabelle dupliziert.

VechordRegistry: Zentrale Datenbankverwaltung

Die Klasse VechordRegistry dient als einheitliche Schnittstelle zur Arbeit mit der Datenbank. Sie automatisiert:

  • Installation von Erweiterungen (vchord, vchord_bm25, pg_tokenizer)
  • Erstellung von Tabellen und Tokenizern
  • CRUD-Operationen und Suche

Initialisierung:

from vechord import VechordRegistry
from core import settings
from .document import Document
from .doc_chunk import Chunk

vr = VechordRegistry(
    namespace=settings.db.namespace,
    url=settings.db.db_url,
    tables=[Document, Chunk]
)

Beim ersten Einsatz (im Kontext async with vr:) läuft die Methode init_extension, die Erweiterungen installiert und search_path konfiguriert. Dadurch entfällt die manuelle Einrichtung und Konfigurationsfehler werden minimiert.

Beispiel für das Einfügen eines Dokuments:

async with vr:
    doc = Document(title='Note', text='Some text', metadata=Jsonb({}))
    await vr.insert(doc)

Nach Ausführung dieses Codes erscheinen alle notwendigen Erweiterungen, Tabellen und der Datensatz in der DB.

Datenverarbeitungs-Pipelines

Für komplexe Operationen wie die Erstellung eines Dokuments gefolgt von Chunking wird VechordPipeline verwendet. Sie ermöglicht das Kombinieren mehrerer Schritte zu einer Pipeline, wobei jede Stufe automatisch Ergebnisse in der DB speichert.

Schritt 1: Dokumentenerstellung. Der Dekorator @vr.inject(output=Document) sorgt für die Persistierung des Objekts:

@vr.inject(output=Document)
async def _create_document(doc_data: DocumentCreate) -> Document:
    doc = Document(
        title=doc_data.title,
        text=doc_data.text,
        metadata=Jsonb(doc_data.metadata)
    )
    return doc

Schritt 2: Chunk-Generierung. Nimmt Daten aus der Document-Tabelle und gibt eine Liste von Chunks zurück:

@vr.inject(input=Document, output=Chunk)
async def create_chunks(uid: UUID, text: str) -> list[Chunk]:
    chunks = await _chunker.segment(text)
    return [
        Chunk(
            doc_id=uid,
            content=chunk,
            content_tsv=Keyword(chunk),
            embedding=DenseVector(await _embedder.vectorize_chunk(chunk)),
            metadata=Jsonb({}),
            chunk_index=i
        )
        for i, chunk in enumerate(chunks, start=1)
    ]

Zusammenbau der Pipeline:

from db_models import vr
from .utils import create_chunks, create_document

class DocumentService:
    async def create_document(self, doc_data: DocumentCreate):
        pipeline = vr.pipeline(
            _create_document,
            create_chunks
        )
        await pipeline(doc_data)

Dieser Ansatz gewährleistet die Atomarität der Operationen und minimiert DB-Zugriffe.

Wichtige Punkte: Essenzielle Aspekte der Implementierung

Beim Implementieren der hybriden Suche basierend auf VectorChord beachten Sie:

  • Automatische Initialisierung von Erweiterungen: VechordRegistry installiert alle notwendigen Komponenten bei der ersten Verbindung und vereinfacht so den Deployment-Prozess.
  • Tabellenstruktur: Die Trennung von Dokumenten und Chunks ist entscheidend für eine effiziente Suche; Fremdschlüssel gewährleisten die Datenintegrität.
  • Verarbeitungs-Pipelines: Die Nutzung von VechordPipeline kapselt komplexe Operationen und sorgt für Datenkonsistenz.

Diese Elemente bilden die Grundlage für ein skalierbares Suchsystem, das die Präzision der Vektorsuche mit der Flexibilität der Volltextsuche kombiniert.

— Editorial Team

Advertisement 728x90

Weiterlesen