DocumentDB - Partizionamento

Quando il database inizia a crescere oltre i 10 GB, puoi ridimensionare semplicemente creando nuove raccolte e quindi diffondendo o partizionando i dati su un numero sempre maggiore di raccolte.

Prima o poi una singola raccolta, che ha una capacità di 10 GB, non sarà sufficiente per contenere il database. Ora 10 GB potrebbero non sembrare un numero molto elevato, ma ricorda che stiamo archiviando documenti JSON, che è solo testo normale e puoi adattare molti documenti di testo normale in 10 GB, anche se si considera il sovraccarico di archiviazione per gli indici.

Lo storage non è l'unica preoccupazione quando si parla di scalabilità. La velocità effettiva massima disponibile su una raccolta è di duemila unità di richiesta al secondo ottenute con una raccolta S3. Pertanto, se è necessaria una velocità effettiva maggiore, sarà anche necessario eseguire la scalabilità orizzontale partizionando con più raccolte. Viene anche chiamato il partizionamento con scalabilità orizzontalehorizontal partitioning.

Esistono molti approcci che possono essere usati per il partizionamento dei dati con Azure DocumentDB. Di seguito sono riportate le strategie più comuni:

  • Partizionamento spillover
  • Partizionamento dell'intervallo
  • Partizionamento di ricerca
  • Partizionamento hash

Partizionamento spillover

Il partizionamento con spillover è la strategia più semplice perché non esiste una chiave di partizione. Spesso è una buona scelta iniziare quando non sei sicuro di molte cose. Potresti non sapere se dovrai mai scalare oltre una singola raccolta o quante raccolte potresti dover aggiungere o quanto velocemente potresti dover aggiungerle.

  • Il partizionamento in spillover inizia con una singola raccolta e non è presente alcuna chiave di partizione.

  • La raccolta inizia a crescere e poi cresce ancora, e poi ancora di più, fino a quando non inizi ad avvicinarti al limite di 10 GB.

  • Quando si raggiunge la capacità del 90%, si passa a una nuova raccolta e si inizia a utilizzarla per nuovi documenti.

  • Una volta che il tuo database si ridimensiona a un numero maggiore di raccolte, probabilmente vorrai passare a una strategia basata su una chiave di partizione.

  • Quando lo fai, dovrai ribilanciare i tuoi dati spostando i documenti in raccolte diverse in base alla strategia a cui stai migrando.

Partizionamento dell'intervallo

Una delle strategie più comuni è il partizionamento dell'intervallo. Con questo approccio si determina l'intervallo di valori in cui potrebbe rientrare la chiave di partizione di un documento e si indirizza il documento a una raccolta corrispondente a tale intervallo.

  • Le date vengono utilizzate molto in genere con questa strategia in cui si crea una raccolta per contenere documenti che rientrano nell'intervallo di date definito. Quando si definiscono intervalli sufficientemente piccoli, in cui si è certi che nessuna raccolta supererà mai il limite di 10 GB. Ad esempio, potrebbe esserci uno scenario in cui una singola raccolta può ragionevolmente gestire i documenti per un intero mese.

  • Può anche accadere che la maggior parte degli utenti stia interrogando i dati correnti, che sarebbero dati per questo mese o forse il mese scorso, ma gli utenti raramente cercano dati molto più vecchi. Quindi inizi a giugno con una raccolta S3, che è la raccolta più costosa che puoi acquistare e offre la migliore produttività possibile.

  • A luglio acquisti un'altra raccolta S3 per archiviare i dati di luglio e riduci i dati di giugno a una raccolta S2 meno costosa. Poi ad agosto, ottieni un'altra collezione S3 e riduci luglio a S2 e giugno fino a S1. Va, mese dopo mese, dove si mantengono sempre i dati correnti disponibili per un throughput elevato e i dati più vecchi sono tenuti disponibili a throughput inferiori.

  • Finché la query fornisce una chiave di partizione, verrà interrogata solo la raccolta che deve essere interrogata e non tutte le raccolte nel database come accade con il partizionamento spillover.

Partizionamento di ricerca

Con il partizionamento di ricerca è possibile definire una mappa delle partizioni che instrada i documenti a raccolte specifiche in base alla loro chiave di partizione. Ad esempio, potresti partizionare per regione.

  • Archivia tutti i documenti statunitensi in una raccolta, tutti i documenti europei in un'altra raccolta e tutti i documenti di qualsiasi altra regione in una terza raccolta.

  • Usa questa mappa delle partizioni e un risolutore di partizioni di ricerca può capire in quale raccolta creare un documento e quali raccolte interrogare, in base alla chiave di partizione, che è la proprietà della regione contenuta in ogni documento.

Partizionamento hash

Nel partizionamento hash, le partizioni vengono assegnate in base al valore di una funzione hash, consentendo di distribuire uniformemente richieste e dati su un numero di partizioni.

