Na Databricks, o Kubernetes está no centro dos nossos sistemas internos. Em um único cluster do Kubernetes, as primitivas de rede padrão, como serviços ClusterIP, CoreDNS e kube-proxy, geralmente são suficientes. Eles oferecem uma abstração simples para rotear o tráfego de serviço. Mas quando o desempenho e a confiabilidade são importantes, essas configurações padrão começam a mostrar suas limitações.
Neste post, compartilharemos como construímos um sistema inteligente de balanceamento de carga no lado do cliente para melhorar a distribuição do tráfego, reduzir as latências de cauda e tornar a comunicação de serviço a serviço mais resiliente.
Se você for um usuário do Databricks, não precisa entender este blog para poder usar a plataforma em todo o seu potencial. Mas, se você tiver interesse em dar uma espiada nos bastidores, continue lendo para saber sobre algumas das coisas legais em que estamos trabalhando!
A comunicação serviço a serviço de alto desempenho no Kubernetes apresenta vários desafios, especialmente ao usar conexões HTTP/2 persistentes, como fazemos na Databricks com o gRPC.
Embora este modelo geralmente funcione, seu desempenho cai rapidamente em ambientes sensíveis ao desempenho, levando a limitações significativas.
No Databricks, operamos centenas de serviços sem estado (stateless) que se comunicam por gRPC em cada cluster do Kubernetes. Esses serviços geralmente são de alta taxa de transferência, sensíveis à latência e executados em grande escala.
O modelo de balanceamento de carga padrão não é suficiente neste ambiente por vários motivos:
Essas limitações nos levaram a repensar como lidamos com a comunicação de serviço a serviço em um cluster do Kubernetes.
Para lidar com as limitações do kube-proxy e do roteamento de serviço padrão no Kubernetes, construímos um sistema de balanceamento de carga sem proxy e totalmente orientado pelo cliente, suportado por um plano de controle de descoberta de serviço personalizado.
O requisito fundamental que tínhamos era dar suporte ao balanceamento de carga na camada de aplicação e remover a dependência do DNS em um caminho crítico. Um balanceador de carga da Camada 4, como o kube-proxy, não pode tomar decisões inteligentes por solicitação para protocolos da Camada 7 (como o gRPC) que utilizam conexões persistentes. Essa restrição de arquitetura cria gargalos, o que exige uma abordagem mais inteligente para o gerenciamento de tráfego.
A tabela a seguir resume as principais diferenças e as vantagens de uma abordagem no lado do cliente:
Tabela 1: LB padrão do Kubernetes vs. LB do lado do cliente da Databricks
| Recurso/Aspecto | Balanceamento de carga padrão do Kubernetes (kube-proxy) | Balanceamento de carga no lado do cliente do Databricks |
|---|---|---|
| Camada de balanceamento de carga | Camada 4 (TCP/IP) | Camada 7 (Aplicação/gRPC) |
| Frequência de decisão | Uma vez por conexão TCP | Por solicitação |
| Descoberta de serviço | CoreDNS + kube-proxy (IP virtual) | Plano de Controle baseado em xDS + Biblioteca de Cliente |
| Estratégias compatíveis | Básico (Round-robin, Aleatório) | Avançado (P2C, afinidade de zona, modular) |
| Impacto na latência de cauda | Alta (devido à distorção do tráfego em conexões persistentes) | Reduzida (distribuição uniforme, roteamento dinâmico) |
| Utilização de recursos | Ineficiente (provisionamento excessivo) | Eficiente (carga balanceada) |
| Dependência de DNS/Proxy | Alta | Mínimo/Mínimo, não está em um caminho crítico |
| Controle operacional | Limitada | Detalhado |
Este sistema permite um roteamento de solicitações inteligente e atualizado com dependência mínima de DNS ou de rede da Camada 4. Ele dá aos clientes a capacidade de tomar decisões informadas com base em dados de topologia e integridade em tempo real.
A figura mostra nosso Endpoint Discovery Service personalizado em ação. Ele lê dados de serviço e de endpoint da API do Kubernetes e os traduz em respostas xDS. Tanto os clientes Armeria quanto os proxies de API enviam solicitações via stream para ele e recebem metadados de endpoint em tempo real, que são usados pelos servidores de aplicação para roteamento inteligente com clusters de fallback como backup.”
Executamos um plano de controle leve que monitora continuamente a API do Kubernetes em busca de alterações nos Services e EndpointSlices. Ele mantém uma visualização atualizada de todos os pods de backend para cada serviço, incluindo metadados como zona, prontidão e rótulos de shard.
Uma vantagem estratégica para a Databricks foi a adoção generalizada de um framework comum para comunicação entre serviços na maioria dos seus serviços internos, que são predominantemente escritos em Scala. Essa base compartilhada nos permitiu incorporar lógicas de descoberta de serviço e balanceamento de carga do lado do cliente diretamente no framework, facilitando a adoção entre as equipes sem exigir esforço de implementação personalizada.
Cada serviço se integra ao nosso cliente personalizado, que assina as atualizações do plano de controle para os serviços dos quais depende durante a configuração da conexão. O cliente mantém uma lista dinâmica de endpoints íntegros, incluindo metadados como zona ou shard, e é atualizada automaticamente à medida que o plano de controle envia as alterações.
Como o cliente ignora totalmente tanto a resolução de DNS quanto o kube-proxy, ele sempre tem uma visão precisa e em tempo real da topologia do serviço. Isso nos permite implementar estratégias de balanceamento de carga consistentes e eficientes em todos os serviços internos.
O cliente rpc realiza o balanceamento de carga ciente da solicitação usando estratégias como:
Estratégias mais avançadas, como o roteamento ciente da zona, exigiam um ajuste cuidadoso e um contexto mais aprofundado sobre a topologia do serviço, os padrões de tráfego e os modos de falha; um tópico a ser explorado em uma postagem futura dedicada.
Para garantir a eficácia da nossa abordagem, executamos simulações extensivas, experimentos e análises de métricas do mundo real. Validamos que a carga permaneceu distribuída uniformemente e que as métricas principais, como latência de cauda, taxa de erro e custo de tráfego entre zonas, permaneceram dentro dos limites alvo. A flexibilidade para adaptar estratégias por serviço tem sido valiosa, mas, na prática, manter a simplicidade (e a consistência) tem funcionado melhor.
Nosso plano de controle estende sua utilidade para além da comunicação interna de serviço a serviço. Ele desempenha um papel crucial no gerenciamento do tráfego externo ao se comunicar com a API xDS para o Envoy, o protocolo de descoberta que permite que os clientes busquem dinamicamente configurações atualizadas (como clusters, endpoints e regras de roteamento). Especificamente, ele implementa o Endpoint Discovery Service (EDS) para fornecer ao Envoy metadados consistentes e atualizados sobre os endpoints de backend, programando os recursos ClusterLoadAssignment. Isso garante que o roteamento no nível do gateway (por exemplo, para tráfego de entrada ou público) esteja alinhado com a mesma fonte de verdade usada pelos clientes internos.
Essa arquitetura nos dá controle detalhado sobre o comportamento de roteamento e, ao mesmo tempo, desvincula a descoberta de serviços das limitações do DNS e do kube-proxy. Os principais pontos são:
Após a implantação do nosso sistema de balanceamento de carga no lado do cliente, observamos melhorias significativas tanto no desempenho quanto na eficiência:

