管理资源组
在 Milvus 中,您可以使用资源组来物理隔离某些查询节点与其他节点。本指南介绍如何创建和管理自定义资源组,以及如何在它们之间转移节点。
什么是资源组
资源组可以容纳 Milvus 集群中的几个或全部查询节点。您可以根据最符合您需求的方式决定如何在资源组之间分配查询节点。例如,在多集合场景中,您可以为每个资源组分配适当数量的查询节点,并将集合加载到不同的资源组中,使每个集合内的操作在物理上独立于其他集合中的操作。
请注意,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 中。
当然,如果集群中查询节点的数量发生变化,Milvus 将持续尝试调整以满足最终条件。因此,您可以先应用资源组配置更改,然后执行查询节点扩缩。
使用声明式 API 管理资源组
本页面上的所有代码示例都使用 PyMilvus 2.5.6。在运行之前请升级您的 PyMilvus 安装。
-
创建资源组。
要创建资源组,在连接到 Milvus 实例后运行以下命令。以下代码片段假设
default
是您的 Milvus 连接的别名。import pymilvus
# 资源组名称应该是 1 到 255 个字符的字符串,以字母或下划线 (_) 开头,只包含数字、字母和下划线 (_)。
name = "rg"
node_num = 0
# 创建一个恰好不容纳任何查询节点的资源组。
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>, // 资源组名称
# <capacity:0>, // 资源组容量
# <num_available_node:1>, // 资源组节点数量
# <num_loaded_replica:{}>, // 资源组中加载的集合副本数量
# <num_outgoing_node:{}>, // 仍被其他资源组副本使用的节点数量
# <num_incoming_node:{}>, // 正在使用但属于其他资源组的副本的节点数量
# <config:{}>, // 资源组配置
# <nodes:[]> // 节点详细信息 -
在资源组之间转移节点。
您可能注意到描述的资源组还没有任何查询节点。将一些节点从默认资源组移动到您创建的资源组,如下所示: 假设集群的 __default_resource_group 中当前有 1 个查询节点,我们想要将一个节点转移到创建的 rg 中。
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.")
# 一段时间后,成功将 1 个节点从 __default_resource_group 移动到 rg。 -
将集合和分区加载到资源组。
一旦资源组中有查询节点,您就可以将集合加载到此资源组中。以下代码片段假设名为
demo
的集合已经存在。from pymilvus import Collection
collection_name = "demo"
# Milvus 将集合加载到默认资源组。
milvus_client.load_collection(collection_name, replica_number=2)
# 或者,您可以要求 Milvus 将集合加载到所需的资源组。
# 确保查询节点数量应该大于或等于 replica_number
resource_groups = ['rg']
milvus_client.load_collection(replica_number=2, _resource_groups=resource_groups)此外,您还可以只将分区加载到资源组中,并将其副本分布在多个资源组中。以下假设名为
Books
的集合已经存在,并且它有一个名为Novels
的分区。collection = "Books"
partition = "Novels"
# 使用集合的 load 方法加载其中一个分区
milvus_client.load_partitions(collection, [partition], replica_number=2, _resource_groups=resource_groups)请注意,
_resource_groups
是一个可选参数,如果不指定,Milvus 会将副本加载到默认资源组中的查询节点上。要让 Milvus 将集合的每个副本加载到单独的资源组中,请确保资源组的数量等于副本的数量。
-
在资源组之间转移副本。
Milvus 使用副本来实现分布在多个查询节点上的段之间的负载平衡。您可以将集合的某些副本从一个资源组移动到另一个资源组,如下所示:
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.")
# 成功将 c 的 1 个副本从 __default_resource_group 移动到 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。此资源组无法删除,也作为所有集合的默认加载资源组,多余的查询节点总是分配给它。因此,我们可以创建一个待处理资源组来容纳未使用的查询节点资源,防止查询节点资源被 __default_resource_group 占用。
此外,如果我们严格执行约束
sum(.requests.nodeNum) <= queryNodeNum
,我们可以精确控制集群中查询节点的分配。假设集群中当前只有一个查询节点并初始化集群。 以下是一个示例设置: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")
# 创建一个待处理资源组,可用于容纳不持有任何数据的待处理节点。
milvus_client.create_resource_group(name=_PENDING_NODES_RESOURCE_GROUP, config=ResourceGroupConfig(
requests={"node_num": 0}, # 此资源组可以容纳 0 个节点,不会在其上加载任何数据。
limits={"node_num": 10000}, # 此资源组最多可以容纳 10000 个节点
))
# 更新默认资源组,可用于容纳所有初始节点的节点。
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}], # 优先从待处理资源组恢复缺失的节点。
transfer_to=[{"resource_group": _PENDING_NODES_RESOURCE_GROUP}], # 低优先级将多余的节点恢复到待处理资源组。
)})
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 的资源组来容纳额外的查询节点。我们还创建了两个用户特定的资源组,名为 rg1 和 rg2。此外,我们确保其他资源组优先从 __pending_nodes 恢复缺失或多余的查询节点。
-
集群扩容
假设我们有以下扩缩函数:
def scale_to(node_num: int):
# 将 Milvus 中的查询节点数量扩缩到 node_num。
pass我们可以使用 API 将特定资源组扩缩到指定数量的查询节点,而不影响任何其他资源组。
# 将 rg1 扩缩到 3 个节点,rg2 扩缩到 1 个节点
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 有 3 个节点,rg2 有 1 个节点,__default_resource_group 有 1 个节点。 -
集群缩容
同样,我们可以建立优先从 __pending_nodes 资源组选择查询节点的缩容规则。此信息可以通过
describe_resource_group
API 获得。实现指定资源组的缩容目标。# 将 rg1 从 3 个节点缩容到 2 个节点
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 有 2 个节点,rg2 有 1 个节点,__default_resource_group 有 1 个节点,__pending_nodes 有 1 个节点。
scale_to(4)
# 缩容 __pending_nodes 中的节点
资源组如何与多副本交互
- 单个集合的副本和资源组具有 N 对 N 的关系。
- 当单个集合的多个副本加载到一个资源组中时,该资源组的查询节点在副本之间均匀分布,确保每个副本拥有的查询节点数量差异不超过 1。
下一步
要部署多租户 Milvus 实例,请阅读以下内容: