milvus-logo
LFAI
홈페이지
  • 사용자 가이드

리소스 그룹 관리

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" }]
}
  • 요청 속성은 리소스 그룹이 충족해야 하는 조건을 지정합니다.
  • 제한 속성은 리소스 그룹의 최대 제한을 지정합니다.
  • transfer_fromtransfer_to 속성은 각각 리소스 그룹이 어떤 리소스 그룹으로부터 리소스를 획득하고 어떤 리소스 그룹으로 리소스를 전송해야 하는지를 설명합니다.

리소스 그룹의 구성이 변경되면 Milvus는 새 구성에 따라 현재 쿼리 노드 리소스를 가능한 한 많이 조정하여 모든 리소스 그룹이 결국 다음 조건을 충족하도록 합니다:

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

단, 다음의 경우는 예외입니다:

  • 밀버스 클러스터의 쿼리 노드 수가 부족한 경우(예: NumOfQueryNode < sum(.requests.nodeNum), 항상 충분한 쿼리 노드가 없는 리소스 그룹이 존재합니다.
  • Milvus 클러스터의 쿼리 노드 수가 과다한 경우(예: NumOfQueryNode > sum(.limits.nodeNum))에는 항상 여분의 쿼리 노드가 __default_resource_group에 먼저 배치됩니다.

물론 클러스터의 쿼리 노드 수가 변경되면 Milvus는 최종 조건을 충족하기 위해 지속적으로 조정을 시도합니다. 따라서 먼저 리소스 그룹 구성 변경 사항을 적용한 다음 쿼리 노드 스케일링을 수행할 수 있습니다.

선언적 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개의 쿼리 노드가 있고 하나의 노드를 생성한 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. 리소스 그룹 간에 복제본을 전송합니다.

    Milus는 복제본을 사용하여 여러 쿼리 노드에 분산된 세그먼트 간에 로드 밸런싱을 달성합니다. 다음과 같이 컬렉션의 특정 복제본을 한 리소스 그룹에서 다른 리소스 그룹으로 이동할 수 있습니다:

    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 에는 이제 하나의 쿼리 노드가 있습니다. 먼저 리소스 그룹의 limits.node_num 구성을 0으로 변경해야 합니다.

    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는 클라우드 네이티브 환경에서 독립적으로 스케일 인/아웃이 불가능합니다. 그러나 컨테이너 오케스트레이션과 함께 선언적 리소스 그룹 API를 사용하면 Milvus는 쉽게 리소스 격리를 달성하고 쿼리 노드를 관리할 수 있습니다. 다음은 클라우드 환경에서 쿼리 노드를 관리하기 위한 모범 사례입니다:

  1. 기본적으로 Milvus는 __default_resource_group을 생성합니다. 이 리소스 그룹은 삭제할 수 없으며 모든 컬렉션의 기본 로딩 리소스 그룹으로도 사용되며 항상 중복 쿼리 노드가 할당됩니다. 따라서 보류 중인 리소스 그룹을 생성하여 사용하지 않는 쿼리 노드 리소스를 보관하여 __default_resource_group이 쿼리 노드 리소스를 점유하지 못하도록 할 수 있습니다.

    또한 sum(.requests.nodeNum) <= queryNodeNum 이라는 제약 조건을 엄격하게 적용하면 클러스터에서 쿼리 노드 할당을 정밀하게 제어할 수 있습니다. 현재 클러스터에 쿼리 노드가 하나만 있다고 가정하고 클러스터를 초기화해 보겠습니다. 다음은 설정 예제입니다:

    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)
    

    위의 예제 코드를 사용하여 추가 쿼리 노드를 보유하기 위해 __pending_nodes라는 리소스 그룹을 만듭니다. 또한 rg1과 rg2라는 두 개의 사용자별 리소스 그룹을 만듭니다. 또한 다른 리소스 그룹이 __pending_nodes에서 누락되거나 중복된 쿼리 노드를 복구하는 데 우선순위를 두도록 합니다.

  2. 클러스터 스케일 아웃

    다음과 같은 스케일링 함수가 있다고 가정합니다:

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

    API를 사용하여 다른 리소스 그룹에 영향을 주지 않고 특정 리소스 그룹을 지정된 수의 쿼리 노드로 확장할 수 있습니다.

    # 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 리소스 그룹에서 쿼리 노드를 우선적으로 선택하는 스케일 인 규칙을 설정할 수 있습니다. 이 정보는 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
    

리소스 그룹이 여러 복제본과 상호 작용하는 방식

  • 단일 컬렉션의 복제본과 리소스 그룹은 N 대 N 관계를 갖습니다.
  • 단일 컬렉션의 여러 복제본이 하나의 리소스 그룹에 로드되면 해당 리소스 그룹의 쿼리 노드가 복제본 간에 균등하게 분산되어 각 복제본의 쿼리 노드 수의 차이가 1을 초과하지 않도록 합니다.

다음 단계

멀티테넌트 Milvus 인스턴스를 배포하려면 다음을 따르세요:

번역DeepL

Try Managed Milvus for Free

Zilliz Cloud is hassle-free, powered by Milvus and 10x faster.

Get Started
피드백

이 페이지가 도움이 되었나요?