Este artigo tem como objetivo apresentar um estudo sobre a produção e consumo de eventos no Azure Event Hubs, com foco na obtenção da menor latência possível. Para alcançar esse objetivo, foi montado um ambiente completo com as configurações necessárias, visando obter resultados satisfatórios. Embora este artigo não forneça um passo a passo detalhado para reproduzir os testes, você não precisa se preocupar, pois serão disponibilizados os artefatos e links necessários para implementar o ambiente por conta própria.
Para salvar seu tempo, vou deixar aqui um resumo de tudo que fizemos para otimizar a latência:
Azure Event Hubs Premium
.Rede Acelerada
das interfaces de rede.LingerMS
igual a 0, nos produtores.Fetch.wait.max.ms
para 10ms
, nos consumidores.
Latência end-to-end é o tempo que o evento leva para sair da aplicação produtora Client.send()
, até chegar a aplicação consumidora Client.poll()
.
A imagem abaixo descreve qual é o caminho que esse evento passa entre a produção e o consumo da mensagem:
O termo "produce time" é o tempo da chamada do método Client.send()
, até o momento em que o registro chegue no broker
líder da partição do tópico.
A biblioteca do Confluent
agrupa os eventos (batching
) para enviar em lote, a fim de otimizar o uso de rede e I/O
. Para isso, ela implementa um delay
artificial na produção dos eventos, a fim de dar tempo para agrupar mais eventos. Esse delay
é parametrizável na configuração linger.ms
. Ao produzir um evento, ele espera o tempo configurado no linger.ms
, antes de liberar os eventos para broker
, caso o limite do tamanho do lote configurado na propriedade batch.size
tenha sido atingido antes desse tempo, o produtor libera as mensagens agrupadas para o broker
.
O lote pode precisar esperar mais tempo no produtor se o número de solicitações de produção não confirmadas enviadas para o broker
líder já tiver atingido o máximo max.inflight.requests.per.connection
, que é cinco por padrão. Portanto, quanto mais rápido o broker
confirmar as solicitações de produção, menor serão as chances de haver um tempo de espera adicional no produtor.
O tempo de publicação inclui o tempo de envio da mensagem pela rede, o tempo na fila de espera do Event Hub e o tempo para adicionar a mensagem no broker
líder. Em momentos de baixa demanda, o envio e a adição ao registro são rápidos, mas conforme a demanda aumenta, o tempo de espera na fila se torna mais relevante para o tempo total de publicação.
O Commit time
ou tempo de confirmação é o tempo necessário para replicar a mensagem para todas as réplicas em sincronia. O Event Hubs só expõe as mensagens ao consumidor depois que elas foram confirmadas, ou seja, replicadas para todas as réplicas em sincronia para garantir tolerância a falhas.
As mensagens são consumidas na ordem em que são produzidas, a menos que haja um novo consumidor lendo a partir do offset mais recente. Um consumidor só consome o ultimo registro após ler todos os registros publicados anteriormente para a mesma partição do tópico. Suponha que, no momento em que a mensagem foi confirmada, o deslocamento do consumidor estava N mensagens atrás da mensagem confirmada. Nesse caso, o tempo de recuperação (catch-up time
) é o tempo necessário para consumir N mensagens.
O consumidor inscrito em um tópico consulta continuamente o broker
líder em busca de mais dados. O tempo de busca (fetch-time
) é o tempo necessário para obter o registro do broker
, aguardando pacotes suficientes para formar a resposta e retornar o registro para o método Client.poll()
. A configuração padrão do consumidor é otimizada para latência (fetch.min.bytes é definido como um), em que a resposta a uma solicitação de busca é retornada assim que um único byte de dados estiver disponível, ou após um tempo limite fetch.max.wait.ms
.
O Azure Event Hubs é um serviço escalável de processamento de eventos que permite a ingestão e processamento de grandes volumes de dados, com baixa latência e alta confiabilidade.
A camada premium oferece isolamento de recursos de processamento e memória, o que ajuda a reduzir o risco de impacto dos vizinhos barulhentos.
Para criar um
Namespace
, sigo o passo a passo: Criar um hub de eventos usando o portal do Azure .
Neste exemplo utilizei o protocolo do Apache Kafka, para escrever e consumir mensagens, utilizei a biblioteca Confluent kafka no .NET.
O objetivo de utilizar esse protocolo, é facilitar uma migração de um ambiente On-Premise
para o Azure. O serviço mais utilizado nesse tipo de ambiente é o Apache Kafka.
Com o Event Hubs você pode produzir/consumir mensagens, utilizando o melhor protocolo para seu cenário. Veja mais em: Trocar eventos entre consumidores e produtores que usam protocolos diferentes: AMQP, Kafka e HTTPS.
O diagrama a seguir ilustra como duas VMs se comunicam com e sem a Rede Acelerada.
Sem Rede Acelerada , todo o tráfego de rede que entra e sai da VM atravessa o host e o comutador virtual. O comutador virtual fornece toda a aplicação de políticas ao tráfego de rede. As políticas incluem grupos de segurança de rede, listas de controle de acesso, isolamento e outros serviços virtualizados de rede.
Com a Rede Acelerada, o tráfego de rede que chega à interface de rede (NIC) da VM é encaminhado diretamente para a VM. A Rede Acelerada descarrega todas as políticas de rede que o comutador virtual aplicou e as aplica no hardware. Como o hardware aplica políticas, a NIC pode encaminhar o tráfego de rede diretamente para a VM. A NIC ignora o host e o comutador virtual, enquanto mantém todas as políticas aplicadas no host.
Para criar a VM com essa funcionalidade habilitada, siga o passo a passo: Usar a CLI do Azure para criar uma VM no Windows ou no Linux com Rede Acelerada.
Neste teste utilizamos duas VM's
Linux, para dividir o processamento respectivamente entre produtor e consumidor.
Para atingirmos a melhor acurácia no teste, precisamos garantir que os relógios entre as duas VM's estão sincronizados. Chrony é uma implementação do Protocolo de Tempo de Rede (NTP), cujo o objetivo é sincronizar os relógios das VM's. O Chrony
consiste no chronyd
, um daemon) que é executado no espaço do usuário, e chronyc
, um programa de linha de comando que pode ser usado para monitorar o desempenho do chronyd
e alterar vários parâmetros operacionais quando ele está em execução.
Em algumas distribuições do Linux ele pode vir instalado por padrão, para verificar se o Chrony
está instalado execute o comando:
systemctl status chronyd
Se o resultado for parecido com o seguinte, ele está instalado e ativo:
chronyd.service - NTP client/server
Loaded: loaded (/usr/lib/systemd/system/chronyd.service; enabled)
Active: active (running) since Wed 2013-06-12 22:23:16 CEST; 11h ago
Caso o seguimento Active: active (running) since Wed 2013-06-12 22:23:16 CEST; 11h ago
não aparecer, significa que ele está instalado, mas não ativo, neste caso execute o comando responsável por rodar o chronyd
:
systemctl start chronyd
Caso o resultado do primeiro comando systemctl status chronyd
, seja um erro, será necessário instalar e configurar o pacote. Para isto, siga o passo a passo: Sincronização de tempo para VMs Linux no Azure.
O projeto base para executar o teste está localizado no repositório do git: latency-eh
O teste consiste em um benchmark, com algumas variações de setup
de configurações nos consumidores e produtores, para cada setup
enviaremos 10.000 eventos sequências, após o consumo de cada mensagem, calcularemos a latência, no final do teste, poderemos comparar as estáticas, sendo elas: Latência média, mínima, máxima, 95th, 99th. Outra variável importante é a MPS (Mensagens por segundo), testaremos cada setup
O ambiente do foi configurado com todos os artefatos mencionados anteriormente:
A seguir as configurações das VM's utilizadas:
{
"name": "vm-consumer-linux",
"type": "Microsoft.Compute/virtualMachines",
"location": "eastus",
"tags": {},
"properties": {
"vmId": "e534ecf0-8c7e-46a5-a545-9d243591c6b5",
"hardwareProfile": {
"vmSize": "Standard_DS4_v2"
},
"storageProfile": {
"imageReference": {
"publisher": "Canonical",
"offer": "UbuntuServer",
"sku": "18.04-LTS",
"version": "latest",
"exactVersion": "18.04.202306070"
},
"osDisk": {
"osType": "Linux",
"name": "vm-consumer-linux_OsDisk_1_66f4f029de60432dbb2c7bf68e4f623c",
"createOption": "FromImage",
"caching": "ReadWrite",
"managedDisk": {
"storageAccountType": "Premium_LRS",
"id": "/subscriptions/54345470-2d94-4fe7-8504-14087a4b0326/resourceGroups/rg-artigo-eh/providers/Microsoft.Compute/disks/vm-consumer-linux_OsDisk_1_66f4f029de60432dbb2c7bf68e4f623c"
},
"deleteOption": "Detach",
"diskSizeGB": 30
},
"dataDisks": []
},
"osProfile": {
"computerName": "vm-consumer-linux",
"adminUsername": "claudio",
"linuxConfiguration": {
"disablePasswordAuthentication": false,
"provisionVMAgent": true,
"patchSettings": {
"patchMode": "ImageDefault",
"assessmentMode": "ImageDefault"
}
},
"secrets": [],
"allowExtensionOperations": true,
"requireGuestProvisionSignal": true
},
"networkProfile": {
"networkInterfaces": [
{
"id": "/subscriptions/54345470-2d94-4fe7-8504-14087a4b0326/resourceGroups/rg-artigo-eh/providers/Microsoft.Network/networkInterfaces/nic-accelerated-2",
"properties": {
"primary": true
}
}
]
}
}
}
O Confluent Kafka por padrão empacota as mensagens, a fim de aumentar a eficiência do consumo da rede, isso funciona muito bem para aplicações com um auto volume de produção e consumo de eventos (throughput
), porém, a aplicação irá sofrer um delay
adicional, para que o empacotamento ocorra. As configurações principais para controlar esse comportamento são:
LingerMS
: Tempo que o produtor vai esperar em milissegundos, para empacotar as mensagens. Configurar LingerMS
igual a 0
, vai fazer com as mensagens serão enviadas ao broker
imediatamente.Socket.Nagle.Disable
: Desabilitar o algoritmo de Nagle, esse algoritmo visa melhorar a eficiência do tráfego de pacotes do socket TCP
, desabilitando-o diminuiremos a latência em um cenário com pouco throughput
.Fetch.wait.max.ms
: Tempo máximo para realizar o download
das mensagens, para melhorar a latência, diminuímos esse tempo para 10 milissegundos, isso vai fazer com que a aplicação faça o download
, mais rápido das mensagens. Em um cenário de auto volumo de mensagens, é melhor deixar esse tempo maior, para que a aplicação consiga baixar um pacote de mensagens, melhorando a eficiência do tráfego de rede.Para evidenciar o impacto de cada configuração, vamos testar alguns setups
, e enumerar seus resultados.
[
{
"Key": "compression.type",
"Value": "none" //Event Hubs não da suporte à essa config
},
{
"Key": "acks",
"Value": "0"
},
{
"Key": "linger.ms",
"Value": "0"
},
{
"Key": "socket.nagle.disable",
"Value": "True"
}
]
[
{
"Key": "enable.auto.commit",
"Value": "True"
},
{
"Key": "fetch.wait.max.ms",
"Value": "10"
},
{
"Key": "socket.nagle.disable",
"Value": "True"
}
]
[
{
"Key": "acks",
"Value": "0"
}
]
[
{
"Key": "fetch.wait.max.ms",
"Value": "100"
}
]
[
{
"Key": "enable.auto.commit",
"Value": "True"
},
{
"Key": "fetch.wait.max.ms",
"Value": "100"
}
]
Config | Min | Max | Media | 75th | 95th | 99th | MPS |
---|---|---|---|---|---|---|---|
producer otimizado - consumer otimizado | 9.0892 | 49.4253 | 19.6181 | 23.8136 | 36.1738 | 45.3069 | 100 |
producer default - consumer fetch 100ms | 9.0997 | 110.384 | 19.1102 | 21.9132 | 26.1992 | 110.2540 | 100 |
producer otimizado - consumer otimizado | 7.8575 | 50.917 | 18.1515 | 21.7498 | 38.5029 | 50.6393 | 100 |
producer otimizado - consumer fetch 100ms | 8.1567 | 81.636 | 19.4703 | 21.6645 | 41.8902 | 63.7061 | 100 |
producer default - consumer fetch 100ms | 16.302 | 50.9272 | 27.5178 | 30.3209 | 48.4584 | 50.6922 | 300 |
producer otimizado - consumer fetch 100ms | 9.6265 | 79.5632 | 25.2977 | 27.9920 | 51.3451 | 79.1143 | 300 |
producer otimizado - consumer fetch 10ms | 9.5102 | 52.8139 | 22.5398 | 25.7416 | 34.4743 | 52.6098 | 300 |
producer otimizado - consumer otimizado | 9.7563 | 51.8449 | 25.4748 | 29.4510 | 46.6061 | 51.2764 | 300 |
O Trade-off entre latência e throughput
é que a capacidade de agrupar mais registros requer a introdução de atrasos artificiais no caminho da mensagem. No entanto, em aplicações com um baixo índice de mensagens por segundo, o aumento desses atrasos artificiais pode acabar excedendo os ganhos de latência obtidos pela estratégia de empacotamento. Como resultado, os objetivos de latência podem limitar a quantidade de agrupamento alcançada, o que, por sua vez, pode reduzir a quantidade efetiva de throughput
obtida dos brokers
e aumentar as latências dos mesmos, conforme discutido anteriormente. Portanto, o caminho ideal é reproduzir esse teste, otimizando o ambiente para a menor latência possível, e ajustando as variáveis de volume e mensagens por segundo, em conformidade com as mudanças nas configurações apontadas nesse artigo, assim você chegará à melhor configuração possível para o seu cenário.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.