Scaling and High Availability#
User Info Service separates the work of keeping the directory current from the work of serving requests, and this shapes how it scales and how available it is. The read path scales out simply by running more readers; the write path runs as a single instance and is deliberately not scaled. This page describes the scaling model for each role, how the two roles coordinate through the shared store, how the deployment behaves when a component is lost, how to size memory and CPU, and how to hand the reader replica count to an external autoscaler.
For the roles themselves and how data reaches the readers, see Directory Synchronization and Data Freshness; for the shared store the two roles use, see Database Configuration.
Reader and Writer Scaling Model#
The service runs as two separate workloads with opposite scaling characteristics:
Readers serve the REST and gRPC APIs and scale horizontally. Each reader holds its own in-memory copy of the directory, so adding replicas adds read capacity without any coordination between them. This is the dimension operators scale.
The writer keeps the shared store aligned with the identity provider and runs as a single instance that is not scaled. It is off the request path, so it does not contribute to read capacity.
Because the two roles are independent workloads, they are sized and scaled independently: the reader count follows request volume, while the single writer is sized only for the cost of synchronizing the directory.
Scaling Read Capacity#
Every reader loads the stored directory into memory on startup and reloads it on its refresh interval, then answers every request from that in-memory copy. Readers never contact each other and never contact the identity provider for synchronization, so a request served by one reader takes no coordination with any other. Read capacity therefore grows roughly linearly with the number of readers.
The Kubernetes Service created by the chart selects reader pods only, and routes both the REST and gRPC ports to them. Traffic is balanced across the ready readers, so a request may land on any reader; because each reader refreshes on its own schedule, two readers can briefly return slightly different data for the same request, as described in Data Freshness and Eventual Consistency.
Set the number of readers with reader.replicaCount, or hand the count to an external autoscaler
as described in Horizontal Autoscaling. Each
additional reader opens its own connections to the shared store, so scaling readers out also
raises the store’s connection load; size the database’s connection limit for the whole fleet as
described in
Connection Budget and Scaling.
Why the Writer Runs as a Single Instance#
A full synchronization rebuilds the stored directory from a single sequential stream of changes read from the identity provider, and only one may run at a time. The writer coordinates this through an advisory lock in the shared database, so even if a second writer were started it could not run a full synchronization concurrently — it would only duplicate identity-provider and database load while waiting for the lock. Running more than one writer therefore cannot speed synchronization up. See Coordinating a Single Full Synchronization for the locking model.
For this reason the writer’s replica count is fixed at one by the chart and is not configurable.
The writer Deployment also uses the Recreate update strategy, which stops the existing writer
pod before starting the replacement, so a rollout never briefly runs two writers at once.
High Availability#
Availability of each part of the service follows from the scaling model above.
Read path. Run more than one reader for a highly available API. The Service routes only to ready readers, so losing a single reader pod — to a node failure, an eviction, or a rolling update — does not interrupt the API as long as another reader stays ready. Spreading readers across nodes with
affinityimproves resilience to node loss; the chart does not set a default anti-affinity rule or a PodDisruptionBudget, so an operator that needs those guarantees supplies them through the chart’saffinityvalue and their own PodDisruptionBudget.Writer outage. Because the writer is off the request path, a writer that is down, restarting, or rolling out does not affect read availability: readers keep serving the last complete directory they hold in memory. Only data freshness pauses — the directory stops advancing until the writer returns — after which synchronization resumes from its saved progress, as described in Recovering from Interruptions.
Store outage. A reader that is already serving keeps serving its in-memory copy when the store is briefly unreachable, rather than dropping to an empty directory. A reader that starts while the store is unavailable, or against an empty store, has nothing to load and serves an empty directory until the store is reachable and holds data. For a highly available deployment, make the PostgreSQL store highly available as well; see Database Configuration.
Live mode. The reader-only live mode has no writer and no store, so every request depends on the identity provider directly and its availability and latency are tied to the provider. It is intended for initial testing rather than for a highly available production deployment; see Live Mode.
Memory and CPU Sizing#
Each instance — the writer and every reader — holds the full directory it serves in memory, so memory is the resource most sensitive to directory size. The largest factor is whether group memberships are synchronized: memberships are the most expensive category to hold, and enabling them raises the memory each instance needs substantially. For a large directory — on the order of 100,000 users and 170,000 groups — the in-memory membership index alone can approach 2.5 GB, on top of the memory used for users and groups. Directories without memberships need considerably less.
The chart’s default reader and writer requests and limits (reader.resources, writer.resources)
suit a small to moderate directory. Raise them when serving a large directory, and raise them
further when membership synchronization is enabled, so the limit comfortably exceeds the resident
directory size. Because every reader holds an independent copy, memory use across the fleet is the
per-instance figure multiplied by the reader count; horizontal scaling trades total memory for
read capacity and availability.
Startup is also sensitive to directory size, but only for readiness rather than steady-state
resources. The writer reports ready only after its first full synchronization completes, which can
take hours for a large directory with memberships. The readiness probe’s failure budget
(readinessProbe.failureThreshold, together with initialDelaySeconds and periodSeconds) must
be large enough to cover that first synchronization, or Kubernetes restarts the writer before it
finishes. The chart’s comments include a probe example sized for a large directory. Readers, by
contrast, become ready in seconds by loading whatever the store already holds, so their readiness
budget does not need to grow with directory size. See
What Gets Synchronized
for the effect of memberships on synchronization time and memory.
Horizontal Autoscaling#
Reader replicas can be driven by an external autoscaler instead of a fixed count. The
autoscaling.enabled value controls only whether the reader Deployment carries a fixed replica
count:
When
autoscaling.enabledisfalse(the default), the reader Deployment setsreplicas: reader.replicaCount.When
autoscaling.enabledistrue, the reader Deployment omitsreplicasentirely, so an external controller can own the replica count without the chart fighting it on every upgrade.
The chart does not create a HorizontalPodAutoscaler. Enabling autoscaling only frees the
replica field; the operator must supply their own HorizontalPodAutoscaler (or equivalent
controller) targeting the reader Deployment. A CPU-based HorizontalPodAutoscaler works with this
chart because the reader container sets a CPU resource request, which is what the autoscaler
divides observed usage against; the writer is never a scaling target.
Configuration#
The settings that shape scaling and sizing are all in the Helm chart.
Setting |
Helm value |
Default |
Description |
|---|---|---|---|
Reader replicas |
|
|
Number of readers when |
Externalize the replica count |
|
|
Omit |
Reader resources |
|
|
CPU and memory requests and limits for each reader. Raise for large directories and when memberships are enabled. |
Writer resources |
|
|
CPU and memory requests and limits for the single writer. |
Reader reload interval |
|
|
How often a reader reloads the directory from the store. See Directory Synchronization and Data Freshness. |
Writer readiness budget |
|
|
Number of failed readiness checks tolerated at startup, with |
Store connection budget |
|
|
Connections each instance opens to the store. See Connection Budget and Scaling. |
Reader scheduling |
|
|
Pod affinity/anti-affinity for the reader, e.g. to spread readers across nodes for high availability. |
Set the replica bounds and target metric on the HorizontalPodAutoscaler you create yourself; the chart carries no autoscaling tuning of its own.
Configuring Scaling in the Helm Chart#
For a fixed number of readers, set reader.replicaCount and size the reader and writer resources
to the directory. The example below runs three readers and raises memory for a directory with
membership synchronization enabled:
reader:
enabled: true
replicaCount: 3
resources:
requests:
cpu: 500m
memory: 2Gi
limits:
memory: 4Gi
writer:
enabled: true
resources:
requests:
cpu: 500m
memory: 2Gi
limits:
memory: 4Gi
readinessProbe:
initialDelaySeconds: 5
periodSeconds: 60
failureThreshold: 300 # 5 + 300×60 ≈ 5 hours for a long initial sync
To hand the reader count to an external autoscaler instead of pinning it, enable autoscaling so
the Deployment omits its replica field, and create your own HorizontalPodAutoscaler targeting the
reader Deployment:
autoscaling:
enabled: true
Install or upgrade the release with the same commands used for the synchronized directory deployment in User Info Service Configuration.
References#
Horizontal Pod Autoscaling — the Kubernetes controller and API object an operator creates to drive the reader replica count, and its requirement that the target’s containers set the resource requests it scales against.
Recreate Deployment strategy — the writer’s update strategy, which stops the existing pod before starting its replacement.