## 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:
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.
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.
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
VechordPipelinekapselt 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
Noch keine Kommentare.