HeadlinesBriefing favicon HeadlinesBriefing.com

Princípios para aplicações Tokio rápidas

Hacker News •
×

Estou voltando do Rust Conf. No Unconf, tivemos uma discussão produtiva sobre depuração e benchmarking de aplicações assíncronas. Muitos insights interessantes foram compartilhados. Estou tentando enumerar alguns deles aqui, junto com algumas das minhas próprias experiências. Este é o primeiro rascunho do que espero que possa se tornar um documento vivo de melhores práticas. Sinta-se à vontade para abrir uma issue ou enviar um PR. Espero também adicionar um aplicativo de exemplo nos próximos dias demonstrando esses problemas junto com a aparência do trace do dial9.— Russell

Há poucas regras rígidas e definitivas para escrever código que tenha bom desempenho em runtimes Tokio; a resposta para tantas perguntas é "depende". O desempenho de uma carga de trabalho depende do que mais está rodando no runtime naquele momento. É por isso que tantos problemas só aparecem em produção! Escrever aplicações assíncronas com bom desempenho é um equilíbrio entre justiça e batching, contenção e isolamento. Este post apresenta alguns princípios gerais e cobre exceções onde posso. Ele assume familiaridade básica com o runtime work-stealing do Tokio; um resumo de alto nível está incluído no apêndice.

Princípios gerais

Primeiro, determine se você tem um problema. Se você começar a procurar sinais de alerta em uma aplicação Tokio, você os encontrará. Quase toda aplicação real que vi tem polls muito mais longos que os 10-100 microssegundos que Alice Ryhl recomenda em seu excelente post What is Blocking?. Esses problemas podem ou não afetar as métricas ou o comportamento da aplicação que você realmente se importa. É importante trabalhar de trás para frente a partir de uma métrica real que você está tentando melhorar. Por exemplo, uma aplicação pode ter polls longos que são completamente benignos; "corrigi-los" não impactará de forma mensurável as métricas voltadas ao usuário. Na esmagadora maioria dos problemas que encontrei, o problema estava no próprio código da aplicação, frequentemente na interação entre múltiplos componentes de um sistema distribuído (e não realmente no Tokio). O dial9 deu muita visibilidade ao Tokio; pelo menos com a mesma frequência com que encontra um problema do Tokio, ele na verdade demonstra claramente a ausência de um. Claro, às vezes é um problema do Tokio. Em termos de métricas do Tokio, a mais útil é o histograma de latência de agendamento (schedule latency) adicionado recentemente. A latência de agendamento é a quantidade de tempo entre sua tarefa estar pronta para executar e o Tokio realmente fazer o poll do future. Embora isso não lhe diga qual é a causa, a latência de agendamento é o sintoma mais comum de interações ruins entre o Tokio e seu código.

Divida para latência, agrupe para throughput. Faça yield com mais frequência para otimizar a latência. Baixa latência em muitas requisições requer justiça entre conexões. Considere o Redis (ou qualquer aplicação que suporte pipelining de requisições). Uma implementação ingênua lerá dados diretamente da conexão enquanto mais dados estiverem disponíveis. Quando as requisições são pipelined, toda a requisição em pipeline acabará em um buffer na memória. Quando você lê frames dele, cada um será Poll::Ready sem voltar à rede. Isso cria tanto polls longos quanto injustiça entre clientes. O impacto no throughput geralmente é menor: o mesmo número de requisições é processado. A latência, no entanto, muda drasticamente porque um pipeline inteiro pode esperar atrás de outro. Fazer yield explicitamente após cada requisição pode reduzir a latência em cerca de 10× neste exemplo. Você pode fazer ainda melhor fazendo yield apenas após várias leituras consecutivas imediatamente prontas.

Entidades principais: Empresas: Tokio, Redis | Pessoas: Russell, Alice Ryhl | Locais: Rust Conf