Overload protection — Versola Docs
/docs
Pre-1.00.7.5versola.ioGitHub

Overload protection

How an edge refuses surplus requests quickly instead of queueing them, the three modes, the priorities, what every number means, how to set it in the console or by API, and how to roll it out and tune it.

When an edge is asked for more than it can serve, it is better to refuse the surplus at once than to queue it until every request is slow. Overload protection does that, and it is configured per edge from central, so you can change it without redeploying. You need an edge that is already registered and an operator with edges:manage; edges:read shows the settings and nothing more.

What it protects

Two guards, each with its own mode and its own settings:

GuardWhereWhat it limits
Inbound admissionedgeRequests in flight at the edge. Above the limit a request is refused with 503 and a Retry-After header.
Outbound callsevery service, per peerCalls the edge makes to auth and to your resource backends: calls in flight, a bounded wait, a request timeout, a circuit breaker and a retry budget. A refusal is 503, a call that took too long is 504.

central is never limited by the outbound guard, so a service can still wait for its configuration at start-up, and nothing is enforced until the service has started.

The three modes

ModeWhat happens
offNothing is counted, nothing is learned, nothing is refused.
shadowThe edge counts what enforce would refuse and refuses nothing. This is the default, so an upgrade changes nothing until you choose otherwise.
enforceThe edge refuses what is above the limit.

Priorities

Inbound admission does not treat requests alike. A request is admitted while the requests in flight are below its tier’s share of the limit, so the top of the limit stays free for work that matters more:

  1. New login (the most expensive, work not yet started): refused first. It may use the start share of the limit, default 0.65.
  2. Work already begun (code exchange, logout): refused next. It may use the continuation share, default 0.85.
  3. Proxied calls to your API: refused last. They may use the whole limit.
  4. Health and metrics, configuration sync and back-channel logout: never counted and never refused.

What the numbers mean

The limit is not a number someone guessed. The edge learns it from how long calls take and from how late its own executor runs a timer: it shrinks when calls slow down or the executor stalls, and grows back while calls are fast, between a lowest and a highest limit. It is learned whenever the inbound mode is not off; in shadow it is learned and nothing is refused. The starting limit is only where it begins (clamped into the bounds).

Every field is optional. A field left empty is not sent, and the edge uses its own deployment’s value: the HTTP_ADMISSION_* or HTTP_CLIENT_GUARD_* environment variable when the edge sets one, otherwise the built-in default. Setting one field does not pin the rest.

Inbound

FieldDefaultEnvironment baseMeaning
modeshadowHTTP_ADMISSION_MODEoff, shadow or enforce.
maxInFlight (Starting limit)256HTTP_ADMISSION_MAX_IN_FLIGHTWhere the learned limit starts, 1 to 100000.
continuationShare0.85HTTP_ADMISSION_CONTINUATION_SHAREShare of the limit work already begun may use, 0.01 to 1.
startShare0.65HTTP_ADMISSION_START_SHAREShare a new login may use, 0.01 to 1, not above continuationShare.
retryAfterSeconds1HTTP_ADMISSION_RETRY_AFTERThe Retry-After of a refusal, 1 to 60 s.
adaptive.minLimit16noneThe learned limit never goes below this, 1 to 100000.
adaptive.maxLimit1024noneThe learned limit never goes above this, 1 to 100000, not below minLimit.
adaptive.tolerance1.5noneHow much slower than usual a call may get before the limit shrinks, 1 to 5.
adaptive.smoothing0.2noneShare of each new estimate that replaces the limit, 0.01 to 1.
adaptive.lagTargetMillis50noneHow late the executor may run a timer before it counts as stalled, 5 to 10000 ms.

A starting limit below minLimit or above maxLimit starts at the nearest bound.

Outbound

FieldDefaultEnvironment baseMeaning
modeshadowHTTP_CLIENT_GUARD_MODEoff, shadow or enforce.
maxConcurrentconnection pool size (128)HTTP_CLIENT_GUARD_MAX_CONCURRENTCalls in flight towards one peer, 1 to 100000.
maxQueue256HTTP_CLIENT_GUARD_MAX_QUEUECalls allowed to wait for a place; one more is rejected at once, 1 to 100000.
queueTimeoutMillis1000HTTP_CLIENT_GUARD_QUEUE_TIMEOUTThe longest a call waits for a place, 1 to 600000 ms.
requestTimeoutMillis10000HTTP_CLIENT_GUARD_REQUEST_TIMEOUTThe longest a call may take to the response headers, 1 to 600000 ms.
breaker.enabledoffnoneThe circuit breaker is off unless set.
breaker.failureRatio0.5noneShare of failed calls (a transport error, a timeout, 502, 503, 504) that opens the circuit, 0.01 to 1.
breaker.minCalls20noneCalls the window must hold before the ratio counts, 1 to 10000.
breaker.windowSeconds10noneThe period the ratio is measured over, 1 to 300 s.
breaker.openSeconds5noneHow long the circuit refuses at once before probing, 1 to 600 s.
breaker.probeCalls3noneCalls let through to test a half-open circuit, 1 to 100.
retryBudget.ratio0.1noneRetries allowed per call made, 0 to 1.
retryBudget.minPerSecond5noneRetries always allowed, whatever the ratio, 0 to 1000.

