Mecânicas de Controle que Mantêm a Internet Fluindo

Daniel SuhettDaniel Suhett
6 min read

Hoje em dia nos acostumamos a pensar que todo hardware é capaz, nunca nos faltará recurso e o tempo de resposta será bom o suficiente, porém aqui quando saimos da camada de aplicação isso passa a não ser realidade, roteadores domésticos tem capacidade média de 64 KB a 256 KB em seu buffer que recebe dados, você logo deve pensar, hoje em dia temos acesso bem mais barato a hardware, por que não aumentam essa capacidade?

Vamos imaginar o seguinte cenário

Nós temos um Sender que está enviando dados pela rede, temos também os segments que são os dados propriamente ditos, e esses segments são enviados para um Receiver, que contém o buffer para retenção de segments, onde os pacotes que estão esperando o ack se acumulam.

$$\begin{align*} \text{Bytes por segundo} &= 4 \times 1500 = 6000 \, \text{bytes/s} \\ \\ \text{Tempo para encher o buffer} &= \frac{1.048.576 \, \text{bytes}}{6000 \, \text{bytes/s}} \approx 174{,}76 \, \text{segundos} \end{align*}$$

Com um buffer de 1 MB nosso Receiver permitiria que o Sender enviasse durante quase 3 minutos dados que nunca seriam recebidos, causando bufferbloat que é o aumento excessivo da latência e jitter na rede, causado principalmente por buffers grandes demais, onde ao invés de descartar os pacotes quando há congestionamento os armazena, o que atrasa a detecção e controle do congestionamento.

É por isso que não é tão simples aumentar o tamanho do buffer, agora se voltarmos esse buffer para 64 KB, fazendo a mesma conta que fizemos a cima, o resultado do tempo é de apenas 10.92 segundos e você pode achar que isso resolve o problema, mas não resolve, nós precisamos de mais, nós precisamos de um algoritmo robusto para gerenciar essa fila que pode congestionar a rede e seus dispositivos.

Congestion Control

Como você pode ver roteadores tem limite, a grande questão que queremos responder aqui é qual o limite dos nossos roteadores? Se você ainda não reparou, enviar o máximo possível de dados pelos nossos roteadores é o nosso trabalho, não só em quantidade mas em velocidade e confiabilidade, para isso usamos estado chamado Congestion Window (CWND).

Congestion Window é um dado TCP que limita quanto de dados uma conexão TCP pode enviar.

Cada algoritmo lida com a CWND de uma forma diferente, mas o conceito básico que você precisa entender antes de ver cada implementação é, quando o Sender consegue enviar um segment e receber um ack ele aumenta o tamanho da CWND permitindo que mais dados passem por ele expandindo seu volume, quando Sender para de receber acks ele diminui o tamanho da CWND, assim o Sender sabe que a rede não vai bem.

Para facilitar as diferentes implementações vamos utilizar de referência esse paper https://www.rfc-editor.org/rfc/pdfrfc/rfc2581.txt.pdf

Slow Start

Esse algoritmo começa com o CWND baixo utilizando o número 1, por isso o nome de “inicio lento” apesar disso se engana se você pensa que ele não é veloz, esse algoritmo por definição escala exponencialmente onde para cada ack você soma uma unidade de MSS à CWND, e esse ciclo se repete até que a CWND atinja seu threshold.

💡
MSS significa Maximum Segment Size que é uma “dependência” do MTU (Maximum Transmission Unit) configurado na rede. Normalmente o MTU é 1500 e resulta em um MSS de 1460.

$$\begin{aligned} &\textbf{Inicio:} \\ &\quad \text{cwnd} \leftarrow 1 \quad \text{(unidade em MSS)} \\ &\quad \text{ssthresh} \leftarrow \infty \quad\\[1em] &\textbf{Ao receber um ACK:} \\ &\quad \text{if } \text{cwnd} < \text{ssthresh} \text{ then} \\ &\qquad \text{cwnd} \leftarrow \text{cwnd} + 1 \quad \text{(Exponencial)} \\ &\quad \text{else} \\ &\qquad \text{Entra em Congestion Avoidance (Linear)} \\[1em] \end{aligned}$$

Congestion Avoidance

Quando o ritmo exponencial chega no seu limite ele entra em um modo que chama-se congestion avoidance, aqui o ritmo muda e passa a ser linear isso porque a CWND não pode passar a RWND que é a janela máxima que o Receiver da conexão consegue lidar.

Entendendo nosso Receiver, voltamos ao funcionamento de rede do congestion avoidance, nós só iremos somar algo ao CWND(que já está com um valor muito alto) quando todos os pacotes tiverem leitura confirmada pelo Receiver e fizerem a viagem completa RTT (Round Trip Time), garantindo que ele está conseguindo lidar com o buffer que está gerenciando.

$$\ \text{CWND} = \text{CWND} + \frac{\text{MSS} ^ 2}{\text{CWND}}$$

Explicit Congestion Notification

Sabendo que não podemos permitir que essa escalada seja ilimitada, o roteador vai chegar no seu limite, e a ECN é utilizada justamente para fazer essa comunicação do roteador com o Sender, pedindo para o Sender parar de enviar pacotes porque a congestão é eminente.

Mas e se o Sender não parar?

  • Todos os pacotes são descartados quando esse limite for realmente esgotado

  • CWND volta para 1

  • o threshold do Slow Start é cortado na metade

Isso pode não parecer muito mas significa uma perda absurda de performance na sua rede

Como nesse exemplo inspirado no que é usado no curso de Hussein Nasser sobre os fundamentos de redes para desenvolvedores, chegar nesse ponto de congestionar de fato tem um impacto enorme e é um ponto onde nós NUNCA queremos chegar.

Flow Control

Nós já sabemos como não congestionar a rede, entretanto ainda temos que lidar com a sobrecarga direto no Receiver, isso gera a pergunta

Quantos segments eu posso mandar sem sobrecarregar o Receiver?

No caso da imagem o Sender decidiu enviar um Segment por vez e aguardar a confirmação, se isso acontece com sua aplicação backend você estaria perdido pois o tempo total de comunicação seria muito lento, por isso você precisa de Flow Control.

Esse controle existe para sincronizar cliente e servidor em um canal de comunicação onde eles possam extrair a maior velocidade um do outro, sem afetar o funcionamento do Receiver.
Para solucionar isso precisamos introduzir um conceito de window size, que tem por utilidade dizer ao Sender quanto cabe no Buffer do Receiver e como está operando, a window size do Receiver se chama Receive Window (RWND) e fica contida dentro do header TCP.

A cada segment com ack marcado pelo Receiver essa RWND é atualizada, e enquanto isso o Sender pode olhar a cada iteração para continuar enviando segments constantemente enquanto houver disponibilidade.

Já do lado do Sender temos o conceito de Sliding Window onde para qualquer ack o ponteiro na janela avança e se mantém sincronizado com a RWND, isso acontece para o Sender ter a confiança de seguir enviando constantemente os segmentos e saber quais não reenviar no próximo envio

Somando o buffer do Receiver ao conceito de Sliding Window no Sender nós poderemos responder a pergunta que é quanto mandar sem sobrecarregar o nosso receiver.

Conclusão

Eu gostaria muito de abordar todos os tópicos desse assunto mas não terminaria esse post em um mês, se você chegou até aqui saiba que isso é um recorte de muitos anos atrás, diferentes abordagens surgiram para o TCP

Enfim muitas possibilidades e muitos casos reais de engenharia que a gente acaba pensando que “nunca vai usar para nada” não deixe se enganar, lidamos com isso o tempo todo.

0
Subscribe to my newsletter

Read articles from Daniel Suhett directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Daniel Suhett
Daniel Suhett

"Enquanto viver, continue aprendendo a viver." Seneca