Viene comunemente utilizzato per partizionare i dati prodotti o consumati da un gran numero di client distinti ed è utile per memorizzare i profili utente, gli elementi del catalogo, ecc.

Diamo un'occhiata a un semplice esempio di partizionamento di intervalli utilizzando RangePartitionResolver fornito da .NET SDK.

Step 1- Crea un nuovo DocumentClient e creeremo due raccolte nell'attività CreateCollections. Uno conterrà i documenti per gli utenti che hanno ID utente che iniziano da A a M e l'altro per gli ID utente da N a Z.

private static async Task CreateCollections(DocumentClient client) {
   await client.CreateDocumentCollectionAsync(“dbs/myfirstdb”, new DocumentCollection {
      Id = “CollectionAM” }); 
		
   await client.CreateDocumentCollectionAsync(“dbs/myfirstdb”, new DocumentCollection {
      Id = “CollectionNZ” }); 
}

Step 2 - Registra il risolutore di intervalli per il database.

Step 3- Crea un nuovo RangePartitionResolver <string>, che è il tipo di dati della nostra chiave di partizione. Il costruttore accetta due parametri, il nome della proprietà della chiave di partizione e un dizionario che è la mappa di partizioni o mappa di partizione, che è solo un elenco degli intervalli e delle raccolte corrispondenti che stiamo predefinendo per il risolutore.

private static void RegisterRangeResolver(DocumentClient client) {

   //Note: \uffff is the largest UTF8 value, so M\ufff includes all strings that start with M.
		
   var resolver = new RangePartitionResolver<string>(
      "userId", new Dictionary<Range<string>, string>() {
      { new Range<string>("A", "M\uffff"), "dbs/myfirstdb/colls/CollectionAM" },
      { new Range<string>("N", "Z\uffff"), "dbs/myfirstdb/colls/CollectionNZ" },
   });
	
   client.PartitionResolvers["dbs/myfirstdb"] = resolver;
 }

È necessario codificare il valore UTF-8 più grande possibile qui. Oppure il primo intervallo non corrisponderebbe su nessuna M eccetto quella singola M, e allo stesso modo per Z nel secondo intervallo. Quindi, puoi semplicemente pensare a questo valore codificato qui come un carattere jolly per la corrispondenza sulla chiave di partizione.

Step 4- Dopo aver creato il resolver, registralo per il database con il DocumentClient corrente. Per farlo basta assegnarlo alla proprietà del dizionario di PartitionResolver.

Creeremo e interrogheremo i documenti sul database, non una raccolta come fai normalmente, il risolutore utilizzerà questa mappa per instradare le richieste alle raccolte appropriate.

Ora creiamo alcuni documenti. Prima ne creeremo uno per userId Kirk, e poi uno per Spock.

private static async Task CreateDocumentsAcrossPartitions(DocumentClient client) { 
   Console.WriteLine(); 
   Console.WriteLine("**** Create Documents Across Partitions ****");
	
   var kirkDocument = await client.CreateDocumentAsync("dbs/myfirstdb", new { userId =
      "Kirk", title = "Captain" }); 
   Console.WriteLine("Document 1: {0}", kirkDocument.Resource.SelfLink);
	
   var spockDocument = await client.CreateDocumentAsync("dbs/myfirstdb", new { userId =
      "Spock", title = "Science Officer" });		
   Console.WriteLine("Document 2: {0}", spockDocument.Resource.SelfLink); 
}

Il primo parametro qui è un collegamento automatico al database, non una raccolta specifica. Questo non è possibile senza un risolutore di partizioni, ma con uno funziona perfettamente.

Entrambi i documenti sono stati salvati nel database myfirstdb, ma sappiamo che Kirk viene archiviato nella raccolta da A a M e Spock viene archiviato nella raccolta da N a Z, se il nostro RangePartitionResolver funziona correttamente.

Chiamiamoli dall'attività CreateDocumentClient come mostrato nel codice seguente.

private static async Task CreateDocumentClient() {
   // Create a new instance of the DocumentClient 
   using (var client = new DocumentClient(new Uri(EndpointUrl), AuthorizationKey)) {
      await CreateCollections(client);  
      RegisterRangeResolver(client);  
      await CreateDocumentsAcrossPartitions(client); 
   } 
}

Quando il codice precedente viene eseguito, riceverai il seguente output.

**** Create Documents Across Partitions **** 
Document 1: dbs/Ic8LAA==/colls/Ic8LAO2DxAA=/docs/Ic8LAO2DxAABAAAAAAAAAA==/ 
Document 2: dbs/Ic8LAA==/colls/Ic8LAP12QAE=/docs/Ic8LAP12QAEBAAAAAAAAAA==/

Come si è visto, i collegamenti automatici dei due documenti hanno ID risorsa diversi perché esistono in due raccolte separate.