PostgreSQL 和 VectorChord 上的混合搜索:基础设施搭建
混合搜索结合了向量搜索和全文搜索方法,正成为现代搜索系统的标准配置。本文将深入探讨基于 PostgreSQL 和 VectorChord 的基础设施搭建方案——这一解决方案简化了混合搜索的实现,无需手动安装扩展或进行复杂配置。
环境搭建:Docker 和 PostgreSQL
开始之前,我们需要一个容器化环境。我们将使用 Docker Compose 部署带有预装 VectorChord 扩展的 PostgreSQL。请注意:在配置中,我们无需显式指定扩展——它们会在首次连接时通过 VechordRegistry 自动添加。
示例 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:
关键点:tensorchord/vchord-suite 镜像已包含所有必要组件,但扩展仅在首次通过 VechordRegistry 访问时激活。这避免了手动设置,并确保版本兼容性。
混合搜索的表设计
数据结构基于三个表。基础表 BaseTable 包含通用字段:
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))
metadata 字段以 JSONB 格式存储任意数据,时间戳会在记录创建时自动更新。请注意:updated_at 不会在记录更新时自动刷新——需要在更新方法中手动处理。
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
主键使用 UUID。Chunk 表通过外键关联文档:
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
这里,content_tsv 用于全文搜索(BM25),embedding 存储块的向量表示。外键 doc_id 会自动实现级联删除。
重要提示:尝试将 uid 移到基类会导致错误,因为 ForeignKey 会在 BaseTable 而非具体表中查找字段。因此,每个表都需要重复定义主键。
VechordRegistry:集中式数据库管理
VechordRegistry 类作为与数据库交互的统一接口,自动处理:
- 扩展安装(
vchord、vchord_bm25、pg_tokenizer) - 表和分词器创建
- CRUD 操作和搜索
初始化:
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]
)
首次使用(在 async with vr: 上下文中),会运行 init_extension 方法,安装扩展并配置 search_path。这消除了手动设置的需求,并减少配置错误。
插入文档的示例用法:
async with vr:
doc = Document(title='Note', text='Some text', metadata=Jsonb({}))
await vr.insert(doc)
运行此代码后,数据库中会出现所有必要扩展、表和记录。
数据处理管道
对于创建文档后进行分块等复杂操作,使用 VechordPipeline。它允许将多个步骤组合成管道,每阶段结果自动保存到数据库。
步骤 1:文档创建。@vr.inject(output=Document) 装饰器确保对象持久化:
@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
步骤 2:分块生成。从 Document 表获取数据,返回块列表:
@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)
]
管道组装:
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)
这种方法确保操作的原子性,并最小化数据库访问。
关键要点:核心实现注意事项
基于 VectorChord 实现混合搜索时,需要注意:
- 自动扩展初始化:VechordRegistry 在首次连接时安装所有必要组件,简化部署。
- 表结构:将文档和块分离对高效搜索至关重要;外键确保数据完整性。
- 处理管道:使用
VechordPipeline封装复杂操作,确保数据一致性。
这些要素构成了可扩展搜索系统的基石,结合了向量搜索的精确性和全文搜索的灵活性。
— Editorial Team
暂无评论。