Diagnosticar e solucionar problemas de solicitações lentas no Azure Cosmos DB com o SDK do .NET
APLICA-SE A: NoSQL
No Azure Cosmos DB, você pode observar solicitações lentas. Atrasos podem acontecer por vários motivos, como limitação de solicitações ou a maneira como seu aplicativo foi projetado. Este artigo explica as diferentes causas raiz desse problema.
Taxa de solicitação grande demais
A limitação de solicitações é o motivo mais comum para solicitações lentas. O Azure Cosmos DB limita as solicitações se elas excederem as unidades de solicitação alocadas para o banco de dados ou o contêiner. O SDK possui lógica interna para repetir essas solicitações. O artigo de solução de problemas taxa de solicitação grande demais explica como verificar se as solicitações estão sendo limitadas. O artigo também aborda como dimensionar sua conta para evitar esses problemas no futuro.
Design do aplicativo
Ao projetar seu aplicativo, siga as melhores práticas do SDK do .NET para ter o melhor desempenho. Se o seu aplicativo não seguir as melhores práticas do SDK, poderão ocorrer solicitações lentas ou com falha.
Considere o seguinte ao desenvolver seu aplicativo:
- O aplicativo deve estar na mesma região que sua conta do Azure Cosmos DB.
- Seu ApplicationRegion ou ApplicationPreferredRegions deve refletir sua preferência regional e apontar para a região em que seu aplicativo está implantado.
- Pode haver um gargalo no adaptador de rede devido ao alto tráfego. Se o aplicativo estiver em execução nas Máquinas Virtuais do Azure, haverá possíveis soluções alternativas:
- Considere o uso de uma máquina virtual com a rede acelerada habilitada.
- Habilite a rede acelerada em uma máquina virtual existente.
- Considere o uso de uma máquina virtual de alta capacidade.
- Prefira o modo de conectividade direta.
- Evite um uso elevado da CPU. Observe o uso máximo da CPU, não o médio, que é o padrão para a maioria dos sistemas de log. Qualquer valor acima de cerca de 40% pode aumentar a latência.
Operações de metadados
Se você precisar verificar se um banco de dados ou um contêiner existe, não faça isso chamando Create...IfNotExistsAsync
ou Read...Async
antes de fazer 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. Essas operações de metadados geram latência extra, não têm nenhum SLA (Contrato de Nível de Serviço) e têm limitações separadas. Elas não são dimensionadas como operações de dados.
Solicitações lentas no modo em massa
O modo em massa é um modo otimizado para taxa de transferência destinado a operações de alto volume de dados, e não um modo otimizado para latência; ele deve saturar a taxa de transferência disponível. Se você estiver enfrentando solicitações lentas ao usar o modo em massa, certifique-se de que:
- Seu aplicativo está compilado na configuração de versão.
- Você não está medindo a latência durante a depuração do aplicativo (sem depurador anexado).
- O volume de operações é alto. Não use o modo em massa para menos de 1000 operações. Sua taxa de transferência provisionada determina quantas operações por segundo você pode processar. Seu objetivo com o modo em massa seria utilizar o máximo possível dela.
- Monitore o contêiner para cenários de limitação. Se o contêiner estiver sendo muito limitado, isso significa que o volume de dados é maior do que a taxa de transferência provisionada. Você precisará escalar verticalmente o contêiner ou reduzir o volume de dados (talvez criando lotes menores de dados de cada vez).
- Você está usando corretamente o padrão
async/await
para processar todas as tarefas simultâneas e não está bloqueando nenhuma operação assíncrona.
Capturar diagnósticos
Todas as respostas no SDK, incluindo CosmosException
, têm uma propriedade Diagnostics
. Ela registra todas as informações relacionadas à solicitação única, incluindo se houve novas tentativas ou quaisquer falhas temporárias.
Os diagnósticos são retornados como uma cadeia de caracteres. A cadeia de caracteres muda a cada versão, à medida que é aprimorada para solucionar melhor os diferentes cenários. Com cada versão do SDK, a cadeia de caracteres terá alterações interruptivas na formatação. Não analise a cadeia de caracteres para evitar alterações interruptivas. O exemplo de código a seguir mostra como ler os logs de diagnóstico usando o SDK do .NET:
try
{
ItemResponse<Book> response = await this.Container.CreateItemAsync<Book>(item: testItem);
if (response.Diagnostics.GetClientElapsedTime() > ConfigurableSlowRequestTimeSpan)
{
// Log the response.Diagnostics.ToString() and add any additional info necessary to correlate to other logs
}
}
catch (CosmosException cosmosException)
{
// Log the full exception including the stack trace with: cosmosException.ToString()
// The Diagnostics can be logged separately if required with: cosmosException.Diagnostics.ToString()
}
// When using Stream APIs
ResponseMessage response = await this.Container.CreateItemStreamAsync(partitionKey, stream);
if (response.Diagnostics.GetClientElapsedTime() > ConfigurableSlowRequestTimeSpan || !response.IsSuccessStatusCode)
{
// Log the diagnostics and add any additional info necessary to correlate to other logs with: response.Diagnostics.ToString()
}
Diagnósticos na versão 3.19 e mais recentes
A estrutura JSON tem alterações interruptivas a cada versão do SDK. Devido a isso, a análise dele não é segura. O JSON representa uma estrutura em árvore da solicitação que passa pelo SDK. As seções a seguir abrangem alguns aspectos importantes a serem examinados.
Histórico da CPU
A alta utilização da CPU é a causa mais comum de solicitações lentas. Para ter uma latência ideal, o uso da CPU deve ser de aproximadamente 40%. Use 10 segundos como o intervalo para monitorar a utilização máxima (não média) da CPU. Picos de uso de CPU são mais comuns com consultas de partição cruzada, em que as solicitações podem fazer várias conexões para uma única consulta.
Os tempos limites incluem diagnósticos, que contêm o seguinte, por exemplo:
"systemHistory": [
{
"dateUtc": "2021-11-17T23:38:28.3115496Z",
"cpu": 16.731,
"memory": 9024120.000,
"threadInfo": {
"isThreadStarving": "False",
....
}
},
{
"dateUtc": "2021-11-17T23:38:38.3115496Z",
"cpu": 16.731,
"memory": 9024120.000,
"threadInfo": {
"isThreadStarving": "False",
....
}
},
...
]
- Se os valores
cpu
estão acima de 70%, é provável que o tempo limite seja causado pelo esgotamento da CPU. Nesse caso, a solução é investigar a origem da alta utilização da CPU e reduzi-la, ou dimensionar a máquina para um tamanho de recurso maior. - Se os nós
threadInfo/isThreadStarving
têm valoresTrue
, a causa é a privação de threads. Nesse caso, a solução é investigar as origens da privação de thread (threads potencialmente bloqueados) ou dimensionar as máquinas para um tamanho de recurso maior. - Se o tempo
dateUtc
entre as medições não for de cerca de 10 segundos, isso também indicará contenção no pool de threads. A CPU é medida como uma tarefa independente que é enfileirada no pool de threads a cada 10 segundos. Se o tempo entre as medidas for maior, isso indicará que as tarefas assíncronas não poderão ser processadas em tempo hábil. O cenário mais comum é quando o código do seu aplicativo está bloqueando chamadas por código assíncrono.
Solução
O aplicativo cliente que usa o SDK deve ser expandido ou escalado verticalmente.
HttpResponseStats
HttpResponseStats
são solicitações que vão para o gateway. Mesmo no modo direto, o SDK obtém todas as informações de metadados do gateway.
Se a solicitação estiver lenta, primeiro verifique se nenhuma das sugestões anteriores produz os resultados desejados. Se o problema persistir, observe que padrões diferentes apontam para problemas diferentes. A tabela a seguir fornece mais detalhes.
Número de solicitações | Cenário | Descrição |
---|---|---|
Único para todos | Tempo limite da solicitação ou HttpRequestExceptions |
Aponta para Esgotamento de porta SNAT ou falta de recursos no computador para processar a solicitação a tempo. |
Porcentagem única ou pequena (o SLA não é violado) | Tudo | Uma porcentagem única ou pequena de solicitações lentas pode ser causada por vários problemas temporários diferentes, sendo, portanto, algo esperado. |
Tudo | Tudo | Aponta para um problema de infraestrutura ou de rede. |
SLA violado | Nenhuma alteração no aplicativo e SLA descartado. | Aponta para um problema com o serviço do Azure Cosmos DB. |
"HttpResponseStats": [
{
"StartTimeUTC": "2021-06-15T13:53:09.7961124Z",
"EndTimeUTC": "2021-06-15T13:53:09.7961127Z",
"RequestUri": "https://127.0.0.1:8081/dbs/347a8e44-a550-493e-88ee-29a19c070ecc/colls/4f72e752-fa91-455a-82c1-bf253a5a3c4e",
"ResourceType": "Collection",
"HttpMethod": "GET",
"ActivityId": "e16e98ec-f2e3-430c-b9e9-7d99e58a4f72",
"StatusCode": "OK"
}
]
StoreResult
O StoreResult
representa apenas uma solicitação ao Azure Cosmos DB usando o modo direto com o protocolo TCP.
Se o problema persistir, observe que padrões diferentes apontam para problemas diferentes. A tabela a seguir fornece mais detalhes.
Número de solicitações | Cenário | Descrição |
---|---|---|
Único para todos | StoreResult contém TransportException |
Aponta para Esgotamento de porta SNAT ou falta de recursos no computador para processar a solicitação a tempo. |
Porcentagem única ou pequena (o SLA não é violado) | Tudo | Uma porcentagem única ou pequena de solicitações lentas pode ser causada por vários problemas temporários diferentes, sendo, portanto, algo esperado. |
Tudo | Tudo | Um problema com a infraestrutura ou a rede. |
SLA violado | As solicitações contêm vários códigos de erro de falha, como 410 |
Aponta para um problema com o serviço do Azure Cosmos DB ou com o computador cliente. |
SLA violado | StorePhysicalAddress são os mesmos, sem código de status de falha. |
Provavelmente um problema com Azure Cosmos DB. |
SLA violado | StorePhysicalAddress tem a mesma ID de partição, mas diferentes IDs de replicação, sem código de status de falha. |
Provavelmente um problema com Azure Cosmos DB. |
SLA violado | StorePhysicalAddress é aleatório, sem código de status de falha. |
Aponta para um problema com o computador. |
Para vários resultados de armazenamento para apenas uma solicitação, esteja ciente do seguinte:
- A consistência forte e a consistência de desatualização limitada sempre têm pelo menos dois resultados de armazenamento.
- Confira o código de status de cada
StoreResult
. O SDK tenta lidar novamente com várias falhas temporárias diferentes de maneira automática. O SDK é constantemente aprimorado para abranger mais cenários.
RequestTimeline
Mostra o tempo das diferentes etapas de envio e recebimento de uma solicitação na camada de transporte.
ChannelAcquisitionStarted
: o tempo para obter ou criar uma conexão. As conexões podem ser criadas por vários motivos, como: a conexão anterior foi fechada devido à inatividade usando CosmosClientOptions.IdleTcpConnectionTimeout, o volume de solicitações simultâneas excede o CosmosClientOptions.MaxRequestsPerTcpConnection, a conexão foi fechada devido a um erro de rede ou o aplicativo não está seguindo o padrão Singleton, e novas instâncias são criadas constantemente. Depois que uma conexão é estabelecida, ela é reusada nas solicitações subsequentes, portanto, isso não deve afetar a latência P99, a menos que os problemas mencionados anteriormente estejam acontecendo.Pipelined
: O tempo gasto escrevendo a solicitação no soquete TCP. A solicitação só pode ser escrita em um soquete TCP, uma de cada vez; um valor grande indica um gargalo no soquete TCP, que é comumente associado a threads bloqueados pelo código do aplicativo ou por tamanho de solicitação grande.Transit time
: O tempo gasto na rede após a solicitação ser gravada no soquete TCP. Compare esse número comBELatencyInMs
. SeBELatencyInMs
for pequeno, o tempo foi gasto na rede e não no serviço do Azure Cosmos DB. Se a solicitação falhou com tempo limite, indica quanto tempo o cliente esperou sem resposta e a origem é a latência da rede.Received
: O tempo entre a resposta ser recebida e processada pelo SDK. Um valor grande normalmente é causado por falta de thread ou threads bloqueados.
ServiceEndpointStatistics
Informações sobre um servidor de back-end específico. O SDK pode abrir várias conexões com um servidor de back-end dependendo do número de solicitações pendentes e de MaxConcurrentRequestsPerConnection.
inflightRequests
O número de solicitações pendentes para um servidor de back-end (talvez de partições diferentes). Um número alto pode levar a mais tráfego e latências mais altas.openConnections
é o número total de conexões abertas para um servidor de back-end. Isso pode ser útil para mostrar a exaustão da porta SNAT quando esse número é muito alto.
ConnectionStatistics
Informações sobre a conexão específica (nova ou antiga) à qual a solicitação foi atribuída.
waitforConnectionInit
: a solicitação atual estava aguardando a conclusão da inicialização da nova conexão. Isso levará a latências mais altas.callsPendingReceive
: número de chamadas com recebimento pendente antes dessa chamada ter sido enviada. Um número alto pode mostrar que havia muitas chamadas antes dessa, o que pode levar a latências mais altas. Se o número for alto, isso apontará para um problema de bloqueio de linha, possivelmente causado por outra solicitação, como uma operação de consulta ou de feed que está demorando muito para ser processada. Tente reduzir CosmosClientOptions.MaxRequestsPerTcpConnection para aumentar o número de canais.LastSentTime
: hora da última solicitação enviada para este servidor. Em conjunto com LastReceivedTime, pode ser usado para ver problemas de conectividade ou ponto de extremidade. Por exemplo, se houver muitos tempos limite de recebimento, a hora de envio será muito posterior à hora de recebimento.lastReceive
: hora da última solicitação que foi recebida desse servidorlastSendAttempt
: hora da última tentativa de envio
Tamanhos de solicitação e resposta
requestSizeInBytes
: o tamanho total da solicitação enviada ao Azure Cosmos DBresponseMetadataSizeInBytes
: o tamanho dos cabeçalhos retornados do Azure Cosmos DBresponseBodySizeInBytes
: o tamanho do conteúdo retornado do Azure Cosmos DB
"StoreResult": {
"ActivityId": "bab6ade1-b8de-407f-b89d-fa2138a91284",
"StatusCode": "Ok",
"SubStatusCode": "Unknown",
"LSN": 453362,
"PartitionKeyRangeId": "1",
"GlobalCommittedLSN": 0,
"ItemLSN": 453358,
"UsingLocalLSN": true,
"QuorumAckedLSN": -1,
"SessionToken": "-1#453362",
"CurrentWriteQuorum": -1,
"CurrentReplicaSetSize": -1,
"NumberOfReadRegions": 0,
"IsValid": true,
"StorePhysicalAddress": "rntbd://127.0.0.1:10253/apps/DocDbApp/services/DocDbServer92/partitions/a4cb49a8-38c8-11e6-8106-8cdcd42c33be/replicas/1s/",
"RequestCharge": 1,
"RetryAfterInMs": null,
"BELatencyInMs": "0.304",
"transportRequestTimeline": {
"requestTimeline": [
{
"event": "Created",
"startTimeUtc": "2022-05-25T12:03:36.3081190Z",
"durationInMs": 0.0024
},
{
"event": "ChannelAcquisitionStarted",
"startTimeUtc": "2022-05-25T12:03:36.3081214Z",
"durationInMs": 0.0132
},
{
"event": "Pipelined",
"startTimeUtc": "2022-05-25T12:03:36.3081346Z",
"durationInMs": 0.0865
},
{
"event": "Transit Time",
"startTimeUtc": "2022-05-25T12:03:36.3082211Z",
"durationInMs": 1.3324
},
{
"event": "Received",
"startTimeUtc": "2022-05-25T12:03:36.3095535Z",
"durationInMs": 12.6128
},
{
"event": "Completed",
"startTimeUtc": "2022-05-25T12:03:36.8621663Z",
"durationInMs": 0
}
],
"serviceEndpointStats": {
"inflightRequests": 1,
"openConnections": 1
},
"connectionStats": {
"waitforConnectionInit": "False",
"callsPendingReceive": 0,
"lastSendAttempt": "2022-05-25T12:03:34.0222760Z",
"lastSend": "2022-05-25T12:03:34.0223280Z",
"lastReceive": "2022-05-25T12:03:34.0257728Z"
},
"requestSizeInBytes": 447,
"responseMetadataSizeInBytes": 438,
"responseBodySizeInBytes": 604
},
"TransportException": null
}
A taxa de falha viola o SLA do Azure Cosmos DB
Entre em contato com o Suporte do Azure.
Próximas etapas
- Diagnosticar e solucionar problemas ao usar o SDK do .NET no Azure Cosmos DB.
- Saiba mais sobre as diretrizes de desempenho do SDK do .NET.
- Saiba mais sobre as melhores práticas do SDK do .NET