Dicas de desempenho para o Azure Cosmos DB e .NET SDK v2

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 latência e produtividade. 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, veja como provisionar a taxa de transferência do contêiner ou como provisionar a taxa de transferência do banco de dados. No entanto, como o Azure Cosmos DB é acessado por meio de chamadas de rede, há otimizações do lado do cliente que você pode fazer para obter o melhor desempenho quando usar o SDK de SQL .NET.

Portanto, se estiver tentando aprimorar o desempenho do banco de dados, considere estas opções:

Atualizar para o SDK do .NET V3

O SDK do .NET v3 foi lançado. Se você usa o SDK do .NET v3, confira o guia de desempenho do .NET v3 para obter as seguintes informações:

  • Uso padrão do modo TCP direto
  • Suporte à API de Stream
  • Suporte ao serializador personalizado para permitir o uso de System.Text.JSON
  • Suporte integrado para lote e massa

Recomendações de hospedagem

Ativar a GC (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

Se você estiver testando em altos níveis de taxa de transferência (mais de 50.000 RUs/s), o aplicativo cliente poderá se tornar o gargalo devido à limitação do computador quanto à 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 Coleção 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 últimas versões do SDK (posteriores à 2.16.2) o removem 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.Documents.DefaultTrace,Microsoft.Azure.DocumentDB.Core");
    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 V2 é o gateway. Você configura o modo de conexão durante a construção da instância de DocumentClient usando o parâmetro ConnectionPolicy. Se usar o modo direto, você também precisará definir o Protocol usando o parâmetro ConnectionPolicy. Para saber mais sobre as diferentes opções de conectividade, consulte o artigo Modos de conectividade.

Uri serviceEndpoint = new Uri("https://contoso.documents.net");
string authKey = "your authKey from the Azure portal";
DocumentClient client = new DocumentClient(serviceEndpoint, authKey,
new ConnectionPolicy
{
   ConnectionMode = ConnectionMode.Direct, // ConnectionMode.Gateway is the default
   ConnectionProtocol = Protocol.Tcp
});

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.

Ao executar no protocolo TCP, o cliente otimiza a latência usando conexões de longa duração, diferente do protocolo HTTPS, que encerra as conexões após 2 minutos de inatividade.

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

  • Configure a propriedade ConnectionPolicy.PortReuseMode no PrivatePortPool (em vigor na versão do framework >= 4.6.1 e na versão do .NET Core >= 2.0). Essa propriedade 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 ConnectionPolicy.IdleConnectionTimeout, que deve ser maior ou igual a 10 minutos. Os valores recomendados estão entre 20 minutos e 24 horas.

Chamar OpenAsync para evitar a latência de inicialização na primeira solicitação

Por padrão, a primeira solicitação tem uma latência maior porque precisa buscar a tabela de roteamento de endereço. Quando usar o SDK V2, chame OpenAsync() uma vez durante a inicialização para evitar essa latência de inicialização na primeira solicitação. A chamada é semelhante a: await client.OpenAsync();

Observação

OpenAsync gera solicitações para obter a tabela de roteamento de endereço para todos os contêineres na conta. Para contas que têm muitos contêineres, mas cujo aplicativo acessa um subconjunto deles, OpenAsync geraria uma quantidade desnecessária de tráfego, o que tornaria a inicialização lenta. Portanto, o uso de OpenAsync pode não ser útil nesse cenário porque torna a inicialização do aplicativo mais lenta.

Para fins de desempenho, coloque os clientes na mesma região do Azure

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: chamadas para o Azure Cosmos DB na mesma região são concluídas em 1 a 2 ms, 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. Você pode alcançar a menor latência possível garantindo que o aplicativo autor da 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.

A política de conexão do Azure Cosmos DB

Aumentar o número de threads/tarefas

Como as chamadas ao Azure Cosmos DB são feitas pela rede, pode ser necessário variar o grau de paralelismo das solicitações, de maneira 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 habilitar a rede acelerada nas máquinas virtuais cliente. Confira Criar uma máquina virtual do Windows com a rede acelerada ou Criar uma máquina virtual do Linux com a rede acelerada.

Uso do SDK

Instalar o SDK mais recente

Os SDKs do Azure Cosmos DB estão constantemente sendo aprimorados para fornecer o melhor desempenho. Consulte as páginas do SDK do Azure Cosmos DB para determinar o SDK mais recente e examinar as melhorias.

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

Cada instância de DocumentClient é thread-safe e realiza um gerenciamento de conexão eficiente e o cache de endereço 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.

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.
  • Use ToList() em DocumentClient.CreateDocumentQuery(...), que usa chamadas de bloqueio para drenar a consulta de maneira síncrona. Use AsDocumentQuery() 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.

Aumentar System.Net MaxConnections por host usando o modo de gateway

As solicitações do Azure Cosmos DB são feitas por 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 1.000) para que a biblioteca de clientes 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 alterar o valor, você pode definir Documents.Client.ConnectionPolicy.MaxConnectionLimit como um valor mais alto.

