Search

↑↓ to move↵ to openesc to closetop 6 documentation results

Book a demoTry TokenTriage

In development · Explore the product preview. Join the launch list or book a walkthrough.

Browse docs

Autoscaling

Turn on the HorizontalPodAutoscaler, meet the gate that stops it growing an uncoordinated fleet, and know what a scale event moves.

How-to guideLast updated

On this page

Let Kubernetes add replicas under load. The chart ships a HorizontalPodAutoscaler for the StatefulSet, off by default, and turning it on means committing to a coordinated fleet. The chart refuses to render an autoscaler that could grow past one replica without the shared state underneath it.

Prerequisites: metrics-server installed in the cluster, and a Redis and Postgres you are willing to point at. Read multi-replica & scale-out first if you have not; this page is the mechanism, that one is the model.

Meet the gate first

The topology guard reads the ceiling of the reachable fleet, the maximum of replicaCount, minReplicas and maxReplicas, rather than the floor. An autoscaler with a floor of one and a ceiling of six could still run six uncoordinated gateways under load, which is exactly what the guard exists to prevent.

Miss the tier and nothing renders:

$ helm template tt deploy/helm/tokentriage -f autoscaling-values.yaml
Error: values don't meet the specifications of the schema(s) in the following chart(s):
tokentriage:
- at '': 'allOf' failed
  - at '': validation failed
    - at '/experimental/multiReplica/enabled': value must be true
    - at '/externalState': validation failed
      - at '/externalState/postgres/enabled': value must be true
      - at '/externalState/redis/enabled': value must be true

The schema refuses first and names every missing piece at once. A template guard behind it carries the longer explanation, and you meet that one only if schema validation is skipped — multi-replica & scale-out quotes it in full.

Enable it

The chart maps externalState.redis and externalState.postgres onto the binary’s shared_state config; that key is chart-managed, so set it through these values rather than inside config:.

autoscaling:
  enabled: true
  minReplicas: 2 # chart default; the fleet floor. Keep >= 2.
  maxReplicas: 6 # chart default; the HPA ceiling.
  targetCPUUtilizationPercentage: 80 # chart default; 0 omits the CPU metric.
  targetMemoryUtilizationPercentage: 0 # 0 omits the memory metric (the default).

experimental:
  multiReplica:
    enabled: true # required for any fleet > 1

externalState:
  redis:
    enabled: true
    external: 'allow' # the binary's own consent string
    addr: redis.data.svc:6379
    auth: { existingSecret: tt-redis, key: password }
  postgres:
    enabled: true
    external: 'allow'
    dsn: { existingSecret: tt-postgres, key: dsn }
$ helm upgrade --install tt deploy/helm/tokentriage \
    --namespace tokentriage --create-namespace \
    -f autoscaling-values.yaml

Those five autoscaling values are the chart defaults verbatim. Two escape hatches exist for setups the defaults do not reach: autoscaling.extraMetrics appends raw autoscaling/v2 metric entries, and autoscaling.behavior passes a scaleUp/scaleDown block through.

Confirm what rendered

An autoscaling/v2 object targeting the StatefulSet, with the memory metric omitted because its target is zero:

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: tt-tokentriage
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: StatefulSet
    name: tt-tokentriage
  minReplicas: 2
  maxReplicas: 6
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 80

Then check it is attached and reading a metric:

$ kubectl get hpa -n tokentriage

A TARGETS column showing <unknown> means metrics-server is missing or not yet reporting, and the autoscaler will not act until it resolves.

What a scale event moves

A limit with store: share enforces a global approximation by splitting across the live fleet, and on every scale event that split re-derives from the new membership, so the fleet ceiling holds at any N:

fleet ≤ N × ceil(amount / N_live), over-admitting at most N−1 from rounding.

The one transient worth knowing is the join. A newly started pod is not in the roster until its first heartbeat, so for that window the split is computed against the smaller live N and the fleet can briefly admit slightly more than the steady-state ceiling. It is bounded, fail-open, and disclosed on the response header and the limits panel rather than smoothed over.

Token and request quotas converge over the larger fleet rather than splitting, so a scale event changes their convergence lag and not their bound.

Two consequences of an autoscaled fleet

A disruption budget becomes valid. A PodDisruptionBudget on a singleton is refused, because an availability budget on one pod blocks node drains for no benefit. The guard keys on the autoscaling floor, so a fleet with minReplicas: 2 is not a singleton and pdb.enabled: true is accepted even with replicaCount left at its default.

An upgrade re-seeds and then re-scales. A helm upgrade sets the StatefulSet back to minReplicas, and the autoscaler climbs again toward current load. Treat minReplicas as the value a redeploy settles to momentarily, not as a step-down of your live fleet.

Why no Service sets session affinity

None of the Services set sessionAffinity, and that is deliberate rather than an omission. The data plane is stateless per request, and each pod owns its own ledger, so pinning a client to a pod would defeat load spreading without buying any correctness, because the fleet read merges per-pod ledgers regardless of which pod served which request.

The one thing that is pod-affine is a routing affinity pin, which lives inside a request’s decision rather than in the load balancer.

What this does not cover

The coordination model, the disclosed bounds and the churn residuals belong to multi-replica & scale-out. Choosing a store per limit is enforcement classes. Deciding whether you need more replicas at all, and what saturates first, is sizing and capacity.