Teste de latência no Azure Event Hubs usando protocolo do Apache Kafka
Published Jul 31 2023 06:28 AM 2,702 Views
Microsoft

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:

  • Utilizar o Azure Event Hubs Premium.
  • Utilizar a funcionalidade Rede Acelerada das interfaces de rede.
  • Desabilitar o algoritmo de Nagle, nos produtores/consumidores.
  • Definir LingerMS igual a 0, nos produtores.
  • Definir Fetch.wait.max.ms para 10ms, nos consumidores.

 

Conceito de latência end-to-end

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:

 

diagrama latencia end-to-enddiagrama latencia end-to-end

 

Producer time: Processamento e agrupamento de registros na camada interna da aplicação

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.

 

Publish Time - Tempo de envio da mensagem ao Broker líder

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.

 

Commit: Tempo de replicaçã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.

 

Catch-up time: Tempo de deslocamento do offset

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.

 

Fetch time: Tempo de busca

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.

 

Azure Event Hubs Premium

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 .

 

Protocolo do Apache Kafka

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.

 

Interface de rede com a funcionalidade de Rede Acelerada

O diagrama a seguir ilustra como duas VMs se comunicam com e sem a Rede Acelerada.

Diagrama de comunicação com e sem 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.

 

Chrony

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.

 

Execução do teste

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

 

Ambiente

O ambiente do foi configurado com todos os artefatos mencionados anteriormente:

 

Setup VM's

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
                    }
                }
            ]
        }
    }
}

 

Configurações chave

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:

  • Produtor - 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.
  • Produtor/Consumidor - 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.
  • Consumidor - 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.

 

Producer Otimizado

 [
    {
        "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"
    }
]

 

Consumer Otimizado

[
    {
        "Key": "enable.auto.commit",
        "Value": "True"
    },
    {
        "Key": "fetch.wait.max.ms",
        "Value": "10"
    },
    {
        "Key": "socket.nagle.disable",
        "Value": "True"
    }
]

 

Producer Default

[
    {
        "Key": "acks",
        "Value": "0"
    }
]

 

Consumer Fetch 10ms

[
    {
        "Key": "fetch.wait.max.ms",
        "Value": "100"
    }
]

 

Consumer Fetch 100ms

[ 
    {
        "Key": "enable.auto.commit",
        "Value": "True"
    },
    {
        "Key": "fetch.wait.max.ms",
        "Value": "100"
    }
]

 

Resultados

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

 

Conclusão

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.

1 Comment
Co-Authors
Version history
Last update:
‎Jul 31 2023 06:47 AM
Updated by: