DEV Community

Machine coding Master
Machine coding Master

Posted on

Stop Using Round-Robin: High-Throughput Java Virtual Thread Routing with P2C

Stop Using Round-Robin: High-Throughput Java Virtual Thread Routing with P2C

Virtual threads allow Java microservices to comfortably run 50,000 concurrent requests per instance, but your legacy round-robin load balancer is utterly destroying your p99 latency. Blindly distributing traffic without accounting for dynamic queue depth creates catastrophic head-of-line blocking across Loom-enabled nodes.

Why Most Developers Get This Wrong

  • Assuming uniform execution speed: Round-robin assumes every request takes equal time, which breaks down the moment heavy virtual thread workloads hit asymmetric I/O bottlenecks.
  • Using raw CPU/Memory metrics for routing: Host-level metrics update every few seconds, making them completely blind to microsecond-level virtual thread queue spikes.
  • Falling into the "least-connections" herd effect: Naive least-connections algorithms target newly recovered instances simultaneously, flooding them with traffic until they instantly collapse under thread contention.

The Right Way

Use Power-of-Two-Choices (P2C) coupled with Exponentially Weighted Moving Average (EWMA) to make high-throughput, $O(1)$ routing decisions.

  • Randomly sample exactly two instances from your service discovery pool using ThreadLocalRandom.
  • Compute a real-time health score for both: $\text{Score} = (\text{Active Virtual Threads} + 1) \times \text{EWMA Latency}$.
  • Route the current request to the node with the lower score, mitigating tail latencies without lock contention.
  • Decay latency historical data continuously using an exponential decay factor to ignore outdated network blips.

Want to go deeper? javalld.com — machine coding interview problems with working Java code and full execution traces.

Show Me The Code (or Example)

public class P2CLoadBalancer {
    private final List<UpstreamNode> nodes;

    public UpstreamNode select() {
        int size = nodes.size();
        if (size == 0) throw new IllegalStateException("No healthy nodes");
        if (size == 1) return nodes.getFirst();

        int i1 = ThreadLocalRandom.current().nextInt(size);
        int i2 = (i1 + 1 + ThreadLocalRandom.current().nextInt(size - 1)) % size;

        UpstreamNode n1 = nodes.get(i1);
        UpstreamNode n2 = nodes.get(i2);

        // Pick the node with lower (ActiveRequests * EWMA Latency)
        return (n1.getEwmaScore() <= n2.getEwmaScore()) ? n1 : n2;
    }
}
Enter fullscreen mode Exit fullscreen mode

Key Takeaways

  • Round-robin is dead for high-concurrency Java architectures; virtual threads demand dynamic, latency-aware routing.
  • Power-of-Two-Choices (P2C) delivers near-optimal global load distribution while avoiding the heavy synchronization costs of full node scans.
  • Combine active request tracking with EWMA decay to keep your p99 and p999 tail latencies flat under bursty traffic.

Top comments (0)