Chaves de partição hierárquicas no Azure Cosmos DB

APLICA-SE A: NoSQL

O Azure Cosmos DB distribui seus dados entre partições lógicas e físicas com base em sua chave de partição para suportar o dimensionamento horizontal. Usando as chaves de partição hierárquicas, também chamadas de subparticionamento, você pode configurar uma hierarquia de até três níveis para suas chaves de partição para otimizar ainda mais a distribuição de dados e para um nível de escala mais alto.

Se você usar chaves sintéticas hoje, tenha cenários em que as chaves de partição podem exceder 20 GB de dados ou gostaria de garantir que o documento de cada locatário seja mapeado para sua própria partição lógica, o subparticionamento pode ajudar. Se você usar esse recurso, os prefixos de chave de partição lógica poderão exceder 20 GB e 10.000 unidades de solicitação por segundo (RU/s). As consultas por prefixo são roteados com eficiência para o subconjunto de partições que armazenam os dados.

Escolha suas chaves de partição hierárquicas

Se você tiver aplicativos multilocatários e isolar locatários por chave de partição, partições hierárquicas poderão beneficiar você. As partições hierárquicas permitem dimensionar além do limite de chave de partição lógica de 20 GB e são uma boa solução se você quiser garantir que cada documento de seus locatários possa ser dimensionado infinitamente. Se a chave de partição atual ou uma única chave de partição atingir 20 GB com frequência, as partições hierárquicas serão uma ótima opção para sua carga de trabalho.

No entanto, dependendo da natureza de sua carga de trabalho e de como sua chave de primeiro nível é cardinal, pode haver algumas compensações que abordamos detalhadamente em nossa página de cenários de partição hierárquica.

Quando você escolhe cada nível da chave de partição hierárquica, é importante ter em mente os seguintes conceitos gerais de particionamento e entender como cada um deles pode afetar sua carga de trabalho:

  • Para todos os contêineres, cada nível do caminho completo (começando com o primeiro nível) da chave de partição hierárquica deve:

    • Ter uma alta cardinalidade. A primeira, a segunda e a terceira (se aplicável) chaves da partição hierárquica devem ter uma ampla gama de valores possíveis.

      • Ter baixa cardinalidade no primeiro nível da chave de partição hierárquica limitará todas as operações de gravação no momento da ingestão a apenas uma partição física até atingir 50 GB e se dividir em duas partições físicas. Por exemplo, suponha que sua chave de primeiro nível esteja ativada TenantId e tenha apenas cinco locatários exclusivos. Cada uma dessas operações de locatários terá como escopo apenas uma partição física, limitando seu consumo de taxa de transferência ao que está nessa partição física. Isso ocorre porque as partições hierárquicas se otimizam para todos os documentos com a mesma chave de primeiro nível a ser colocada na mesma partição física para evitar consultas de fanout total.
      • Embora isso possa ser bom para cargas de trabalho em que fazemos uma ingestão única de todos os dados de nossos locatários e as operações a seguir são principalmente de leitura pesada depois, isso pode ser não recaída para cargas de trabalho em que seus requisitos de negócios envolvem ingestão de dados em um momento específico. Por exemplo, se você tiver requisitos de negócios estritos para evitar latências, a taxa de transferência máxima que sua carga de trabalho pode teoricamente alcançar para ingerir dados é o número de partições físicas * 10k. Se sua chave de nível superior tiver baixa cardinalidade, seu número de partições físicas provavelmente será 1, a menos que haja dados suficientes para que a chave de nível 1 seja distribuída entre várias partições após divisões que podem levar entre 4 e 6 horas para serem concluídas.
    • Distribuir armazenamento de dados e consumo de RU (unidade de solicitação) uniformemente em todas as partições lógicas. Esse espalhamento garante o consumo de RU e a distribuição de armazenamento uniformes nas partições físicas.

      • Se você escolher uma chave de primeiro nível que parece ter uma cardinalidade alta como UserId, mas na prática sua carga de trabalho executa operações em apenas uma UserId específica, é provável que você encontre uma partição ativa, pois todas as suas operações terão escopo para apenas uma ou poucas partições físicas.
  • Cargas de trabalho pesadas de leitura: recomendamos escolher chaves de partição hierárquicas que aparecem com frequência em suas consultas.

    • Por exemplo, uma carga de trabalho que frequentemente executa consultas para filtrar sessões de usuário específicas em um aplicativo multilocatário pode se beneficiar de chaves de partição hierárquicas de TenantId, UserId e SessionId, nessa ordem. Consultas podem ser roteadas com eficiência somente para as partições físicas relevantes ao incluir a chave de partição no predicado de filtro. Para obter mais informações sobre a escolha de chaves de partição para cargas de trabalho de leitura pesada, consulte a visão geral do particionamento.
  • Cargas de trabalho pesadas de gravação: é recomendável usar um valor cardinal alto para o primeiro nível da chave de partição hierárquica. Alta cardinalidade significa que a chave de primeiro nível (e os níveis subsequentes também) tem pelo menos milhares de valores exclusivos e valores mais exclusivos do que o número de partições físicas.

    • Por exemplo, suponha que tenhamos uma carga de trabalho que isole os locatários por chave de partição e tenha alguns locatários grandes que são mais pesados em gravação do que outros. Hoje, o Azure Cosmos DB deixará de ingerir dados em qualquer valor de chave de partição se exceder 20 GB de dados. Nessa carga de trabalho, a Microsoft e a Contoso são grandes locatários e prevíamos que ele cresça muito mais rápido do que nossos outros locatários. Para evitar o risco de não poder ingerir dados para esses locatários, as chaves de partição hierárquica nos permitem dimensionar esses locatários além do limite de 20 GB. Podemos adicionar mais níveis como UserId e SessionId para garantir maior escalabilidade entre locatários.

    • Para garantir que sua carga de trabalho possa acomodar gravações para todos os documentos com a mesma chave de primeiro nível, considere usar a ID do item como uma chave de segundo ou terceiro nível.

    • Se o primeiro nível não tiver alta cardinalidade e você estiver atingindo o limite de partição lógica de 20 GB na chave de partição hoje, sugerimos usar uma chave de partição sintética em vez de uma chave de partição hierárquica.

