Dicas de desempenho para o Azure Cosmos DB e .NET

APLICA-SE A: NoSQL

O Azure Cosmos DB é um banco de dados distribuído, rápido e flexível que pode ser dimensionado perfeitamente com garantia de níveis de latência e taxa de transferência. Você não precisa fazer grandes alterações de arquitetura nem escrever códigos complexos para dimensionar seu banco de dados com o Azure Cosmos DB. Aumentar e diminuir a escala é tão fácil quanto fazer uma única chamada de API. Para saber mais, consulte Provisionar a taxa de transferência do contêiner ou Provisionar a taxa de transferência do banco de dados.

Como o Azure Cosmos DB é acessado por meio de chamadas de rede, você pode otimizar o lado do cliente para obter o melhor desempenho ao usar o SDK do .NET de SQL.

Paea melhorar o desempenho do seu banco de dados, considere as opções apresentadas nas seções a seguir.

Recomendações de hospedagem

Ativar a coleta de lixo do lado do servidor

A redução da frequência da coleta de lixo pode ajudar em alguns casos. No .NET, configure gcServer como true.

Dimensionar a carga de trabalho do cliente

Em testes com altos níveis de taxa de transferência ou taxas maiores que 50.000 RU/s (unidades de solicitação por segundo), o aplicativo cliente pode se tornar um gargalo de carga de trabalho. Isso ocorre porque o computador pode se limitar à utilização da CPU ou da rede. Se você chegar a este ponto, poderá continuar aumentando a conta do Azure Cosmos DB ainda mais distribuindo seus aplicativos cliente entre vários servidores.

Observação

O uso intenso de CPU pode causar aumento de latência e exceções de tempo limite de solicitação.

Operações de metadados

Não verifique se existe um Banco de dados e/ou Contêiner chamando Create...IfNotExistsAsync e/ou Read...Async no caminho crítico e/ou antes de executar uma operação de item. A validação só deverá ser feita na inicialização do aplicativo quando for necessário, se você esperar que eles sejam excluídos (caso contrário, não será necessário). Essas operações de metadados gerarão latência extra de ponta a ponta, não têm SLA e suas próprias limitações separadas que não são dimensionados como operações de dados.

Registro em log e rastreamento

Alguns ambientes têm o DefaultTraceListener do .NET habilitado. O DefaultTraceListener apresenta problemas de desempenho em ambientes de produção que causam altos gargalos de CPU e E/S. Verifique se o DefaultTraceListener está desabilitado no seu aplicativo removendo-o dos TraceListeners em ambientes de produção.

As versões mais recentes do SDK (posteriores à 3.23.0) removem o DefaultTraceListener automaticamente quando o detectam. Nas versões mais antigas, você pode removê-lo do seguinte modo:

if (!Debugger.IsAttached)
{
    Type defaultTrace = Type.GetType("Microsoft.Azure.Cosmos.Core.Trace.DefaultTrace,Microsoft.Azure.Cosmos.Direct");
    TraceSource traceSource = (TraceSource)defaultTrace.GetProperty("TraceSource").GetValue(null);
    traceSource.Listeners.Remove("Default");
    // Add your own trace listeners
}

Rede

Política de conexão: usar o modo de conexão direta

O modo de conexão padrão do SDK do .NET V3 é direto com o protocolo TCP. Você o configura ao criar a instância CosmosClient em CosmosClientOptions. Para saber mais sobre as diferentes opções de conectividade, consulte o artigo Modos de conectividade.

string connectionString = "<your-account-connection-string>";
CosmosClient client = new CosmosClient(connectionString,
new CosmosClientOptions
{
    ConnectionMode = ConnectionMode.Gateway // ConnectionMode.Direct is the default
});

Esgotamento de porta efêmera

Ao enfrentar um alto volume de conexões ou uma alta utilização de porta em suas instâncias, verifique primeiro se as instâncias cliente são singletons. Em outras palavras, elas devem ser exclusivas durante o tempo de vida do aplicativo.

