Sugestões de desempenho para o SDK Java v4 do Azure Cosmos DB
APLICA-SE A: NoSQL
Importante
As dicas de desempenho neste artigo são apenas para o SDK Java v4 do Azure Cosmos DB. Consulte as Notas de versão do SDK Java do Azure Cosmos DB v4, o repositório Maven e o guia de solução de problemas do SDK Java do Azure Cosmos DB v4 para obter mais informações. Se você estiver usando uma versão mais antiga do que a v4, consulte o guia Migrar para o SDK Java do Azure Cosmos DB v4 para obter ajuda na atualização para a v4.
O Azure Cosmos DB é um banco de dados distribuído rápido e flexível que pode ser dimensionado perfeitamente com latência e taxa de transferência garantidas. Você não precisa fazer grandes alterações na arquitetura ou escrever código complexo para dimensionar seu banco de dados com o Azure Cosmos DB. Escalar para cima e para baixo é tão fácil quanto fazer uma única chamada de API ou chamada de método SDK. 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 desempenho máximo ao usar o SDK Java v4 do Azure Cosmos DB.
Portanto, se você estiver perguntando "Como posso melhorar o desempenho do meu banco de dados?", considere as seguintes opções:
Rede
Coloque clientes na mesma região do Azure para desempenho
Quando possível, coloque todos os aplicativos que chamam o Azure Cosmos DB na mesma região do banco de dados do Azure Cosmos DB. Para uma comparação aproximada, as chamadas para o Azure Cosmos DB dentro da mesma região são concluídas dentro de 1 a 2 ms, mas a latência entre a costa oeste e leste dos EUA é >de 50 ms. Essa latência provavelmente pode variar de solicitação para solicitação, dependendo da rota tomada pela solicitação à medida que ela passa do cliente para o limite do datacenter do Azure. A menor latência possível é alcançada garantindo que o aplicativo de chamada esteja localizado 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.
Um aplicativo que interage com uma conta do Azure Cosmos DB de várias regiões precisa configurar locais preferenciais para garantir que as solicitações sejam enviadas para uma região colocada.
Habilite a rede acelerada para reduzir a latência e os desvios da CPU
É altamente recomendável seguir as instruções para habilitar a Rede Acelerada em sua VM do Azure do Windows (selecione para obter instruções) ou Linux (selecione para obter instruções) para maximizar o desempenho reduzindo a latência e os desvios da CPU.
Sem rede acelerada, a E/S que transita entre sua VM do Azure e outros recursos do Azure pode ser roteada por meio de um host e comutador virtual situado entre a VM e sua placa de rede. Ter o host e o comutador virtual embutidos no caminho de dados não só aumenta a latência e os desvios no canal de comunicação, como 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 política de rede são tratados no hardware da NIC, ignorando o host e o comutador virtual. Geralmente, você pode esperar menor latência e maior taxa de transferência, bem como latência mais consistente e menor utilização da CPU quando você habilita a rede acelerada.
Limitações: a rede acelerada deve ser suportada no sistema operacional da VM e só pode ser habilitada quando a VM é interrompida e deslocalizada. A VM não pode ser implantada com o Azure Resource Manager. O Serviço de Aplicativo não tem rede acelerada habilitada.
Para obter mais informações, consulte as instruções do Windows e Linux .
Elevada disponibilidade
Para obter orientações gerais sobre como configurar a alta disponibilidade no Azure Cosmos DB, consulte Alta disponibilidade no Azure Cosmos DB.
Além de uma boa configuração fundamental na plataforma de banco de dados, existem técnicas específicas que podem ser implementadas no próprio Java SDK, o que pode ajudar em cenários de paralisação. Duas estratégias notáveis são a estratégia de disponibilidade baseada em limites e o disjuntor de nível de partição.
Essas técnicas fornecem mecanismos avançados para lidar com desafios específicos de latência e disponibilidade, indo além dos recursos de repetição entre regiões que são incorporados ao SDK por padrão. Ao gerenciar proativamente possíveis problemas nos níveis de solicitação e partição, essas estratégias podem melhorar significativamente a resiliência e o desempenho de seu aplicativo, especialmente em condições de alta carga ou degradadas.
Estratégia de disponibilidade baseada em limites
A estratégia de disponibilidade baseada em limites pode melhorar a latência e a disponibilidade finais enviando solicitações de leitura paralelas para regiões secundárias e aceitando a resposta mais rápida. Essa abordagem pode reduzir drasticamente o impacto de interrupções regionais ou condições de alta latência no desempenho do aplicativo. Além disso, o gerenciamento proativo de conexões pode ser empregado para melhorar ainda mais o desempenho, aquecendo conexões e caches na região de leitura atual e nas regiões remotas preferidas.
Exemplo de configuração:
// Proactive Connection Management
CosmosContainerIdentity containerIdentity = new CosmosContainerIdentity("sample_db_id", "sample_container_id");
int proactiveConnectionRegionsCount = 2;
Duration aggressiveWarmupDuration = Duration.ofSeconds(1);
CosmosAsyncClient clientWithOpenConnections = new CosmosClientBuilder()
.endpoint("<account URL goes here")
.key("<account key goes here>")
.endpointDiscoveryEnabled(true)
.preferredRegions(Arrays.asList("sample_region_1", "sample_region_2"))
.openConnectionsAndInitCaches(new CosmosContainerProactiveInitConfigBuilder(Arrays.asList(containerIdentity))
.setProactiveConnectionRegionsCount(proactiveConnectionRegionsCount)
//setting aggressive warmup duration helps in cases where there is a high no. of partitions
.setAggressiveWarmupDuration(aggressiveWarmupDuration)
.build())
.directMode()
.buildAsyncClient();
CosmosAsyncContainer container = clientWithOpenConnections.getDatabase("sample_db_id").getContainer("sample_container_id");
int threshold = 500;
int thresholdStep = 100;
CosmosEndToEndOperationLatencyPolicyConfig config = new CosmosEndToEndOperationLatencyPolicyConfigBuilder(Duration.ofSeconds(3))
.availabilityStrategy(new ThresholdBasedAvailabilityStrategy(Duration.ofMillis(threshold), Duration.ofMillis(thresholdStep)))
.build();
CosmosItemRequestOptions options = new CosmosItemRequestOptions();
options.setCosmosEndToEndOperationLatencyPolicyConfig(config);
container.readItem("id", new PartitionKey("pk"), options, JsonNode.class).block();
// Write operations can benefit from threshold-based availability strategy if opted into non-idempotent write retry policy
// and the account is configured for multi-region writes.
options.setNonIdempotentWriteRetryPolicy(true, true);
container.createItem("id", new PartitionKey("pk"), options, JsonNode.class).block();
Como funciona:
Solicitação inicial: No momento T1, uma solicitação de leitura é feita para a região primária (por exemplo, Leste dos EUA). O SDK aguarda uma resposta por até 500 milissegundos (o
threshold
valor).Segunda solicitação: se não houver resposta da região primária dentro de 500 milissegundos, uma solicitação paralela será enviada para a próxima região preferida (por exemplo, Leste dos EUA 2).
Terceira solicitação: Se nem a região primária nem a secundária responderem dentro de 600 milissegundos (500ms + 100ms, o
thresholdStep
valor), o SDK enviará outra solicitação paralela para a terceira região preferida (por exemplo, Oeste dos EUA).Ganha a resposta mais rápida: qualquer região que responda primeiro, essa resposta é aceita e as outras solicitações paralelas são ignoradas.
O gerenciamento proativo de conexões ajuda aquecendo conexões e caches para contêineres nas regiões preferidas, reduzindo a latência de inicialização a frio para cenários de failover ou gravações em configurações de várias regiões.
Essa estratégia pode melhorar significativamente a latência em cenários em que uma determinada região está lenta ou temporariamente indisponível, mas pode incorrer em mais custos em termos de unidades de solicitação quando solicitações paralelas entre regiões são necessárias.
Nota
Se a primeira região preferida retornar um código de status de erro não transitório (por exemplo, documento não encontrado, erro de autorização, conflito, etc.), a operação em si falhará rapidamente, pois a estratégia de disponibilidade não teria nenhum benefício nesse cenário.
Disjuntor de nível de partição
O disjuntor de nível de partição melhora a latência da cauda e a disponibilidade de gravação rastreando e curto-circuitando solicitações para partições físicas não íntegras. Ele melhora o desempenho, evitando partições problemáticas conhecidas e redirecionando solicitações para regiões mais saudáveis.
Exemplo de configuração:
Para ativar o disjuntor de nível de partição:
System.setProperty(
"COSMOS.PARTITION_LEVEL_CIRCUIT_BREAKER_CONFIG",
"{\"isPartitionLevelCircuitBreakerEnabled\": true, "
+ "\"circuitBreakerType\": \"CONSECUTIVE_EXCEPTION_COUNT_BASED\","
+ "\"consecutiveExceptionCountToleratedForReads\": 10,"
+ "\"consecutiveExceptionCountToleratedForWrites\": 5,"
+ "}");
Para definir a frequência do processo em segundo plano para verificar regiões indisponíveis:
System.setProperty("COSMOS.STALE_PARTITION_UNAVAILABILITY_REFRESH_INTERVAL_IN_SECONDS", "60");
Para definir a duração durante a qual uma partição pode permanecer indisponível:
System.setProperty("COSMOS.ALLOWED_PARTITION_UNAVAILABILITY_DURATION_IN_SECONDS", "30");
Como funciona:
Rastreando falhas: O SDK rastreia falhas de terminal (por exemplo, 503s, 500s, tempos limites) para partições individuais em regiões específicas.
Marcação como Indisponível: Se uma partição em uma região exceder um limite configurado de falhas, ela será marcada como "Indisponível". Os pedidos subsequentes a esta partição são curto-circuitados e redirecionados para outras regiões mais saudáveis.
Recuperação automatizada: um thread em segundo plano verifica periodicamente partições indisponíveis. Após uma certa duração, essas partições são marcadas provisoriamente como "HealthyTentative" e submetidas a solicitações de teste para validar a recuperação.
Promoção/Rebaixamento de Saúde: Com base no sucesso ou fracasso dessas solicitações de teste, o status da partição é promovido de volta para "Saudável" ou rebaixado mais uma vez para "Indisponível".
Esse mecanismo ajuda a monitorar continuamente a integridade da partição e garante que as solicitações sejam atendidas com latência mínima e disponibilidade máxima, sem serem bloqueadas por partições problemáticas.
Nota
O disjuntor só se aplica a contas de gravação de várias regiões, pois quando uma partição é marcada como Unavailable
, tanto as leituras quanto as gravações são movidas para a próxima região preferida. Isso é para evitar que leituras e gravações de diferentes regiões sejam atendidas a partir da mesma instância do cliente, pois isso seria um antipadrão.
Importante
Você deve estar usando a versão 4.63.0 do Java SDK ou superior para ativar o Disjuntor de Nível de Partição.
Comparando otimizações de disponibilidade
Estratégia de disponibilidade baseada em limites:
- Benefício: Reduz a latência final enviando solicitações de leitura paralelas para regiões secundárias e melhora a disponibilidade antecipando solicitações que resultarão em tempos limite de rede.
- Compensação: Incorre em custos adicionais de RU (Unidades de Solicitação) em comparação com o disjuntor, devido a solicitações paralelas adicionais entre regiões (embora apenas durante períodos em que os limites são violados).
- Caso de uso: ideal para cargas de trabalho de leitura pesada onde a redução da latência é crítica e algum custo adicional (tanto em termos de carga de RU quanto de pressão da CPU do cliente) é aceitável. As operações de gravação também podem se beneficiar, se optarem pela política de repetição de gravação não idempotente e a conta tiver gravações em várias regiões.
Disjuntor de nível de partição:
- Benefício: Melhora a disponibilidade e a latência, evitando partições não íntegras e garantindo que as solicitações sejam encaminhadas para regiões mais saudáveis.
- Compensação: Não incorre em custos adicionais de RU, mas ainda pode permitir alguma perda de disponibilidade inicial para solicitações que resultarão em tempos limite de rede.
- Caso de uso: Ideal para cargas de trabalho pesadas ou mistas em que o desempenho consistente é essencial, especialmente ao lidar com partições que podem se tornar intermitentemente não íntegras.
Ambas as estratégias podem ser usadas juntas para melhorar a disponibilidade de leitura e gravação e reduzir a latência final. O Disjuntor de Nível de Partição pode lidar com uma variedade de cenários de falha transitória, incluindo aqueles que podem resultar em réplicas de desempenho lento, sem a necessidade de executar solicitações paralelas. Além disso, a adição de Estratégia de Disponibilidade baseada em Limite minimizará ainda mais a latência final e eliminará a perda de disponibilidade, se o custo adicional de RU for aceitável.
Ao implementar essas estratégias, os desenvolvedores podem garantir que seus aplicativos permaneçam resilientes, mantenham o alto desempenho e forneçam uma melhor experiência ao usuário, mesmo durante interrupções regionais ou condições de alta latência.
Consistência da sessão com escopo de região
Descrição geral
Para obter mais informações sobre configurações de consistência em geral, consulte Níveis de consistência no Azure Cosmos DB. O Java SDK fornece uma otimização para consistência de sessão para contas de gravação de várias regiões, permitindo que ela tenha escopo de região. Isso melhora o desempenho ao reduzir a latência de replicação entre regiões minimizando as tentativas do lado do cliente. Isso é conseguido gerenciando tokens de sessão no nível da região em vez de globalmente. Se a consistência em seu aplicativo puder ser definida para um número menor de regiões, implementando a consistência de sessão com escopo de região, você poderá obter melhor desempenho e confiabilidade para operações de leitura e gravação em contas de várias gravações, minimizando atrasos e tentativas de replicação entre regiões.
Benefícios
- Latência reduzida: ao localizar a validação do token de sessão para o nível da região, as chances de tentativas interregionais dispendiosas são reduzidas.
- Desempenho aprimorado: minimiza o impacto do failover regional e do atraso de replicação, oferecendo maior consistência de leitura/gravação e menor utilização da CPU.
- Utilização otimizada de recursos: reduz a sobrecarga de CPU e rede em aplicativos cliente, limitando a necessidade de novas tentativas e chamadas inter-regionais, otimizando assim o uso de recursos.
- Alta disponibilidade: ao manter tokens de sessão com escopo regional, os aplicativos podem continuar a operar sem problemas, mesmo que determinadas regiões enfrentem latência mais alta ou falhas temporárias.
- Garantias de consistência: Garante que as garantias de consistência da sessão (ler sua gravação, leitura monotônica) sejam atendidas de forma mais confiável sem repetições desnecessárias.
- Eficiência de custos: reduz o número de chamadas inter-regionais, reduzindo potencialmente os custos associados às transferências de dados entre regiões.
- Escalabilidade: Permite que os aplicativos sejam dimensionados de forma mais eficiente, reduzindo a contenção e a sobrecarga associadas à manutenção de um token de sessão global, especialmente em configurações de várias regiões.
Compensações
- Maior uso de memória: O filtro bloom e o armazenamento de token de sessão específico da região exigem memória adicional, o que pode ser uma consideração para aplicativos com recursos limitados.
- Complexidade da configuração: ajustar a contagem de inserção esperada e a taxa de falsos positivos para o filtro bloom adiciona uma camada de complexidade ao processo de configuração.
- Potencial para falsos positivos: Embora o filtro bloom minimize as tentativas entre regionais, ainda há uma pequena chance de falsos positivos afetarem a validação do token de sessão, embora a taxa possa ser controlada. Um falso positivo significa que o token de sessão global é resolvido, aumentando assim a chance de novas tentativas inter-regionais se a região local não tiver alcançado essa sessão global. As garantias da sessão são cumpridas mesmo na presença de falsos positivos.
- Aplicabilidade: Este recurso é mais benéfico para aplicativos com uma alta cardinalidade de partições lógicas e reinicializações regulares. Aplicativos com menos partições lógicas ou reinicializações pouco frequentes podem não ver benefícios significativos.
Como funciona
Definir o token de sessão
- Conclusão da solicitação: depois que uma solicitação é concluída, o SDK captura o token de sessão e o associa à região e à chave de partição.
- Armazenamento em nível de região: os tokens de sessão são armazenados em um aninhado
ConcurrentHashMap
que mantém mapeamentos entre intervalos de chaves de partição e progresso em nível de região. - Bloom Filter: Um filtro bloom controla quais regiões foram acessadas por cada partição lógica, ajudando a localizar a validação do token de sessão.
Resolver o token de sessão
- Inicialização da solicitação: antes de enviar uma solicitação, o SDK tenta resolver o token de sessão para a região apropriada.
- Verificação de token: o token é verificado em relação aos dados específicos da região para garantir que a solicitação seja roteada para a réplica mais atualizada.
- Lógica de repetição: Se o token de sessão não for validado dentro da região atual, o SDK tentará novamente com outras regiões, mas dado o armazenamento localizado, isso será menos frequente.
Usar o SDK
Veja como inicializar o CosmosClient com consistência de sessão com escopo de região:
CosmosClient client = new CosmosClientBuilder()
.endpoint("<your-endpoint>")
.key("<your-key>")
.consistencyLevel(ConsistencyLevel.SESSION)
.buildClient();
// Your operations here
Habilitar a consistência da sessão com escopo regional
Para habilitar a captura de sessão com escopo de região em seu aplicativo, defina a seguinte propriedade do sistema:
System.setProperty("COSMOS.SESSION_CAPTURING_TYPE", "REGION_SCOPED");
Configurar filtro bloom
Ajuste o desempenho configurando as inserções esperadas e a taxa de falsos positivos para o filtro bloom:
System.setProperty("COSMOS.PK_BASED_BLOOM_FILTER_EXPECTED_INSERTION_COUNT", "5000000"); // adjust as needed
System.setProperty("COSMOS.PK_BASED_BLOOM_FILTER_EXPECTED_FFP_RATE", "0.001"); // adjust as needed
System.setProperty("COSMOS.SESSION_CAPTURING_TYPE", "REGION_SCOPED");
System.setProperty("COSMOS.PK_BASED_BLOOM_FILTER_EXPECTED_INSERTION_COUNT", "1000000");
System.setProperty("COSMOS.PK_BASED_BLOOM_FILTER_EXPECTED_FFP_RATE", "0.01");
Implicações na memória
Abaixo está o tamanho retido (tamanho do objeto e o que quer que ele dependa) do contêiner de sessão interno (gerenciado pelo SDK) com inserções esperadas variáveis no filtro bloom.
Inserções esperadas | False Positive Rate | Tamanho retido |
---|---|---|
10, 000 | 0.001 | 21 KB |
100, 000 | 0.001 | 183 KB |
1 milhão | 0.001 | 1,8 MB |
10 milhões | 0.001 | 17,9 MB |
100 milhões | 0.001 | 179 MB |
1 bilhão | 0.001 | 1,8 GB |
Importante
Você deve estar usando a versão 4.60.0 do Java SDK ou superior para ativar a consistência da sessão com escopo de região.
Ajustando a configuração de conexão direta e de gateway
Para otimizar as configurações de conexão no modo direto e gateway, consulte como ajustar as configurações de conexão para o Java SDK v4.
Utilização do SDK
- Instalar o SDK mais recente
Os SDKs do Azure Cosmos DB estão sendo constantemente aprimorados para fornecer o melhor desempenho. Para determinar as melhorias mais recentes do SDK, visite o SDK do Azure Cosmos DB.
Cada instância de cliente do Azure Cosmos DB é thread-safe e executa gerenciamento de conexão eficiente e cache de endereço. Para permitir um gerenciamento de conexão eficiente e um melhor desempenho pelo cliente Azure Cosmos DB, é altamente recomendável usar uma única instância do cliente Azure Cosmos DB durante o tempo de vida do aplicativo.
Quando você cria um CosmosClient, a consistência padrão usada, se não definida explicitamente, é Session. Se a consistência da sessão não for exigida pela lógica do aplicativo, defina a Consistência como Eventual. Observação: recomenda-se usar pelo menos a consistência de sessão em aplicativos que empregam o processador de Feed de Alterações do Azure Cosmos DB.
- Usar a API assíncrona para maximizar a taxa de transferência provisionada
O SDK Java v4 do Azure Cosmos DB inclui duas APIs, uma Síncrona e uma Assíncrona. Grosso modo, a API Assíncrona implementa a funcionalidade do SDK, enquanto a API de Sincronização é um wrapper fino que faz chamadas de bloqueio para a API Assíncrona. Isso contrasta com o antigo Azure Cosmos DB Async Java SDK v2, que era somente Async, e com o antigo Azure Cosmos DB Sync Java SDK v2, que era somente Sync e tinha uma implementação separada.
A escolha da API é determinada durante a inicialização do cliente; um CosmosAsyncClient suporta API Assíncrona, enquanto um CosmosClient suporta API de Sincronização.
A API Assíncrona implementa E/S sem bloqueio e é a escolha ideal se o seu objetivo for maximizar a taxa de transferência ao emitir solicitações para o Azure Cosmos DB.
Usar a API de sincronização pode ser a escolha certa se você quiser ou precisar de uma API, que bloqueia a resposta a cada solicitação, ou se a operação síncrona for o paradigma dominante em seu aplicativo. Por exemplo, você pode querer a API de sincronização quando estiver persistindo dados para o Azure Cosmos DB em um aplicativo de microsserviços, desde que a taxa de transferência não seja crítica.
Observação A taxa de transferência da API de sincronização se degrada com o aumento do tempo de resposta da solicitação, enquanto a API assíncrona pode saturar todos os recursos de largura de banda do seu hardware.
A colocação geográfica pode oferecer uma taxa de transferência maior e mais consistente ao usar a API de sincronização (consulte Colocar clientes na mesma região do Azure para obter desempenho), mas ainda não se espera que exceda a taxa de transferência alcançável da API assíncrona.
Alguns usuários também podem não estar familiarizados com o Project Reator, a estrutura Reative Streams usada para implementar a API assíncrona do SDK Java do Azure Cosmos DB v4. Se isso for uma preocupação, recomendamos que você leia nosso Guia introdutório de Padrão de Reator e, em seguida, dê uma olhada nesta Introdução à Programação Reativa para se familiarizar. Se você já usou o Azure Cosmos DB com uma interface assíncrona e o SDK usado foi o Azure Cosmos DB Async Java SDK v2, talvez esteja familiarizado com o ReactiveX/RxJava, mas não tenha certeza do que mudou no Project Reator. Nesse caso, dê uma olhada em nosso Guia Reator vs. RxJava para se familiarizar.
Os trechos de código a seguir mostram como inicializar seu cliente Azure Cosmos DB para a operação da API Assíncrona ou da API de Sincronização, respectivamente:
Java SDK V4 (Maven com.azure::azure-cosmos) API assíncrona
CosmosAsyncClient client = new CosmosClientBuilder()
.endpoint(HOSTNAME)
.key(MASTERKEY)
.consistencyLevel(CONSISTENCY)
.buildAsyncClient();
- Dimensione a carga de trabalho do cliente
Se você estiver testando em altos níveis de taxa de transferência, o aplicativo cliente pode se tornar o gargalo devido à máquina limitar a utilização da CPU ou da rede. Se você chegar a esse ponto, poderá continuar a impulsionar ainda mais a conta do Azure Cosmos DB dimensionando seus aplicativos cliente em vários servidores.
Uma boa regra geral é não exceder >50% de utilização da CPU em qualquer servidor, para manter a latência baixa.
- Use o Agendador Apropriado (Evite roubar threads do Event loop IO Netty)
A funcionalidade assíncrona do SDK Java do Azure Cosmos DB é baseada em E/S sem bloqueio. O SDK utiliza um número fixo de threads Netty do ciclo de eventos de E/S (como muitos núcleos da CPU que a sua máquina tem) para executar operações de E/S. O Fluxo devolvido pela API emite o resultado num dos threads Netty do ciclo de eventos de E/S partilhado. Assim, é importante não bloquear os threads Netty do ciclo de eventos de E/S partilhados. Fazer um trabalho intensivo de CPU ou bloquear a operação no thread netty do loop de eventos de E/S pode causar deadlock ou reduzir significativamente a taxa de transferência do SDK.
Por exemplo, o código a seguir executa um trabalho intensivo de CPU no thread de rede IO do loop de eventos:
Mono<CosmosItemResponse<CustomPOJO>> createItemPub = asyncContainer.createItem(item);
createItemPub.subscribe(
itemResponse -> {
//this is executed on eventloop IO netty thread.
//the eventloop thread is shared and is meant to return back quickly.
//
// DON'T do this on eventloop IO netty thread.
veryCpuIntensiveWork();
});
Depois que o resultado for recebido, você deve evitar fazer qualquer trabalho intensivo de CPU no resultado no thread de IO netty do loop de eventos. Em vez disso, você pode fornecer seu próprio Agendador para fornecer seu próprio thread para executar seu trabalho, conforme mostrado abaixo (requer import reactor.core.scheduler.Schedulers
).
Mono<CosmosItemResponse<CustomPOJO>> createItemPub = asyncContainer.createItem(item);
createItemPub
.publishOn(Schedulers.parallel())
.subscribe(
itemResponse -> {
//this is now executed on reactor scheduler's parallel thread.
//reactor scheduler's parallel thread is meant for CPU intensive work.
veryCpuIntensiveWork();
});
Com base no tipo de seu trabalho, você deve usar o Agendador de Reator existente apropriado para o seu trabalho. Leia aqui Schedulers
.
Para entender melhor o modelo de threading e agendamento do projeto Reator, consulte esta postagem do blog do Project Reator.
Para obter mais informações sobre o SDK Java v4 do Azure Cosmos DB, consulte o diretório do Azure Cosmos DB do SDK do Azure para Java monorepo no GitHub.
- Otimizar as configurações de registro em log em seu aplicativo
Por vários motivos, você deve adicionar o registro em log em um thread que está gerando alta taxa de transferência de solicitação. Se seu objetivo for saturar totalmente a taxa de transferência provisionada de um contêiner com solicitações geradas por esse thread, as otimizações de log podem melhorar muito o desempenho.
- Configurar um registrador assíncrono
A latência de um logger síncrono necessariamente leva em conta o cálculo de latência geral do thread gerador de solicitação. Um registrador assíncrono, como o log4j2 , é recomendado para separar a sobrecarga de log dos threads de aplicativos de alto desempenho.
- Desativar o registo da netty
O registro da biblioteca Netty é tagarela e precisa ser desativado (suprimir o sinal na configuração pode não ser suficiente) para evitar custos adicionais de CPU. Se você não estiver no modo de depuração, desative completamente o registro do netty. Portanto, se você estiver usando o Log4j para remover os custos adicionais de CPU incorridos pela org.apache.log4j.Category.callAppenders()
netty, adicione a seguinte linha à sua base de código:
org.apache.log4j.Logger.getLogger("io.netty").setLevel(org.apache.log4j.Level.OFF);
- OS Open files Limite de recursos
Alguns sistemas Linux (como o Red Hat) têm um limite superior no número de arquivos abertos e, portanto, no número total de conexões. Execute o seguinte para exibir os limites atuais:
ulimit -a
O número de arquivos abertos (nofile
) precisa ser grande o suficiente para ter espaço suficiente para o tamanho do pool de conexões configurado e outros arquivos abertos pelo sistema operacional. Ele pode ser modificado para permitir um tamanho maior do pool de conexões.
Abra o arquivo limits.conf:
vim /etc/security/limits.conf
Adicione/modifique as seguintes linhas:
* - nofile 100000
- Especificar chave de partição em gravações pontuais
Para melhorar o desempenho de gravações pontuais, especifique a chave de partição do item na chamada da API de gravação de ponto, conforme mostrado abaixo:
Java SDK V4 (Maven com.azure::azure-cosmos) API assíncrona
asyncContainer.createItem(item,new PartitionKey(pk),new CosmosItemRequestOptions()).block();
Em vez de fornecer apenas a instância do item, como mostrado abaixo:
Java SDK V4 (Maven com.azure::azure-cosmos) API assíncrona
asyncContainer.createItem(item).block();
Este último é suportado, mas adicionará latência ao seu aplicativo; o SDK deve analisar o item e extrair a chave de partição.
Operações de consulta
Para operações de consulta, consulte as dicas de desempenho para consultas.
Política de indexação
- Excluir os caminhos não utilizados da indexação para assegurar escritas mais rápidas
A política de indexação do Azure Cosmos DB permite especificar quais caminhos de documento devem ser incluídos ou excluídos da indexação usando Caminhos de Indexação (setIncludedPaths e setExcludedPaths). O uso de caminhos de indexação pode oferecer melhor desempenho de gravação e menor armazenamento de índice para cenários nos quais os padrões de consulta são conhecidos de antemão, já que os custos de indexação estão diretamente correlacionados ao número de caminhos exclusivos indexados. Por exemplo, o código a seguir mostra como incluir e excluir seções inteiras dos documentos (também conhecida como subárvore) da indexação usando o curinga "*".
CosmosContainerProperties containerProperties = new CosmosContainerProperties(containerName, "/lastName");
// Custom indexing policy
IndexingPolicy indexingPolicy = new IndexingPolicy();
indexingPolicy.setIndexingMode(IndexingMode.CONSISTENT);
// Included paths
List<IncludedPath> includedPaths = new ArrayList<>();
includedPaths.add(new IncludedPath("/*"));
indexingPolicy.setIncludedPaths(includedPaths);
// Excluded paths
List<ExcludedPath> excludedPaths = new ArrayList<>();
excludedPaths.add(new ExcludedPath("/name/*"));
indexingPolicy.setExcludedPaths(excludedPaths);
containerProperties.setIndexingPolicy(indexingPolicy);
ThroughputProperties throughputProperties = ThroughputProperties.createManualThroughput(400);
database.createContainerIfNotExists(containerProperties, throughputProperties);
CosmosAsyncContainer containerIfNotExists = database.getContainer(containerName);
Para obter mais informações, consulte Políticas de indexação do Azure Cosmos DB.
Débito
- Meça e ajuste para unidades de solicitação mais baixas/segundo de uso
O Azure Cosmos DB oferece um conjunto avançado de operações de banco de dados, incluindo 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 destas operações varia com base na CPU, E/S e memória necessárias para concluir a operação. Em vez de pensar e gerenciar recursos de hardware, você pode pensar em uma unidade de solicitação (RU) como uma única medida para os 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 unitário de solicitação é avaliado como uma taxa por segundo. Os aplicativos que excedem a taxa unitária de solicitação provisionada para seu contêiner são limitados até que a taxa caia abaixo do nível provisionado para o contêiner. Se seu aplicativo exigir um nível mais alto de taxa de transferência, você poderá aumentar sua taxa de transferência 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 dos predicados, 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 qualquer operação (criar, atualizar ou excluir), inspecione o cabeçalho x-ms-request-charge para medir o número de unidades de solicitação consumidas por essas operações. Você também pode examinar a propriedade RequestCharge equivalente em ResourceResponse<T> ou FeedResponse<T>.
Java SDK V4 (Maven com.azure::azure-cosmos) API assíncrona
CosmosItemResponse<CustomPOJO> response = asyncContainer.createItem(item).block();
response.getRequestCharge();
A cobrança de solicitação retornada neste cabeçalho é uma fração da taxa de transferência provisionada. Por exemplo, se você tiver 2000 RU/s provisionados e se a consulta anterior retornar 1.000 documentos de 1KB, o custo da operação será 1000. Como tal, dentro de um segundo, o servidor honra apenas duas dessas solicitações antes de limitar as solicitações subsequentes. Para obter mais informações, consulte Unidades de solicitação e a calculadora de unidades de solicitação.
- Lidar com limitação de 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 nenhum uso da capacidade de taxa de transferência além do nível reservado. O servidor terminará preventivamente a solicitação com RequestRateTooLarge (código de status HTTP 429) e retornará o cabeçalho x-ms-retry-after-ms indicando a quantidade de tempo, em milissegundos, que o usuário deve aguardar antes de tentar novamente a solicitação.
HTTP Status 429,
Status Line: RequestRateTooLarge
x-ms-retry-after-ms :100
Todos os SDKs capturam implicitamente essa resposta, respeitam o cabeçalho retry-after especificado pelo servidor e tentam novamente 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 cumulativamente acima da taxa de solicitação, a contagem de tentativas padrão atualmente definida como 9 internamente pelo cliente pode não ser suficiente; nesse caso, o cliente lança um CosmosClientException com o código de status 429 para o aplicativo. A contagem de tentativas padrão pode ser alterada usando setMaxRetryAttemptsOnThrottledRequests()
na ThrottlingRetryOptions
instância. Por padrão, o CosmosClientException com o código de status 429 é retornado após um tempo de espera cumulativo de 30 segundos se a solicitação continuar a operar acima da taxa de solicitação. Isso ocorre mesmo quando a contagem de tentativas atual é menor do que a contagem máxima de tentativas, seja o padrão de 9 ou um valor definido pelo usuário.
Embora o comportamento de repetição automatizada ajude a melhorar a resiliência e a usabilidade para a maioria dos aplicativos, ele pode entrar em desacordo ao fazer benchmarks de desempenho, especialmente ao medir a latência. A latência observada pelo cliente aumentará se o experimento atingir o acelerador do servidor e fizer com que o SDK do cliente tente novamente. Para evitar picos de latência durante experimentos 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 obter mais informações, consulte Unidades de solicitação.
- Design para documentos menores para maior taxa de transferência
A taxa de solicitação (o custo de processamento da solicitação) de uma determinada operação está diretamente correlacionada com o tamanho do documento. As operações em documentos grandes custam mais do que as operações em documentos pequenos. Idealmente, arquitete seu aplicativo e fluxos de trabalho para que o tamanho do item seja de ~1 KB, ou ordem ou magnitude semelhante. Para aplicativos sensíveis à latência, itens grandes devem ser evitados - documentos de vários MB tornam seu aplicativo mais lento.
Próximos passos
Para saber mais sobre como projetar seu aplicativo para dimensionamento e alto desempenho, consulte Particionamento e dimensionamento no Azure Cosmos DB.
Tentando fazer o planejamento de capacidade para uma migração para o Azure Cosmos DB? Você pode usar informações sobre seu cluster de banco de dados existente para planejamento de capacidade.
- Se tudo o que você sabe é o número de vCores e servidores em seu cluster de banco de dados existente, leia sobre como estimar unidades de solicitação usando vCores ou vCPUs
- Se você souber as taxas de solicitação típicas para sua carga de trabalho de banco de dados atual, leia sobre como estimar unidades de solicitação usando o planejador de capacidade do Azure Cosmos DB