欢迎光临
我们一直在努力

在单集群内通过 Partition 标签还是独立的 Collection 来实现数千租户的最优资源隔离?

在构建大规模多租户的RAG(检索增强生成)系统时,AI基础设施工程师经常面临一个核心挑战:如何在单个向量数据库集群内安全且高效地隔离数千个租户(Tenant)的数据和查询请求?主要有两种方案:为每个租户创建一个独立的 Collection(集合),或将所有租户的数据放入一个 Collection 中,并使用 Partition(分区)进行逻辑隔离。

本文将深入分析这两种方案的优缺点,并提供实操代码,展示如何在 Milvus 或 Zilliz 这样的向量数据库中,通过 Partition 机制实现数千租户的最优资源管理。

1. 方案对比:Collection vs. Partition

特性 独立 Collection Partition 机制 (推荐)
隔离级别 物理隔离(Schema、索引独立) 逻辑隔离(数据文件分离,共享 Schema 和 Index)
扩展性 极差。数千 Collection 导致元数据开销巨大,影响系统启动和维护效率。 极佳。单个 Collection 可支持数万个 Partition。
资源成本 高。每个 Collection 都要占用额外的内存和计算资源。 低。共享一套查询节点和索引节点资源。
查询效率 极高(无需过滤)。 较高(通过 Partition 标签快速定位数据,过滤效率极高)。

对于数千级别的租户场景,采用独立 Collection 的方法会迅速达到集群的元数据和连接限制,导致性能急剧下降。因此,Partition 机制是实现低成本、高效率多租户隔离的最佳实践。

2. 实现 Partition 租户隔离的步骤

我们将使用 PyMilvus 作为示例,演示如何将租户ID(tenant_id)映射到 Collection 的 Partition。

步骤一:定义 Schema 和 Partition Key

我们首先需要定义一个包含 tenant_id 字段的 Schema。虽然 tenant_id 可以作为普通字段,但为了实现基于 Partition 的高效隔离查询,我们需要在插入和查询时利用 Partition 名称。

from pymilvus import Collection, FieldSchema, CollectionSchema, DataType, connections

# 建立连接
connections.connect(host="localhost", port="19530")

# 定义Schema
fields = [
    FieldSchema(name="id", dtype=DataType.INT64, is_primary=True, auto_id=True),
    FieldSchema(name="tenant_id", dtype=DataType.INT64, description="租户标识符"),
    FieldSchema(name="vector", dtype=DataType.FLOAT_VECTOR, dim=128)
]
schema = CollectionSchema(fields, description="多租户RAG Collection")

# 创建集合
COLLECTION_NAME = "multi_tenant_vectors"
collection = Collection(name=COLLECTION_NAME, schema=schema)

print(f"Collection '{COLLECTION_NAME}' created successfully.")

步骤二:动态创建 Partition 并插入数据

当新租户接入时,我们不需要创建新的 Collection,只需要为其创建一个新的 Partition。Partition 的名称通常是基于租户ID构建的字符串,例如 p_{tenant_id}

# 假设有两个租户:Tenant 1001 和 Tenant 1002
tenant_ids = [1001, 1002]

for tid in tenant_ids:
    partition_name = f"p_{tid}"

    # 1. 创建 Partition
    if partition_name not in collection.get_partition_names():
        collection.create_partition(partition_name)
        print(f"Partition {partition_name} created for Tenant {tid}.")

    # 2. 准备数据
    data = [
        [tid] * 5,  # 5条数据属于该租户
        [[float(i)] * 128 for i in range(5)]
    ]

    # 3. 插入数据,指定 Partition Name
    res = collection.insert(data, partition_name=partition_name)
    print(f"Inserted {len(res.primary_keys)} entities into {partition_name}.")

# 确认数据是否加载
collection.flush()

步骤三:实现租户级别的隔离查询

这是实现高效隔离的关键。在查询(搜索或检索)时,通过指定 partition_names 参数,查询请求将只会在该租户对应的数据分区内执行。这极大地提高了查询效率,并确保了数据的严格隔离。

隔离查询示例 (只搜索 Tenant 1001 的数据):

# 准备查询向量
search_vector = [[0.5] * 128]

# 针对 Tenant 1001 进行隔离搜索
tenant_1_partition = [f"p_1001"]
search_params = {"metric_type": "L2", "params": {"nprobe": 10}}

results = collection.search(
    data=search_vector,
    anns_field="vector",
    param=search_params,
    limit=3,
    partition_names=tenant_1_partition, # 关键:只在指定分区搜索
    output_fields=["tenant_id"]
)

print("\n--- Query Results for Tenant 1001 ---")
for hit in results[0]:
    print(f"ID: {hit.id}, Distance: {hit.distance}, Tenant ID: {hit.entity.get('tenant_id')}")

# 针对 Tenant 1002 进行隔离搜索
tenant_2_partition = [f"p_1002"]
results_2 = collection.search(
    data=search_vector,
    anns_field="vector",
    param=search_params,
    limit=3,
    partition_names=tenant_2_partition 
)

print("\n--- Query Results for Tenant 1002 ---")
print(f"Found {len(results_2[0])} results in partition p_1002.")

3. 性能和资源考量

虽然 Partition 实现了逻辑隔离,但需要注意以下几点:

  1. 共享 Index: 所有的 Partition 共享同一个索引结构(如果已创建索引)。这意味着索引构建只进行一次,大幅节约资源。
  2. 查询节点压力: 所有租户的查询请求都通过同一组 Query Node 处理。如果某些租户的负载特别高,可能会影响到其他租户(即共享资源带来的抖动)。对于查询量极高的关键租户,可能需要额外的资源配额或者混合部署(少数高优租户使用独立 Collection,多数租户使用 Partition)。
  3. 数据安全: Partition 提供了强大的数据隔离能力,因为查询 API 严格限定了查询范围。除非通过错误的配置或绕过 Milvus API 直接访问底层存储,否则数据是安全的。

通过将 tenant_id 转化为 Partition Key,我们可以在单个高性能集群上高效地管理和隔离数千个租户,极大地优化了AI基础设施的部署效率和维护成本。

【本站文章皆为原创,未经允许不得转载】:汤不热吧 » 在单集群内通过 Partition 标签还是独立的 Collection 来实现数千租户的最优资源隔离?
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址