Durante a execução do protocolo TCP, o cliente é otimizado para latência usando conexões de longa duração. Isso é diferente no caso do protocolo HTTPS, que encerra as conexões após dois minutos de inatividade.

Em cenários em que você tem acesso esparso e percebe uma contagem de conexões maior em comparação com o acesso do modo de gateway, você pode fazer o seguinte:

  • Configure a propriedade CosmosClientOptions. PortReuseMode como PrivatePortPool (em vigor nas versões 4.6.1 e mais recentes do Framework e nas versões 2.0 e mais recentes do .NET Core). Isso permite que o SDK use um pequeno pool de portas efêmeras para diferentes pontos de extremidade de destino do Azure Cosmos DB.
  • Configure a propriedade CosmosClientOptions.IdleTcpConnectionTimeout como maior ou igual a dez minutos. Os valores possíveis são de 20 minutos a 24 horas.

Coloque os clientes na mesma região do Azure para aumentar o desempenho

Quando possível, coloque os aplicativos que chamam o Azure Cosmos DB na mesma região do banco de dados dele. Uma comparação aproximada: as chamadas para o Azure Cosmos DB na mesma região são concluídas de 1 a 2 ms (milissegundos), mas a latência entre a Costa Leste e a Oeste dos EUA é maior que 50 ms. Essa latência pode variar entre as solicitações, dependendo da rota seguida por elas ao passar do limite do cliente para o do datacenter do Azure.

A menor latência possível é alcançada garantindo que o aplicativo de chamada esteja na mesma região do Azure que o ponto de extremidade provisionado do Azure Cosmos DB. Para obter uma lista de regiões disponíveis, consulte Regiões do Azure.

Coloque os clientes na mesma região.

Aumentar o número de threads/tarefas

Como as chamadas ao Azure Cosmos DB são feitas sobre a rede, pode ser necessário variar o grau de simultaneidade das solicitações, de forma que o aplicativo cliente aguarde um tempo mínimo entre as solicitações. Por exemplo, ao usar a biblioteca de paralelismo de tarefas do .NET, crie centenas de tarefas que leem ou gravam no Azure Cosmos DB.

Habilitar a rede acelerada para reduzir a latência e a tremulação da CPU

É recomendável que você siga as instruções para habilitar a Rede Acelerada na VM do Azure do Windows (clique para obter instruções) ou Linux (clique para obter instruções) para maximizar o desempenho.

Sem a rede acelerada, a E/S que transita entre a VM do Azure e outros recursos do Azure pode ser desnecessariamente encaminhada por meio de um host e um comutador virtual situados entre a VM e sua placa de rede. Ter o host e o comutador virtual embutidos no caminho de dados não apenas aumenta a latência e a tremulação no canal de comunicação, ele também rouba ciclos de CPU da VM. Com a rede acelerada, a VM interage diretamente com a NIC sem intermediários; todos os detalhes da diretiva de rede que estavam sendo manipulados pelo host e pelo comutador virtual agora são manipulados no hardware na NIC; o host e o comutador virtual são ignorados. Geralmente, você pode esperar uma latência mais baixa e uma taxa de transferência mais alta, bem como uma latência mais consistente e menor utilização da CPU quando você habilita a rede acelerada.

Limitações: a rede acelerada deve ter suporte no sistema operacional da VM e só pode ser habilitada quando a VM é interrompida e desalocada. A VM não pode ser implantada com o Azure Resource Manager. O Serviço de Aplicativo não tem a rede acelerada habilitada.

Confira as instruções do Windows e do Linux para obter mais detalhes.

Uso do SDK

Instalar o SDK mais recente

Os SDKs do Azure Cosmos DB estão constantemente sendo aprimorados para fornecer o melhor desempenho. Para determinar o SDK mais recente e examinar as melhorias, consulte SDK do Azure Cosmos DB.

Usar APIs de fluxo

O SDK do .NET v3 contém APIs de fluxo que podem receber e retornar dados sem serialização.

