大规模使用 Weaviate 导入和向量化数据
大多数向量数据库的原型在数据导入环节失败,而非搜索。本文介绍了在 Weaviate 中大规模导入数据的最佳实践,包括服务端批处理、错误处理、数据类型选择、blobHash 的使用、多模态数据摄取以及避免常见陷阱。
大多数向量数据库的原型在数据导入阶段就失败了,而不是在搜索阶段。你构建了一个巧妙的检索管道,看着它在数千个文档上工作,然后有人递给你五千万行数据。接下来的两周你将陷入速率限制、部分失败以及三次重写批处理逻辑的泥潭。
本文是我希望自己在第一次将真实数据集导入 Weaviate 时就能拥有的指南。它涵盖了服务端批处理、错误处理、那些一旦选错就会让你付出最大代价的数据类型决策,以及如何在不搭建 OCR 管道的情况下摄入媒体和 PDF。
没有人警告过你的导入问题
一个能用的原型无法告诉你大规模运行时会发生什么。那些困扰生产团队的问题几乎从未在教程中出现过。四个最棘手的问题:
- 嵌入提供商的速率限制:大多数团队在真正导入后一小时内就会遇到,然后写重试代码,再为重试代码写重试代码。
- HTTP 200 的谎言:成功的批处理响应并不代表每个对象都已写入。单个对象可能在绿色状态码背后悄然失败。
- 重试时的重复工作:如果你每次重新运行都生成新的 ID,你会重新向量化相同的文档并为此支付两次费用。
- 媒体文件的内存爆炸:将一百万个产品照片加载到 Python 列表会在批处理逻辑运行之前就终结你的脚本。
服务端批处理
服务端批处理是一种流式导入模式,Weaviate 服务器会根据自身当前的工作负载告诉客户端接下来应发送多少数据。你无需猜测批次大小和并发级别,服务器会测量其队列深度并通过持久连接施加背压。
这很重要,因为正确的批次大小不是一个常数。它取决于你拥有的属性数量、文本字段的大小、是否实时向量化、向量化器在底层做什么以及集群的其他负载。手动调整很脆弱。服务器已经拥有所有这些信息。
以下是 Python 客户端中的模式:
import weaviate
from weaviate.classes.init import Auth
client = weaviate.connect_to_weaviate_cloud(
cluster_url=WCD_URL,
auth_credentials=Auth.api_key(WCD_API_KEY),
)
collection = client.collections.get("Products")
with collection.batch.stream() as batch:
for row in iter_rows():
batch.add_object(properties=row)
if batch.number_errors > 10:
print("错误过多,停止。")
break
if collection.batch.failed_objects:
print(f"{len(collection.batch.failed_objects)} 个对象失败。")这就是整个模式。没有 batch_size,没有 concurrent_requests,没有调优。stream() 上下文管理器打开持久连接,服务器设置速率,错误异步流回而不会中断流程。
错误处理与重试
生产环境中导入脚本最常见的错误是将 200 响应视为成功的证明。事实并非如此。200 表示你的请求到达服务器并被接受,但并不代表每个对象都已被写入。向量化器错误、模式不匹配以及上游嵌入 API 的速率限制响应都会作为逐对象错误出现在一个原本正常的批处理响应中。
Python 客户端会在每次批处理中公开三件事:
- batch.failed_objects:每个失败的对象及错误信息
- batch.failed_references:每个失败的交叉引用
- batch.number_errors:上下文管理器内的运行计数
将 failed_objects 视为一个队列。将其写入文件,重试,如果同一错误再次失败,则将其移至死信位置,这样其余部分的导入就不会因一个损坏的行而停滞。
以下是一个在生产中经得起考验的重试+检查点模式:
import json
from weaviate.util import generate_uuid5
with collection.batch.stream() as batch:
for row in iter_rows():
batch.add_object(
properties=row,
uuid=generate_uuid5(row["source_id"]),
)
with open("failed.jsonl", "a") as f:
for obj in collection.batch.failed_objects:
f.write(json.dumps({
"properties": obj.object_.properties,
"error": obj.message,
}) + "\n")有两件事使得这个模式可以安全地重新运行。首先,generate_uuid5 对相同的 source_id 产生相同的 UUID,因此重试会覆盖而不是重复。其次,错误会被写入一个文件,你可以在修复根本问题后重新导入该文件。没有静默丢失,也没有嵌入的双重计费。
常见失败模式及应对措施:
| 症状 | 可能原因 | 修复 | |------|----------|------| | HTTP 200,对象缺失 | 向量化器速率受限 | 检查 failed_objects,重试失败子集 | | 客户端内存爆炸 | 在流式传输前加载整个数据集 | 从磁盘或数据库游标流式读取,不要预加载 | | 重试后出现重复对象 | 每次运行使用新鲜的随机 UUID | 使用基于稳定源键的 generate_uuid5 | | 导入后向量为空 | 集合上未配置向量化器模块 | 重新运行前检查集合配置 |
通过 MCP 服务器导入
Weaviate 内置了一个 MCP 服务器(预览版,v1.37.1 添加),允许 LLM 或 IDE 助手(如 Claude Code、Cursor、VS Code)通过模型上下文协议读写你的实例。启用 MCP_SERVER_ENABLED=true,并选择启用写入(MCP_SERVER_WRITE_ACCESS_ENABLED=true),服务器会暴露一个 weaviate-objects-upsert 工具,允许在对话中创建或更新对象。它运行在与 REST API 相同的端口上,并尊重 RBAC,因此无需额外部署。
当代理需要在工作时写入少量记录时(持久化代理记忆、同步小型集合或在编辑器中修复少量对象),这是正确的工具。
但它不是导入管道。每个对象都是由模型组装并通过工具调用传递的,因此受限于上下文窗口和每次调用的延迟,且没有上述章节中的背压、流式或重试检查点机制。超过几十个对象后,请使用 collection.batch.stream()(或客户端的批处理 API),将 MCP 服务器留给它擅长的小批量对话式写入。
导入前选择数据类型
模式决策在导入运行后修复的成本要高出十倍。第一次就做对。
在导入时最重要的决策:
- 使用正确分词法的 text。分词法决定了哪些 BM25 查询匹配哪些记录。对于英文散文,默认值很好。对于产品代码、URL 或任何字面字符串重要的内容,请切换为字段分词。分词教程介绍了权衡。
- 用于外键的 uuid。索引后用于快速过滤,在插入时验证,并在客户端中作为真正的 UUID 呈现,而不是字符串。
- int 与 number。对于计数和 ID 使用 int,对于价格和比率使用 number。混用会在每个查询中强制进行类型转换。
- 对于实际过滤的关系,使用引用类型。如果要独立查询相关字段,不要将所有内容都展平到一个大型嵌套对象中。
完整列表在数据类型参考中。
blobHash:存储嵌入,跳过字节
如果你正在导入媒体(图像、音频、视频、PDF),这是需要了解的数据类型。
常规 blob 将完整的 base64 有效负载存储在磁盘上。blobHash 则不然。它在导入时将原始字节发送给向量化器,以便模型看到实际媒体,然后丢弃除 SHA-256 哈希之外的所有内容。向量索引保留嵌入。blob 存储保留一个 32 字节的指纹。
{
"properties": [
{
"name": "product_image",
"dataType": ["blobHash"]
}
]
}实际影响:一个 10 TB 的图像语料库会缩减为几 GB 的哈希加上向量索引。相似性搜索的行为与使用 blob 完全相同。你只是不需要在 Weaviate 中存储原始字节。将它们保存在应有的对象存储中。
还有另一个不错的特性。当你更新一个对象时,新的 base64 会被哈希并与存储的哈希进行比较。如果哈希匹配,Weaviate 会完全跳过重新向量化。仅此一点就能在有人错误地重新运行导入管道时收回成本。
无需 OCR 管道的 PDF 向量化
对于大多数读者来说,实际问题是“如何在不编写 OCR 管道的情况下导入 PDF 文件夹。”最简短的答案是启动 Weaviate Cloud 试用版并使用 Weaviate Embeddings。
Weaviate Embeddings 有一个专为基于图像的文档检索设计的多模态模型。你给它一个页面图像,它会生成一个向量。无需 OCR 步骤。无需布局检测。无需文本提取。表格、图表、扫描表单、混合语言文档都以相同的方式处理。这是仅云端方案,但它是尝试在真实数据集上进行 PDF 检索的最简单途径,对于数十万页以内的集合,无需你做任何架构决策。
另一个现成选项是 Google 的 multi2vec-google(与 gemini-embedding-2 搭配,3072 维),在 Weaviate Cloud 上默认启用。它遵循相同的工作流程:你将页面渲染为图像并嵌入这些图像。该模块接受图像输入,而非原始 PDF 文件,因此栅格化步骤与 Weaviate Embeddings 相同。
如果你在规模上自行托管且文档布局很重要,请查看多向量 ColPali 配方。它使用视觉语言模型为每页生成多个向量,并完全跳过分块。组件更多,但对于视觉丰富文档的检索而言,这是最先进的技术。
所有三种方案都在同一位置——模型提供商参考中。
多模态导入:文本、图像、音频、视频
Weaviate 中的多模态并非独立产品。它是集合上的一个向量化器模块。你声明集合使用哪个模型,导入具有媒体属性(理想情况下为 blobHash)的对象,并使用你已有的同一客户端跨模态查询。
各提供商覆盖范围:
| 提供商 | 模块 | 文本 | 图像 | 音频 | 视频 | |--------|------|------|------|------|------| | Weaviate Embeddings | native (WCD) | ✓ | ✓ | | | | Google | multi2vec-google | ✓ | ✓ | ✓ | ✓ | | Voyage AI | multi2vec-voyageai | ✓ | ✓ | ✓ | | | Jina AI | multi2vec-jinaai | ✓ | ✓ | | | | Cohere | multi2vec-cohere | ✓ | ✓ | | | | NVIDIA | multi2vec-nvidia | ✓ | ✓ | | | | CLIP (self-hosted) | multi2vec-clip | ✓ | ✓ | | | | ImageBind (self-hosted) | multi2vec-bind | ✓ | ✓ | | |
一个具体场景:你正在为电商目录构建搜索。每个产品都有名称、描述、三张照片和一个十五秒的演示视频。你想要一个查询——“紧凑型无线耳塞,带主动降噪”——来找到正确的产品,无论相关信号在文本、照片还是视频中。
你声明一个集合,该集合在命名向量上使用 multi2vec-google:一个用于名称和描述的文本向量,加上一个用于产品图像的独立 blobHash 向量,以及另一个用于演示视频的向量。每个 blobHash 属性都需要自己的命名向量——一旦 Weaviate 将原始字节替换为哈希,它们就无法再与其他字段一起重新向量化,因此模式将它们分开。然后,一个多目标查询同时对所有三个向量进行排名:一个集合,一个查询,三种模态——而且媒体字节永远不会在 Weaviate 内部存储两次。
每个提供商的完整设置细节都在模型提供商参考中。
开始前的检查清单
- 仔细选择数据类型。任何不需要检索原始数据的媒体使用 blobHash。任何字面字符串重要的文本使用字段分词。
- 在集合级别选择向量化器,而不是在导入脚本中。在 Weaviate Cloud 上,Weaviate Embeddings 是最简单的默认选项。
- 使用确定性 UUID(从稳定源键生成 generate_uuid5),以便重试具有幂等性。
- 使用服务端流式批处理,避免手动调整批次大小。
- 实现死信队列以处理持久性错误。
- 对于多模态内容,使用 blobHash 并配置带有独立命名向量的多模态模型。