Traffic Shadowing
License: pro tier and above. See Licensing.
Standing up a new datacenter or pool? Traffic shadowing clones a percentage of a service's real queries and resolves them a second time against a shadow pool — same candidate building, same filter chain — but the result is discarded, never returned to the client. You get real production query patterns hitting the candidate infrastructure before it ever serves a single real answer, with zero risk to the live path.
How It Works
query arrives ──▶ resolve against the primary pool (as always) ──▶ answer sent to client
│
└─(shadowPercent% of the time)─▶ go resolve against the shadow pool
(same filter chain, discarded result)
│
└─▶ recorded: healthy? how long did it take?
Shadow resolution always runs in its own goroutine, fired independently of whatever the primary route decided (even if the primary pool is down and served a fallback CNAME) — dark-testing wants to see the traffic a query would generate, regardless of whether the live pool happened to serve it. It never blocks or slows down the real answer.
Configuring It
WebUI: open a service (from the topology view or its detail page) → Traffic Shadowing card → pick a shadow pool and a percentage.
API:
{
"shadowPoolId": "pool-uuid",
"shadowPercent": 10
}gslbctl:
gslbctl services update <id> --name web --domain www.example.com \
--shadow-pool <pool-id> --shadow-percent 10
gslbctl services shadow-stats <id>Terraform:
resource "nexus_service" "web" {
name = "web"
domain = "www.example.com"
pool_id = nexus_pool.primary.id
shadow_pool_id = nexus_pool.candidate_dc.id
shadow_percent = 10
}
Set shadowPercent to 100 to shadow every query while you're confident in the candidate pool's capacity; dial it down for a cautious ramp-up.
Watching the Results
The Traffic Shadowing card (WebUI) and gslbctl services shadow-stats <id> both show, from the last completed 60-second window:
- Primary — the live path's QPS and average latency (the same numbers the dashboard already tracks).
- Shadow — how many shadow resolutions ran, what fraction found a healthy candidate in the shadow pool, and the shadow path's own resolution latency.
The topology view also draws a dashed amber edge from the service to its shadow pool, labeled with the configured percentage — display only, it can't be dragged or deleted from the canvas (use the edit panel or API to change shadow config).
Notes
- Shadow stats are in-memory, rolling-window telemetry — the same approach this codebase already uses for primary QPS/latency, not a per-query database write. At
shadowPercent=100, writing one DB row per shadowed query would double the DNS engine's write load on the datastore for a feature that's pure observability; a 60-second in-memory window is the honest trade. - A shadow resolution reuses the service's own filter chain (or algorithm) against the shadow pool's members — so it exercises the same routing logic, health checks, and any scriptable steps, just against different backends.
- Not gated by DNSSEC, RRL, or RPZ — shadow resolution runs after those checks, mirroring only the pool-selection part of the primary path.