Os aplicativos de camada intermediária que não consomem diretamente as respostas do SDK, mas as retransmitem a outras camadas de aplicativo, podem se beneficiar das APIs de fluxo. Para obter exemplos de manipulação de fluxo, consulte os exemplos de Gerenciamento de itens.

Usar um cliente do Azure Cosmos DB singleton para obter o tempo de vida do aplicativo

Cada instância CosmosClient tem um thread-safe e realiza o gerenciamento de conexão e o armazenamento de endereços em cache com eficiência ao operar no Modo Direto. Para permitir um gerenciamento de conexão eficiente e melhor desempenho do cliente SDK, recomenda-se usar uma única instância por AppDomain durante o tempo de vida do aplicativo para cada conta com a qual o seu aplicativo interage.

Para aplicativos multilocatário que lidam com várias contas, confira as melhores práticas relacionadas.

Ao trabalhar no Azure Functions, as instâncias também devem seguir as diretrizes existentes e manter uma única instância.

Evitar chamadas de bloqueio

O SDK do Azure Cosmos DB deve ser projetado de modo a processar várias solicitações simultaneamente. As APIs assíncronas permitem que um pequeno pool de threads cuide de milhares de solicitações simultâneas sem aguardar chamadas de bloqueio. Em vez de aguardar a conclusão de uma tarefa síncrona de execução longa, o thread pode trabalhar em outra solicitação.

Um problema de desempenho comum em aplicativos que usam o SDK do Azure Cosmos DB é o bloqueio de chamadas que poderiam ser assíncronas. Muitas chamadas de bloqueio síncronas levam à Privação do pool de threads e a tempos de resposta degradados.

Não:

  • Bloqueie a execução assíncrona chamando Task.Wait ou Task.Result.
  • Use Task.Run para tornar uma API síncrona em assíncrona.
  • Adquira bloqueios em caminhos de código comuns. O SDK do .NET do Azure Cosmos DB tem melhor desempenho quando projetado para executar código em paralelo.
  • Chame Task.Run e aguarde imediatamente. O ASP.NET Core já executa o código do aplicativo em threads de Pool de Threads normais, portanto, chamar Task.Run resulta apenas no agendamento extra desnecessário do Pool de Threads. Mesmo que o código agendado bloqueie um thread, Task.Run não impede isso.
  • Não use ToList() em Container.GetItemLinqQueryable<T>(), que usa chamadas de bloqueio para drenar a consulta de maneira síncrona. Use ToFeedIterator() para drenar a consulta de maneira assíncrona.

Certo:

  • Chame as APIs do .NET do Azure Cosmos DB de maneira assíncrona.
  • Toda a pilha de chamadas é assíncrona para se beneficiar de padrões async/await.

Um criador de perfil, como PerfView, pode ser usado para localizar threads adicionados frequentemente ao Pool de threads. O evento Microsoft-Windows-DotNETRuntime/ThreadPoolWorkerThread/Start indica um thread adicionado ao pool de threads.

Desabilitar a resposta de conteúdo em operações de gravação

Para cargas de trabalho com cargas de criação pesadas, defina a opção de solicitação EnableContentResponseOnWrite como false. O serviço não retornará mais o recurso criado ou atualizado para o SDK. Normalmente, como o aplicativo tem o objeto que está sendo criado, ele não precisa que o serviço o retorne. Os valores de cabeçalho ainda são acessíveis, como uma carga de solicitação. Desabilitar a resposta de conteúdo pode ajudar a melhorar o desempenho, pois o SDK não precisa mais alocar memória ou serializar o corpo da resposta. Isso também reduz o uso de largura de banda da rede e ajudar ainda mais no desempenho.

ItemRequestOptions requestOptions = new ItemRequestOptions() { EnableContentResponseOnWrite = false };
ItemResponse<Book> itemResponse = await this.container.CreateItemAsync<Book>(book, new PartitionKey(book.pk), requestOptions);
// Resource will be null
itemResponse.Resource

Habilitar o Bulk para otimizar para a taxa de transferência em vez de para a latência