Queueing and timeouts apply in enforce only.

Set it in the console

Open Edges, open the edge, and use Edit on the Overload protection card. The editor opens in the side panel: an inbound section and an outbound section, each with a short note on how to tune it, the mode as a choice of Deployment default, Off, Shadow and Enforce, and every other field with the default (and its environment variable) written under it. The rarely changed tolerance, smoothing and lag target sit under Advanced.

The Overload protection card shows what is set here; everything else comes from the deployment
The edge panel with the Overload protection card: inbound Enforce and outbound Shadow set here, the fields that differ from the deployment, and the Reset to deployment defaults button
Every field is optional; the default it falls back to is written under it
The Edit overload protection panel: the inbound and outbound sections with their modes, fields, the defaults under each field and the Reset to deployment defaults, Cancel and Save buttons

The editor checks the ranges as you type, with the same messages central uses, and does not send a policy that fails them. Save overload protection sends only the fields you filled in. Reset to deployment defaults removes the policy after a confirmation, and the edge goes back to its own deployment’s values.

A change reaches the edge on its next configuration sync, inside the refresh interval (5 minutes by default). While central is unreachable the edge keeps the last policy it received. A policy that fails validation is refused by central and not applied by an edge.

Set it by API

PATCH /configuration/edges/policies?edgeId=<id> with an overload section replaces the policy as a whole; "overload": null removes it, and the other sections of the edge (accessLog, rateLimits) are left as they are. The section is the OverloadPolicy schema in the central OpenAPI file. Fields the schema does not know are ignored. expectedRevisions and dryRun are described on Rate limits.

{
  "overload": {
    "inbound": {
      "mode": "enforce",
      "adaptive": { "maxLimit": 1536 }
    },
    "outbound": { "mode": "shadow" }
  }
}

A value outside its range answers 400 with no body; the first problem found is in central’s log.

Roll it out

  1. Leave shadow, the default, for at least a day of real traffic that includes a peak.
  2. Read http_admission_refused_total{mode="shadow"}. Near zero while the edge has capacity means the limit is not in the way. Many shadow refusals at the peak mean enforce would shed that traffic: raise adaptive.maxLimit or add capacity first.
  3. Set the inbound mode to enforce on one edge. Watch refusals, throughput and latency through a peak, then do the others.
  4. Do the outbound guard one step later in the same way, and turn the circuit breaker on last.

What a client sees in enforce: 503 with Retry-After: <retryAfterSeconds>. A client should wait at least that long, add jitter, and retry a bounded number of times; an immediate retry loop makes the overload worse.

Read the metrics

MetricWhat it tells you
http_admission_refused_total{tier,mode}Refusals by tier; in shadow, what enforce would have refused.
http_admission_in_flight, http_admission_in_flight_peakRequests in flight now, and the highest value since the previous sample.
http_admission_limitThe learned limit in force.
http_admission_adaptive_limit_ratioThe learned limit over maxLimit; near 1 means room, low means the edge is squeezed.
http_admission_rtt_seconds{tier}Mean call time of the last window, by tier.
http_executor_lag_secondsThe worst lateness of a 10 ms timer in the last second.
http_client_guard_rejected_total{mode}Outbound rejections; in shadow, what enforce would have rejected.
http_client_guard_in_flight, _waiting, _breaker_stateOutbound calls in flight, waiting, and the circuit state.

Tune it

SymptomChange
Refusals in enforce while the pod is idleRaise adaptive.minLimit, or adaptive.maxLimit if http_admission_limit sits at it.
Latency climbs before the edge refuses anythingLower adaptive.tolerance (nearer 1) or adaptive.lagTargetMillis.
The limit swings up and downLower adaptive.smoothing.
New logins are refused while proxied calls are fineThis is the intended order. Raise startShare only if logins matter more than proxied calls.
Clients retry in a tight loop after a refusalRaise retryAfterSeconds.
Calls to a slow backend pile upLower outbound maxConcurrent, and set requestTimeoutMillis slightly above the backend’s p99.
A failing backend keeps receiving callsTurn the circuit breaker on, and tune failureRatio and openSeconds.

Next steps

  • Rate limits — refusing a caller that sends more than its share, with 429
  • Observability — the metrics endpoint and what else to alert on
  • Edges — the screen this page adds a card to
  • Edge configuration — the environment of an edge