O LinkedIn substituiu JSON por Protobuf e conseguiu uma redução na latência de até 60% (2023)
Semana passada fiz a publicação "Como um jogador diminuiu o tempo de carregamento do GTA Online em 70% usando engenharia reversa (2021)", onde o kht criou um comentário apontando que um dos problemas explicados na publicação, o parse de um grande arquivo JSON, poderia ser evitado ao usar Protobuf (Protocol Buffers), que parecia ser mais adequado. Ele explicou como o Protobuf funciona aqui. Aproveitando o assunto, resolvi trazer uma outra história sobre melhoria de desempenho, dessa vez envolvendo a troca de JSON por Protobufs.
Os autores do artigo compartilharam alguns dos desafios que enfrentaram com JSON, o processo que usaram para avaliar novas soluções e para decidir avançar com o Protobuf como substituto. Também compartilharam detalhes da integração e implementação do Protobuf no Rest.li e os benefícios que encontraram, incluindo redução de até 60% na latência e melhoria de 8% na utilização de recursos. Boa leitura!
Como funciona o LinkedIn
Em 2023, Karthik Ramgopal e Aman Gupta publicaram um artigo com o título LinkedIn Integrates Protocol Buffers With Rest.li for Improved Microservices Performance no blog de engenharia do LinkedIn.
O LinkedIn usa um framework chamado Rest.li, que é "uma estrutura REST de código aberto para a construção de arquiteturas RESTful robustas e escalonáveis usando ligações de tipo seguro e entrada/saída assíncronas e sem bloqueio".
De acordo com o artigo, ao introduzir esse framework, eles tiveram uma redução na latência das requisições e uma melhoria na utilização dos recursos. Dentre os vários níveis da pilha de tecnologias do LinkedIn, existem mais de 50.000 endpoints de API Rest.li usados. É bastante coisa.
Desde quando começaram a usar o Rest.li, o framework usou JSON como formato de serialização padrão. Embora JSON tenha servido bem em termos de suporte amplo a linguagens de programação e legibilidade humana (o que facilita a depuração), o desempenho em tempo de execução não estava bom. Eles tentaram várias abordagens diferentes para melhorar o desempenho da serialização JSON em Rest.li, mas ela continuou a ser um gargalo em muitos perfis de desempenho.
Os problemas do JSON
No artigo, mencionam que o JSON trazia dois desafios:
- É um formato textual e verboso. Isso resulta em maior uso de largura de banda da rede e latências mais altas. Compactação e descompactação poderiam ajudar nisso, mas consomem recursos de hardware adicionais e podem ser desproporcionalmente caras ou indisponíveis em alguns ambientes.
- A latência e a taxa de transferência de serialização e desserialização não estavam boas.
Então, uma alternativa ao JSON deveria satisfazer alguns critérios:
- Payload compacto para conservar a largura de banda da rede e reduzir as latências.
- Alta eficiência de serialização e desserialização para reduzir ainda mais a latência e aumentar a taxa de transferência.
- Rest.li é usado em várias linguagens de programação no LinkedIn (Java, Kotlin, Scala, Object C, Swift, JavaScript, Python, Go), então é necessário ter suporte em todas elas.
- Esse substituto deve ser facilmente conectado ao mecanismo de serialização existente do Rest.li. Isso permitiria continuar a usar a modelagem de dados do Rest.li usando PDL e permitiria uma migração incremental com escopo limitado e interrupção mínima.
Após avaliar vários formatos como Protobuf, Flatbuffers, Cap'n Proto, SMILE, MessagePack, CBOR e Kryo, determinaram que o Protobuf era a melhor opção porque teve o desempenho mais eficaz nos critérios acima.
Integração do Protobuf no Rest.li
Eles criaram um conceito de tabelas de símbolos, um mapeamento bidirecional de nomes de campos/valores enum
do PDL para ordinais inteiros, assim podiam continuar usando a modelagem PDL e o esquema do Protobuf.
Para reduzir o trabalho dos serviços de backend em gerar essas tabelas de símbolos, eles escolheram uma abordagem de geração e troca em tempo de execução, conforme ilustrado abaixo:
A tabela de símbolos é gerada automaticamente na inicialização do serviço, examinando todos os esquemas usados por todos os terminais do serviço e exposta por meio de um endpoint especial symbolTable
hospedado no mesmo serviço. Quando qualquer cliente se comunica com qualquer servidor, ele primeiro verifica seu cache local em busca da tabela de símbolos e, se não for encontrado, solicita ao servidor uma cópia da tabela de símbolos. A tabela de símbolos recuperada é então armazenada em cache no lado do cliente para solicitações futuras.
Para a comunicação entre os aplicativos web/móveis e seus servidores via API, optaram por não usar essa abordagem dinâmica para evitar latência na inicialização do aplicativo ou tráfego intermitente em implantações de novas versões do servidor da API.
Em vez disso, um plugin de construção nos servidores da API gera a tabela de símbolos a cada build, mas a atualiza no modo "somente anexar" (append only). A tabela de símbolos gerada é publicada pelo sistema de build como um artefato versionado. Quando o aplicativo cliente é criado com base nesse artefato versionado, ele usa os símbolos ao serializar solicitações ou desserializar respostas do servidor API.
Plano de implantação (rollout)
- Integraram o suporte ao Protobuf na biblioteca Rest.li usando o mecanismo descrito acima.
- Transferiram todos os serviços do LinkedIn para a nova versão do Rest.li e os implantaram em produção.
- Para aumentar o uso gradualmente, usaram configurações do cliente.
Essas configurações do cliente permitiam que o cliente codificasse a carga usando Protobuf em vez de JSON e definisse o cabeçalho do tipo de conteúdo como Protobuf, ou definisse o cabeçalho de aceitação para instruir o servidor a preferir Protobuf em vez de JSON para codificação de respostas.
Dessa forma, conseguiram corrigir bugs, validar melhorias e expandir gradualmente para todo o LinkedIn com o mínimo de interrupção para os desenvolvedores ou para a empresa.
Melhorias com o Protobuf
- Aumento médio de taxa de transferência por host de 6,25% para payloads de resposta e 1,77% para payloads de solicitação em todos os serviços.
- Para serviços com grandes cargas, houve uma melhoria de até 60% na latência.
- Não notaram nenhuma degradação estatisticamente significativa quando comparado ao JSON em nenhum serviço.
Abaixo está o gráfico de comparação de latência P99 do benchmarking do Protobuf em relação ao JSON quando os servidores estão sob carga pesada.
Conclusão e planos futuros no LinkedIn
O uso do Protobuf em vez do JSON resultou em ganhos reais de eficiência, traduzindo-se em economias tangíveis na escala do LinkedIn. A forma de implementação tornou isso quase completamente transparente para os desenvolvedores de aplicativos e resultou na obtenção dessas vitórias com o mínimo de interrupção.
Com base nos aprendizados sobre a implementação suave de uma mudança dessa escala no LinkedIn, eles planejam trabalhar na automação avançada para permitir uma migração contínua em todo o LinkedIn do Rest.li para o gRPC. De acordo com os autores, o gRPC oferecerá melhor desempenho, suporte para mais linguagens de programação, streaming e uma comunidade robusta de código aberto.
Infelizmente não encontrei um artigo no blog de engenharia do LinkedIn falando sobre a implementação do gRPC.
Eu não conhecia nada sobre Protobuf, então foi legal ler a publicação do kht e esse artigo do LinkedIn. Também não conheço sobre gRPC, mas se me deparar com algum artigo interessante sobre, trarei para o TabNews também.