Radiator Server Documentation — v10.34.2
Table of Contents

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

The backend stores user documents in the users collection. Each document uses the user name as its key. The FIND_USER query finds the document of the requesting user:

backends {
    radiatordb "MAIN_DB" {
        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;
        }
    }
}

The @once pipeline of the backend action copies the password and session timeout from the document, and pap verifies the password:

aaa {
    policy "RADIUS" {
        conditions all {
            aaa.protocol == protocol.RADIUS;
        }

        handler "AUTHENTICATION" {
            @authentication {
                backend {
                    name "MAIN_DB";
                    query "FIND_USER";
                    @once {
                        user.password = doc | jsonpath("$.password");
                        vars.session_timeout = doc | jsonpath("$.sessionTimeout");
                    }
                }
                pap;
            }
        }
    }
}

If no document has the key, the backend action rejects and the @once pipeline does not run. See Rejecting.

Configuration Options

setup_timeout

Limits each background node setup attempt, including connection, collection initialization, and replication setup. The default is 30s. The value must be greater than 0 and accepts duration units. Connection failures retry automatically. A setup failure that requires repair stops that node until you remove and reprovision it or restart Radiator Server.

setup_timeout 1m;

lifecycle

Configures node health checks and automatic retry delays. All values must be greater than 0 and accept duration units.

lifecycle {
    health_check_interval 5s;
    health_check_timeout 2s;
    retry_max_delay 30s;
}
  • health_check_interval is the delay between successful idle health checks. The default is 5s.
  • health_check_timeout limits one health check. The default is 2s, capped by setup_timeout. A timed-out check removes the node from request routing. It also limits each lock wait when setup creates a newly configured collection, because the creation blocks requests on nodes it has already locked.
  • retry_max_delay caps the increasing delay between automatic retries. The default is 30s.

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 Radiator 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 the configured collections on each PostgreSQL node before it creates replication subscriptions. A newly added collection is created on every node at once and is unavailable until every configured node is set up. See Add a Collection.

Use identical collection storage configuration in every Radiator process that connects to the same PostgreSQL databases. A conflicting configuration keeps the affected node unavailable and prevents subscription creation until corrected.

Removing a collection block keeps the stored data of the collection on every node until the collection is destroyed. See Remove a Collection for removing a collection permanently and for changing its storage.

Every possible runtime value of a query or put collection expression must match a declared collection block. When collection is a string literal that does not match a declared collection, Radiator fails at startup. When collection is an expression, Radiator rejects unknown names when the query runs without trying another PostgreSQL node.

query

Defines a named read operation that retrieves documents from a collection. The backend action selects the query with its query parameter. A query selects documents in one of these ways:

  • An exact key lookup returns at most one document.
  • Key filters and a where block scan the collection and can return many documents.
  • A query without key scans the whole collection.

A query that can return many documents can order them by a field and limit their number.

Each matched document is one result. The backend action that calls the query processes the results in an @once or @each pipeline. A query can also contain a mapping block that preprocesses each result.

AAA backend pipeline query operations do not support time-series collections. Use the Management API to read time-series documents.

Expressions in collection, key, key filters, and where filters can read request-local arguments supplied by the calling backend action, for example key args.identity;. Put query key, collection, and binding expressions can use the same scope.

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;
}

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;
}

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;
    }
}

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;
    }
}

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";
        last_seen >= "2026-01-01";
    }
}

Each filter line specifies a top-level JSON field name, an operator, and a value expression.

Supported operators:

OperatorDescription
==Field value equals the value expression.
!=Field value is valid for the field type and does not equal the value expression.
>Field value is greater than the value expression.
>=Field value is greater than or equal to the value expression.
<Field value is less than the value expression.
<=Field value is less than or equal to the value expression.
containsText of the field value contains the value expression. The match ignores case.

Multiple filters - all filters in a where block must match (AND logic). Documents that fail any filter are excluded.

Where field types

The field type declared in the collection decides how ==, !=, >, >=, <, and <= compare the stored value with the value expression:

Field type== and !=>, >=, <, and <=
text, enum, password, secretExact text. The comparison is case-sensitive.Text by Unicode code point. The comparison is case-sensitive: "Z" < "a".
numberNumeric. 1.0 equals 1.Numeric. 9 < 10.
datetimeSame instant. Offsets are taken into account.Chronological. Offsets and fractional seconds are taken into account.
hexSame bytes. "AABB", "0xAABB", and "0xaabb" are equal.Not supported.
ipSame address.Not supported.
networkThe stored network contains the whole network of the value expression.Not supported.
booleanSame boolean.Not supported.
listNot supported.Not supported.
anySee Fields without a declared type.See Fields without a declared type.

!= is the negation of == among stored values that are valid for the field type. Missing fields do not match any operator. Stored values that are not valid for the declared type do not match any operator, including !=. Such values can exist when the field type was changed after the documents were stored, because existing documents are not converted.

contains and regular expressions match the text of the stored value for every field type.

Where value conversion

Radiator converts the value expression to the field type in the same way as it converts a value when a document is written. The comparison then uses the converted value:

  • number: finite numbers and numeric strings, such as 10 or "10".
  • datetime: timestamp values and every format that datetime fields accept when a document is written, such as "2026-01-01", "2026-01-01T12:00:00+02:00", and Unix seconds. Dates and date-times without an offset are interpreted as UTC. The value must be between years 1 and 9999.
  • hex: hexadecimal strings with or without a 0x prefix in any letter case, and byte values.
  • boolean: true, false, "true", and "false".
  • text, enum, password, secret: strings. Numbers and booleans are compared as their text. An enum value outside the configured value options matches no document with ==.
  • ip: an IP address string. A CIDR network string with a full-length prefix, such as 192.0.2.10/32 or 2001:db8::1/128, is the same as its address. Other CIDR network strings fail.
  • network: an IP address string or a CIDR network string without host bits. An address is a network with a full-length prefix. For example, subnet == "10.0.0.0/16" matches the stored networks 10.0.0.0/8 and 10.0.0.0/16, but not 10.0.0.0/24 or 10.1.0.0/16. subnet != "10.0.0.0/16" matches 10.0.0.0/24 and 10.1.0.0/16.

When the value expression cannot be converted, the query fails. For example, count == "abc" fails when count is a number field.

The value expression must have one string, number, boolean, timestamp, byte, IP address, or network value. Timestamps are compared as RFC3339 strings and byte values as lowercase hexadecimal strings. The query fails when the value is missing, such as a request attribute that the request does not contain, and when it is a JSON null, array, or object.

Where fields without a declared type

A field has no declared type when its type is any, when the collection declares no fields, or when the field name is a key field name that is not declared as a payload field. For these fields:

  • == and != compare the text of the stored value exactly. Numbers and booleans are compared as their JSON text, so the stored number 9 equals "9".
  • >, >=, <, and <= with an RFC3339 value expression, such as "2026-01-01T00:00:00Z" or a timestamp value, compare stored RFC3339 strings chronologically. Other stored values do not match.
  • With a decimal number value expression, stored numbers are compared numerically and stored strings are compared as text by Unicode code point.
  • With any other value expression, stored strings are compared as text by Unicode code point. Stored numbers do not match.
  • Stored booleans, objects, and arrays never match >, >=, <, or <=.
Where typed value expressions

Two kinds of value expressions change how == compares for every field type. Regex literals also change !=:

Regex matching - use a regex literal (delimited by /) as the value. The filter matches when the text of the stored value matches the regular expression. With !=, the filter matches when the stored value exists and its text does not match:

where {
    username == /^ali.*$/;
    role != /^guest/;
}

A regex literal with any other operator fails the query.

IP containment - when the value expression is an IP address or network value, such as a RADIUS address attribute, the filter matches when the stored address or CIDR network contains that address. For a network value, Radiator uses its network address. A string value on a network field compares the whole network instead, as described in Where value conversion:

where {
    network == radius.request.attr.NAS-Ip-Address;
}
Where filter validation

When collection is a string literal, Radiator checks each filter at startup. If the collection declares fields, the filter field must be declared. The field type must support the operator. A literal value, such as "abc" in count == "abc", must convert to the field type, and a literal enum value must be one of the configured value options.

When collection is an expression, Radiator does the same checks when the query runs and fails the query if a check fails. Values computed when the query runs, such as request attributes, are always converted when the query runs.

A literal value that can never be compared, such as none or a regex literal with >, fails at startup even when collection is an expression.

Result order

By default, a query that can return many documents returns the most recently updated document first. Use order_by to order the documents by one top-level field instead. The statement takes the field name and a direction:

query "BY_EXPIRY" {
    collection "users";
    order_by expires_at asc;
}
  • asc: the smallest value first.
  • desc: the largest value first.

The field must be declared in the collection with one of these types. The order is the same as the order that >, >=, <, and <= use in a where block:

Field typeOrder
text, enum, password, secretUnicode code point. The order is case-sensitive: "Z" sorts before "a".
numberNumeric. 9 sorts before 10.
datetimeChronological. Offsets and fractional seconds are taken into account.

order_by does not support other field types, fields of type any, or collections that declare no fields.

Documents that do not have the field sort last in both directions. Documents whose stored value is not valid for the field type also sort last. Such values can exist when the field type was changed after the documents were stored. Documents with equal values are ordered by their key in the same direction.

A query with an exact key cannot have order_by.

Result limit

Use limit to return at most the given number of documents. Together with order_by, the query returns the documents with the smallest or largest values:

query "TOP_USAGE" {
    collection "usage";
    order_by used_bytes desc;
    limit 10;
}

The limit value is an expression that Radiator evaluates every time the query runs. It can read the request or the backend action arguments, for example limit args.count;. The value must be an integer, or text that contains an integer, from 1 to 10000. Otherwise, the query fails. When the value is a literal, such as limit 10;, Radiator checks it when it loads the configuration.

Without limit, a query returns every matching document. A query with an exact key cannot have limit.

Order and limit validation

When collection is a string literal, Radiator checks the order_by field at startup. The field must be declared, and its type must support ordering. When collection is an expression, Radiator does the same checks when the query runs and fails the query if a check fails.

Example: next user to expire

Each user document has an expires_at field. This query finds the user that expires next among the users that have not expired yet:

collection "users" {
    roles {
        read "support";
        write "ops";
    }

    key {
        field {
            name "Username";
            type text;
        }
    }

    field "expires_at" {
        type datetime;
    }
}

query "NEXT_TO_EXPIRE" {
    collection "users";
    where {
        expires_at > datetime.timestamp;
    }
    order_by expires_at asc;
    limit 1;
}

With limit 1, the query returns at most one document, so an @once pipeline never rejects because of multiple results.

Example: largest usage in a period

Each usage document holds the number of bytes that one user used in one period. This query finds the 10 users with the largest usage in October 2026:

collection "usage" {
    roles {
        read "support";
        write "ops";
    }

    key {
        field {
            name "Username";
            type text;
        }
        field {
            name "Period";
            type text;
        }
    }

    field "period_start" {
        type datetime;
    }

    field "used_bytes" {
        type number;
    }
}

query "TOP_USAGE" {
    collection "usage";
    where {
        period_start >= "2026-10-01";
        period_start < "2026-11-01";
    }
    order_by used_bytes desc;
    limit 10;
}

Users without a used_bytes value in the period sort last.

put

Defines a write operation that stores a document in a collection. The document content is built from the bindings block.

A put replaces the stored document that has the same key. Fields that the bindings block does not set are not kept.

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.

Processing query results

The backend action runs a RadiatorDB query and processes its results in an @once or @each pipeline. The pipeline has access to these variables:

  • doc is the current matched document.
  • key holds the key of the current document. It is a multivalue variable with one value for each key part, in the same order as the stored key. Read a part with a zero-based index, for example key[0].
  • args holds the arguments of the backend action.

Read document fields with the jsonpath filter, for example doc | jsonpath("$.username"). The doc and key variables are available only inside the query mappings and the result pipeline.

Radiator completes the database operation, including failover to other servers, before it runs any pipeline. Radiator then processes the documents one at a time, in the order the query returns them. A query with key filters, a where block, or no key returns the most recently updated document first, unless it has order_by.

The result pipeline starts without a previous result, and Radiator discards the result of the pipeline. The backend action result depends only on the query. Changes that the pipeline makes to variables and attributes remain after the backend action. If an action in the pipeline fails with an error, Radiator stops processing the results and the backend action fails.

The query parameter of the backend action can be a literal name or a runtime expression. Radiator validates literal names when it loads the configuration and names from expressions when the backend action runs. A put operation does not support @once or @each.

Processing a single result with @once

Use @once when the query must match exactly one document, such as a user entry. Radiator runs the pipeline once, with the matched document as doc. If the query matches zero documents or more than one document, the backend action rejects without running the pipeline. To process only the first document in an order, use order_by with limit 1;.

This backend action calls the FIND_USER query of the example. It copies the password and the user name from the document and its key:

backend {
    name "MAIN_DB";
    query "FIND_USER";
    @once {
        user.username = key[0];
        user.password = doc | jsonpath("$.password");
    }
}

Processing every result with @each

Use @each to run a pipeline for every matched document. In this example, each user document has a group field:

{ "group": "staff", "vlan": "100" }

The query uses a backend argument to select the users of one group:

query "FIND_GROUP" {
    collection "users";
    where {
        group == args.group;
    }
}

The @each pipeline collects the user names from the document keys:

backend {
    name "MAIN_DB";
    query "FIND_GROUP";
    args {
        group = "staff";
    }
    @each {
        vars.staff_users += key[0];
    }
}

Use continue; to skip the remaining actions for the current document, and break; to stop processing the remaining documents. See @each.

Mapping

The mapping block preprocesses each document before the @each or @once pipeline runs. Radiator runs the mappings for each document with the same doc and key variables, and the pipeline then uses the mapped values.

Map the fields of each document to vars.* variables in the query, and read these variables in the pipeline. The pipeline then does not need to know the document format or use the jsonpath filter. If the document format changes, update only the query mappings.

This query maps the user name and the VLAN of each user document in the @each example to vars.username and vars.vlan:

query "FIND_GROUP" {
    collection "users";
    where {
        group == args.group;
    }
    mapping {
        vars.username = key[0];
        vars.vlan = doc | jsonpath("$.vlan");
    }
}

Each @each run sees the values mapped from the current document, so the pipeline uses vars.username and vars.vlan instead of key and doc:

backend {
    name "MAIN_DB";
    query "FIND_GROUP";
    args {
        group = "staff";
    }
    @each {
        vars.staff_users += vars.username;
        vars.staff_vlans += vars.vlan;
    }
}

The mappings also run when the backend action has no @each or @once pipeline. Variables set by the mappings stay set after each document. With =, each document overwrites the value, so after the backend action the variable holds the value of the last document. With +=, the values of all documents accumulate.

If a mapping fails, for example because a mapped value is not valid for the target variable, Radiator stops processing the documents and the backend action fails. Changes made for the earlier documents remain. With @once, the mappings do not run when the query matches more than one document.

Mapping without a result pipeline

This query collects the names of all devices of a site. The device documents use multipart keys where the first part is the site and the second part is the device name:

query "FIND_SITE_DEVICES" {
    collection "devices";
    key {
        part vars.site;
        at 0;
    }
    mapping {
        vars.devices += key[1];
    }
}

After the backend action, vars.devices holds the names of all matched devices:

backend {
    name "MAIN_DB";
    query "FIND_SITE_DEVICES";
}

Rejecting

The backend action rejects in these cases:

  • Exact key lookup: No document has the key. Radiator sets aaa.reason to Key not found: <collection>/<key>.
  • Key filters, where block, or no key: No document matches. Radiator sets aaa.reason to No documents found from collection: <collection>.
  • @once with many documents: More than one document matches. Radiator sets aaa.reason to: RadiatorDB query '<query>' @once expected exactly one result, but found <count> documents from collection: <collection>.

When the backend action rejects, Radiator does not run the mappings or the @each or @once pipeline. Otherwise, the backend action accepts.

Storing documents from AAA pipelines

Use the backend action to run a put operation. This handler stores an accounting document for each accounting request:

aaa {
    policy "RADIUS" {
        conditions all {
            aaa.protocol == protocol.RADIUS;
        }

        handler "ACCOUNTING" {
            conditions all {
                radius.request.code == radius.ACCOUNTING_REQUEST;
            }

            @execute {
                backend {
                    name "MAIN_DB";
                    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:

  • age removes archived revisions older than the configured duration. If omitted, RadiatorDB does not remove revisions based on age.
  • max keeps 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:

  • enable controls whether scheduled garbage collection runs. The default is true.
  • max_tombstone_age removes tombstones older than the configured duration. The default is 30d.
  • interval defines one or more schedules. The default is duration 1h with random 5m jitter. 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.

Storage Statistics

RadiatorDB counts the stored rows on each PostgreSQL node every five minutes and publishes the counts as statistics. Use them to see how much data each node holds and which collection grows. Each node stores its own copy of the data, so do not add the counts of different nodes together.

Each node publishes its totals in the namespace backend::RadiatorDB::<backend>::node::<server>. Each collection on that node publishes its counts in backend::RadiatorDB::<backend>::node::<server>::collection::<collection>. <backend> is the name of the radiatordb block, <server> is the name of a server block, and <collection> is the name of a collection block.

StatisticDescription
DocumentCountCurrent documents that are neither deleted nor expired.
TombstoneCountDeleted or expired documents that garbage collection has not removed yet.
RevisionCountArchived revisions. See Revision Retention.

A collection without rows reports 0. The node namespace also contains the StoragePolls and StoragePollFailures counters, which count completed and failed storage counts.

For example, read the live document count of the users collection on the primary server of the mydb backend:

GET /api/v1/statistics/counter/backend/RadiatorDB/mydb/node/primary/collection/users/DocumentCount

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;
}
  • on waits for PostgreSQL to durably flush the WAL before acknowledging.
  • off acknowledges 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.

Internal timing environment variables

Radiator Server reads these variables when it starts a RadiatorDB backend. They adjust internal timing and retry policies without adding RadConf parameters. These variables are internal and may change without notice. Duration values use the same duration syntax as setup_timeout. Attempt counts are positive integers. Invalid values prevent the backend from starting.

VariableDefaultPurpose
RADIATORDB_RECOVERY_PEER_PROBE_TIMEOUT500msMaximum time to inspect one recovery peer, capped by the remaining setup deadline.
RADIATORDB_WRITE_GATE_ACQUISITION_TIMEOUT30sMaximum wait for each cluster write gate, capped by the setup deadline when one applies.
RADIATORDB_MAINTENANCE_LOCK_TIMEOUT30sPostgreSQL wait for each collection topology or table lock during cluster maintenance.
RADIATORDB_NODE_REMOVAL_TIMEOUT30sShared deadline for bounded connection, lock, subscription and replication slot drop, and replication catch-up waits during Remove Node. Independent of setup_timeout; not a deadline for the entire request or every cleanup query. Forced removal does not wait for replication catch-up.
RADIATORDB_COLLECTION_DESTROY_TIMEOUT30sShared deadline for the node connections, topology and write gates, replication catch-up, and lock waits of one collection destroy. Each node connection also waits at most health_check_timeout, and each lock wait at most RADIATORDB_MAINTENANCE_LOCK_TIMEOUT. The destroy fails and destroys nothing when replication between the reachable nodes does not catch up within this deadline, with or without force.
RADIATORDB_BACKUP_IMPORT_LOCK_TIMEOUT30sPostgreSQL lock wait during backup import.
RADIATORDB_HEARTBEAT_QUERY_TIMEOUT2sMaximum time for one replication heartbeat query, and for each diagnostic query that inspects replication state after a failed node setup or removal catch-up.
RADIATORDB_HEARTBEAT_INTERVAL5sDelay between completed steady replication monitoring passes. Each publisher emits at most one heartbeat per interval.
RADIATORDB_HEARTBEAT_FOLLOW_UP_INTERVAL250msDelay before an extra monitoring pass while a replication edge waits for its subscriber to apply the latest heartbeat. Extra passes run only within one heartbeat interval after the monitor starts, a node enters routing, or a replication edge becomes ready.
RADIATORDB_HEARTBEAT_LAG_THRESHOLD15sRaw heartbeat distance above which replication is lagging. Must be greater than RADIATORDB_HEARTBEAT_INTERVAL.
RADIATORDB_REPLICATION_OBSERVATION_STALE_AFTER30sAge above which a replication observation no longer supports readiness. Must be greater than RADIATORDB_HEARTBEAT_INTERVAL.
RADIATORDB_DEFAULT_NODE_OBSERVATION_STALE_AFTER30sNode observation age before managed startup takes over. Managed startup derives it from lifecycle.
RADIATORDB_SLOT_DROP_TIMEOUT2sDeadline for retrying an active replication slot drop.
RADIATORDB_BACKEND_TERMINATION_TIMEOUT1sPostgreSQL wait for an active replication sender to terminate.
RADIATORDB_TOPOLOGY_GATE_RELEASE_TIMEOUT5sMaximum wait to release each held cluster-wide lock on one database, such as a replication topology gate or a cluster write gate.
RADIATORDB_SUBSCRIPTION_POLL_INTERVAL100msDelay between subscription catch-up and synchronization checks.
RADIATORDB_SUBSCRIPTION_READY_MAX_ATTEMPTS100Maximum subscription-readiness polls during explicit synchronization.
RADIATORDB_REPLICATION_FLUSH_MAX_ATTEMPTS100Maximum WAL-flush polls during explicit synchronization.
RADIATORDB_POOL_PRUNER_MAX_INTERVAL5sMaximum interval between idle connection pruning checks.
RADIATORDB_PARTITION_RECONCILIATION_INTERVAL20mBase interval between partition reconciliation passes.
RADIATORDB_PARTITION_RECONCILIATION_MAX_JITTER20mMaximum additional random delay between reconciliation passes; 0s disables jitter.
RADIATORDB_RETRY_INITIAL_DELAY1sInitial nominal lifecycle retry delay, capped by lifecycle.retry-max-delay.
RADIATORDB_RETRY_JITTER_MIN_PERCENT50Minimum percentage of nominal delay used for randomized retries; valid range is 0 to 100.
RADIATORDB_NODE_INSPECTION_TIMEOUT5sBudget for inspecting one physical node through the maintenance API.
RADIATORDB_NODE_METRICS_TIMEOUT5sBudget for sampling one physical node through the maintenance API.
RADIATORDB_METRICS_FAST_INTERVAL30sDelay after each completed backend statistics poll before the next poll.
RADIATORDB_METRICS_STORAGE_INTERVAL5mDelay after each completed physical storage-count poll before the next poll.
RADIATORDB_METRICS_POLL_TIMEOUT5sMaximum time for one node's backend statistics or storage-count poll.

RADIATORDB_MAINTENANCE_LOCK_TIMEOUT and RADIATORDB_BACKUP_IMPORT_LOCK_TIMEOUT accept whole milliseconds from 1ms through 2147483647ms. RADIATORDB_BACKEND_TERMINATION_TIMEOUT also requires whole milliseconds. Polling attempt counts include database query time, so they are not wall-clock deadlines. To set setup_timeout, the lifecycle values, or the server connection idle timeout from environment variables, use RadConf env.VAR expressions in those existing settings.