Scaling and availability¶
HOG is stateless by design: there is no leader election, no shared control plane, and no in-memory state that a request depends on having hit a particular replica before. Scale it the same way you'd scale any stateless HTTP service.
Horizontal scaling¶
Run as many replicas as you want behind a load balancer. Any replica can
serve any request — including completing an OIDC login flow a different
replica started, since the transient login state (state, nonce, PKCE
verifier) travels in a short-lived, encrypted hog_login cookie, not
server-side memory.
Rolling updates are safe: because there's no coordination between
instances, an old and a new replica can serve traffic side by side during a
deploy with no compatibility handshake required, as long as the config
(especially session.key) doesn't change out from under a running session.
Session state¶
How much a request depends on shared state is entirely a function of
Gateway.spec.stateProvider:
-
Cookie-self-contained (default — no
stateProvider). The whole session — passport, groups, access token, expiry, fingerprint — is sealed with AES-256-GCM directly in the cookie. Any replica holding the samesession.keycan decrypt any session; there is no server-side session store to keep in sync, replicate, or fail over. The trade-off: the refresh token is never stored (it's too sensitive to hold client-side), so there is no silent access-token refresh — an expired session simply requires re-login. -
Delegated (opt-in,
stateProviderconfigured). The cookie holds only an opaque session ID; the full record — including the refresh token — lives in an external store you provide, encrypted by HOG before it's written. This unlocks silent refresh: HOG renews the access token behind the scenes as it nears expiry (stateProvider.refreshSkew, default60s), single-flighted per session within one process and tolerant of a benign double-refresh across instances. A store implements a smallGet/Set/Delete-with-TTL interface (session.StateStore) as a plugin, registered under atypename — HOG ships one such plugin,github.com/paulopiriquito/hog/plugins/statestore-valkey, backed by Valkey:
kind: Gateway
metadata: { name: my-gateway }
spec:
listen: ":8080"
plugins:
- github.com/paulopiriquito/hog/plugins/statestore-valkey@v1.0.0
session:
key: ${SESSION_KEY}
stateProvider:
type: valkey
refreshSkew: 60s
config:
address: valkey.example.com:6379
user: hog
password: ${VALKEY_PASSWORD}
db: 0
tls: true
timeout: 3s
plugins compiles the module into the binary via
hog-build; stateProvider.config's
address, user, password, db, tls, and timeout are that plugin's
own fields — the plugin has no TTL setting of its own, since a record's
lifetime is always whatever ttl HOG's session manager passes to Set
(Gateway.spec.session.ttl), not something the store configures
independently. A different store is still a small interface to implement —
see the plugin's own module for a worked example.
Choosing delegated state means your session store (not HOG) becomes the
thing that needs its own availability story — size and replicate it the way
you would for any shared cache. See
developer: writing plugins to implement
one, and configuration: Gateway.stateProvider
for the field reference.
No shared control plane¶
Every HOG instance loads its own copy of the config at startup from
--config and never phones home to any other instance. There is nothing
like a control-plane API, a gossip protocol, or a shared cache that
instances must agree on to serve correctly — consistency across replicas is
achieved simply by giving every instance the same config and the same
session.key, which any config-distribution mechanism you already use
(a baked image, a mounted ConfigMap, etc.) already gives you.
Health checks¶
HOG has no mandatory built-in health endpoint — mount one explicitly with
the health terminal handler, which always returns 200
{"status":"ok"} and needs no config:
kind: Route
metadata: { name: healthz, labels: { tier: system } }
spec:
match: /healthz
handler: { type: health }
access: { auth: public }
Point your load balancer's/orchestrator's liveness and readiness probes at
this path. Because it's just a route, you can label it and put it behind
whatever authorization policy fits your environment (typically public, so
the probe doesn't need credentials).