Caso de uso de exemplo

Suponha que você tenha um cenário multilocatário em que armazena informações de evento para usuários em cada locatário. Essas informações de evento podem ter ocorrências de evento, incluindo, mas não se limitando a eventos de credenciais, clickstream ou pagamento.

Em um cenário do mundo real, alguns locatários podem crescer muito com milhares de usuários, enquanto os muitos outros locatários são menores e ter alguns usuários. O particionamento por /TenantId pode levar a exceder o limite de armazenamento de 20 GB do Azure Cosmos DB em uma única partição lógica. O particionamento por /UserId faz todas as consultas em uma partição cruzada de locatário. Ambas as abordagens têm desvantagens significativas.

O uso de uma chave de partição sintética que combina TenantId eUserId adiciona complexidade ao aplicativo. Além disso, as consultas de chave de partição sintética para um locatário ainda são de partição cruzada, a menos que todos os usuários sejam conhecidos e especificados com antecedência.

Se sua carga de trabalho tiver locatários com aproximadamente os mesmos padrões de carga de trabalho, a chave de partição hierárquica poderá ajudar. Com chaves de partição hierárquicas, você pode particionar primeiro em TenantId e, em seguida, UserId. Se você espera que a combinação TenantId e UserId produza partições que excedem 20 GB, você pode até mesmo particionar para outro nível inferior, como SessionId. A profundidade geral não pode exceder três níveis. Quando uma partição física exceder 50 GB de armazenamento, o Azure Cosmos DB divide automaticamente a partição física para que cerca de metade dos dados esteja em uma partição física e metade na outra. Efetivamente, o subparticionamento significa que um único TenantId pode exceder 20 GB de dados e é possível que os dados de um TenantId alcancem várias partições físicas.

