milvus-logo
LFAI
Home
  • Guia do utilizador

Gerenciar grupos de recursos

No Milvus, é possível utilizar um grupo de recursos para isolar fisicamente determinados nós de consulta de outros. Este guia mostra-lhe como criar e gerir grupos de recursos personalizados, bem como transferir nós entre eles.

O que é um grupo de recursos

Um grupo de recursos pode conter vários ou todos os nós de consulta em um cluster Milvus. O usuário decide como deseja alocar os nós de consulta entre os grupos de recursos com base no que faz mais sentido para ele. Por exemplo, num cenário de várias colecções, pode atribuir um número adequado de nós de consulta a cada grupo de recursos e carregar colecções em diferentes grupos de recursos, de modo a que as operações dentro de cada coleção sejam fisicamente independentes das de outras colecções.

Note-se que uma instância do Milvus mantém um grupo de recursos predefinido para conter todos os nós de consulta no arranque e dá-lhe o nome de __default_resource_group.

A partir da versão 2.4.1, Milvus fornece uma API declarativa de grupo de recursos, enquanto a antiga API de grupo de recursos foi descontinuada. A nova API declarativa permite aos utilizadores alcançar a idempotência, para facilitar o desenvolvimento secundário em ambientes cloud-native.

Conceitos de grupo de recursos

Um grupo de recursos é descrito por uma configuração de grupo de recursos:

{
    "requests": { "nodeNum": 1 },
    "limits": { "nodeNum": 1 },
    "transfer_from": [{ "resource_group": "rg1" }],
    "transfer_to": [{ "resource_group": "rg2" }]
}
  • O atributo requests especifica as condições que um grupo de recursos deve cumprir.
  • O atributo limits especifica os limites máximos para um grupo de recursos.
  • Os atributos transfer_from e transfer_to descrevem de que grupos de recursos um grupo de recursos deve preferencialmente adquirir recursos e para que grupos de recursos deve transferir recursos, respetivamente.

Quando a configuração de um grupo de recursos muda, o Milvus ajusta os recursos actuais do Query Node tanto quanto possível de acordo com a nova configuração, assegurando que todos os grupos de recursos acabam por satisfazer a seguinte condição:

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

Exceto nos seguintes casos:

  • Quando o número de QueryNodes no cluster Milvus é insuficiente, ou seja, NumOfQueryNode < sum(.requests.nodeNum), haverá sempre grupos de recursos sem QueryNodes suficientes.
  • Quando o número de QueryNodes no cluster Milvus é excessivo, i.e., NumOfQueryNode > sum(.limits.nodeNum), os QueryNodes redundantes serão sempre colocados primeiro no __default_resource_group.

Naturalmente, se o número de QueryNodes no cluster mudar, o Milvus tentará continuamente ajustar-se para satisfazer as condições finais. Por conseguinte, pode aplicar primeiro as alterações de configuração do grupo de recursos e, em seguida, efetuar o escalonamento do QueryNode.

Usar a API declarativa para gerenciar o grupo de recursos

