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
- 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 |
| 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 four 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" {
# ...
}
}
}
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 three behaviors at the
same time:
- Warm connections are kept ready.
- A server with zero live connections is skipped immediately.
- The
Unavailablecounter increases when that skip happens.
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.status(defaultfalse): enables periodic Status-Server health checks.
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
status true if you want periodic active health checks in addition to
warm-pool recovery.
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 validates it with an LDAP WhoAmI round-trip. - 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 Scraping - metrics collection and monitoring
- Backend Load Balancing
- Quick Decision Guide
- How Radiator Chooses a Server
- fallback
- round-robin
- 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
About Radiator software development security
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
Log storage and formatting
Management API privilege levels
Namespaces
Password Hashing
Password Rehashing During Login
Probabilistic Sampling
Prometheus scraping
PROXY Protocol Support
Radiator server health and boot up logic
Radiator sizing
Radiator software releases
RadiatorDB
RadiatorDB Backup
RadiatorDB CLI
RadiatorDB Installation
RadiatorDB REST API
Rate Limiting
Rate Limiting Algorithms
Reverse Dynamic Authorization
Service Level Objective
TACACS+ Authentication, Authorization, and Accounting
Template Rendering CLI
Timestamp Format
Timestamps
Tools radiator-client
TOTP/HOTP Authentication
What is Radiator?
YubiKey Authentication
YubiKey Context Variables
About Radiator software development security
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
Log storage and formatting
Management API privilege levels
Namespaces
Password Hashing
Password Rehashing During Login
Probabilistic Sampling
Prometheus scraping
PROXY Protocol Support
Radiator server health and boot up logic
Radiator sizing
Radiator software releases
RadiatorDB
RadiatorDB Backup
RadiatorDB CLI
RadiatorDB Installation
RadiatorDB REST API
Rate Limiting
Rate Limiting Algorithms
Reverse Dynamic Authorization
Service Level Objective
TACACS+ Authentication, Authorization, and Accounting
Template Rendering CLI
Timestamp Format
Timestamps
Tools radiator-client
TOTP/HOTP Authentication
What is Radiator?
YubiKey Authentication
YubiKey Context Variables