Consultas que especificam o TenantId ou TenantId e UserId serão roteadas com eficiência apenas para o subconjunto de partições físicas que contêm os dados relevantes. A especificação do caminho completo ou de chave de partição subparticionada de prefixo evita efetivamente uma consulta do tipo fan-out completa. Por exemplo, se o contêiner tivesse mil partições físicas, mas o valor de uma TenantId específica estivesse somente em cinco partições físicas, a consulta seria roteada para o número menor de partições físicas relevantes.

Usar a ID do item na hierarquia

Se o contêiner tiver uma propriedade com uma grande variedade de valores possíveis, provavelmente a propriedade será uma ótima opção de chave de partição para o último nível da hierarquia. Um exemplo possível de tal propriedade é a ID do item. A ID do item de propriedade do sistema existe em cada item em seu contêiner. Adicionar a ID do item como outro nível garante que você possa escalar além do limite de chave de partição lógica de 20 GB. Você pode escalar além desse limite para o primeiro nível ou para o primeiro e segundo nível de chaves.

Por exemplo, você pode ter um contêiner para uma carga de trabalho multilocatário particionada por TenantId e UserId. Se for possível que uma única combinação de TenantId e UserId exceda 20 GB, recomendamos que você particione usando três níveis de chaves e no qual a chave de terceiro nível tenha alta cardinalidade. Um exemplo desse cenário é se a chave de terceiro nível for um GUID que tem a cardinalidade naturalmente alta. É improvável que a combinação de TenantId, UserId e um GUID exceda 20 GB, de modo que a combinação de TenantId e UserId possa ser dimensionada efetivamente além de 20 GB.

Para obter mais informações sobre como usar a ID do item como uma chave de partição, consulte a visão geral do particionamento.

Introdução

Importante

Trabalhar com contêineres que usam chaves de partição hierárquica só tem suporte nas seguintes versões do SDK. Você deve usar um SDK com suporte para criar novos contêineres com chaves de partição hierárquicas e executar operações de consulta/criação, leitura, atualização e exclusão (CRUD) nos dados. Se você quiser usar um SDK ou conector que não tenha suporte no momento, registre uma solicitação em nosso fórum da comunidade.

Localize a versão prévia mais recente de cada SDK compatível:

. Versões com suporte Link do gerenciador de pacotes
SDK v3 do .NET >= 3.33.0 https://www.nuget.org/packages/Microsoft.Azure.Cosmos/3.33.0/
SDK do Java v4 >= 4.42.0 https://github.com/Azure/azure-sdk-for-java/blob/main/sdk/cosmos/azure-cosmos/CHANGELOG.md#4420-2023-03-17/
SDK v4 do JavaScript 4.0.0 https://www.npmjs.com/package/@azure/cosmos/
SDK do Python >= 4.6.0 https://pypi.org/project/azure-cosmos/4.6.0/

Criar um contêiner usando chaves de partição hierárquicas

Para começar, crie um novo contêiner usando uma lista predefinida de caminhos de chave de subparticionamento até três níveis de profundidade.

Você pode criar um contêiner usando uma destas opções:

  • Portal do Azure
  • .
  • Modelo do Azure Resource Manager
  • Emulador do Azure Cosmos DB

Portal do Azure

A maneira mais simples de criar um contêiner e especificar chaves de partição hierárquicas é usando o portal do Azure.

  1. Entre no portal do Azure.

  2. Vá até a página de conta do Azure Cosmos DB for NoSQL.

  3. No menu à esquerda, selecione Data Explorer.

    Captura de tela que mostra a página de uma nova conta do Azure Cosmos DB for NoSQL com a opção do menu Data Explorer realçada.

  4. No Data Explorer, selecione a opção Novo Contêiner.

    Captura de tela da opção Novo contêiner no Data Explorer.

  5. Em Novo Contêiner, para Chave de partição, insira /TenantId. Para os campos restantes, insira qualquer valor que corresponda ao seu cenário.

    Observação

    Usamos /TenantId como exemplo aqui. Você pode especificar qualquer chave para o primeiro nível ao implementar chaves de partição hierárquicas em seus próprios contêineres.

  6. Selecione Adicionar chave de partição hierárquica duas vezes.

    Captura de tela do botão para adicionar uma nova chave de partição hierárquica.

  7. Para a segunda e terceira camadas de subparticionamento, insira /UserId e /SessionId, respectivamente.

    Captura de tela de uma lista de três chaves de partição hierárquicas.

  8. Selecione OK para criar o contêiner.

.

Ao criar um novo contêiner usando o SDK, defina uma lista de caminhos de chave de subparticionamento até três níveis de profundidade. Use a lista de chaves de subpartição ao configurar as propriedades do novo contêiner.

// List of partition keys, in hierarchical order. You can have up to three levels of keys.
List<string> subpartitionKeyPaths = new List<string> { 
    "/TenantId",
    "/UserId",
    "/SessionId"
};

// Create a container properties object
ContainerProperties containerProperties = new ContainerProperties(
    id: "<container-name>",
    partitionKeyPaths: subpartitionKeyPaths
);

// Create a container that's subpartitioned by TenantId > UserId > SessionId
Container container = await database.CreateContainerIfNotExistsAsync(containerProperties, throughput: 400);

Modelos do Azure Resource Manager

O modelo do Resource Manager para um contêiner subparticionado é quase idêntico a um contêiner padrão. A única diferença de chave é o valor do caminho properties/partitionKey. Para obter mais informações sobre como criar um modelo do ARM para um recurso do Azure Cosmos DB, consulte a referência de modelo do ARM para o Azure Cosmos DB.

Configure o objeto partitionKey usando os valores na tabela a seguir para criar um contêiner subpartitioned:

Caminho Valor
paths Lista de chaves de partição hierárquicas (máximo de três níveis de profundidade)
kind MultiHash
version 2

Definição de chave de partição de exemplo

Por exemplo, suponha que você tenha uma chave de partição hierárquica composta por TenantId>UserId>SessionId. O objeto partitionKey seria configurado para incluir todos os três valores na propriedade paths, um valor kind de MultiHash e um valor version de 2.

partitionKey: {
  paths: [
    '/TenantId'
    '/UserId'
    '/SessionId'
  ]
  kind: 'MultiHash'
  version: 2
}

Para obter mais informações sobre o objeto partitionKey, consulte a Especificação de ContainerPartitionKey.

Emulador do Azure Cosmos DB

Você pode testar o recurso de subparticionamento usando a versão mais recente do emulador local do Azure Cosmos DB. Para habilitar o subparticionamento no emulador, inicie o emulador do diretório de instalação com o sinalizador /EnablePreview:

.\CosmosDB.Emulator.exe /EnablePreview

Aviso

No momento, o emulador não dá suporte a todos os recursos de chave de partição hierárquica como o portal. Atualmente, o emulador não dá suporte ao:

  • Uso do Data Explorer para criar contêineres com chaves de partição hierárquicas
  • Uso do Data Explorer para navegar em itens e interagir com eles usando as chaves de partição hierárquicas

Para obter mais informações, consulte Emulador do Azure Cosmos DB.

Usar os SDKs para trabalhar com contêineres com chaves de partição hierárquicas

Depois de ter um contêiner com chaves de partição hierárquicas, use as versões especificadas anteriormente dos SDKs do .NET ou Java para executar operações e executar consultas nesse contêiner.

Adicionar um item a um contêiner

Há duas opções para adicionar um novo item a um contêiner com chaves de partição hierárquicas habilitadas:

  • Extração automática
  • Especificar o caminho manualmente

Extração automática

Se você passar um objeto com o valor da chave de partição definido, o SDK poderá extrair automaticamente o caminho completo da chave de partição.

// Create a new item
UserSession item = new UserSession()
{
    id = "f7da01b0-090b-41d2-8416-dacae09fbb4a",
    TenantId = "Microsoft",
    UserId = "8411f20f-be3e-416a-a3e7-dcd5a3c1f28b",
    SessionId = "0000-11-0000-1111"
};

