Gestire i gruppi di risorse
In Milvus è possibile utilizzare un gruppo di risorse per isolare fisicamente alcuni nodi di query da altri. Questa guida spiega come creare e gestire gruppi di risorse personalizzati e come trasferire i nodi tra di essi.
Cos'è un gruppo di risorse
Un gruppo di risorse può contenere alcuni o tutti i nodi di query di un cluster Milvus. Si decide come allocare i nodi di query tra i gruppi di risorse in base a ciò che è più sensato per l'utente. Ad esempio, in uno scenario con più collezioni, è possibile allocare un numero appropriato di nodi di query a ciascun gruppo di risorse e caricare le collezioni in gruppi di risorse diversi, in modo che le operazioni all'interno di ciascuna collezione siano fisicamente indipendenti da quelle di altre collezioni.
Si noti che un'istanza di Milvus mantiene un gruppo di risorse predefinito per contenere tutti i nodi di query all'avvio e lo nomina __default_resource_group.
A partire dalla versione 2.4.1, Milvus fornisce un'API dichiarativa per i gruppi di risorse, mentre la vecchia API per i gruppi di risorse è stata deprecata. La nuova API dichiarativa consente agli utenti di ottenere l'idempotenza, per facilitare lo sviluppo secondario in ambienti cloud-nativi.
Concetti di gruppo di risorse
Un gruppo di risorse è descritto da una configurazione di gruppo di risorse:
{
"requests": { "nodeNum": 1 },
"limits": { "nodeNum": 1 },
"transfer_from": [{ "resource_group": "rg1" }],
"transfer_to": [{ "resource_group": "rg2" }]
}
- L'attributo requests specifica le condizioni che un gruppo di risorse deve soddisfare.
- L'attributo limits specifica i limiti massimi per un gruppo di risorse.
- Gli attributi transfer_from e transfer_to descrivono da quali gruppi di risorse un gruppo di risorse deve preferibilmente acquisire risorse e a quali gruppi di risorse deve trasferire risorse, rispettivamente.
Una volta modificata la configurazione di un gruppo di risorse, Milvus regolerà le risorse del Nodo di interrogazione corrente il più possibile in base alla nuova configurazione, assicurando che tutti i gruppi di risorse alla fine soddisfino la seguente condizione:
.requests.nodeNum < nodeNumOfResourceGroup < .limits.nodeNum.
Tranne nei seguenti casi:
- Quando il numero di QueryNodes nel cluster Milvus è insufficiente, cioè
NumOfQueryNode < sum(.requests.nodeNum)
, ci saranno sempre gruppi di risorse senza QueryNodes sufficienti. - Quando il numero di QueryNodes nel cluster Milvus è eccessivo, cioè
NumOfQueryNode > sum(.limits.nodeNum)
, i QueryNodes ridondanti saranno sempre collocati prima nel gruppo di risorse __default_.
Naturalmente, se il numero di QueryNodes nel cluster cambia, Milvus cercherà continuamente di adattarsi per soddisfare le condizioni finali. Pertanto, è possibile applicare prima le modifiche alla configurazione del gruppo di risorse e poi eseguire il ridimensionamento dei QueryNode.
Utilizzare l'API dichiarativa per gestire il gruppo di risorse
Tutti gli esempi di codice di questa pagina sono in PyMilvus 2.5.0. Aggiornare l'installazione di PyMilvus prima di eseguirli.
Creare un gruppo di risorse.
Per creare un gruppo di risorse, eseguite quanto segue dopo esservi connessi a un'istanza di Milvus. Il seguente snippet presuppone che
default
sia l'alias della connessione a 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.")
Elenca i gruppi di risorse.
Una volta creato un gruppo di risorse, lo si può vedere nell'elenco dei gruppi di risorse.
Per visualizzare l'elenco dei gruppi di risorse in un'istanza Milvus, procedere come segue:
rgs = utility.list_resource_groups(using='default') print(f"Resource group list: {rgs}") # Resource group list: ['__default_resource_group', 'rg']
Descrivere un gruppo di risorse.
È possibile far descrivere a Milvus un gruppo di risorse nel modo seguente:
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
Trasferire i nodi tra i gruppi di risorse.
Si può notare che il gruppo di risorse descritto non ha ancora alcun nodo di interrogazione. Spostare alcuni nodi dal gruppo di risorse predefinito a quello creato come segue: supponendo che attualmente ci siano 1 QueryNodes nel __default_resource_group del cluster e che si voglia trasferire un nodo nell'rg creato.
update_resource_groups
assicura l'atomicità per le modifiche multiple della configurazione, quindi nessuno stato intermedio sarà visibile a 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.
Caricare collezioni e partizioni in un gruppo di risorse.
Una volta che ci sono nodi di query in un gruppo di risorse, è possibile caricare collezioni in questo gruppo di risorse. Lo snippet seguente presuppone che esista già una raccolta denominata
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)
Inoltre, è possibile caricare una partizione in un gruppo di risorse e avere le sue repliche distribuite tra diversi gruppi di risorse. Di seguito si ipotizza che esista già un insieme chiamato
Books
e che abbia una partizione chiamataNovels
.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)
Si noti che
_resource_groups
è un parametro opzionale, e lasciandolo non specificato Milvus caricherà le repliche sui nodi di query nel gruppo di risorse predefinito.Per fare in modo che Milus carichi ogni replica di una collezione in un gruppo di risorse separato, assicurarsi che il numero di gruppi di risorse sia uguale al numero di repliche.
Trasferire le repliche tra i gruppi di risorse.
Milvus utilizza le repliche per ottenere il bilanciamento del carico tra i segmenti distribuiti su diversi nodi di interrogazione. È possibile spostare alcune repliche di una collezione da un gruppo di risorse a un altro come segue:
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.
Eliminare un gruppo di risorse.
È possibile eliminare un gruppo di risorse che non contiene nodi di query (
limits.node_num = 0
) in qualsiasi momento. In questa guida, il gruppo di risorserg
ha ora un nodo di query. È necessario modificare prima la configurazionelimits.node_num
del gruppo di risorse in 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}.")
Per maggiori dettagli, consultare gli esempi pertinenti in pymilvus.
Una buona pratica per gestire il ridimensionamento del cluster
Attualmente Milvus non può scalare in modo indipendente in ambienti cloud-nativi. Tuttavia, utilizzando l'API Declarative Resource Group insieme all'orchestrazione dei container, Milvus può facilmente ottenere l'isolamento e la gestione delle risorse per i QueryNodes. Ecco una buona pratica per gestire i QueryNodes in un ambiente cloud:
Per impostazione predefinita, Milvus crea un __default_resource_group. Questo gruppo di risorse non può essere eliminato e serve anche come gruppo di risorse di caricamento predefinito per tutte le raccolte e i QueryNode ridondanti sono sempre assegnati ad esso. Pertanto, è possibile creare un gruppo di risorse in sospeso per contenere le risorse QueryNode inutilizzate, evitando che le risorse QueryNode vengano occupate dal gruppo __default_resource_group.
Inoltre, se applichiamo rigorosamente il vincolo
sum(.requests.nodeNum) <= queryNodeNum
, possiamo controllare con precisione l'assegnazione dei QueryNode nel cluster. Supponiamo che al momento ci sia un solo QueryNode nel cluster e inizializziamo il cluster. Ecco un esempio di configurazione: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)
Utilizzando il codice di esempio precedente, creiamo un gruppo di risorse chiamato __pending_nodes per contenere altri QueryNode. Si creano anche due gruppi di risorse specifici per l'utente, denominati rg1 e rg2. Inoltre, ci assicuriamo che l'altro gruppo di risorse dia priorità al recupero dei QueryNode mancanti o ridondanti da __pending_nodes.
Ridimensionamento del cluster
Supponiamo di avere la seguente funzione di scalatura:
def scale_to(node_num: int): # scale the querynode number in Milvus into node_num. pass
Possiamo usare l'API per scalare un gruppo di risorse specifico a un numero designato di QueryNodes senza influenzare altri gruppi di risorse.
# 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.
Scala del cluster in
Allo stesso modo, possiamo stabilire regole di scaling-in che danno priorità alla selezione dei QueryNodes dal gruppo di risorse __pending_nodes. Queste informazioni possono essere ottenute tramite l'API
describe_resource_group
. Raggiungere l'obiettivo di scalare il gruppo di risorse specificato.# 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
Come i gruppi di risorse interagiscono con le repliche multiple
- Le repliche di una singola collezione e i gruppi di risorse hanno una relazione N a N.
- Quando più repliche di una singola raccolta vengono caricate in un gruppo di risorse, i QueryNode di quel gruppo di risorse vengono distribuiti in modo uniforme tra le repliche, assicurando che la differenza nel numero di QueryNode di ciascuna replica non superi 1.
Cosa fare dopo
Per distribuire un'istanza Milvus multi-tenant, leggere quanto segue: