- radiatordb
- Example
- Configuration Options
- server-selection
- server
- RadiatorDB cluster addresses
- replication-host
- collection
- query
- Query with exact key
- Query with multipart key
- Query with key filters
- Where clause
- Mapping
- put
- put key
- put collection
- put expire
- put bindings
- Multipart Keys
- Using RadiatorDB in AAA Pipelines
- Revision Retention
- Garbage Collection
- Performance Tuning
- synchronous-commit
radiatordb
RadiatorDB is a document-oriented backend that stores and retrieves JSON documents in PostgreSQL. See the RadiatorDB article for an overview of features and concepts. See RadiatorDB REST API for management API document operations.
Example
radiatordb "RADIATORDB" {
server "primary" {
host "db.example.com";
port 5432;
database "radiator";
username "radiator";
password "secret";
}
collection "users" {
roles {
read "support";
write "ops";
}
name "Users";
description "User credentials for authentication";
key {
field {
name "Username";
type text;
}
}
field "password" {
name "Password";
type password;
required true;
}
field "sessionTimeout" {
name "Session Timeout";
type number;
}
}
query "FIND_USER" {
collection "users";
key aaa.identity;
mapping {
user.username = doc | jsonpath("$.username");
user.password = doc | jsonpath("$.password");
vars.session_timeout = doc | jsonpath("$.sessionTimeout");
}
}
}
Configuration Options
server-selection
Selects a server when multiple RadiatorDB server blocks are configured. The
allowed algorithms are fallback, round-robin, hash-balance,
least-connections, and no-fallback. The default is fallback.
See Backend Load Balancing for syntax, algorithm behavior, field selection, and failover details.
server
Defines a PostgreSQL server for document storage. Multiple server blocks can be configured for high availability. Each server is identified by a unique name.
For failover, set host to an address that every Radiator instance can reach. Do not use localhost unless each Radiator instance connects only to its local RadiatorDB instance. If those local instances form a cluster, also set replication-host.
RadiatorDB uses the same PostgreSQL server configuration as the postgres backend. See that reference for the full list of server options including url, host, port, database, username, password, connections { ... }, priority, tls, and service-level-objective.
server "primary" {
host "db.example.com";
port 5432;
database "radiator";
username "radiator";
password "secret";
connections {
max 10;
}
}
RadiatorDB cluster addresses
RadiatorDB cluster members must be able to discover and reach each other for replication. In the typical setup, the host address is reachable by both Radiator and other RadiatorDB members. No separate replication address is needed.
Use replication-host only when Radiator should connect through one address, but other RadiatorDB members must use another address to reach the same member.
replication-host
replication-host is an optional RadiatorDB server setting. It gives RadiatorDB a separate address that the database engine uses to discover this member for replication.
Set replication-host when each Radiator instance connects to its local RadiatorDB instance with host "localhost", but those RadiatorDB instances also form a cluster. In that layout, set replication-host to the address that other RadiatorDB members can reach.
server "primary" {
host "localhost";
replication-host "db1.internal.example.com";
port 5432;
database "radiator";
username "radiator";
}
replication-host cannot be combined with url. Use separate host, port, database, username, and password settings when this override is needed.
collection
Defines a document collection and its field schema. Collections organize documents into logical groups and describe their structure for the management UI.
See collection for all available options.
Collection storage defaults to normal mutable storage. Configure time-series
storage in the collection storage block. Radiator initializes every configured
collection on every PostgreSQL node before it creates replication subscriptions.
Use identical collection storage configuration in every Radiator process that connects to the same PostgreSQL databases. A conflicting configuration prevents startup or subscription creation.
Every possible runtime value of a query or put collection expression must
match a declared collection block. Radiator rejects unknown names without
trying another PostgreSQL node.
query
Defines a read operation that retrieves documents from a collection. The query can look up a single document by exact key, or scan the collection using key filters and where clauses.
AAA backend pipeline query operations do not support time-series collections.
Use the Management API to read time-series documents.
When no documents match, the query result is reject and aaa.reason is set to a descriptive message (either "Key not found: collection/key" for key lookups, or "No documents found from collection: name" for filtered scans).
When one or more documents match, the result is accept and the configured mapping is applied to each matched document.
Query with exact key
The simplest query form looks up a single document by its full key:
query "FIND_USER" {
collection "users";
key aaa.identity;
mapping {
user.username = doc | jsonpath("$.username");
user.password = doc | jsonpath("$.password");
}
}
The key value is an expression that resolves to the document key at runtime.
Query with multipart key
Multiple key directives compose a multipart key. All parts are joined to form the full document key for an exact lookup:
query "FIND_DEVICE" {
collection "devices";
key vars.site;
key radius.request.attr.NAS-Identifier;
mapping {
user.username = doc | jsonpath("$.username");
user.password = doc | jsonpath("$.password");
}
}
When the key is not found, the reject reason includes the composed key: "Key not found: devices/["site:helsinki","edge-1"]".
Query with key filters
Key filters match documents by inspecting individual parts of multipart keys without requiring a full exact match. Two filter types are available:
at filter - matches a specific key part by index (zero-based, supports negative indexes):
query "FIND_DEVICE" {
collection "devices";
key {
part vars.site;
at 0;
}
key {
part radius.request.attr.NAS-Identifier;
at 1;
}
where {
username == aaa.identity;
}
mapping {
user.username = doc | jsonpath("$.username");
user.password = doc | jsonpath("$.password");
}
}
Negative indexes count from the end of the key (-1 is the last part, -2 is second to last). If the negative index is out of range for a document's key length, that document does not match.
contains filter - matches documents where any key part equals the specified value:
query "FIND_DEVICE" {
collection "devices";
key {
contains vars.site;
}
where {
username == aaa.identity;
}
mapping {
user.username = doc | jsonpath("$.username");
user.password = doc | jsonpath("$.password");
}
}
Key filters cannot be mixed with plain key directives in the same query.
Where clause
The where block filters documents by comparing JSON field values. It is valid only in queries that use key filters or that have no key at all (collection scan). A query with an exact key cannot have a where block.
query "FIND_USER" {
collection "users";
where {
username == aaa.identity;
role != "guest";
}
mapping {
user.username = doc | jsonpath("$.username");
user.password = doc | jsonpath("$.password");
}
}
Each filter line specifies a top-level JSON field name, an operator, and a value expression.
Supported operators:
| Operator | Description |
|---|---|
== | Equals. Automatically promotes to IP containment check when the field value is a CIDR network, or to regex match when the value is a regex literal. |
!= | Not equals. |
contains | Case-insensitive substring match. |
Regex matching - use a regex literal (delimited by /) as the value:
where {
username == /^ali.*$/;
}
IP containment - when the document field contains a CIDR network value (e.g. "192.0.2.0/24"), the == operator checks whether the compared IP address is contained within that network:
where {
network == radius.request.attr.NAS-Ip-Address;
}
Multiple filters - all filters in a where block must match (AND logic). Documents that fail any filter are excluded.
Mapping
The mapping block extracts values from matched documents into the request context. Documents are available through the doc variable with the jsonpath() filter for field extraction:
mapping {
user.username = doc | jsonpath("$.username");
user.password = doc | jsonpath("$.password");
vars.ip = doc | jsonpath("$.framedIpAddress");
}
The matched document key is available through the key local variable. key is a multivalue variable with one value for each document key part. Use zero-based indexing to read individual parts:
mapping {
vars.site = key[0];
vars.username = key[1];
}
For single-part keys, use key[0]. For multipart keys, each key part appears in the same order as the stored document key. The key variable is available for exact key lookups, key-filter queries, and collection scans.
For queries that return multiple documents, the mapping is applied to each document. Use the += operator to accumulate values into a list:
mapping {
vars.users += doc | jsonpath("$.username");
}
put
Defines a write operation that stores a document in a collection. The document content is built from the bindings block.
Time-series puts are immutable events. Do not configure expire for a
time-series collection.
put "STORE_ACCOUNTING" {
collection "accounting";
key aaa.identity;
key radius.request.attr.acct-session-id;
expire 1h;
bindings {
username = aaa.identity;
attrs = radius.request.attrs;
metadata.source = "radius";
}
}
put key
At least one key directive is required. The key identifies the document within the collection.
A single key expression:
put "STORE_SESSION" {
collection "sessions";
key aaa.identity;
bindings { ... }
}
Multiple key directives create a multipart key:
put "STORE_ACCOUNTING" {
collection "accounting";
key aaa.identity;
key radius.request.attr.acct-session-id;
bindings { ... }
}
put collection
Specifies the target collection. Required.
put expire
Sets a time-to-live for the stored document. After this duration, the document is no longer returned by queries. Accepts duration units.
put "STORE_SHORT" {
collection "sessions";
key aaa.identity;
key radius.request.attr.acct-session-id;
expire 30m;
bindings { ... }
}
If expire is not set, the document persists indefinitely.
put bindings
The bindings block defines the document content as field-value assignments. Only the = operator is allowed.
bindings {
username = aaa.identity;
attrs = radius.request.attrs;
metadata.source = "radius";
metadata.timestamp = datetime.timestamp;
}
Dotted field names (e.g. metadata.source) create nested JSON objects in the stored document.
Multipart Keys
Multipart keys allow hierarchical document organization. See RadiatorDB REST API multipart keys for key formatting in CLI arguments and management API URLs.
Using RadiatorDB in AAA Pipelines
Reference a RadiatorDB query from an AAA pipeline handler. Define conditions in the handler block, then use @execute and the backend action to run the query for access requests:
aaa {
policy "DEFAULT" {
handler "AUTHENTICATION" {
conditions all {
radius.request.code == radius.ACCESS_REQUEST;
}
@execute {
backend {
name "RADIATORDB";
query "FIND_USER";
}
pap;
}
}
}
}
For put operations, define the accounting request condition in the handler block and use @execute to store the document:
aaa {
policy "DEFAULT" {
handler "ACCOUNTING" {
conditions all {
radius.request.code == radius.ACCOUNTING_REQUEST;
}
@execute {
backend {
name "RADIATORDB";
query "STORE_ACCOUNTING";
}
accept;
}
}
}
}
Revision Retention
RadiatorDB stores revision history for each document insert, update, and delete. By default, RadiatorDB does not remove archived revisions. Add a collection revisions block to configure archived revision cleanup limits:
collection "users" {
roles {
read "support";
write "ops";
}
revisions {
age 30d;
max 100;
}
key {
field {
name "Username";
type text;
}
}
}
The revisions block supports these parameters:
ageremoves archived revisions older than the configured duration. If omitted, RadiatorDB does not remove revisions based on age.maxkeeps at most this many archived revisions for each document key. If omitted, RadiatorDB does not remove revisions based on count.
Both limits are enforced by RadiatorDB garbage collection. RadiatorDB can remove a revision when it is older than age or when the document key has more than max retained revisions.
If you omit the revisions block, garbage collection keeps all archived revisions for the collection. This does not change tombstone garbage collection.
Garbage Collection
RadiatorDB garbage collection removes old tombstones. It also removes old archived revisions from normal collections that configure a revisions block. A tombstone is the delete marker that RadiatorDB keeps after a document is deleted so that deletion can replicate to other RadiatorDB nodes.
Configure scheduled garbage collection in the backend gc block:
radiatordb "mydb" {
gc {
enable true;
max_tombstone_age 30d;
interval {
duration 1h;
random 5m;
}
}
server "primary" {
host "db.example.com";
database "radiator";
username "radiator";
}
collection "users" {
roles {
read "support";
write "ops";
}
key {
field {
name "Username";
type text;
}
}
}
}
The gc block supports these parameters:
enablecontrols whether scheduled garbage collection runs. The default istrue.max_tombstone_ageremoves tombstones older than the configured duration. The default is30d.intervaldefines one or more schedules. The default isduration 1hwithrandom 5mjitter. See interval for the shared interval syntax.
If the gc block is omitted, RadiatorDB uses these defaults and scheduled garbage collection is enabled.
Use only one scheduled GC runner for a shared RadiatorDB deployment. If multiple Radiator Server processes use the same RadiatorDB backend, enable scheduled GC on one process and disable it on the others. This avoids duplicate maintenance work against the same PostgreSQL databases.
You can keep the same configuration file on all servers by reading enable from an environment variable:
radiatordb "mydb" {
gc {
enable env.RADIATORDB_GC_ENABLE | default("false");
max_tombstone_age 30d;
}
# Other backend settings are identical on every Radiator Server.
}
Set RADIATORDB_GC_ENABLE=true for exactly one Radiator Server process. Leave it unset, or set it to false, on the other processes. See Environment Variables for more information about env.VAR_NAME configuration values.
Performance Tuning
synchronous-commit
Set synchronous-commit in the postgresql block to control when PostgreSQL
acknowledges commits for all servers in this backend:
postgresql {
synchronous-commit off;
}
onwaits for PostgreSQL to durably flush the WAL before acknowledging.offacknowledges before the local WAL flush. It improves throughput for loss-tolerant data such as accounting logs, but a host crash can lose recent acknowledged transactions.
RadiatorDB does not override this setting by default. If you omit the parameter,
PostgreSQL uses its configured value and defaults to on. This parameter does
not make RadiatorDB logical replication synchronous.
- radiatordb
- Example
- Configuration Options
- server-selection
- server
- RadiatorDB cluster addresses
- replication-host
- collection
- query
- Query with exact key
- Query with multipart key
- Query with key filters
- Where clause
- Mapping
- put
- put key
- put collection
- put expire
- put bindings
- Multipart Keys
- Using RadiatorDB in AAA Pipelines
- Revision Retention
- Garbage Collection
- Performance Tuning
- synchronous-commit