// Pass in the object, and the SDK automatically extracts the full partition key path
ItemResponse<UserSession> createResponse = await container.CreateItemAsync(item);

Especificar o caminho manualmente

A classe PartitionKeyBuilder no SDK pode construir um valor para um caminho de chave de partição hierárquico definido anteriormente. Use essa classe ao adicionar um novo item a um contêiner que tenha o subpartimento habilitado.

Dica

Em escala, o desempenho deve melhorar se você especificar o caminho completo da chave de partição, mesmo que o SDK possa extrair o caminho do objeto.

// Create a new item object
PaymentEvent item = new PaymentEvent()
{
    id = Guid.NewGuid().ToString(),
    TenantId = "Microsoft",
    UserId = "8411f20f-be3e-416a-a3e7-dcd5a3c1f28b",
    SessionId = "0000-11-0000-1111"
};

// Specify the full partition key path when creating the item
PartitionKey partitionKey = new PartitionKeyBuilder()
            .Add(item.TenantId)
            .Add(item.UserId)
            .Add(item.SessionId)
            .Build();

// Create the item in the container
ItemResponse<PaymentEvent> createResponse = await container.CreateItemAsync(item, partitionKey);

Executar uma pesquisa chave/valor (leitura de ponto) de um item

As pesquisas de chave/valor (leituras de ponto) são executadas de maneira semelhante a um contêiner não subparticionado. Por exemplo, suponha que você tenha uma chave de partição hierárquica que consiste em TenantId>UserId>SessionId. O identificador exclusivo do item é um GUID. Ele é representado como uma cadeia de caracteres que serve como um identificador de transação de documento exclusivo. Para executar um ponto lido em um único item, passe a propriedade id do item e o valor completo da chave de partição, incluindo os três componentes do caminho.

// Store the unique identifier
string id = "f7da01b0-090b-41d2-8416-dacae09fbb4a";

// Build the full partition key path
PartitionKey partitionKey = new PartitionKeyBuilder()
    .Add("Microsoft") //TenantId
    .Add("8411f20f-be3e-416a-a3e7-dcd5a3c1f28b") //UserId
    .Add("0000-11-0000-1111") //SessionId
    .Build();

// Perform a point read
ItemResponse<UserSession> readResponse = await container.ReadItemAsync<UserSession>(
    id,
    partitionKey
);

Executar uma consulta

O código SDK que você usa para executar uma consulta em um contêiner subparticionado é idêntico à execução de uma consulta em um contêiner não subparticionado.

Quando a consulta especifica todos os valores das chaves de partição no filtro WHERE ou um prefixo da hierarquia de chaves, o SDK roteia automaticamente a consulta para as partições físicas correspondentes. As consultas que fornecem apenas o "meio" da hierarquia são consultas de partição cruzada.

Por exemplo, considere uma chave de partição hierárquica composta por TenantId>UserId>SessionId. Os componentes do filtro da consulta determinam se a consulta é uma partição única, partição cruzada direcionada ou consulta do tipo fan-out.

Consulta Roteiro
SELECT * FROM c WHERE c.TenantId = 'Microsoft' AND c.UserId = '8411f20f-be3e-416a-a3e7-dcd5a3c1f28b' AND c.SessionId = '0000-11-0000-1111' Roteado para a única partição lógica e física que contém os dados para os valores especificados de TenantId, UserId e SessionId.
SELECT * FROM c WHERE c.TenantId = 'Microsoft' AND c.UserId = '8411f20f-be3e-416a-a3e7-dcd5a3c1f28b' Roteado apenas para o subconjunto direcionado de partições lógicas e físicas que contêm dados para os valores especificados de TenantId e UserId. Essa consulta é uma consulta de partição cruzada direcionada que retorna dados para um usuário específico no locatário.
SELECT * FROM c WHERE c.TenantId = 'Microsoft' Roteado apenas para o subconjunto direcionado de partições lógicas e físicas que contêm dados para o valor especificado de TenantId. Essa consulta é uma consulta de partição cruzada direcionada que retorna dados para todos os usuários em um locatário.
SELECT * FROM c WHERE c.UserId = '8411f20f-be3e-416a-a3e7-dcd5a3c1f28b' Roteado para todas as partições físicas, resultando em uma consulta de partição cruzada.
SELECT * FROM c WHERE c.SessionId = '0000-11-0000-1111' Roteado para todas as partições físicas, resultando em uma consulta de partição cruzada.

