管理资源组
在 Milvus 中,您可以使用资源组将某些查询节点与其他节点进行物理隔离。本指南将向您详细介绍如何创建和管理自定义资源组,以及如何在资源组之间迁移节点。
什么是资源组
一个资源组可以包含 Milvus 集群中的部分或全部查询节点。 您可以根据自身需求,灵活决定如何在资源组之间分配查询节点。例如,在多 Collection 场景中,您可以将适当数量的查询节点分配给每个资源组,并将 Collection 加载到不同的资源组中,从而使每个 Collection 内的操作在物理上与其他 Collection 的操作相互独立。
请注意,Milvus 实例在启动时会维护一个默认资源组来容纳所有查询节点,并将其命名为**__default_resource_group**。
从 2.4.1 版本开始,Milvus 提供了声明式资源组 API,而旧版资源组 API 已被废弃。新的声明式 API 使用户能够实现幂等性,从而更轻松地在云原生环境中进行二次开发。
资源组的概念
资源组通过资源组配置进行描述:
{
"requests": { "nodeNum": 1 },
"limits": { "nodeNum": 1 },
"transfer_from": [{ "resource_group": "rg1" }],
"transfer_to": [{ "resource_group": "rg2" }]
}
- requests属性指定了资源组必须满足的条件。
- limits属性指定了资源组的最大限制。
- transfer_from和transfer_to属性分别描述了该资源组应优先从哪些资源组获取资源,以及应将资源转移至哪些资源组。
一旦资源组的配置发生变化,Milvus 将根据新配置尽可能调整当前查询节点的资源,以确保所有资源组最终满足以下条件:
.requests.nodeNum < nodeNumOfResourceGroup < .limits.nodeNum.
以下情况除外:
- 当 Milvus 集群中的查询节点数量不足时,即
NumOfQueryNode < sum(.requests.nodeNum),总会存在某些资源组无法获得足够的查询节点。 - 当 Milvus 集群中的查询节点数量过多时,即
NumOfQueryNode > sum(.limits.nodeNum),冗余的查询节点将始终首先被分配到**__default_resource_group**中。
当然,如果集群中的 QueryNodes 数量发生变化,Milvus 会持续尝试调整以满足最终条件。因此,您可以先应用资源组配置更改,然后执行 QueryNode 扩展。
使用声明式 API 管理资源组
本页面上的所有代码示例均基于 PyMilvus 2.6.16。在运行这些代码之前,请先升级您的 PyMilvus 安装版本。
-
创建资源组。
要创建资源组,请在连接到 Milvus 实例后运行以下命令。以下代码片段假设
default是您的 Milvus 连接别名。import pymilvus
# A resource group name should be a string of 1 to 255 characters, starting with a letter or an underscore (_) and containing only numbers, letters, and underscores (_).
name = "rg"
node_num = 0
# create a resource group that exactly hold no query node.
try:
milvus_client.create_resource_group(name, config=ResourceGroupConfig(
requests={"node_num": node_num},
limits={"node_num": node_num},
))
print(f"Succeeded in creating resource group {name}.")
except Exception:
print("Failed to create the resource group.") -
列出资源组。
创建资源组后,您可以在资源组列表中看到它。
要查看 Milvus 实例中的资源组列表,请按以下步骤操作:
rgs = milvus_client.list_resource_groups()
print(f"Resource group list: {rgs}")
# Resource group list: ['__default_resource_group', 'rg'] -
描述资源组。
您可以按照以下方式让 Milvus 描述目标资源组:
info = milvus_client.describe_resource_group(name)
print(f"Resource group description: {info}")
# Resource group description:
# ResourceGroupInfo:
# <name:rg1>, // resource group name
# <capacity:0>, // resource group capacity
# <num_available_node:1>, // resource group node num
# <num_loaded_replica:{}>, // collection loaded replica num in resource group
# <num_outgoing_node:{}>, // node num which still in use by replica in other resource group
# <num_incoming_node:{}>, // node num which is in use by replica but belong to other resource group
# <config:{}>, // resource group config
# <nodes:[]> // node detail info -
在资源组之间迁移节点。
您可能会注意到,该资源组目前尚无任何查询节点。请按以下步骤将部分节点从默认资源组迁移至您创建的资源组: 假设集群的**__default_resource_group中目前有 1 个 QueryNode,我们希望将其中一个节点转移到新创建的资源组中**。
update_resource_groups确保了多个配置更改的原子性,因此 Milvus 不会看到任何中间状态。source = '__default_resource_group'
target = 'rg'
expected_num_nodes_in_default = 0
expected_num_nodes_in_rg = 1
try:
milvus_client.update_resource_groups({
source: ResourceGroupConfig(
requests={"node_num": expected_num_nodes_in_default},
limits={"node_num": expected_num_nodes_in_default},
),
target: ResourceGroupConfig(
requests={"node_num": expected_num_nodes_in_rg},
limits={"node_num": expected_num_nodes_in_rg},
)
})
print(f"Succeeded in move 1 node(s) from {source} to {target}.")
except Exception:
print("Something went wrong while moving nodes.")
# After a while, succeeded in moving 1 node(s) from __default_resource_group to rg. -
将 Collection 和 Partition 加载到资源组中。
一旦资源组中存在查询节点,即可将 Collection 加载到该资源组中。以下代码片段假设名为
demo的 Collection 已存在。from pymilvus import Collection
collection_name = "demo"
# Milvus loads the collection to the default resource group.
milvus_client.load_collection(collection_name, replica_number=2)
# Or, you can ask Milvus load the collection to the desired resource group.
# make sure that query nodes num should be greater or equal to replica_number
resource_groups = ['rg']
milvus_client.load_collection(replica_number=2, _resource_groups=resource_groups)此外,您也可以仅将一个 Partition 加载到某个资源组中,并将其副本分布在多个资源组中。以下示例假设已存在一个名为
Books的 Collection,且该 Collection 包含一个名为Novels的 Partition。collection = "Books"
partition = "Novels"
# Use the load method of a collection to load one of its partition
milvus_client.load_partitions(collection, [partition], replica_number=2, _resource_groups=resource_groups)请注意,
_resource_groups是可选参数,若未指定,Milvus 会将副本加载到默认资源组中的查询节点上。若要让 Milvus 将 Collection 的每个副本加载到不同的资源组中,请确保资源组的数量等于副本的数量。
-
在资源组之间转移副本。
Milvus 利用副本在分布于多个查询节点的分段之间实现负载均衡。您可以按照以下步骤将 Collection 的某些副本从一个资源组移动到另一个资源组:
source = '__default_resource_group'
target = 'rg'
collection_name = 'c'
num_replicas = 1
try:
milvus_client.transfer_replica(source, target, collection_name, num_replicas)
print(f"Succeeded in moving {num_replicas} replica(s) of {collection_name} from {source} to {target}.")
except Exception:
print("Something went wrong while moving replicas.")
# Succeeded in moving 1 replica(s) of c from __default_resource_group to rg. -
删除资源组。
您可以随时删除不包含任何查询节点的资源组(
limits.node_num = 0)。在本指南中,资源组rg目前包含一个查询节点。您需要先将该资源组的配置limits.node_num修改为零。resource_group = "rg
try:
milvus_client.update_resource_groups({
resource_group: ResourceGroupConfig(
requests={"node_num": 0},
limits={"node_num": 0},
),
})
milvus_client.drop_resource_group(resource_group)
print(f"Succeeded in dropping {resource_group}.")
except Exception:
print(f"Something went wrong while dropping {resource_group}.")
更多详细信息,请参阅pymilvus 中的相关示例
管理集群扩展的最佳实践
目前,Milvus 无法在云原生环境中独立进行弹性伸缩。但是,通过结合使用声明式资源组 API和容器编排,Milvus 可以轻松实现对查询节点的资源隔离和管理。 以下是在云环境中管理查询节点的最佳实践:
-
默认情况下,Milvus 会创建一个**__default_resource_group**。该资源组无法被删除,同时也是所有 Collection 的默认加载资源组,冗余的 QueryNodes 始终会被分配到该资源组中。 因此,我们可以创建一个待处理资源组来存放闲置的 QueryNode 资源,从而防止这些资源被**__default_resource_group** 占用。
此外,如果严格执行约束条件 `
sum(.requests.nodeNum) <= queryNodeNum`,我们可以精确控制集群中 QueryNodes 的分配。假设当前集群中仅有一个 QueryNode,并初始化该集群。 以下是一个示例配置:from pymilvus.client.types import ResourceGroupConfig
_PENDING_NODES_RESOURCE_GROUP="__pending_nodes"
def init_cluster(node_num: int):
print(f"Init cluster with {node_num} nodes, all nodes will be put in default resource group")
# create a pending resource group, which can used to hold the pending nodes that do not hold any data.
milvus_client.create_resource_group(name=_PENDING_NODES_RESOURCE_GROUP, config=ResourceGroupConfig(
requests={"node_num": 0}, # this resource group can hold 0 nodes, no data will be load on it.
limits={"node_num": 10000}, # this resource group can hold at most 10000 nodes
))
# update default resource group, which can used to hold the nodes that all initial node in it.
milvus_client.update_resource_groups({
"__default_resource_group": ResourceGroupConfig(
requests={"node_num": node_num},
limits={"node_num": node_num},
transfer_from=[{"resource_group": _PENDING_NODES_RESOURCE_GROUP}], # recover missing node from pending resource group at high priority.
transfer_to=[{"resource_group": _PENDING_NODES_RESOURCE_GROUP}], # recover redundant node to pending resource group at low priority.
)})
milvus_client.create_resource_group(name="rg1", config=ResourceGroupConfig(
requests={"node_num": 0},
limits={"node_num": 0},
transfer_from=[{"resource_group": _PENDING_NODES_RESOURCE_GROUP}],
transfer_to=[{"resource_group": _PENDING_NODES_RESOURCE_GROUP}],
))
milvus_client.create_resource_group(name="rg2", config=ResourceGroupConfig(
requests={"node_num": 0},
limits={"node_num": 0},
transfer_from=[{"resource_group": _PENDING_NODES_RESOURCE_GROUP}],
transfer_to=[{"resource_group": _PENDING_NODES_RESOURCE_GROUP}],
))
init_cluster(1)利用上面的示例代码,我们创建了一个名为**__pending_nodes 的资源组来存放额外的 QueryNode。我们还创建了两个用户专属的资源组,分别命名为rg1和rg2**。此外,我们确保其他资源组优先从**__pending_nodes** 中恢复缺失或冗余的 QueryNode。
-
集群横向扩展
假设我们有以下扩展函数:
def scale_to(node_num: int):
# scale the querynode number in Milvus into node_num.
pass我们可以使用 API 将特定资源组扩展至指定数量的 QueryNodes,而不会影响其他任何资源组。
# scale rg1 into 3 nodes, rg2 into 1 nodes
milvus_client.update_resource_groups({
"rg1": ResourceGroupConfig(
requests={"node_num": 3},
limits={"node_num": 3},
transfer_from=[{"resource_group": _PENDING_NODES_RESOURCE_GROUP}],
transfer_to=[{"resource_group": _PENDING_NODES_RESOURCE_GROUP}],
),
"rg2": ResourceGroupConfig(
requests={"node_num": 1},
limits={"node_num": 1},
transfer_from=[{"resource_group": _PENDING_NODES_RESOURCE_GROUP}],
transfer_to=[{"resource_group": _PENDING_NODES_RESOURCE_GROUP}],
),
})
scale_to(5)
# rg1 has 3 nodes, rg2 has 1 node, __default_resource_group has 1 node. -
集群缩容
同样地,我们可以建立缩容规则,优先从**__pending_nodes**资源组中选择查询节点。可通过
describe_resource_groupAPI 获取此信息,从而实现指定资源组的缩容目标。# scale rg1 from 3 nodes into 2 nodes
milvus_client.update_resource_groups({
"rg1": ResourceGroupConfig(
requests={"node_num": 2},
limits={"node_num": 2},
transfer_from=[{"resource_group": _PENDING_NODES_RESOURCE_GROUP}],
transfer_to=[{"resource_group": _PENDING_NODES_RESOURCE_GROUP}],
),
})
# rg1 has 2 nodes, rg2 has 1 node, __default_resource_group has 1 node, __pending_nodes has 1 node.
scale_to(4)
# scale the node in __pending_nodes
资源组与多个副本的交互方式
- 单个 Collection 的副本与资源组之间存在 N 对 N 的关系。
- 当单个 Collection 的多个副本被加载到同一个资源组中时,该资源组的查询节点会在各副本之间均匀分布,确保每个副本拥有的查询节点数量差异不超过 1。
下一步
要部署多租户 Milvus 实例,请阅读以下内容: