Partizionamento in Azure Cosmos DB con l'API DocumentDB

Microsoft Azure Cosmos DB è un servizio di database multimodello con distribuzione globale progettato per ottenere prestazioni rapide e prevedibili e per eseguire facilmente il ridimensionamento in base alla crescita dell'applicazione.

Questo articolo offre una panoramica dell'uso del partizionamento dei contenitori Cosmos DB con l'API DocumentDB. Per una panoramica dei concetti e delle procedure consigliate per il partizionamento con qualsiasi API di Azure Cosmos DB, vedere l'articolo relativo a partizionamento e scalabilità orizzontale.

Per iniziare a usare il codice, scaricare il progetto da Github.

Dopo la lettura di questo articolo, si potrà rispondere alle domande seguenti:

  • Come funziona il partizionamento in Azure Cosmos DB?
  • Come si configura il partizionamento in Azure Cosmos DB?
  • Cosa sono le chiavi di partizione e come scegliere la chiave di partizione corretta per l'applicazione?

Per iniziare a usare il codice, scaricare il progetto dell'esempio di driver di test delle prestazioni di Azure Cosmos DB.

Chiavi di partizione, Nell'API DocumentDB la definizione delle chiavi di partizione viene specificata sotto forma di percorso JSON. La tabella seguente mostra esempi di definizioni di chiavi di partizione e i valori corrispondenti a ognuna. La chiave di partizione viene specificata come percorso, ad esempio /department rappresenta la proprietà department.

Chiave di partizione

Descrizione

/department

Corrisponde al valore doc.department, in cui doc è l'elemento.

/properties/name

Corrisponde al valore doc.properties.name, in cui doc è l'elemento (proprietà annidata).

/id

Corrisponde al valore doc.id (ID e chiave di partizione sono la stessa proprietà).

/"nome reparto"

Corrisponde al valore doc["nome reparto"], in cui doc è l'elemento.

Nota

La sintassi della chiave di partizione è simile alla specifica dei percorsi dei criteri di indicizzazione con la differenza fondamentale che il percorso corrisponde alla proprietà anziché al valore, ossia non sono presenti caratteri jolly alla fine. Ad esempio, per indicizzare i valori nel reparto si specifica /department/?, mentre per la definizione della chiave di partizione si usa /department. La chiave di partizione viene indicizzata in modo implicito e non può essere esclusa dall'indicizzazione usando override dei criteri di indicizzazione.

Di seguito viene esaminato come la scelta della chiave di partizione influisce sulle prestazioni dell'applicazione.

Utilizzo di Azure Cosmos DB SDK

Azure Cosmos DB ha aggiunto il supporto per il partizionamento automatico con la versione 2015-12-16 dell'API REST. Per creare contenitori partizionati, è necessario scaricare la versione 1.6.0 o una successiva dell'SDK in una delle piattaforme di SDK supportate (.NET, Node.js, Java, Python e MongoDB).

Creazione di contenitori

L'esempio seguente illustra un frammento .NET per la creazione di un contenitore per archiviare i dati di telemetria dei dispositivi a una velocità effettiva di 20.000 unità richiesta al secondo. L'SDK imposta il valore OfferThroughput, che a sua volta imposta l'intestazione di richiesta x-ms-offer-throughput nell'API REST. Qui viene impostato /deviceId come chiave di partizione. La scelta della chiave di partizione viene salvata con il resto dei metadati del contenitore, come il nome e i criteri di indicizzazione.

Per questo esempio è stato scelto deviceId per due motivi: (a) considerato il numero elevato di dispositivi, le scritture possono essere distribuite in modo uniforme tra le partizioni consentendo di scalare il database per l'inserimento di volumi elevati di dati e (b) molte richieste, ad esempio il recupero della lettura più recente per un dispositivo, sono limitate a un singolo deviceId e possono essere recuperate da una partizione singola.

DocumentClient client = new DocumentClient(new Uri(endpoint), authKey);
await client.CreateDatabaseAsync(new Database { Id = "db" });

// Container for device telemetry. Here the property deviceId will be used as the partition key to 
// spread across partitions. Configured for 10K RU/s throughput and an indexing policy that supports 
// sorting against any number or string property.
DocumentCollection myCollection = new DocumentCollection();
myCollection.Id = "coll";
myCollection.PartitionKey.Paths.Add("/deviceId");

await client.CreateDocumentCollectionAsync(
    UriFactory.CreateDatabaseUri("db"),
    myCollection,
    new RequestOptions { OfferThroughput = 20000 });

Questo metodo effettua una chiamata API REST a Cosmos DB e il servizio eseguirà il provisioning di una serie di partizioni in base alla velocità effettiva richiesta. È possibile modificare la velocità effettiva di un contenitore in base all'evoluzione delle esigenze in termini di prestazioni.

Lettura e scrittura di elementi

A questo punto si inseriscono i dati in Cosmos DB. Di seguito è riportata una classe di esempio contenente la lettura di un dispositivo e una chiamata a CreateDocumentAsync per inserire una nuova lettura del dispositivo in un contenitore. Questo esempio sfrutta l'API DocumentDB:

public class DeviceReading
{
    [JsonProperty("id")]
    public string Id;

    [JsonProperty("deviceId")]
    public string DeviceId;

    [JsonConverter(typeof(IsoDateTimeConverter))]
    [JsonProperty("readingTime")]
    public DateTime ReadingTime;

    [JsonProperty("metricType")]
    public string MetricType;

    [JsonProperty("unit")]
    public string Unit;

    [JsonProperty("metricValue")]
    public double MetricValue;
  }

// Create a document. Here the partition key is extracted as "XMS-0001" based on the collection definition
await client.CreateDocumentAsync(
    UriFactory.CreateDocumentCollectionUri("db", "coll"),
    new DeviceReading
    {
        Id = "XMS-001-FE24C",
        DeviceId = "XMS-0001",
        MetricType = "Temperature",
        MetricValue = 105.00,
        Unit = "Fahrenheit",
        ReadingTime = DateTime.UtcNow
    });

L'elemento viene letto in base alla chiave di partizione e all'ID, viene aggiornato e infine viene eliminato in base alla chiave di partizione e all'ID. Si noti che le letture includono un valore PartitionKey, corrispondente all'intestazione di richiesta x-ms-documentdb-partitionkey nell'API REST.

// Read document. Needs the partition key and the ID to be specified
Document result = await client.ReadDocumentAsync(
  UriFactory.CreateDocumentUri("db", "coll", "XMS-001-FE24C"), 
  new RequestOptions { PartitionKey = new PartitionKey("XMS-0001") });

DeviceReading reading = (DeviceReading)(dynamic)result;

// Update the document. Partition key is not required, again extracted from the document
reading.MetricValue = 104;
reading.ReadingTime = DateTime.UtcNow;

await client.ReplaceDocumentAsync(
  UriFactory.CreateDocumentUri("db", "coll", "XMS-001-FE24C"), 
  reading);

// Delete document. Needs partition key
await client.DeleteDocumentAsync(
  UriFactory.CreateDocumentUri("db", "coll", "XMS-001-FE24C"), 
  new RequestOptions { PartitionKey = new PartitionKey("XMS-0001") });

Esecuzione di query sui contenitori partizionati

Quando si eseguono query sui dati dei contenitori partizionati, Azure Cosmos DB instrada automaticamente la query alle partizioni corrispondenti ai valori di chiave di partizione specificati nel filtro (se presenti). Ad esempio, questa query viene instradata solo alla partizione contenente la chiave di partizione "XMS-0001".

// Query using partition key
IQueryable<DeviceReading> query = client.CreateDocumentQuery<DeviceReading>(
    UriFactory.CreateDocumentCollectionUri("db", "coll"))
    .Where(m => m.MetricType == "Temperature" && m.DeviceId == "XMS-0001");

La query seguente non dispone di un filtro per la chiave di partizione (DeviceId) e viene effettuato il fan-out a tutte le partizioni in cui viene eseguita a fronte dell'indice della partizione. Si noti che è necessario specificare EnableCrossPartitionQuery (x-ms-documentdb-query-enablecrosspartition nell'API REST) affinché l'SDK esegua una query tra le partizioni.

// Query across partition keys
IQueryable<DeviceReading> crossPartitionQuery = client.CreateDocumentQuery<DeviceReading>(
    UriFactory.CreateDocumentCollectionUri("db", "coll"), 
    new FeedOptions { EnableCrossPartitionQuery = true })
    .Where(m => m.MetricType == "Temperature" && m.MetricValue > 100);

Cosmos DB supporta le funzioni di aggregazione COUNT, MIN, MAX, SUM e AVG su contenitori partizionati con SQL a partire da SDK 1.12.0 e versioni successive. Le query devono includere un unico operatore di aggregazione e un singolo valore nella proiezione.

Esecuzione di query in parallelo

Gli SDK di Cosmos DB 1.9.0 e versioni successive supportano opzioni per l'esecuzione di query in parallelo che consentono di eseguire query a bassa latenza sulle raccolte partizionate, anche quando è coinvolto un numero elevato di partizioni. Ad esempio, la query seguente è configurata in modo da essere eseguita in parallelo tra le partizioni.

// Cross-partition Order By Queries
IQueryable<DeviceReading> crossPartitionQuery = client.CreateDocumentQuery<DeviceReading>(
    UriFactory.CreateDocumentCollectionUri("db", "coll"), 
    new FeedOptions { EnableCrossPartitionQuery = true, MaxDegreeOfParallelism = 10, MaxBufferedItemCount = 100})
    .Where(m => m.MetricType == "Temperature" && m.MetricValue > 100)
    .OrderBy(m => m.MetricValue);

È possibile gestire l'esecuzione di query in parallelo, ottimizzando i parametri seguenti:

  • Impostando MaxDegreeOfParallelism è possibile controllare il grado di parallelismo, ossia il numero massimo di connessioni di rete simultanee alle partizioni del contenitore. Se si imposta questo valore su -1, il grado di parallelismo viene gestito dall'SDK. Se MaxDegreeOfParallelism non è specificato o è impostato su 0, ovvero il valore predefinito, esisterà una sola connessione di rete alle partizioni del contenitore.
  • Impostando MaxBufferedItemCount è possibile raggiungere un compromesso tra latenza della query e uso della memoria dal lato client. Se si omette questo parametro o si imposta su -1, il numero di elementi memorizzati nel buffer durante l'esecuzione di query in parallelo viene gestito dall'SDK.

Considerato lo stato della raccolta, una query in parallelo restituirà i risultati nello stesso ordine dell'esecuzione seriale. Quando si esegue una query tra partizioni che include l'ordinamento (ORDER BY e/o TOP), Azure Cosmos DB SDK esegue la query in parallelo tra le partizioni e unisce i risultati ordinati parzialmente sul lato client per produrre risultati ordinati a livello globale.

Esecuzione di stored procedure

È anche possibile eseguire transazioni atomiche su documenti con lo stesso ID dispositivo, ad esempio se si gestiscono le aggregazioni o lo stato più recente di un dispositivo in un singolo elemento.

await client.ExecuteStoredProcedureAsync<DeviceReading>(
    UriFactory.CreateStoredProcedureUri("db", "coll", "SetLatestStateAcrossReadings"),
    new RequestOptions { PartitionKey = new PartitionKey("XMS-001") }, 
    "XMS-001-FE24C");

Nella sezione successiva verrà illustrato come passare a contenitori partizionati da contenitori a partizione singola.

Passaggi successivi

Questo articolo include una panoramica dell'uso del partizionamento dei contenitori Azure Cosmos DB con l'API DocumentDB. Per una panoramica dei concetti e delle procedure consigliate per il partizionamento con qualsiasi API di Azure Cosmos DB, vedere anche l'articolo relativo a partizionamento e scalabilità orizzontale.