#14961·kong

Latency (EWMA) balancer's power-of-two-choices degrades into a full scan for 3+ targets

Author: Adityaj0Created Aug 2, 2026Updated Aug 2, 2026

Is there an existing issue for this?

  • I have searched the existing issues

Kong version

Reproduced by code inspection against master @ 391ee48.

Current Behavior

The latency (EWMA) balancer algorithm implements "power of two choices" (P2C, per the module's own header comment referencing Twitter Finagle's PeakEwma), sampling a small candidate set (PICK_SET_SIZE = 2) instead of scanning every address, specifically to avoid greedily piling traffic onto whichever backend currently looks fastest.

In kong/runloop/balancer/latency.lua#getPeer, k is first correctly computed as min(address_count, PICK_SET_SIZE):

lua
local k = (address_count < PICK_SET_SIZE) and address_count or PICK_SET_SIZE

But immediately afterward, when there is more than one candidate left after removing previously-failed addresses, k is unconditionally grown back up to the full candidate count whenever that count exceeds it:

lua
if filtered_addresses_num > 1 then
  k = filtered_addresses_num > k and filtered_addresses_num or k
  address, score = pick_and_score(self, filtered_addresses, k)

This is backwards: on any ordinary (non-retry) request against an upstream with 3 or more available targets, filtered_addresses_num equals address_count, which is > PICK_SET_SIZE, so this line always overrides k back up to the full address count. pick_and_score then scans all k candidates and always returns the address with the strict global-minimum EWMA score, rather than comparing a 2-candidate sample.

Expected Behavior

For any upstream with more than PICK_SET_SIZE (2) available targets, getPeer() should sample only PICK_SET_SIZE candidates and pick the better of those, per the documented P2C design -- not perform a full scan / greedy global argmin.

Steps To Reproduce

Given an upstream configured with algorithm = "latency" and 3+ targets with distinct, stable EWMA scores, repeated calls to getPeer() (with a fresh handle each time, so filtered_addresses_num == address_count every call) will deterministically always return the single lowest-scored address, never sampling any other target — this can be observed directly by seeding self.ewma/self.ewma_last_touched_at with fixed scores for 5 addresses and calling getPeer() many times: 100% of picks land on the global minimum, regardless of which two addresses a true 2-candidate P2C pass would have compared.

At a system level: configure an upstream with algorithm = "latency" and 3+ targets; traffic will concentrate entirely on whichever backend currently has the lowest measured latency, which is exactly the herd/overload behavior P2C exists to prevent (the "winning" backend gets all traffic, its own latency rises under load, and the pattern can oscillate/concentrate rather than balance).

Anything else?

I have a fix and a regression test ready and will open a pull request referencing this issue.