Habilite o Bulk nos cenários em que a carga de trabalho requer uma grande quantidade de taxa de transferência e a latência não é tão importante. Para obter mais informações sobre como habilitar o recurso Bulk e saber em quais cenários ele deve ser usado, consulte Introdução ao suporte do Bulk.

Aumentar System.Net MaxConnections por host ao usar o modo de Gateway

As solicitações do Azure Cosmos DB são feitas sobre HTTPS/REST ao usar o modo de gateway. Elas estão sujeitas ao limite de conexão padrão por nome de host ou endereço IP. Pode ser necessário definir MaxConnections como um valor mais alto (de 100 a 1000) para que a biblioteca de cliente possa utilizar várias conexões simultâneas com o Azure Cosmos DB. No SDK do .NET 1.8.0 e mais recente, o valor padrão de ServicePointManager.DefaultConnectionLimit é 50. Para alterá-lo, defina Documents.Client.ConnectionPolicy.MaxConnectionLimit como um valor mais alto.

Aumentar o número de threads/tarefas

Consulte Aumentar o número de threads/tarefas na seção Rede deste artigo.

Operações de consulta

Para operações de consulta, consulte as dicas de desempenho para consultas.

Política de indexação

Excluir caminhos não utilizados da indexação para ter gravações mais rápidas

A política de indexação do Azure Cosmos DB também permite especificar quais caminhos de documento serão incluídos ou excluídos da indexação usando os caminhos de indexação (IndexingPolicy.IncludedPaths e IndexingPolicy.ExcludedPaths).

Indexar somente os caminhos necessários pode melhorar o desempenho de gravação, reduzir as cargas de RU em operações de gravação e diminuir o armazenamento de índice nos cenários em que os padrões de consulta são conhecidos com antecedência. Isso ocorre porque os custos de indexação se correlacionam diretamente com o número de caminhos exclusivos indexados. Por exemplo, o código a seguir mostra como excluir da indexação uma seção inteira dos documentos (uma subárvore) usando o curinga "*".

var containerProperties = new ContainerProperties(id: "excludedPathCollection", partitionKeyPath: "/pk" );
containerProperties.IndexingPolicy.IncludedPaths.Add(new IncludedPath { Path = "/*" });
containerProperties.IndexingPolicy.ExcludedPaths.Add(new ExcludedPath { Path = "/nonIndexedContent/*");
Container container = await this.cosmosDatabase.CreateContainerAsync(containerProperties);

Para obter mais informações, consulte Políticas de indexação do Azure Cosmos DB.

Produtividade

Medição e ajustes para obter menor uso de RU/s

O Azure Cosmos DB oferece um amplo conjunto de operações de banco de dados. Elas incluem consultas relacionais e hierárquicas com arquivos UDF, procedimentos armazenados e gatilhos, que operam nos documentos de uma coleção de banco de dados.

O custo associado a cada uma dessas operações varia com base na CPU, na E/S e na memória necessárias para realizar a operação. Em vez de considerar e gerenciar recursos de hardware, considere uma Unidade de Solicitação como uma medida única dos recursos necessários para executar várias operações de banco de dados e atender a uma solicitação de aplicativo.

A taxa de transferência é provisionada com base no número de Unidades de solicitação definidas para cada contêiner. O consumo dessa Unidade de Solicitação é avaliado por segundo. Os aplicativos que excedem a taxa de unidades de solicitação provisionada para o contêiner são limitados até que ela fique abaixo do nível provisionado. Se o aplicativo precisar de um nível mais alto de taxa de transferência, aumente essa taxa provisionando unidades de solicitação adicionais.

A complexidade de uma consulta afeta quantas unidades de solicitação são consumidas para uma operação. O número de predicados, a natureza deles, o número de arquivos UDF e o tamanho do conjunto de dados de origem influenciam o custo das operações de consulta.

Para medir a sobrecarga de quaisquer operações (criação, atualização ou exclusão), analise o cabeçalho x-ms-request-charge (ou a propriedade equivalente RequestCharge no ResourceResponse<T> ou FeedResponse<T> no SDK do .NET) para medir o número de unidades de solicitação consumidas por elas:

// Measure the performance (Request Units) of writes
ItemResponse<Book> response = await container.CreateItemAsync<Book>(myBook, new PartitionKey(myBook.PkValue));
Console.WriteLine("Insert of item consumed {0} request units", response.RequestCharge);
// Measure the performance (Request Units) of queries
FeedIterator<Book> queryable = container.GetItemQueryIterator<ToDoActivity>(queryString);
while (queryable.HasMoreResults)
    {
        FeedResponse<Book> queryResponse = await queryable.ExecuteNextAsync<Book>();
        Console.WriteLine("Query batch consumed {0} request units", queryResponse.RequestCharge);
    }

A carga de solicitação retornada nesse cabeçalho é uma fração de sua taxa de transferência provisionada (ou seja, 2.000 RUs/s). Por exemplo, se a consulta anterior retornar mil documentos de 1 KB, o custo da operação será de 1.000. Assim, em um segundo, o servidor mantém apenas duas dessas solicitações, limitando as subsequentes. Para saber mais, consulte Unidades de solicitação e Calculadora de unidades de solicitação.

Lidar com uma limitação da taxa/taxa de solicitação muito grande

Quando um cliente tenta exceder a taxa de transferência reservada para uma conta, não há degradação de desempenho no servidor e não ocorre nenhum uso da capacidade dela além do nível reservado. O servidor encerra preventivamente a solicitação com RequestRateTooLarge (código de status HTTP 429). Ele retorna um cabeçalho x-ms-retry-after-ms que indica a quantidade de tempo, em milissegundos, que o usuário deve aguardar antes de tentar realizar novamente a solicitação.

    HTTP Status 429,
    Status Line: RequestRateTooLarge
    x-ms-retry-after-ms :100

Os SDKs irão capturar implicitamente essa resposta, respeitarão o cabeçalho server-specified retry-after e repetirão a solicitação. A menos que sua conta esteja sendo acessada simultaneamente por vários clientes, a próxima tentativa será bem-sucedida.

Se você tiver mais de um cliente operando de forma cumulativa consistentemente acima da taxa de solicitação, a contagem de repetição padrão definida internamente no momento como 9 pelo cliente poderá não ser suficiente. Nesse caso, o cliente gerará um CosmosException com o código de status 429 para o aplicativo.

Você pode alterar a contagem de repetição padrão definindo o RetryOptions na instância CosmosClientOptions. Por padrão, o CosmosException com o código de status 429 será retornado após uma espera cumulativa de 30 segundos se a solicitação continuar operando acima da taxa de solicitação. Este erro é retornado mesmo quando a contagem de repetições atual é menor que a contagem máxima de repetições, independentemente de o valor atual corresponder ao padrão de 9 ou a um valor definido pelo usuário.

O comportamento de repetição automatizada ajuda a melhorar a resiliência e a usabilidade da maioria dos aplicativos. Porém, pode não ser o melhor comportamento ao fazer avaliações de desempenho, especialmente para medir a latência. A latência observada pelo cliente terá um pico se o teste atingir a limitação do servidor e fizer com que o SDK do cliente repita silenciosamente. Para evitar picos de latência durante os testes de desempenho, meça a carga retornada por cada operação e verifique se as solicitações estão operando abaixo da taxa de solicitação reservada.

Para saber mais, consulte Unidades de solicitação.

Para ter uma taxa de transferência maior, leve documentos menores em conta em seu design

A carga da solicitação (ou seja, o custo de processamento dela) para uma operação especificada está diretamente correlacionada ao tamanho do documento. As operações em documentos grandes custam mais que as de documentos pequenos.

Próximas etapas

Para obter um aplicativo de exemplo que é usado na avaliação do Azure Cosmos DB para cenários de alto desempenho em alguns computadores cliente, consulte Teste de desempenho e escala com o Azure Cosmos DB.

Para saber mais sobre como projetar seu aplicativo para escala e alto desempenho, consulte Particionamento e escala no Azure Cosmos DB.