Decision records

ADR-0042: Per-domain request counters

Status: accepted — 2026-10-08

Context

The console shows traffic per application; operators of hosting servers ask per domain (“how many requests reach example.com, how many fail, how much is served”). Request counters are labelled by tenant, application, listener, method, status class and handler; adding the request host as a label would let clients choose the cardinality and would put a label-set hash on every request.

Decision

  1. The domain is a configured pattern, not the request’s Host. The host router (exact and single-label wildcard) already resolves the request to a configured domain; that entry carries a small integer id assigned when the configuration is prepared. A wildcard is one series. Requests whose host matches nothing, and domains beyond metrics.max_hosts (default 1000, at most 16383), count as _other.
  2. Counters by index, sharded per event loop. Each id has ten 64-bit counters: status class (5), response body bytes, request body bytes read, cache hit (incl. stale), cache miss, in flight. Shards are per event loop (ADR-0029), allocated in chunks of 64 domains on first use; updates are relaxed atomic adds on the loop’s own cache lines. /metrics sums the shards. No lock, no label hashing, no allocation per request.
  3. Small first version: no per-domain latency (a histogram per domain costs memory and update time with thousands of domains; latency stays per application), no HTTP version split. Both can be added as opt-ins.
  4. Reloads keep counters of domains that stay; removed domains are retired after the old generation drained, and their ids are reused.
  5. metrics.hosts: false turns it off (no ids assigned, nothing recorded or exported).

Gate

On by default only if, against metrics.hosts: false on the same binary, throughput regresses by at most 1 % (2 % hard limit) and CPU per request shows no meaningful increase on respond, static-small, proxy and PHP. Results: docs/benchmarks/2026-10-08-domain-metrics.md.

Consequences

  • Series per instance: up to 5 status classes + 4 others per domain with traffic (domains without requests are not exported).
  • A straggler request of a retired generation may count into the id’s next owner (only after the drain window).