Todos os exemplos de código nesta página estão no PyMilvus 2.4.5. Atualize sua instalação do PyMilvus antes de executá-los.

  1. Criar um grupo de recursos.

    Para criar um grupo de recursos, execute o seguinte depois de se conectar a uma instância do Milvus. O seguinte snippet assume que default é o alias da sua conexão 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. Listar grupos de recursos.

    Depois de criar um grupo de recursos, pode vê-lo na lista de grupos de recursos.

    Para ver a lista de grupos de recursos numa instância do Milvus, faça o seguinte:

    rgs = utility.list_resource_groups(using='default')
    print(f"Resource group list: {rgs}")
    
    # Resource group list: ['__default_resource_group', 'rg']
    
  3. Descrever um grupo de recursos.

    É possível fazer com que o Milvus descreva um grupo de recursos da seguinte forma:

    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. Transferir nós entre grupos de recursos.

    É possível notar que o grupo de recursos descrito ainda não possui nenhum nó de consulta. Mova alguns nós do grupo de recursos predefinido para o grupo que criou da seguinte forma: Supondo que existem atualmente 1 QueryNodes no __default_resource_group do cluster, e queremos transferir um nó para o rg criado.update_resource_groups assegura a atomicidade de múltiplas alterações de configuração, pelo que nenhum estado intermédio será visível para o 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. Carregar colecções e partições para um grupo de recursos.

    Uma vez que existam nós de consulta num grupo de recursos, é possível carregar colecções para este grupo de recursos. O seguinte snippet assume que uma coleção chamada demo já existe.

    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) 
    

    Além disso, pode simplesmente carregar uma partição num grupo de recursos e distribuir as suas réplicas por vários grupos de recursos. O seguinte assume que uma coleção chamada Books já existe e tem uma partição chamada 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)
    

    Observe que _resource_groups é um parâmetro opcional e, se ele não for especificado, o Milvus carregará as réplicas nos nós de consulta no grupo de recursos padrão.

    Para que o Milus carregue cada réplica de uma coleção em um grupo de recursos separado, certifique-se de que o número de grupos de recursos seja igual ao número de réplicas.

  6. Transferir réplicas entre grupos de recursos.

    O Milvus usa réplicas para obter o balanceamento de carga entre segmentos distribuídos em vários nós de consulta. É possível mover determinadas réplicas de uma coleção de um grupo de recursos para outro da seguinte forma:

    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. Eliminar um grupo de recursos.

    É possível eliminar um grupo de recursos que não contenha nenhum nó de consulta (limits.node_num = 0) a qualquer momento. Neste guia, o grupo de recursos rg tem agora um nó de consulta. Primeiro, é necessário alterar a configuração limits.node_num do grupo de recursos para zero.

    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}.")
    

Para mais pormenores, consulte os exemplos relevantes no pymilvus

Uma boa prática para gerir o escalonamento do cluster

Atualmente, o Milvus não pode ser escalado de forma independente em ambientes nativos da nuvem. No entanto, ao usar a API Declarative Resource Group em conjunto com a orquestração de contêineres, o Milvus pode facilmente obter o isolamento e o gerenciamento de recursos para QueryNodes. Aqui está uma boa prática para gerenciar QueryNodes em um ambiente de nuvem:

  1. Por padrão, o Milvus cria um __default_resource_group. Este grupo de recursos não pode ser eliminado e serve igualmente de grupo de recursos de carregamento por defeito para todas as colecções e os QueryNodes redundantes são-lhe sempre atribuídos. Por conseguinte, podemos criar um grupo de recursos pendentes para manter os recursos QueryNode não utilizados, impedindo que os recursos QueryNode sejam ocupados pelo grupo __default_resource_group.

    Além disso, se aplicarmos rigorosamente a restrição sum(.requests.nodeNum) <= queryNodeNum, podemos controlar com precisão a atribuição de QueryNodes no cluster. Vamos assumir que existe atualmente apenas um QueryNode no cluster e inicializar o cluster. Aqui está um exemplo de configuração:

    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)
    

    Usando o código de exemplo acima, criamos um grupo de recursos chamado __pending_nodes para manter QueryNodes adicionais. Também criamos dois grupos de recursos específicos do usuário denominados rg1 e rg2. Além disso, garantimos que o outro grupo de recursos prioriza a recuperação de QueryNodes ausentes ou redundantes de __pending_nodes.

  2. Escalonamento do cluster

    Supondo que temos a seguinte função de escalonamento:

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

    Podemos utilizar a API para escalar um grupo de recursos específico para um número designado de QueryNodes sem afetar quaisquer outros grupos de recursos.

    # 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. Escala de cluster para dentro

    De forma semelhante, podemos estabelecer regras de escalonamento que dão prioridade à seleção de QueryNodes do grupo de recursos __pending_nodes. Esta informação pode ser obtida através da API describe_resource_group. Atingindo o objetivo de escalonar o grupo de recursos especificado.

    # 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
    

Como os grupos de recursos interagem com várias réplicas

  • As réplicas de uma única coleção e os grupos de recursos têm uma relação N para N.
  • Quando várias réplicas de uma única coleção são carregadas em um grupo de recursos, os QueryNodes desse grupo de recursos são distribuídos uniformemente entre as réplicas, garantindo que a diferença no número de QueryNodes de cada réplica não exceda 1.

O que vem a seguir

Para implementar uma instância Milvus multi-tenant, leia o seguinte:

Traduzido porDeepLogo

Try Zilliz Cloud for Free

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

Get Started
Feedback

Esta página foi útil?