This article explores the evolution from naive load balancing (round-robin) to more intelligent algorithms like least-connections, highlighting its limitations and introducing the **exponential weighted moving average (EWMA)**. It demonstrates how EWMA-smoothed active connection counts provide a reactive yet stable metric for distributing traffic, leading to significant performance improvements and better resource utilization in microservice architectures.
Read original on Dev.to #systemdesignThe article begins by illustrating a common problem in distributed systems: uneven traffic distribution across backend services despite using a basic load balancing strategy like round-robin. This often leads to overloaded instances while others remain underutilized, causing high latency and instability. The core issue is that round-robin treats all backends as equally capable and available, ignoring real-time load conditions.
A significant improvement over round-robin is the least-connections algorithm, which directs new requests to the server with the fewest active connections. While better, it can still suffer from temporary 'thundering herd' issues if a long-running request completes, briefly making a server appear idle before it's truly ready for a new burst of traffic.
To address the limitations of raw least-connections, the article proposes using an Exponential Weighted Moving Average (EWMA) of active connection counts. EWMA assigns greater weight to recent observations while still factoring in historical data, providing a more stable and accurate 'busyness score' for each backend. This ensures the load balancer reacts quickly to significant load changes but doesn't overreact to transient spikes.
Understanding EWMA
EWMA Formula: `new_score = alpha * current_measurement + (1 - alpha) * old_score` - `alpha` (smoothing factor, typically 0.1-0.3) dictates reactivity. - Higher `alpha` means more reactive, lower `alpha` means more stable.
class EwmaLeastConnLB:
def __init__(self, backends, alpha=0.2):
self.backends = backends
self.alpha = alpha
self.scores = [0.0] * len(backends)
def _update_score(self, i, current_conn):
old = self.scores[i]
self.scores[i] = self.alpha * current_conn + (1 - self.alpha) * old
def pick(self):
for i, b in enumerate(self.backends):
self._update_score(i, b.active_connections)
best_idx = min(range(len(self.backends)), key=lambda i: self.scores[i])
return self.backends[best_idx]This EWMA-weighted least-connections approach leads to better resource utilization, reduced latency, and a more resilient system without requiring additional hardware or complex service mesh deployments. It underscores the principle that intelligent measurement and distribution can be more effective than simply scaling up resources.