Load balancing¶
Every pool sits behind a LoadBalancer. The balancer picks which resolved proxy handles the next request. YAML field reference: LoadBalancer · pool membership: Pool.
flowchart LR
Req[Request] --> LB[LoadBalancer]
LB --> P1[Proxy A]
LB --> P2[Proxy B]
LB --> P3[Proxy C]
Strategies at a glance¶
type |
Selects | Pool | Extra config |
|---|---|---|---|
round-robin |
Next proxy in rotation | Static or dynamic | — |
weighted |
Smooth WRR by member weight | Static only | Weights on pool.members |
least-bytes |
Proxy with lowest accumulated downstream bytes | Static or dynamic | Optional reset_interval (default 1m) |
Round-robin¶
Even distribution across whatever the pool currently resolves to. Works with dynamic pools (membership can change as labeled proxies appear/disappear).
kind: LoadBalancer
version: v1
metadata:
name: main-rr
spec:
type: round-robin
pool_id: dynamic-pool
Release is a no-op for this strategy — no per-request feedback is needed.
Weighted¶
Nginx-style smooth weighted round-robin: traffic approximates weight ratios without long bursts on the heavy member.
- Weights live on the static pool, not on the balancer:
kind: Pool
version: v1
metadata:
name: static-pool
spec:
type: static
members:
- proxy_id: proxy-1
weight: 3
- proxy_id: proxy-2
# weight omit → 1
---
kind: LoadBalancer
version: v1
metadata:
name: main-weighted
spec:
type: weighted
pool_id: static-pool
weightmust be ≥ 1 if set; default 1.- Apply rejects
type: weightedwhenpool_idpoints at a dynamic pool. - You cannot convert that pool to dynamic while a weighted balancer still references it.
Release is unused.
Least-bytes¶
Routes to the proxy with the fewest accumulated downstream bytes so far.
kind: LoadBalancer
version: v1
metadata:
name: main-least-bytes
spec:
type: least-bytes
pool_id: dynamic-pool
reset_interval: 1m # optional
What is counted?¶
| Path | Counted |
|---|---|
| HTTP response body → client | Yes |
| CONNECT tunnel upstream → client | Yes |
| Request body / client → upstream | No |
Bytes are reported when the request finishes via the balancer Release hook (handler measures io.Copy into the client).
Timing model¶
- Counters update only on
Release(after the transfer ends). Long-lived tunnels do not publish mid-flight totals yet. - In-flight byte estimates are intentionally out of scope for now.
- On tie (equal counters), the lowest index among current resolved proxies wins.
Reset¶
reset_intervalis a Go duration (30s,1m, …). Only valid onleast-bytes.- Default when omitted:
1m. - Reset is lazy: counters clear on the next
Next()after the interval has elapsed (no background ticker).
Measuring tip¶
curl -I (HEAD) often transfers little body data — poor for verifying least-bytes. Prefer full downloads:
curl -x http://localhost:8080 -o /dev/null https://httpbin.org/bytes/8192
Choosing a strategy¶
| Goal | Prefer |
|---|---|
| Simple even spread; dynamic labels | round-robin |
| Some proxies should take more share | weighted + static pool |
| Prefer less-loaded links by transferred volume | least-bytes |
Use a Router to send different traffic classes to different balancers (e.g. .weighted.local → weighted, .bytes.local → least-bytes). See Router.
Runtime notes¶
- Selection runs on the Data Plane hot path against an in-memory balancer instance.
- Config Watch / rebuild replaces the instance (state such as RR cursor or least-bytes counters resets on rebuild).
- Empty resolved proxy list → request fails (no proxy available).