# Hybrydowe wyszukiwanie w PostgreSQL i VectorChord: konfiguracja infrastruktury
Hybrydowe wyszukiwanie, łączące metody wektorowe i pełnotekstowe, staje się standardem dla nowoczesnych systemów wyszukiwania. W tym artykule szczegółowo omówimy konfigurację infrastruktury opartej na PostgreSQL i VectorChord — rozwiązaniu, które upraszcza implementację hybrydowego wyszukiwania bez konieczności ręcznej instalacji rozszerzeń i skomplikowanej konfiguracji.
Przygotowanie środowiska: Docker i PostgreSQL
Na początek potrzebujemy środowiska konteneryzowanego. Użyjemy Docker Compose do uruchomienia PostgreSQL z preinstalowanymi rozszerzeniami VectorChord. Uwaga: w konfiguracji nie wskazujemy rozszerzeń explicite — zostaną one dodane automatycznie przy pierwszym połączeniu przez VechordRegistry.
Przykład pliku docker-compose-dev.yml:
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:
Kluczowy moment: obraz tensorchord/vchord-suite już zawiera wszystkie niezbędne komponenty, ale rozszerzenia są aktywowane dopiero przy pierwszym obraschenii przez VechordRegistry. Dzięki temu unikamy ręcznej konfiguracji i zapewniamy kompatybilność wersji.
Projektowanie tabel dla hybrydowego wyszukiwania
Struktura danych opiera się na trzech tabelach. Bazowa tabela BaseTable zawiera pola wspólne:
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))
Pole metadata przechowuje dowolne dane w formacie JSONB, a znaczniki czasowe są aktualizowane automatycznie przy tworzeniu rekordu. Uwaga: updated_at nie jest aktualizowane automatycznie przy zmianie rekordu — należy to zrobić ręcznie w metodach aktualizacji.
Tabela dokumentów Document:
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
Dla klucza głównego używany jest UUID. Tabela chunków Chunk jest powiązana z dokumentem przez klucz obcy:
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
Tutaj content_tsv służy do wyszukiwania pełnotekstowego (BM25), a embedding przechowuje wektorowe predstavlenie chunku. Klucz obcy doc_id automatycznie realizuje kaskadowe usuwanie.
Ważne: próba wyprowadzenia uid do klasy bazowej powoduje błąd, ponieważ ForeignKey zaczyna szukać pola w BaseTable, a nie w konkretnej tabeli. Dlatego klucz główny jest duplikowany w każdej tabeli.
VechordRegistry: scentralizowane zarządzanie bazą danych
Klasa VechordRegistry służy jako jednolity interfejs do pracy z bazą. Automatyzuje:
- Instalację rozszerzeń (
vchord,vchord_bm25,pg_tokenizer) - Tworzenie tabel i tokenizatorów
- Wykonywanie operacji CRUD i wyszukiwania
Inicjalizacja:
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]
)
Przy pierwszym użyciu (w kontekście async with vr:) uruchamiana jest metoda init_extension, która instaluje rozszerzenia i konfiguruje search_path. To eliminuje konieczność ręcznej konfiguracji i minimalizuje błędy.
Przykład użycia do wstawienia dokumentu:
async with vr:
doc = Document(title='Note', text='Some text', metadata=Jsonb({}))
await vr.insert(doc)
Po wykonaniu tego kodu w bazie pojawią się wszystkie niezbędne rozszerzenia, tabele i rekord.
Pipeliny przetwarzania danych
Do złożonych operacji, takich jak tworzenie dokumentu z późniejszym chunkowaniem, używa się VechordPipeline. Pozwala on połączyć kilka kroków w konvejery, gdzie każdy etap automatycznie zapisuje wyniki do bazy.
Krok 1: Tworzenie dokumentu. Dekorator @vr.inject(output=Document) gwarantuje zapis obiektu:
@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
Krok 2: Generowanie chunków. Przyjmuje dane z tabeli Document i zwraca listę chunków:
@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)
]
Montaż pipeliny:
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)
To podejście zapewnia atomowość operacji i minimalizuje liczbę zapytań do bazy.
Co ważne: kluczowe aspekty implementacji
Przy wdrażaniu hybrydowego wyszukiwania opartego na VectorChord zwróć uwagę na:
- Automatyczna inicjalizacja rozszerzeń: VechordRegistry instaluje wszystkie niezbędne komponenty przy pierwszym połączeniu, co upraszcza wdrożenie.
- Struktura tabel: podział na dokumenty i chunki jest kluczowy dla efektywnego wyszukiwania; klucze obce zapewniają integralność danych.
- Pipeliny przetwarzania: użycie
VechordPipelinepozwala enkapsulować złożone operacje i gwarantować spójność danych.
Te elementy tworzą podstawę skalowalnego systemu wyszukiwania, łączącego precyzję wyszukiwania wektorowego z elastycznością pełnotekstowego.
— Editorial Team
Brak komentarzy.