milvus-logo
LFAI
フロントページへ
  • ユーザーガイド

リソースグループの管理

Milvusでは、リソースグループを使用して特定のクエリノードを他のノードから物理的に分離することができます。このガイドでは、カスタムリソースグループの作成と管理、およびグループ間でのノードの転送方法について説明します。

リソースグループとは

リソースグループは、Milvusクラスタ内のクエリノードの一部またはすべてを保持することができます。リソースグループ間でクエリノードをどのように割り当てるかは、最も合理的な方法に基づいて決定します。例えば、マルチコレクションシナリオでは、各リソースグループに適切な数のクエリノードを割り当て、各コレクション内の操作が他のコレクション内の操作から物理的に独立するように、異なるリソースグループにコレクションをロードすることができます。

Milvusインスタンスは、起動時にすべてのクエリノードを保持するデフォルトのリソースグループを保持し、__default_resource_groupと命名することに注意してください。

バージョン2.4.1から、Milvusは宣言型リソースグループAPIを提供し、従来のリソースグループAPIは廃止されました。新しい宣言型APIにより、ユーザはidempotencyを実現し、クラウドネイティブ環境での二次開発を容易に行うことができるようになります。

リソースグループの概念

リソースグループはリソースグループ設定によって記述される:

{
    "requests": { "nodeNum": 1 },
    "limits": { "nodeNum": 1 },
    "transfer_from": [{ "resource_group": "rg1" }],
    "transfer_to": [{ "resource_group": "rg2" }]
}
  • requests属性は、リソースグループが満たすべき条件を指定する。
  • limits属性は、リソースグループが満たすべき条件を指定します。
  • transfer_from属性とtransfer_to属性は、それぞれリソースグループがどのリソースグループからリソースを取得するのが望ましいか、どのリソースグループにリソースを転送するのが望ましいかを記述します。

リソースグループのコンフィギュレーションが変更されると、milvusは新しいコンフィギュレーションに従って現在のQuery Nodeのリソースを可能な限り調整し、最終的に全てのリソースグループが以下の条件を満たすようにします:

.requests.nodeNum < nodeNumOfResourceGroup < .limits.nodeNum.

ただし、以下の場合を除く:

  • Milvusクラスタ内のQueryNode数が不足している場合(NumOfQueryNode < sum(.requests.nodeNum))、常に十分なQueryNode数を持たないリソースグループが存在します。
  • MilvusクラスタのQueryNode数が過剰な場合、つまりNumOfQueryNode > sum(.limits.nodeNum) 、冗長なQueryNodeは常に__default_resource_groupに最初に配置されます。

もちろん、クラスタ内のQueryNode数が変更された場合、Milvusは最終的な条件を満たすように継続的に調整を試みます。そのため、最初にリソースグループの設定変更を適用し、その後QueryNodeのスケーリングを実行することができます。

宣言型apiを使用してリソースグループを管理する