Embora a implementação tenha trazido benefícios claros, também descobrimos vários desafios e percepções ao longo do caminho:
Ao desenvolver nossa abordagem de balanceamento de carga do lado do cliente, avaliamos outras soluções alternativas. Veja por que acabamos decidindo não usá-las:
Os serviços headless do Kubernetes (clusterIP: None) fornecem IPs de pod diretos via DNS, permitindo que clientes e proxies (como o Envoy) realizem seu próprio balanceamento de carga. Essa abordagem contorna a limitação da distribuição baseada em conexão no kube-proxy e habilita estratégias avançadas de balanceamento de carga oferecidas pelo Envoy (como round robin, hashing consistente e round robin menos carregado).
Em teoria, a troca de serviços ClusterIP existentes por serviços headless (ou a criação de serviços headless adicionais usando o mesmo seletor) atenuaria os problemas de reutilização de conexão, fornecendo aos clientes visibilidade direta do endpoint. No entanto, essa abordagem tem limitações práticas:
Embora os serviços headless possam oferecer uma melhoria temporária em relação aos serviços ClusterIP, os desafios práticos e as limitações os tornaram inadequados como uma solução de longo prazo na escala da Databricks.
O Istio fornece recursos poderosos de balanceamento de carga da Camada 7 usando sidecars do Envoy injetados em cada pod. Esses proxies lidam com roteamento, novas tentativas, interrupção de circuito e muito mais, tudo gerenciado centralmente por meio de um plano de controle.
Embora esse modelo ofereça muitos recursos, concluímos que ele não era adequado para nosso ambiente na Databricks por alguns motivos:
Também avaliamos o Ambient Mesh do Istio. Como o Databricks já possuía sistemas proprietários para funções como distribuição de certificados e nossos padrões de roteamento eram relativamente estáticos, a complexidade adicional de adotar uma malha completa superou os benefícios. Isso foi especialmente verdadeiro para uma pequena equipe de infraestrutura que oferece suporte a uma base de código predominantemente em Scala.
Vale a pena notar que uma das maiores vantagens das malhas baseadas em sidecar é a independência de linguagem: as equipes podem padronizar a resiliência e o roteamento em serviços poliglotas sem precisar manter bibliotecas de cliente em todos os lugares. No Databricks, no entanto, nosso ambiente é fortemente baseado em Scala, e nossa cultura de monorepo e CI/CD rápido torna a abordagem sem proxy e com biblioteca de cliente muito mais prática. Em vez de introduzir a sobrecarga operacional dos sidecars, investimos na criação de um balanceamento de carga de primeira classe diretamente em nossas bibliotecas e componentes de infraestrutura.
Nossa abordagem atual de balanceamento de carga no lado do cliente melhorou significativamente a comunicação interna de serviço a serviço. No entanto, à medida que o Databricks continua a crescer, estamos explorando várias áreas avançadas para aprimorar ainda mais nosso sistema:
Balanceamento de carga entre clusters e entre regiões: como gerenciamos milhares de clusters do Kubernetes em várias regiões, estender o balanceamento de carga inteligente para além dos clusters individuais é fundamental. Estamos explorando tecnologias como redes L3 planas e soluções de malha de serviço (service-mesh), integrando-se perfeitamente com clusters multirregionais do Endpoint Discovery Service (EDS). Isso permitirá um gerenciamento robusto do tráfego entre clusters, tolerância a falhas e utilização globalmente eficiente de recursos.
Estratégias avançadas de balanceamento de carga para casos de uso de IA: planejamos introduzir estratégias mais sofisticadas, como o balanceamento de carga ponderado, para dar melhor suporte a workloads de IA avançados. Essas estratégias permitirão uma alocação de recursos mais refinada e decisões de roteamento inteligentes com base nas características específicas da aplicação, otimizando, em última análise, o desempenho, o consumo de recursos e a eficiência de custos.
Se você tem interesse em trabalhar com desafios de infraestrutura distribuída em grande escala como este, estamos contratando. Venha construir conosco — explore as vagas abertas na Databricks!
(This blog post has been translated using AI-powered tools) Original Post
