Calendar 第 4 天

大规模数据摄取


在向量搜索应用中,插入几千个数据点是简单的,但当处理数百万或数十亿条记录时,情况就完全不同了。摄取过程中微小的低效率会累积成显著的时间损失、增加内存压力和降低搜索性能。

每一次独立的 upsert 调用都会启动一个事务,该事务会消耗内存和磁盘 I/O 来构建部分索引。在大规模操作中,这种简单的方法可能会使您的系统不堪重负,导致上传时间飙升和搜索质量下降。高效地准备数据并将其加载到 Qdrant 对于构建健壮且可扩展的 AI 应用程序至关重要。

选择您的摄取策略

Qdrant 提供了几种数据摄取方法,每种方法都针对不同的规模和用例进行了定制。值得注意的是,只有 Python 客户端支持 upload_points 和 upload_collection 方法。如果您在不同的客户端上使用 Qdrant,那么我们建议使用带有批量上传的 upsert 进行大规模摄取。了解更多关于批量操作的信息

  • upsert(单个或批量):这是添加或更新点的基本操作。单个 upsert 最适合实时更新,而批量操作最适合更大的工作负载。

  • upload_points:此方法经过优化,可用于上传可轻松放入客户端内存的整个批次点。它利用了惰性批处理、重试和并行性等功能,使其成为中等规模数据集的有力选择。

  • upload_collection:对于真正大规模的数据集,upload_collection 是最强大的工具。它直接从迭代器流式传输数据,这意味着整个数据集不需要一次性加载到内存中。这种内存高效的方法非常适合摄取数百万或数十亿个点。

注意:对于其他语言的客户端,如 TypeScriptRustGo,批量 upsert 调用是高效数据加载的推荐方法。

规模启发式方法

决定使用哪种方法可以遵循一些简单的经验法则。虽然每个用例都不同,但这些启发式方法提供了一个坚实的起点。

  • 少于 100,000 个点:单线程批量 upsert 操作通常表现良好。
  • 100,000 到 100 万个点:建议使用 upload_points,批量大小在 1,000 到 10,000 之间,以平衡网络开销和内存使用。
  • 超过 100 万个点upload_collection 是从磁盘流式传输数据的理想选择。为了最大化吞吐量,您应该通过将 parallel 参数设置为可用 CPU 核心数(例如 4 或 8)来启用并行性。

最佳实践:从小处着手并进行测试。在尝试上传整个数据集之前,先摄取一小部分以验证您的配置和流程。

一个真实世界的例子:摄取 LAION-400M

为了说明这些原则,让我们检查一下摄取 LAION-400M 数据集的过程,该数据集包含大约 4 亿个图像-文本对,具有 512 维 CLIP 嵌入。这个庞大的数据集,包含 400 GB 的向量和 200 GB 的有效载荷,需要精心优化的策略。

最优集合配置

可扩展摄取的基础是精心设计的集合配置。对于如此规模的数据集,目标是智能地平衡内存使用、磁盘 I/O 和搜索性能。

from qdrant_client import QdrantClient, models
import os

client = QdrantClient(url=os.getenv("QDRANT_URL"), api_key=os.getenv("QDRANT_API_KEY"))

client.recreate_collection(
    collection_name="laion400m_collection",
    vectors_config=models.VectorParams(
        size=512,  # CLIP embedding dimensions
        distance=models.Distance.COSINE,
        on_disk=True,  # Store original vectors on disk
    ),
    quantization_config=models.BinaryQuantization(
        binary=models.BinaryQuantizationConfig(
            always_ram=True,  # Keep quantized vectors in RAM
        )
    ),
    optimizers_config=models.OptimizersConfigDiff(
        max_segment_size=5_000_000, # Create larger segments for faster search
    ),
    hnsw_config=models.HnswConfigDiff(
        m=6,  # Lower m to reduce memory usage
        on_disk=False  # Keep the HNSW index graph in RAM
    ),
)

此配置采用了几个关键优化:

  • on_disk=True:这是大型数据集最关键的设置。它指示 Qdrant 将全精度原始向量存储在磁盘上(内存映射存储),而不是存储在 RAM 中,从而显著降低内存要求。

  • always_ram=True 的二进制量化:虽然原始向量在磁盘上,但我们启用了二进制量化并强制压缩向量保留在 RAM 中。这为快速初始候选搜索提供了一个轻量级的内存中表示。

  • 大段大小max_segment_size 被增加以创建更少、更大的段。这可以提高搜索性能,但代价是索引速度略慢。

  • 内存中 HNSW 索引:通过将 HNSW 配置on_disk=False,我们将图索引保留在 RAM 中。这确保了在搜索过程中导航向量关系非常快,避免了磁盘延迟。m 值降低到 6 以进一步节省内存。

上传过程

配置好集合后,可以使用内存高效的流式传输方法进行上传。LAION 数据集被分成 409 个部分,每个部分包含大约 100 万条记录。脚本一次处理一个部分,下载数据,准备点,然后将其流式传输到 Qdrant。

def upload_data_to_qdrant(client, embeddings, metadata, parallel=4):
    """
    Uploads data to Qdrant using the upload_collection method.
    """
    client.upload_collection(
        collection_name="laion400m_collection",
        points=zip(range(len(metadata)), embeddings, metadata),
        batch_size=256,
        parallel=parallel,
        show_progress=True,
    )

# --- Simplified logic for processing chunks ---
# for part in dataset_parts:
#     embeddings, metadata = download_and_process_part(part)
#     upload_data_to_qdrant(client, embeddings, metadata)
#     cleanup_local_files(part)

此方法以可管理的数据块处理数据集,而无需将整个 4 亿个点加载到内存中。使用 parallel=4 允许客户端并发上传多个批次,从而饱和网络连接并最大化摄取速度。

回报:大规模高效架构

这种混合存储配置和流式摄取的组合策略创建了一个高效的系统。通过仅将最基本的组件保留在 RAM 中:量化向量和 HNSW 索引,Qdrant 可以在只有 64GB RAM 的机器上索引和提供 4 亿个向量数据集。原始向量将消耗数百 GB,仅在需要重新评分顶级候选者时才从磁盘高效访问。

此架构通过最大限度地减少 RAM 使用来降低基础设施成本,同时保持快速准确的搜索性能,从而实现了平衡。通过理解和应用这些摄取策略,您可以自信地扩展您的 Qdrant 驱动的应用程序以处理真实世界的数据量。

在我们的大规模搜索教程中了解更多完整的实践指南。

查看参考实现
qdrant/laion-400m-benchmark on GitHub
这个开源存储库包含用于下载、处理和使用高效、生产就绪模式将 LAION-400M 数据集上传到 Qdrant 的完整脚本。

想亲身体验这个工作流程吗?
运行 Google Colab notebook,亲眼看看大规模向量摄取、量化搜索和高效 RAM/磁盘优化是如何运作的!