Skip to content

Web Performance – TCP

Performance definitivamente é uma feature na era da web 3.0. Nesta sequência de artigos, exploraremos a importância da performance e como ela afeta diretamente a experiência do usuário final. Quando se trata de latências na web, é importante entendermos que cada milissegundo conta. Afinal de contas, você já se perguntou como os atrasos são percebidos pelos usuários?

Para garantirmos que a performance não seja uma causa de insatisfação com o produto final, é essencial que os engenheiros e arquitetos responsáveis pelo design sistêmico conheçam alguns aspectos internos das tecnologias básicas envolvidas no processo. Começarmos essa sequência de artigos com dicas de otimização de um dos protocolos mais importantes na história da comunicação de redes de computadores: o Transmission Control Protocol (TCP).

Porém, esse artigo não tem natureza introdutória, então assume-se que o leitor tenha conhecimentos básicos sobre o funcionamento de redes de computadores e seus principais conceitos. Neste post darei sete dicas mais diretas sobre algumas alavancas do protocolo TCP que podem auxiliar em garantir uma performance melhor na web e, consequentemente, a uma melhor experiência do usuário. São elas:

💾 1 – Otimize o envio: o bit mais rápido é aquele que não precisa ser enviado. Eliminar transferências de dados desnecessárias é, definitivamente, a regra número 1. Uma boa dica é examinar minuciosamente o contrato nas APIs e garantir que apenas os dados necessários àquela requisição estão sendo enviados. Nada mais! Além disso, reduza o tamanho dos pacotes aplicando algoritmos de compressão adequados.

💾 2 – Encurte a distância até o cliente: Mova o máximo de conteúdo para o mais próximo do cliente. Isso pode ser alcançado através do uso de CDNs com técnicas de caching e otimização de conexões. A distribuição geográfica dos servidores ajudará a reduzir a latência das requisições melhorando significativamente a performance da aplicação.

💾 3 – Reuse as conexões e habilite o TFO: perceba que carregar um site envolve em carregar inúmeros recursos: imagens, texto, vídeos, animações, etc. Em geral, isso envolve a abertura de diversas conexões para o carregamento do site. Cada conexão aberta, no entanto, impõe, pelo menos, um novo pênalti de latência: o three-way handshake. Além do reuso das conexões, considere habilitar a opção TCP Fast Open (TFO). Ao habilitar o TFO a aplicação já enviará dados dentro do pacote SYN, o que reduz o impacto de latência para novas conexões:

import socket

# Criando um socket TCP
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# Habilitando TCP Fast Open (TFO): capacidade de enviar dados no SYN. 
sock.setsockopt(socket.SOL_TCP, socket.TCP_FASTOPEN, 1)

# Conectando ao servidor usando TFO
server_address = ('example.com', 1234)
sock.connect(server_address)

# Enviando e recebendo dados
sock.send(b'Hello, server!')
response = sock.recv(1024)

# Feche o socket
sock.close()

💾 4 – Mantenha os servidores atualizados: a ciência da computação é viva. Novos algoritmos e otimizações dos tradicionais estão sempre surgindo e com isso o kernel dos sistemas operacionais está em constante evolução. Manter os servidores atualizados pode implicar em grandes otimizações de performance se beneficiando do progresso da computação. Um exemplo disso é a implementação do algoritmo PRR (Proportional Rate Reduction) no kernel do Linux 3.2+ que, teoricamente, é uma evolução no processo de recuperação devido à perda de pacotes quando comparado com o antigo algoritmo Multiplicative Decrease and Additive Increase (AIMD).

💾 5 – Ajuste a janela de congestionamento (cwnd): Quando uma conexão TCP é estabelecida, o remetente começa enviando uma quantidade limitada de segmentos de dados, em vez de enviar uma grande quantidade de dados de uma vez. A esse começo limitado dá-se o nome de Slow Start. O objetivo é evitar a sobrecarga da rede e determinar a capacidade máxima que a rede pode lidar. Começa-se enviando apenas 1 segmento de dados e aguarda-se o ACK. A cada ACK recebido, o remetente dobra a quantidade de dados enviados. Esse processo continua até que o remetente atinja um ponto em que não recebe mais ACKs ou encontre algum sinal de congestionamento, como um timeout ou perda de pacotes. A janela de congestionamento determina a quantidade de dados que um remetente pode enviar antes de receber uma confirmação de recebimento do destinatário. A RFC 6928 estabelece um ajuste inicial para 10 segmentos de dados. Isso pode ajudar a ganhar alguns milissegundos a mais durante a fase de ajuste da janela de congestionamento ao seu valor ideal.

💾 6 Desabilite o SSR para conexões de vida longa: para conexões que permanecem inativas durante um tempo o TCP reinicializa a janela de congestionamento para um valor seguro, pois assume qua após um tempo de inatividade as condições da rede podem ter mudado (o que me parece justo). Esse mecanismo recebe o nome de slow-start restart (SSR) e tem o objetivo de evitar congestionamento na rede devido a mudanças durante o período de inatividade. Porém, isso pode ser bem prejudicial para conexões persistentes que se comportam recebendo rajadas de dados esporadicamente. Para esse cenário pode ser recomendado desabilitar o SSR no servidor. Isso pode beneficiar significativamente as conexões HTTP de vida longa. No Linux isso pode ser alcançado através dos comandos:

// Para checar a configuração
$> sysctl net.ipv4.tcp_slow_start_after_idle 

// Desabilitando o SSR
$> sysctl -w net.ipv4.tcp_slow_start_after_idle=0

💾 7 Configure a janela de recebimento (rwnd): a janela de recebimento estabelece quanto espaço o receptor tem disponível em seu buffer para armazenar os dados recebidos. O remetente deve enviar dados em uma quantidade que caiba na janela de recebimento para evitar o congestionamento da conexão. À medida que o receptor processa e consome os dados recebidos, a janela de recebimento é ajustada para informar ao remetente que mais dados podem ser enviados. Para resolver o problema imposto pelo limite inicial do TCP (de 65535 bytes) da janela de recebimento, a RFC 1323 introduziu o conceito de “Window Scaling“. Isso permite aumentar o tamanho máximo da janela de recebimento para até 1 gigabyte. Hoje em dia, a escala de janela do TCP está ativada por padrão em todas as principais plataformas, mas atenção, pois alguns dispositivos intermediários, como roteadores e firewalls, podem modificar ou até mesmo remover essa opção.

// Verificando a configuração de Window Scaling 
$> sysctl net.ipv4.tcp_window_scaling

// Configurando o Window Scaling 
$> sysctl -w net.ipv4.tcp_window_scaling=1

Conclusão

Esse post resumiu algumas dicas rápidas que podem ser aplicadas junto ao protocolo TCP para a otimização de performance na camada de transporte e, consequentemente, beneficiar a aplicação ajudando a reduzir latência e otimizar as conexões e trocas de dados entre cliente e servidor. Para uma maior compreensão do tema sugiro um aprofundamento maior nas referências indicadas na próxima seção.

Referências

Grigorik, Ilya. (2013). “High Performance Browser Networking.”

Tanenbaum, A. S. (2014). “Computer Networks.” Pearson.

“TCP/IP Illustrated, Volume 1: The Protocols” by W. Richard Stevens