Latency Algorithm

Latency-Based Routing

How It Works

The latency algorithm routes DNS queries using three signals:

  1. EDNS0 Client Subnet — If your resolver forwards the client subnet (RFC 7871), Nexus extracts a /24 (IPv4) or /48 (IPv6) prefix and maps it to a country via MaxMind GeoLite2.
  2. GeoIP Country Lookup — Client country is matched against endpoint regions.
  3. Node RTT — Health probes measure TCP/HTTP round-trip time from each cluster node. A rolling average of the last 5 probes smooths noisy data.

These combine into a weighted selection:

combined_score = region_bonus + (1 / avg_rtt_ns)

Endpoints in the client's country get a region bonus; faster endpoints (lower RTT) get higher selection probability. Traffic is distributed by weighted random selection, not always sent to the single fastest endpoint.

Configuration

loadBalancer:
  algorithm: "latency"
  latency:
    geoipDBPath: "/var/lib/nexus/GeoLite2-Country.mmdb"
    regionBonusMultiplier: 2.0   # region match weighs 2x RTT weight
    minRTTThresholdMs: 5        # don't shift for <5ms differences
    fallbackRegion: "unknown"
Field Type Default Description
geoipDBPath string "" (falls back to geoip.dbPath) Path to MaxMind .mmdb file
regionBonusMultiplier float 1.0 Multiplier for same-region bonus
minRTTThresholdMs int 5 Hysteresis floor in milliseconds
fallbackRegion string "unknown" Region label when GeoIP unavailable

Endpoint Regions

Each member has a region field set when creating or updating via the API:

POST /api/v1/pools/{id}/members
{
  "ipAddress": "10.0.1.5",
  "port": 80,
  "region": "US"
}

Region codes match the ISO 3166-1 alpha-2 country codes returned by the GeoIP database (e.g. "US", "DE", "JP"). Free-form values are also supported — the match is an exact string comparison, so be consistent.

EDNS0 Support

EDNS0 CLIENT_SUBNET is opt-in and depends on your recursive resolver configuration.

Resolver Forwards ECS Notes
Enterprise DNS (BIND, Knot, PowerDNS) ✅ Yes Most DNS operators enable by default
Cloudflare Resolver (1.1.1.1) ❌ No Strips for privacy
Google Public DNS (8.8.8.8) ❌ No Strips for privacy
Quad9 ❌ Mostly no Privacy-focused

When ECS is not available, Nexus uses remoteIP(w.RemoteAddr()) — the resolver's IP — for GeoIP lookup. For public resolvers that strip ECS, the region bonus is applied based on where the resolver is, not the client.

Hysteresis

To prevent rapid endpoint switching when RTT fluctuates slightly:

  • RTT differences below minRTTThresholdMs do not trigger a traffic shift
  • Rolling average of last 5 probes smooths single-probe spikes
  • Traffic can still shift when a region-match bonus outweighs a small RTT penalty

Fallback Chain

Signal Unavailable Fallback
EDNS0 client subnet No ECS option in request Use resolver IP from RemoteAddr()
GeoIP lookup DB not loaded or IP not found client_region = fallbackRegion → no region bonus
Node RTT Fewer than 3 probes collected Endpoint excluded from weighted selection
All RTT data No probes for any candidate Return first healthy candidate

Distributed RTT (Multi-Node Aggregation)

In a multi-node cluster each gslbd node probes backends independently. With distributed RTT enabled, nodes share their probe results via NATS KV and the latency algorithm uses the global minimum RTT across all nodes rather than only the local node's measurement.

How it works

  1. Each publisher writes its per-endpoint RTT to the NATS KV bucket gslb_{clusterID}_rtt under the key {nodeID}/{memberIP}.
  2. Each subscriber watches the same bucket and feeds a local RTTStore cache: ip → nodeID → {rttMs, ts}.
  3. latencyWeighted() calls RTTStore.GlobalMinRTT(ip) which returns the minimum RTT across all nodes whose data is less than 3 minutes old. Falls back to local AvgRTT() when the store has no data.

Requirements

NATS JetStream must be enabled (it is required for cluster state sync anyway). The RTT KV bucket is created automatically with a 5× health-interval TTL on each node that connects.

API and WebUI

GET /api/v1/pools/{id}/status returns both fields per member:

{
  "10.0.1.5": {
    "healthy": true,
    "score": 1.0,
    "rttMs": 4.2,       // local probe RTT from this node
    "globalRttMs": 2.1  // minimum across all cluster nodes
  }
}

The WebUI pool detail shows globalRttMs when available; hovers over it say "Global min RTT across cluster". When distributed RTT is disabled only rttMs is present.

Staleness handling

RTT entries older than 3 minutes are excluded from aggregation. If all entries for an IP are stale the store returns (0, false) and the local probe RTT is used as fallback.

Limitations

  • RTT is measured from cluster nodes to endpoints, not from clients. For clients far from all cluster nodes, the measured RTT may not reflect client-perceived latency.
  • EDNS0 is stripped by major public resolvers — for those clients, geography-based routing uses the resolver's IP, not the client's actual location.
  • The algorithm requires at least 3 successful health probes per endpoint before RTT data is used. During startup, traffic is routed round-robin until sufficient data is collected.

Was this article helpful?
© 2026