Backend Load Balancing
Configuration guide for load balancing across multiple backend servers using Radiator Server's built-in algorithms, including connection pool management and high availability.
- Backend Load Balancing
- Quick Decision Guide
- How Radiator Chooses a Server
- fallback
- round-robin
- hash-balance
- RADIUS and EAP
- RadiatorDB
- least-connections
- no-fallback
- Server Priority
- How Radiator Decides a Server Is Unhealthy
- Server Health and Status
- Service Level Objective
- Fast Skip With Warm Pools
- How a Backend Request Is Handled
- Connection acquisition
- Multi-server routing
- Connection Pool Parameters
- max - pool size per server
- min - warm connection level
- idle-timeout - idle connection lifetime
- timeout - request time budget
- Backend-specific notes
- Backend Differences At A Glance
- Pool Monitoring
- Counter Names
- Common Counter Meanings
- Backend-Specific Differences
- Pool Size Log Fields
- Common Patterns
- SQL Primary/Replica (Active-Passive)
- SQL Read Scaling (Active-Active)
- LDAP Round-Robin
- RADIUS Active-Passive Failover
- Geo-Distributed Backends
- Related Documentation
Backend Load Balancing
Radiator can use multiple backend servers. This gives you load balancing and high availability.
With multiple servers, Radiator can:
- send all traffic to one preferred server and keep others as backup,
- spread traffic across many servers,
- prefer the least busy server,
- skip unhealthy servers, and
- return a recovered server back to use,
- keep warm connections ready for faster authentication and faster failover.
These behaviors are controlled by server-selection, server health settings, and
connection pool settings.
Quick Decision Guide
| Goal | Recommended setting | Notes |
|---|---|---|
| One primary server, others as backup | fallback | Best for active-passive high availability |
| Spread traffic evenly | round-robin | Good for active-active clusters |
| Keep the same request key on one backend server | hash-balance | Requires one or more explicit context fields |
| Prefer the least busy server | least-connections | Available for SQL, LDAP, and RADIUS |
| Do not hide backend failures | no-fallback | Request fails if the first server fails |
| Skip a dead server quickly | connections { min 1; } | Enables fast skip when the pool has zero live connections |
| Remove a server after repeated request failures | service-level-objective | Service Level Objective based health control |
How Radiator Chooses a Server
Radiator supports five server selection methods. Configure them with server-selection.
The default is fallback.
fallback
Radiator tries servers in priority order.
- The server with the lowest
priorityvalue is tried first. - Lower-priority servers are only used when a higher-priority server is not usable.
- This is the default behavior.
Use fallback when you want a primary server and backup servers.
backends {
radius "BACKEND_CLUSTER" {
server-selection fallback; # or omit, fallback is default
server "PRIMARY" {
priority 0; # Highest priority, tried first
# ...
}
server "SECONDARY" {
priority 1; # Lower priority, backup
# ...
}
}
}
round-robin
Radiator rotates requests across all usable servers.
- Request 1 goes to one server.
- Request 2 goes to the next server.
- If one server fails, Radiator tries the next usable server.
Use round-robin when all servers can handle the same work.
backends {
radius "BACKEND_CLUSTER" {
server-selection round-robin;
server "SERVER1" {
# ...
}
server "SERVER2" {
# ...
}
server "SERVER3" {
# ...
}
}
}
hash-balance
A hash converts input values into a repeatable number. Radiator combines the configured AAA context fields into a request key, hashes the key, and uses the result to choose a preferred backend server. The same field values select the same preferred server while the configured server identities remain unchanged.
server-selection hash-balance {
field radius.request.attr.Calling-Station-Id;
field radius.request.attr.User-Name;
}
Configure at least one field. Radiator combines multiple fields in the order
shown in the configuration. Configure the fields in the same order on every
Radiator instance that uses the backend pool.
If a field is not set, it contributes no value to the request key. Radiator still uses any other configured fields. If none of the fields are set, those requests use the same empty key and prefer the same server.
Choose fields that are available before the backend action runs and that vary
across the expected traffic. Use context.id to distribute independent
requests. Use protocol fields when related requests should prefer the same
server.
When multiple Radiator instances distribute traffic to the same backend pool,
map each physical backend to the same priority and server name on every
instance. Radiator uses priority and server name as the stable server identity.
Declaration order does not affect selection.
When a server becomes unavailable, only keys that preferred that server move. Radiator distributes those keys across the remaining servers. Keys whose preferred server remains available do not move. When the failed server recovers, its keys return to it.
RADIUS and EAP
For a multi-round RADIUS authentication, hashing chooses the server for the
first request. After an Access-Challenge, Radiator sends its own State
downstream to resume the proxy session on the selected server. Radiator stores
the backend server's State separately and sends its latest value with the next
backend request. The backend may change or omit this value between rounds. This
keeps EAP exchanges on the server that holds their session state.
Do not use only an outer EAP identity when clients commonly send the same anonymous identity. This can route most new EAP sessions to one server.
RadiatorDB
For RadiatorDB, choose fields that remain the same for every operation on the same document key. This keeps related reads and updates on the same node so write-then-read operations work correctly.
least-connections
Radiator sends the request to the usable server with the fewest busy connections.
If more than one server is tied:
- the lower
priorityvalue wins first, - then server name order is used.
This method is available for SQL backends (PostgreSQL and MySQL), LDAP backends, and RADIUS backends.
Use least-connections when backend work time varies and you want to avoid sending too
much work to one busy server.
backends {
postgres "USER_DB" {
server-selection least-connections;
server "replica1" {
host "pg-replica1.example.com";
connections {
max 15;
min 2;
idle-timeout 5m;
}
}
server "replica2" {
host "pg-replica2.example.com";
connections {
max 15;
min 2;
idle-timeout 5m;
}
}
query "FIND_USER" { ... }
}
}
no-fallback
Radiator only tries the first usable server.
If that server fails, the request fails immediately. Radiator does not try another server.
Use no-fallback when you want backend problems to be visible immediately.
backends {
radius "BACKEND_TEST" {
server-selection no-fallback;
server "SINGLE_SERVER" {
# ...
}
}
}
Server Priority
priority controls server order for fallback and no-fallback.
- Allowed values:
0to255 - Default:
0 - Lower number means higher priority
Example:
backends {
radius "TIERED_BACKENDS" {
server-selection fallback;
server "PRIMARY_DC" {
priority 0; # Highest priority, tried first
# ...
}
server "SECONDARY_DC" {
priority 1; # Tried if PRIMARY fails
# ...
}
server "DR_SITE" {
priority 2; # Last resort
# ...
}
}
}
If two servers have the same priority, Radiator uses alphabetical server name order.
How Radiator Decides a Server Is Unhealthy
Radiator can stop using a server in two different ways:
service-level-objective- Service Level Objective based health control for repeated request failures.- Warm-pool fast skip when a server has zero live connections.
These two mechanisms are different and can work together. See Backend Differences At A Glance for backend-specific support.
Server Health and Status
Each backend server tracks its own health state. Radiator can then:
- mark servers as unavailable after connection failures
- skip unavailable servers during selection
- mark servers as degraded when the
service-level-objectivethreshold is reached - exclude degraded servers from normal routing until probe requests succeed
- keep warm connections where configured (see Fast Skip With Warm Pools)
- attempt to reconnect to failed servers periodically
- return servers to service when connections succeed
Service Level Objective
Each backend server supports a service-level-objective
block.
Use this when you want Radiator to stop using a server after repeated request failures and to return that server to use after successful probe requests.
When the configured limit is reached:
- Radiator marks it as degraded,
- stops sending normal traffic to it, and
- sends only occasional probe requests until it recovers.
See Backend Differences At A Glance for backend-specific support.
Add an explicit service-level-objective block on a server if you want this health check.
For parameter details and examples, see
service-level-objective.
Fast Skip With Warm Pools
The warm-pool minimum tells Radiator to keep a minimum number of live connections open.
Configure it inside connections { ... }:
connections {
min 1;
}
When the warm-pool minimum is greater than 0, Radiator gets four behaviors at the
same time:
- Warm connections are kept ready.
- A server stays out of rotation during startup until the pool reaches the configured minimum.
- A server that reaches zero live connections stays out of rotation until it rebuilds the minimum.
- The
Unavailablecounter increases when Radiator skips the server.
After a pool first reaches the minimum, it can continue serving requests during nonzero capacity fluctuations below that value. Reaching zero starts a recovery cycle and requires the complete minimum again. This prevents request traffic from consuming partially rebuilt capacity.
Set the warm-pool minimum to at least 1 when fast failover matters.
With the default 0:
- the pool is lazy,
- Radiator opens connections only when needed, and
- Radiator learns that a server is bad only after a real request fails.
How a Backend Request Is Handled
The request flow below applies to SQL, RADIUS, and LDAP backends.
In the normal case, an idle connection or socket is already available and the request is sent immediately. The diagrams below show the more important failover and pool decisions.
Before the pool-side checks below, server selection already filters out servers
that are currently degraded by the service-level-objective.
If a warm-pool minimum is in use, the request path can also fast-skip a server that has
zero live connections. See Fast Skip With Warm Pools.
Connection acquisition
Multi-server routing
Connection Pool Parameters
SQL (PostgreSQL, MySQL), RADIUS, and LDAP backends all maintain a connection
pool per server block. The parameters below tune that pool. Most apply to
all three backend families; protocol-specific parameters and behavior are
called out under Backend-specific notes.
| Parameter | SQL | RADIUS | LDAP | Default |
|---|---|---|---|---|
connections { max } | yes | yes | yes (shared pool) | 10 |
connections { min } | yes | yes | yes (shared pool) | 0 (disabled) |
connections { idle-timeout } | yes | yes | yes | SQL: 5m; RADIUS/LDAP: none |
connections { exclusive } | no | no | yes | 10 |
timeout | yes (per query) | yes (server block) | yes (server block) | SQL: none; RADIUS: 5s; LDAP: 3s |
max - pool size per server
Default: 10
Set the maximum pool size with max inside the connections block:
connections {
max 10;
}
This value is the maximum number of open connections or sockets to one backend server.
For SQL, each connection serves one query at a time.
You can estimate pool size with:
connections = target_tps x (round_trip_ms + query_ms) / 1000
Example: with 10 ms network round-trip time and 1 ms query time at 1000 requests per second, you need about 11 connections.
For RADIUS the same limit applies, but each backend socket can carry up to 256 requests in flight at the same time. Because of that, the same number of sockets can support much higher request rates. See Backend-specific notes.
For LDAP the limit applies to the shared connection pool used for
server-bound search operations. Per-user bind operations use a separate
exclusive pool, configured with exclusive inside connections { ... } (default 10).
min - warm connection level
Default: 0 (disabled). Supported by: SQL, LDAP shared pools, and RADIUS.
This is the number of connections Radiator tries to keep open all the time.
Set it with min inside connections { ... }. For LDAP, min applies only to
the shared pool used by server-bound search operations.
When you set the warm-pool minimum above zero, Radiator gets three behaviors:
- Warm connections. A background task keeps the pool at this level
and reopens a replacement immediately when
idle-timeoutcloses a connection that would take the pool below this level. The first request does not wait for a new TCP or TLS connection setup, which improves authentication performance. - Unreachable-server detection. When the pool has zero established connections, Radiator skips that server immediately during server selection and fails over to the next server in the list. See Multi-server routing.
Unavailablecounter. Each fast-skip increments a dedicatedUnavailablecounter.
Set the warm-pool minimum to at least 1 on every server where fast failover matters.
With the default 0, load balancing still works, but fast skip is disabled. Radiator only
learns that a server is unreachable after a real request fails.
Single-server backends: The warm-pool minimum is an HA feature. On a backend with only one server, a warm-pool minimum of
1or higher makes requests fast-fail while the server is unreachable. The request path does not retry the dead server. Only the background maintainer does.
Error: The warm-pool minimum must not exceed the maximum pool size.
idle-timeout - idle connection lifetime
This is how long a connection above the warm-pool minimum can stay idle before being closed. Accepts duration units.
Set it inside connections { ... }. The default is 5m for SQL. For RADIUS
and LDAP, idle connections are kept open indefinitely unless this is set.
timeout - request time budget
This limits the total time for connection acquisition plus the backend operation.
Where you configure it depends on the backend type:
-
SQL: on the
backendaction inside an AAA handler, not on the backend or server blocks. This is set per query because SQL queries can take different amounts of time.@execute { backend { name "USERS"; query "FIND_USER"; timeout 5s; } } -
RADIUS, LDAP: on the
serverblock, as the per-request response deadline.
Backend-specific notes
SQL uses one connection per outstanding query. The pool grows on
demand up to max and shrinks back toward min when
idle. Both PostgreSQL and MySQL behave identically with respect to
load balancing.
RADIUS can keep up to 256 requests in flight at the same time on one
backend socket. This applies to UDP and TCP/TLS. Radiator keeps reusing
existing live sockets until they can no longer accept more requests, only
then opening another up to connections { max ...; }. For UDP, Radiator still maintains
a pool of backend sockets, but opening a UDP socket does not prove that the
remote server is reachable. RADIUS servers additionally support:
retries(default2): how many retransmissions are attempted on the same server before server selection tries another server or returns an error.connection-status { ... }: sends periodic Status-Server health checks over each exact connection.intervalcontrols the polling delay,timeoutcontrols the response deadline, andmax-timeoutscontrols how many consecutive timeouts close that connection. Polling does not open a connection or changeconnections { min ...; }. These probes only affect their connection and do not contribute outcomes to the server-level service-level objective.status(defaultfalse): compatibility syntax that enables connection polling with a10sinterval, the server request timeout, and a one-timeout threshold.
When status false, failed servers do not recover through Status-Server
probes. If connections { min ...; } is greater than 0, Radiator can still
reconnect them through the background connection maintainer. Enable
connection-status { ... } if you want periodic active health checks in addition to
warm-pool recovery. The compatibility form status true has the same effect.
Known limitation (RADIUS): Do not set
connections { min ...; }equal or very close toconnections { max ...; }. During recovery, reconnect attempts and new requests can run at the same time and push the open connection count abovemax.
LDAP maintains two pools per server:
- A shared pool (
connections { max }, default10) of connections bound with the credentials in theauthenticationblock, used forsearchoperations.connections { min }keeps warm shared connections ready and enables fast-skip when no shared connections are live. When a shared connection is reused from the pool, Radiator checks its local TCP state. If a search discovers a failed connection, Radiator discards it and makes up to five replacement attempts. This clears stale pooled connections after an LDAP server restart. - An exclusive pool (
connections { exclusive }, default10) of connections that are rebound with each user's credentials forbindoperations and returned to the pool afterwards. These connections only get a structural liveness check before reuse because they are rebound anyway.
LDAP does not have a configurable periodic heartbeat like RADIUS status. Use
connections { min ... } when you want warm shared connections and fast-skip behavior
for LDAP.
For the full list of server options see the per-backend reference pages linked from Related Documentation.
Backend Differences At A Glance
| Backend type | Load balancing | Warm-pool fast skip | service-level-objective | Special notes |
|---|---|---|---|---|
| PostgreSQL | yes | yes | yes | One connection handles one query |
| MySQL | yes | yes | yes | Same behavior as PostgreSQL for load balancing |
| LDAP | yes | yes (shared pool) | yes | Shared pool for searches, exclusive pool for binds |
| RADIUS | yes | yes | yes | One socket can carry many in-flight requests |
Pool Monitoring
Pool metrics are available through the Management API.
Counter Names
Every backend server exposes the same counter structure under its own prefix:
- PostgreSQL:
backend/Postgres/{NAME}/{SERVER}/... - MySQL:
backend/MySQL/{NAME}/{SERVER}/... - LDAP:
backend/LDAP/{NAME}/{SERVER}/... - RADIUS:
backend/RADIUS/{NAME}/{SERVER}/...
Common Counter Meanings
| Counter suffix | Meaning |
|---|---|
Requests | Total requests sent to this server |
Replies | Successful replies from this server |
Errors | Request failures on this server |
Timeouts | Requests that exceeded the configured timeout |
PoolExhausted | Requests skipped because this server had no free send capacity |
Unavailable | Requests skipped because this server had zero live connections while its warm-pool minimum was greater than 0 |
Backend-Specific Differences
- SQL:
RequestsandRepliescount queries. PostgreSQL and MySQL use the same counter layout. - LDAP:
RequestsandRepliescount LDAP operations. LDAP fast-skip applies to the shared pool whenconnections { min ... }is greater than0. - RADIUS:
PoolExhaustedmeans all RADIUS identifiers were in use across the current connections, not only that all connection slots were full.
Pool Size Log Fields
Pool size metrics are included as fields in structured log messages:
| Field | Meaning |
|---|---|
pool_total | Total connections currently open (idle + in-use) |
pool_idle | Connections open but not currently executing a query |
In-use connections = pool_total - pool_idle. If this stays near the maximum
under normal load, increase the pool size or add more servers.
Common Patterns
SQL Primary/Replica (Active-Passive)
Use fallback when you want one preferred database server and one backup:
backends {
postgres "USERS" {
server-selection fallback;
server "primary" {
host "pg-primary.example.com";
database "radiator";
username "radiator";
password env.DB_PASSWORD;
connections {
max 10;
min 2;
idle-timeout 5m;
}
priority 0;
}
server "replica" {
host "pg-replica.example.com";
database "radiator";
username "radiator";
password env.DB_PASSWORD;
connections {
max 10;
min 2;
idle-timeout 5m;
}
priority 1;
}
query "FIND_USER" {
statement "SELECT username, password FROM users WHERE username = $1";
bindings { aaa.identity; }
mapping { user.password = password; }
}
}
}
SQL Read Scaling (Active-Active)
Use round-robin to spread read queries across many replicas:
backends {
postgres "USER_DB_READ" {
server-selection round-robin;
server "REPLICA1" { host "pg-replica1.example.com"; }
server "REPLICA2" { host "pg-replica2.example.com"; }
server "REPLICA3" { host "pg-replica3.example.com"; }
query "FIND_USER" { ... }
}
}
LDAP Round-Robin
Use round-robin when both LDAP servers can handle the same searches:
backends {
ldap "LDAP_CLUSTER" {
server-selection round-robin;
server "LDAP1" {
url "ldap://ldap1.example.com:389/";
timeout 3s;
connections {
max 10;
min 1;
idle-timeout 60s;
exclusive 10;
}
authentication {
dn "cn=radiator,dc=example,dc=com";
password "ldap_password";
}
}
server "LDAP2" {
url "ldap://ldap2.example.com:389/";
timeout 3s;
connections {
max 10;
min 1;
idle-timeout 60s;
exclusive 10;
}
authentication {
dn "cn=radiator,dc=example,dc=com";
password "ldap_password";
}
}
search "AUTHENTICATE" {
base "ou=users,dc=example,dc=com";
scope sub;
filter "(&(uid=%{aaa.identity})(objectClass=inetOrgPerson))";
mapping {
user.username = uid;
vars.dn = entry::dn;
}
}
}
}
RADIUS Active-Passive Failover
Use fallback when one RADIUS server should be preferred and another should be backup:
backends {
radius "ACTIVE_PASSIVE" {
server-selection fallback;
server "PRIMARY" {
priority 0;
secret "mysecret";
timeout 3s;
retries 2;
connections { min 1; }
connect { protocol udp; host "192.168.1.10"; port 1812; }
}
server "BACKUP" {
priority 1;
secret "mysecret";
timeout 3s;
retries 2;
connections { min 1; }
connect { protocol udp; host "192.168.1.11"; port 1812; }
}
}
}
Geo-Distributed Backends
Use fallback with priority when you want to prefer a local data center and only use a
remote one when needed:
backends {
radius "GEO_DISTRIBUTED" {
server-selection fallback;
server "LOCAL_DC" {
priority 0;
connections { min 1; }
timeout 2s;
# ...
}
server "REMOTE_DC" {
priority 1;
connections { min 1; }
timeout 5s; # Higher timeout for WAN
# ...
}
}
}
Related Documentation
- Service Level Objective - how Radiator marks a server degraded and how it recovers
- High Availability and Load Balancing - wider high availability design patterns
- PostgreSQL server-selection - PostgreSQL selection reference
- MySQL server-selection - MySQL selection reference
- LDAP server-selection - LDAP selection reference
- RADIUS server-selection - RADIUS selection reference
- Prometheus and OpenMetrics scraping - metrics collection and monitoring
- Backend Load Balancing
- Quick Decision Guide
- How Radiator Chooses a Server
- fallback
- round-robin
- hash-balance
- RADIUS and EAP
- RadiatorDB
- least-connections
- no-fallback
- Server Priority
- How Radiator Decides a Server Is Unhealthy
- Server Health and Status
- Service Level Objective
- Fast Skip With Warm Pools
- How a Backend Request Is Handled
- Connection acquisition
- Multi-server routing
- Connection Pool Parameters
- max - pool size per server
- min - warm connection level
- idle-timeout - idle connection lifetime
- timeout - request time budget
- Backend-specific notes
- Backend Differences At A Glance
- Pool Monitoring
- Counter Names
- Common Counter Meanings
- Backend-Specific Differences
- Pool Size Log Fields
- Common Patterns
- SQL Primary/Replica (Active-Passive)
- SQL Read Scaling (Active-Active)
- LDAP Round-Robin
- RADIUS Active-Passive Failover
- Geo-Distributed Backends
- Related Documentation
Application log message index
Architecture Overview
Backend Load Balancing
Basic Installation
Built-in Environment Variables
Byte Size Units
Certificate Revocation Lists
Comparison Operators
Configuration Editor
Configuration Import and Export
Containers
Cron and interval timers
Data Types
Duration Units
Environment Variables
Execution Context
Execution Pipelines
Filters
Getting a Radiator License
Health check /live and /ready
High Availability and Load Balancing
High availability identifiers
HTTP Basic Authentication
Introduction
Linux systemd support
Local AAA Backends
Logging
Management API privilege levels
Namespaces
Password Hashing
Password Rehashing During Login
Probabilistic Sampling
Prometheus and OpenMetrics scraping
PROXY Protocol Support
Radiator server health and boot up logic
Radiator sizing
Radiator software releases
Radiator software security and dependency compliance
RadiatorDB
RadiatorDB Backup
RadiatorDB CLI
RadiatorDB Installation
RadiatorDB PostgreSQL 10k TPS example
RadiatorDB REST API
RadiatorDB sizing
Rate Limiting
Rate Limiting Algorithms
Reverse Dynamic Authorization
Service Level Objective
TACACS+ Authentication, Authorization, and Accounting
Template Rendering CLI
Timestamps
Tools radiator-client
TOTP/HOTP Authentication
What is Radiator?
YubiKey Authentication
YubiKey Context Variables
Application log message index
Architecture Overview
Backend Load Balancing
Basic Installation
Built-in Environment Variables
Byte Size Units
Certificate Revocation Lists
Comparison Operators
Configuration Editor
Configuration Import and Export
Containers
Cron and interval timers
Data Types
Duration Units
Environment Variables
Execution Context
Execution Pipelines
Filters
Getting a Radiator License
Health check /live and /ready
High Availability and Load Balancing
High availability identifiers
HTTP Basic Authentication
Introduction
Linux systemd support
Local AAA Backends
Logging
Management API privilege levels
Namespaces
Password Hashing
Password Rehashing During Login
Probabilistic Sampling
Prometheus and OpenMetrics scraping
PROXY Protocol Support
Radiator server health and boot up logic
Radiator sizing
Radiator software releases
Radiator software security and dependency compliance
RadiatorDB
RadiatorDB Backup
RadiatorDB CLI
RadiatorDB Installation
RadiatorDB PostgreSQL 10k TPS example
RadiatorDB REST API
RadiatorDB sizing
Rate Limiting
Rate Limiting Algorithms
Reverse Dynamic Authorization
Service Level Objective
TACACS+ Authentication, Authorization, and Accounting
Template Rendering CLI
Timestamps
Tools radiator-client
TOTP/HOTP Authentication
What is Radiator?
YubiKey Authentication
YubiKey Context Variables