Skip to content

Performance and Load Baseline

iderex edited this page Aug 12, 2026 · 3 revisions

Performance and load baseline

This page characterizes the login path's behavior under load and concurrency - the RC-readiness question of #742. The login path carries three load-oriented controls; each is pinned by regression tests that double as the behavioral demonstration, and a machine-independent latency baseline is recorded separately (see Latency).

The load controls and their proven behavior

Authorize-state store: cap and sweep

The OpenID authorize-state store bounds outstanding challenges at a global cap (OidcStateStore.DefaultMaxEntries = 100,000) and reclaims expired entries with a sweep throttled to at most once per minute - design in Security Model § In-flight login capacity. The behavior under a challenge flood is a regression test, not a construction claim (SSO-Auth.Tests/Oidc/OidcStateStoreTests):

  • At the cap a fresh challenge is refused, never an in-flight state evicted - TryAdd_AtCap_RefusesANewKeyAndKeepsTheInFlightState, TryAdd_DistinctKeys_FillToGlobalCap_ThenRefuseNotEvict. The refusal surfaces to the user as the clean Could not start login; please retry response (a 500), never an out-of-memory.
  • Per-client isolation - a flood from one client key cannot refuse a different key's challenge (TryAdd_FloodFromOneKey_DoesNotRefuseADifferentKey); each client is additionally held to a 1% share of the cap (up to 1,000 concurrent in-flight logins per client).
  • The capacity warning is throttled to once per interval, so a flood cannot amplify into unbounded logging (TryAdd_Refused_SignalsTheCapacityWarningOncePerInterval).
  • Under concurrency the store stays bounded and the sweep never corrupts it - TryAdd_UnderContention_StaysBoundedAndRejectsSome, PruneExpired_ConcurrentWithAdds_DoesNotThrowAndKeepsFreshEntries.
  • The sweep runs at most once per minute, keeping its O(n) scan off the anonymous hot path (PruneExpired_WithinTheInterval_IsThrottled_AndTheUnsweptEntryIsStillRejected).

Per-client rate limiting

The edge rate limiter throttles a hammering client with a 429 + Retry-After while a normal multi-stage login (including an MFA retry) never trips it - design in Security Model § Rate limiting. Pinned by the SsoRateLimiter and PerClientBudgetLimiter test suites, with the 429/Retry-After emission covered through the login status mapper.

Avatar fetch deadline

The optional avatar fetch is bounded by a deadline (and the SSRF allow-list) so a slow or hostile IdP-hosted image cannot stall the login; the AvatarService tests pin the deadline and abort behavior.

Latency

A machine-independent latency baseline (challenge + callback round-trip, nominal and concurrent) is a characterization, not a CI gate. It must be captured on a controlled runner (CI, or a representative server) rather than a developer laptop so the numbers are comparable release-over-release. The behavioral guarantees above hold regardless of the absolute latency.

Baseline, recorded 2026-08-10

Captured by the SSO-Auth.Bench harness on a GitHub-hosted runner through the Performance baseline (login latency) workflow, which archives the raw output as the login-latency-baseline artifact of its run.

scenario stage n p50 ms p95 ms p99 ms max ms
nominal challenge 2000 0.469 0.776 0.839 21.532
nominal callback 2000 0.369 0.584 0.630 27.548
concurrent challenge 2000 0.264 0.329 0.493 27.132
concurrent callback 2000 0.138 0.204 0.286 18.677

Concurrent throughput: 2104.4 round-trips/s, 2000 round-trips over 0.950 s of wall clock, 16 concurrent callers.

Provenance, which is what makes the next release's numbers comparable to these:

  • runner label ubuntu-24.04, runner image ubuntu24 20260720.247.2, 4 processors
  • .NET SDK 10.0.302; the harness reported runtime 9.0.18 X64
  • plugin commit 43488c88ce6d85d0943c77af09c543d1e6c88c2c
  • parameters --iterations 2000 --warmup 200 --concurrency 16
  • run 31369550693, scheduled trigger

Read them for what the harness measures and no further. The identity provider is served in process, Jellyfin's services are the mocks the unit tests use, and the measured legs stop at the callback, so a real deployment adds the IdP round-trip and session minting on top of these figures.

The max column is a property of a shared runner more than of the plugin: it sits between 25 and 66 times p99 across the four rows, which is what a noisy neighbour plus a JIT or GC pause looks like. Compare p50 and p95 release over release, and treat a moved max as a reason to re-run rather than as a regression. Nothing here gates: the workflow records, it does not fail on a slower number, and it is not a required check.

Mapping to #742

  • Cap / sweep / in-flight under concurrency (acceptance criterion 2) → the OidcStateStore regression tests above.
  • Rate-limit throttle vs. a normal login (criterion 3) → the rate-limiter test suites above.
  • Baseline latency numbers (criterion 1) → recorded from a controlled run (Latency).

Clone this wiki locally