Consulta de partição única em um contêiner subparticionado

Aqui está um exemplo de execução de uma consulta que inclui todos os níveis de subparticionamento efetivamente tornando a consulta como de partição única.

// Define a single-partition query that specifies the full partition key path
QueryDefinition query = new QueryDefinition(
    "SELECT * FROM c WHERE c.TenantId = @tenant-id AND c.UserId = @user-id AND c.SessionId = @session-id")
    .WithParameter("@tenant-id", "Microsoft")
    .WithParameter("@user-id", "8411f20f-be3e-416a-a3e7-dcd5a3c1f28b")
    .WithParameter("@session-id", "0000-11-0000-1111");

// Retrieve an iterator for the result set
using FeedIterator<PaymentEvent> results = container.GetItemQueryIterator<PaymentEvent>(query);

while (results.HasMoreResults)
{
    FeedResponse<UserSession> resultsPage = await resultSet.ReadNextAsync();
    foreach(UserSession result in resultsPage)
    {
        // Process result
    }
}

Consulta de várias partições direcionada em um contêiner subparticionado

Aqui está um exemplo de uma consulta que inclui um subconjunto dos níveis de subparticionamento efetivamente, tornando essa consulta como de várias partições de destino.

// Define a targeted cross-partition query specifying prefix path[s]
QueryDefinition query = new QueryDefinition(
    "SELECT * FROM c WHERE c.TenantId = @tenant-id")
    .WithParameter("@tenant-id", "Microsoft")

// Retrieve an iterator for the result set
using FeedIterator<PaymentEvent> results = container.GetItemQueryIterator<PaymentEvent>(query);

while (results.HasMoreResults)
{
    FeedResponse<UserSession> resultsPage = await resultSet.ReadNextAsync();
    foreach(UserSession result in resultsPage)
    {
        // Process result
    }
}

Limitações e problemas conhecidos

  • O trabalho com contêineres que usam chaves de partição hierárquica só tem suporte no SDK do .NET v3, nos SDKs v4 do Java v4 e nas versões prévias do SDK do JavaScript. Você deve usar um SDK com suporte para criar novos contêineres com chaves de partição hierárquicas e executar operações de consulta ou CRUD nos dados. O suporte para outros SDKs, incluindo Python, não está disponível no momento.
  • Há limitações com vários conectores do Azure Cosmos DB (por exemplo, Azure Data Factory).
  • Você só pode especificar chaves de partição hierárquicas com até três camadas de profundidade.
  • No momento, as chaves de partição hierárquicas só podem ser habilitadas em novos contêineres. Você deve definir caminhos de chave de partição no momento da criação do contêiner e não pode alterá-los mais tarde. Para usar partições hierárquicas nos contêineres existentes, crie um contêiner com as chaves de partição hierárquica definidas e mova os dados usando trabalhos de cópia de contêiner.
  • Atualmente, as chaves de partição hierárquicas são suportadas somente por contas API para NoSQL. No momento, não há suporte para as APIs para MongoDB e Cassandra.
  • No momento, não há suporte para chaves de partição hierárquicas com o recurso Permissões. Você não pode atribuir uma permissão a um prefixo parcial do caminho da chave de partição hierárquica. As permissões só podem ser atribuídas a todo o caminho de chave de partição lógica. Por exemplo, se você tiver particionado por TenantId – >UserId, não poderá atribuir uma permissão que seja para um valor específico de TenantId. No entanto, você poderá atribuir uma permissão para uma chave de partição se especificar o valor para TenantId e ''UserId'''.

Próximas etapas