• Milvusについて
  • はじめに
  • 概念
  • ユーザーガイド
  • データのインポート
  • AIツール
  • 管理ガイド
  • ツール
  • 連携機能
  • チュートリアル
  • よくある質問
  • API Reference

リソースグループの管理

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 3.0.2 に基づいています。実行する前に、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:
        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.")
    
  2. リソースグループの一覧を表示します。

    リソースグループを作成すると、リソースグループ一覧に表示されます。

    Milvus インスタンス内のリソースグループの一覧を表示するには、次のように実行します。

    rgs = milvus_client.list_resource_groups()
    print(f"Resource group list: {rgs}")
    
    # Resource group list: ['__default_resource_group', 'rg']
    
  3. リソースグループの詳細を取得します。

    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
    
  4. リソースグループ間でノードを移動します。

    記述されたリソースグループには、まだクエリノードが1つも存在しないことに気づくかもしれません。以下の手順に従って、デフォルトのリソースグループから作成したリソースグループへノードをいくつか移動させます: クラスタの__default_resource_groupに現在 1 つの QueryNode があり、そのうちの 1 つのノードを作成したリソースグループに移行すると仮定します。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.
    
  5. リソースグループにコレクションとパーティションをロードします。

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

    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) 
    

    また、パーティションを 1 つのリソースグループに読み込むだけで、そのレプリカを複数のリソースグループに分散させることもできます。以下は、Books という名前のコレクションがすでに存在し、その中にNovels という名前のパーティションがあることを前提としています。

    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 にコレクションの各レプリカを別々のリソースグループにロードさせるには、リソースグループの数がレプリカの数と等しくなるようにしてください。

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

    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.")
    
    # Succeeded in moving 1 replica(s) of c from __default_resource_group to rg.
    
  7. リソースグループを削除する。

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

    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はクラウドネイティブ環境において、独自にスケールインおよびスケールアウトを行うことはできません。しかし、Declarative Resource Group APIをコンテナオーケストレーションと組み合わせて使用することで、MilvusはQueryNodesのリソース分離と管理を容易に実現できます。 以下に、クラウド環境におけるQueryNodesの管理に関するベストプラクティスを示します:

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

    さらに、sum(.requests.nodeNum) <= queryNodeNum という制約を厳格に適用することで、クラスター内でのQueryNodeの割り当てを正確に制御できます。現在、クラスター内にQueryNodeが1つしかないと仮定して、クラスターを初期化してみましょう。 設定例は以下の通りです:

    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)
    

    上記のサンプルコードを使用して、追加のQueryNodeを保持するための__pending_nodesという名前のリソースグループを作成します。また、rg1および rg2という2つのユーザー固有のリソースグループも作成します。さらに、他のリソースグループが、欠落しているQueryNodeや冗長なQueryNodeの復旧を__pending_nodesから優先するように設定します。

  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
    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.
    
  3. クラスタのスケールイン

    同様に、__pending_nodesリソースグループからQueryNodeを選択することを優先するスケールインルールを設定できます。この情報は、describe_resource_group APIを通じて取得可能です。これにより、指定されたリソースグループのスケールインという目標を達成できます。

    # 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
    

リソースグループと複数のレプリカとの相互作用

  • 単一のコレクションのレプリカとリソースグループの間には、N対Nの関係があります。
  • 単一のコレクションの複数のレプリカが 1 つのリソースグループに読み込まれると、そのリソースグループの QueryNodes はレプリカ間で均等に分散され、各レプリカが持つ QueryNodes の数の差が 1 を超えないように保証されます。

次のステップ

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