Zalando engineered an in-process, client-side load balancer to optimize a high-throughput API handling millions of requests per second with high fan-out internal traffic. This architectural shift from a shared edge load balancer to a client-side solution resulted in more predictable latency, reduced infrastructure costs, and enhanced observability for their Product Read API. The solution involved reimplementing consistent hashing, implementing occupancy-based load balancing, and a careful rollout strategy.
Read original on InfoQ ArchitectureZalando's Product Read API, serving millions of requests per second, relied on a shared cluster edge load balancer, Skipper. For internal requests involving high fan-out (up to 100 parallel calls per single batch request), the team experienced unpredictable latency spikes. This was due to the batch waiting on the slowest of 100 hops through infrastructure they didn't own, making it difficult to distinguish Skipper's latency issues from their own application's performance.
To address these issues, Zalando decided to move routing for high fan-out internal traffic in-process, within the calling service, while retaining Skipper for edge and single-GET traffic. This meant graduating the internal fan-out path to a client-side load balancer, rather than fully replacing Skipper.
When to Build Your Own Client-Side Load Balancer
The article emphasizes that building a custom client-side load balancer is typically an extreme edge case. For most scenarios, mature proxies like Envoy or existing load balancers offer robust, maintained, and hardened solutions with consistent hashing out-of-the-box. Zalando's decision was driven by specific high fan-out, high-throughput internal traffic patterns and the inability to control external infrastructure latency.
The implementation led to more predictable latency, a significant reduction in infrastructure costs (Skipper fleet shrunk from 50+ to 8 pods, daily cost cut from $450 to $110), and better visibility into failure origins. While an attempt at availability-zone (AZ)-aware routing was made to cut inter-AZ costs, it resulted in fragmented caches and DynamoDB read explosions, indicating a trade-off where cost savings were outweighed by performance and complexity issues.