Implementar a retirada em intervalos de RetryAfter

Durante o teste de desempenho, você deve aumentar a carga até que uma pequena taxa de solicitações seja limitada. Se as solicitações forem limitadas, o aplicativo cliente deverá retirar a limitação do intervalo de nova tentativa especificado pelo servidor. Respeitar a retirada garante que você perca o mínimo de tempo esperando entre as tentativas.

O suporte à política de repetição está incluído nestes SDKs:

Para obter mais informações, consulte RetryAfter.

Na versão 1.19 e posteriores do SDK do .NET, há um mecanismo para registrar informações de diagnóstico adicionais e solucionar problemas de latência, conforme mostrado no exemplo a seguir. Você pode registrar a cadeia de caracteres de diagnóstico para solicitações que tenham uma latência de leitura mais alta. A cadeia de caracteres de diagnóstico capturada ajuda você a entender quantas vezes recebeu erros 429 para uma determinada solicitação.

ResourceResponse<Document> readDocument = await this.readClient.ReadDocumentAsync(oldDocuments[i].SelfLink);
readDocument.RequestDiagnosticsString 

Armazenar em cache os URIs do documento para uma menor latência de leitura

Armazene em cache os URIs do documento sempre que possível para ter o melhor desempenho de leitura. Você precisa definir a lógica para armazenar em cache a ID do recurso quando cria um recurso. Pesquisas baseadas nas IDs do recurso são mais rápidas que pesquisas baseadas em nome, de modo que armazenar esses valores em cache aprimora o desempenho.

Aumentar o número de threads/tarefas

Consulte Aumentar o número de threads/tarefas na seção sobre 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 caminhos de indexação (IndexingPolicy.IncludedPaths e IndexingPolicy.ExcludedPaths). Os caminhos de indexação podem aprimorar o desempenho de gravação e reduzir o armazenamento de índice para cenários nos quais os padrões de consultas são previamente conhecidos. Isso ocorre porque os custos de indexação se correlacionam diretamente com o número de caminhos exclusivos indexados. Por exemplo, este código mostra como excluir da indexação uma seção inteira dos documentos (uma subárvore) usando o curinga "*":

var collection = new DocumentCollection { Id = "excludedPathCollection" };
collection.IndexingPolicy.IncludedPaths.Add(new IncludedPath { Path = "/*" });
collection.IndexingPolicy.ExcludedPaths.Add(new ExcludedPath { Path = "/nonIndexedContent/*");
collection = await client.CreateDocumentCollectionAsync(UriFactory.CreateDatabaseUri("db"), collection);

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

Produtividade

Medir e ajustar para um uso menor de unidades de solicitação/segundo

O Azure Cosmos DB oferece um amplo conjunto de operações de banco de dados. Elas incluem consultas relacionais e hierárquicas com UDFs, procedimentos armazenados e gatilhos, todos operando nos documentos de uma coleção de banco de dados. O custo associado a cada uma dessas operações varia dependendo da CPU, da E/S e da memória necessárias para concluir a operação. Em vez de pensar em recursos de hardware e gerenciá-los, você pode pensar em uma RU (Unidade de Solicitação) como uma medida única para os recursos necessários para realizar várias operações de bancos de dados e atender a uma solicitação do aplicativo.

A taxa de transferência é provisionada com base no número de Unidades de solicitação definidas para cada contêiner. O consumo da Unidade de Solicitação é avaliado em termos de taxa 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 UDFs 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
ResourceResponse<Document> response = await client.CreateDocumentAsync(collectionSelfLink, myDocument);
Console.WriteLine("Insert of document consumed {0} request units", response.RequestCharge);
// Measure the performance (Request Units) of queries
IDocumentQuery<dynamic> queryable = client.CreateDocumentQuery(collectionSelfLink, queryString).AsDocumentQuery();
while (queryable.HasMoreResults)
    {
        FeedResponse<dynamic> queryResponse = await queryable.ExecuteNextAsync<dynamic>();
        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/segundo). 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 antes de limitar as posteriores. 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 encerrará preventivamente a solicitação com RequestRateTooLarge (código de status HTTP 429). Ele retornará 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 maneira 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 vai gerar uma DocumentClientException 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 ConnectionPolicy. Por padrão, a DocumentClientException com o código de status 429 será retornada após uma espera cumulativa de 30 segundos se a solicitação continuar a operar acima da taxa de solicitação. Esse 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 o custo retornado 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 determinada operação 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.