このページのコードサンプルはすべて PyMilvus 2.4.9 のものです。実行する前に PyMilvus をアップグレードしてください。

  1. リソースグループの作成

    リソースグループを作成するには、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:
        utility.create_resource_group(name, config=utility.ResourceGroupConfig(
            requests={"node_num": node_num},
            limits={"node_num": node_num},
        ), using='default')
        print(f"Succeeded in creating resource group {name}.")
    except Exception:
        print("Failed to create the resource group.")
    
  2. リソースグループを一覧表示します。

    リソースグループを作成すると、リソースグループリストで確認することができます。

    Milvusインスタンスのリソースグループのリストを表示するには、以下のようにします:

    rgs = utility.list_resource_groups(using='default')
    print(f"Resource group list: {rgs}")
    
    # Resource group list: ['__default_resource_group', 'rg']
    
  3. リソースグループを記述する。

    Milvusにリソースグループを記述させるには、以下のようにします:

    info = utility.describe_resource_group(name, using="default")
    print(f"Resource group description: {info}")
    
    # Resource group description: 
    #        <name:"rg">,           // string, rg name
    #        <capacity:1>,            // int, num_node which has been transfer to this rg
    #        <num_available_node:0>,  // int, available node_num, some node may shutdown
    #        <num_loaded_replica:{}>, // map[string]int, from collection_name to loaded replica of each collecion in this rg
    #        <num_outgoing_node:{}>,  // map[string]int, from collection_name to outgoging accessed node num by replica loaded in this rg 
    #        <num_incoming_node:{}>.  // map[string]int, from collection_name to incoming accessed node num by replica loaded in other rg
    
  4. リソースグループ間でノードを転送する。

    記述されたリソースグループにはまだクエリノードがないことに気づくかもしれません。クラスタの__default_resource_groupに現在1つのQueryNodesがあり、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:
        utility.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},
            )
        }, using="default")
        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.
    
  5. リソースグループにコレクションとパーティションをロードします。

    リソースグループにクエリノードが存在すると、このリソースグループにコレクションをロードすることができます。以下のスニペットは、demo という名前のコレクションが既に存在することを想定しています。

    from pymilvus import Collection
    
    collection = Collection('demo')
    
    # Milvus loads the collection to the default resource group.
    collection.load(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']
    collection.load(replica_number=2, _resource_groups=resource_groups) 
    

    また、パーティションをリソースグループにロードし、そのレプリカを複数のリソースグループに分散させることもできます。以下は、Books という名前のコレクションがすでに存在し、Novels という名前のパーティションがあると仮定しています。

    collection = Collection("Books")
    
    # Use the load method of a collection to load one of its partition
    collection.load(["Novels"], replica_number=2, _resource_groups=resource_groups)
    
    # Or, you can use the load method of a partition directly
    partition = Partition(collection, "Novels")
    partition.load(replica_number=2, _resource_groups=resource_groups)
    

    _resource_groups はオプションのパラメータで、指定しないままにしておくと、Milvusはデフォルトのリソースグループのクエリノードにレプリカをロードします。

    Milusにコレクションの各レプリカを個別のリソースグループにロードさせるには、リソースグループの数がレプリカの数と等しくなるようにします。

  6. リソースグループ間でレプリカを転送します。

    Milvusは、複数のクエリノードに分散したセグメント間の負荷分散を実現するためにレプリカを使用します。コレクションの特定のレプリカをあるリソースグループから別のリソースグループに移動するには、次のようにします:

    source = '__default_resource_group'
    target = 'rg'
    collection_name = 'c'
    num_replicas = 1
    
    try:
        utility.transfer_replica(source, target, collection_name, num_replicas, using="default")
        print(f"Succeeded in moving {num_node} 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.
    
  7. リソースグループを削除する。

    クエリノード(limits.node_num = 0)を持たないリソースグループは、いつでも削除できます。このガイドでは、リソースグループrg にクエリノードが1つあります。まず、リソースグループの設定limits.node_num をゼロに変更する必要があります。

    try:
        utility.update_resource_groups({
            "rg": utility.ResourceGroupConfig(
                requests={"node_num": 0},
                limits={"node_num": 0},
            ),
        }, using="default")
        utility.drop_resource_group("rg", using="default")
        print(f"Succeeded in dropping {source}.")
    except Exception:
        print(f"Something went wrong while dropping {source}.")
    

詳細については、pymilvusの関連する例を参照してください。

クラスタスケーリング管理のグッドプラクティス

現在のところ、Milvusはクラウドネイティブ環境において独立してスケールイン/スケールアウトすることができません。しかし、Declarative Resource Group APIとコンテナオーケストレーションを併用することで、Milvusはリソースの分離とQueryNodeの管理を容易に実現することができます。 ここでは、クラウド環境でQueryNodeを管理するためのグッドプラクティスを紹介します:

  1. Milvusはデフォルトで__default_resource_groupを作成します。このリソースグループは削除できず、すべてのコレクションのデフォルトのロードリソースグループとしても機能し、冗長なQueryNodeは常に割り当てられます。したがって、使用中のQueryNodeリソースを保持する保留リソースグループを作成し、QueryNodeリソースが__default_resource_groupによって占有されるのを防ぐことができます。

    さらに、制約sum(.requests.nodeNum) <= queryNodeNum を厳密に適用すれば、クラスタ内のQueryNodeの割り当てを正確に制御することができます。現在クラスタにQueryNodeが1つしかないと仮定してクラスタを初期化してみましょう。 セットアップの例を示します:

    from pymilvus import utility
    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.
        utility.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.
        utility.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.
            )})
        utility.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}],
        ))
        utility.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)
    

    上記のコード例を使用して、追加のQueryNodeを保持するために__pending_nodesというリソース・グループを作成します。また、rg1とrg2という2つのユーザー固有のリソース・グループを作成します。さらに、もう1つのリソース・グループが、__pending_nodesから不足または冗長なQueryNodesをリカバリすることを優先するようにします。

  2. クラスタのスケールアウト

    以下のスケーリング機能があると仮定します:

    
    def scale_to(node_num: int):
        # scale the querynode number in Milvus into node_num.
        pass
    

    APIを使用して、他のリソースグループに影響を与えることなく、特定のリソースグループを指定された数のQueryNodeにスケールすることができます。

    # scale rg1 into 3 nodes, rg2 into 1 nodes
    utility.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.
    
  3. クラスタのスケールイン

    同様に、__pending_nodesリソースグループからQueryNodesを優先的に選択するスケーリングインルールを確立することができます。この情報はdescribe_resource_group API から取得できます。指定したリソースグループのスケールインという目標を達成する。

    # scale rg1 from 3 nodes into 2 nodes
    utility.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
    

リソースグループが複数のレプリカとどのように相互作用するか

  • 1つのコレクションのレプリカとリソースグループはN対Nの関係にあります。
  • 1つのコレクションの複数のレプリカが1つのリソースグループにロードされると、そのリソースグループのQueryNodesはレプリカに均等に分配され、各レプリカが持つQueryNodesの数の差が1を超えないようにします。

次のステップ

マルチテナントのMilvusインスタンスをデプロイするには、以下をお読みください: