Skip to content

db_redis: new Redis (server/cluster) DB backend module#4111

Open
Lt-Flash wants to merge 7 commits into
OpenSIPS:masterfrom
Lt-Flash:feature/db-redis-devel
Open

db_redis: new Redis (server/cluster) DB backend module#4111
Lt-Flash wants to merge 7 commits into
OpenSIPS:masterfrom
Lt-Flash:feature/db-redis-devel

Conversation

@Lt-Flash

Copy link
Copy Markdown

Overview

This PR adds db_redis, a new optional OpenSIPS DB API backend that stores data in a Redis server or a Redis Cluster. Unlike cachedb_redis (which exposes Redis through the key-value NoSQL interface), db_redis presents Redis as a relational-style backend behind the standard db_url mechanism: a module is switched to Redis simply by pointing its db_url at a redis:// URL — no code change in the consuming module.

The module is excluded from the default build (it depends on hiredis) and is enabled by removing db_redis from exclude_modules, exactly like the other library-dependent DB drivers.

What the module covers

Data model — Redis is schemaless, so table layouts are declared out of band as Redis hashes:

  • schema:<table> — ordered column list (__cols), primary key (__pk), and one type[,null][,auto] field per column (types: int, bigint, double, string, blob, datetime).
  • <table>:<pk> — one row per key, one hash field per non-NULL column. Blobs are stored binary-safe (no encoding overhead); NULL is the absence of a field.
  • seq:<table>INCR counter backing auto-increment primary keys and last_inserted_id.
  • The standard version table is provisioned like any other table.

Ready-to-load provisioning files ship under scripts/redis/.

Structured DB API implementedquery (primary-key fast path + full-table scan-and-filter, all six operators = < > <= >= != combined with AND/OR, single-column ORDER BY), insert (HSETNX row reservation for SQL-insert uniqueness), update (HSET/HDEL), delete (incl. bulk OR primary-key deletes and truncate), replace, insert_update, last_inserted_id. Typed results are produced from the declared schema.

Dual transport mode, auto-detected at startup via INFO CLUSTER:

  • Single endpoint — a standalone server, or the current master reached through an external load balancer / Sentinel setup. Reconnects transparently on I/O errors and on -READONLY replies (a demoted master after failover).
  • Redis Cluster — starting from a single seed host, the module discovers the full topology (CLUSTER SHARDS, falling back to CLUSTER SLOTS) and keeps a warm connection open to every master shard, so any key can be served immediately without a new TCP handshake on redirect. Keys are routed by CRC16 hash slot (honouring {hash tags}); MOVED is served from the warm pool with a deferred topology refresh, ASK is followed with a one-shot ASKING, and TRYAGAIN is retried after a short back-off. Full-table operations scan every master and merge. Only masters are used (read-your-writes consistency).

The mode can be pinned with the mode modparam (auto | single | cluster), turning a misconfiguration into a clear startup error instead of silently wrong behaviour.

Modules supported in this PR

Module Table(s) Status
dialog dialog (v12) Provisioning + verified end-to-end (table-version check, full load, shutdown flush) — including against a live 3-master Redis Cluster
dispatcher dispatcher (v9) Provisioning + verified end-to-end (destination load & activation)
usrloc location (v1013) Provisioning + code change (see below) + verified end-to-end (contact load in sql-only mode)
clusterer clusterer (v4), clusterer_bridge (v1) Provisioning + schema-validated

usrloc change

usrloc had a single raw SQL query (get_domain_db_ucontacts, used to fetch live contacts for NAT pinging) that excluded any backend without raw-query support. This PR adds a structured-query fallback: when the bound DB module lacks DB_CAP_RAW_QUERY, the query is built through the structured API (column projection + expires > now) and the contact_id modulo partitioning is applied client-side while iterating rows. SQL backends keep the exact same raw-query path, so there is no behaviour change for existing setups — and this also makes usrloc work with db_text. Consequently DB_CAP_RAW_QUERY is no longer a hard requirement in sql-only mode.

Roadmap

db_redis module

  • Phase 1 (this PR) — structured DB API, dual single/cluster transport, schema provisioning.
  • Phase 2 — secondary indexes for non-primary-key lookups (index sets, or RediSearch ON HASH where available), FETCH result cursors, per-node pipelining for bulk operations.
  • Phase 3 — optional SQL-subset raw_query, a dbschema generation target to emit schema:<table> files from db/schema/*.xml, async operations.

Module coverage

Supported now: dialog, dispatcher, usrloc, clusterer.

Ready with only a provisioning file (structured DB API, no code change needed): the large majority of DB consumers, e.g. acc, alias_db, auth_db, domain, domainpolicy, permissions, group, speeddial, dialplan, load_balancer, mqueue, msilo, ratelimit, uac_registrant, userblacklist, tls_mgm, b2b_entities / b2b_logic / b2b_sca, presence / presence_xml / rls / pua, call_center, imc, tracer, trie, qrouting, rate_cacher, emergency, fraud_detection, cpl_c, xcap / xcap_client. These only need their schema hashes provisioned.

Needs a per-module structured fallback (raw SQL today, like usrloc did): drouting, carrierroute, auth_jwt, sipcapture.

Not applicable:

  • sqlops — executes arbitrary user-supplied SQL, which cannot be translated to Redis operations.
  • cachedb_* — these use the NoSQL cache interface, not the structured DB API; use cachedb_redis for Redis caching.
  • db_virtual, db_cachedb, cachedb_sql, sql_cacher — DB-layer plumbing, not end consumers.
  • tm — keeps no database table.

Testing

  • db_redis builds cleanly; module loads and binds via db_bind_api.
  • Verified end-to-end against redis-server (standalone) and a live 3-master / 3-replica Redis 8 cluster: seeded with a single node, all masters are discovered and their connections warmed up; dialog rows load and flush across non-seed masters via the slot map.
  • dispatcher and usrloc load their tables end-to-end through db_redis.
  • All shipped schemas pass a column/type-field consistency check.

Notes

  • New module modules/db_redis/ with DocBook documentation and generated README.
  • New provisioning files under scripts/redis/.
  • db_redis added to exclude_modules in Makefile.conf.template (optional, off by default).

Yury Kirsanov added 6 commits July 15, 2026 15:07
Implements the OpenSIPS db_api on top of Redis, storing rows as hashes
at <table>:<pk> with driver-side schemas provisioned as schema:<table>
hashes (see scripts/redis/README).

Phase 1 scope:
- structured ops: query (pk fast path + scan-filter, all operators,
  single-column ORDER BY), insert, update, delete (incl. bulk OR
  pk-deletes and truncate), replace, insert_update, last_inserted_id
  (auto-increment emulated via INCR seq:<table>)
- dual transport mode, auto-detected per connection via CLUSTER INFO:
  single endpoint (standalone or LB-fronted sentinel master, with
  READONLY-triggered reconnect) and Redis Cluster (topology from
  CLUSTER SHARDS with CLUSTER SLOTS fallback, eager connections to all
  masters, MOVED served from the warm pool, ASK/TRYAGAIN handling,
  deferred topology refresh)
- typed results from declared schemas (int/bigint/double/string/blob/
  datetime), binary-safe blobs, NULL as absent hash field

The module is optional and excluded from the default build (needs
hiredis); enable by removing db_redis from exclude_modules.

Verified against redis-server 5.x with the dialog module in db_mode 1:
table version check, full-table load of seeded dialogs, and the
shutdown update flush.
The cluster probe queried CLUSTER INFO for cluster_enabled, but that
field is only reported by the general INFO command (Cluster section),
not by CLUSTER INFO - so a real cluster was misdetected as a single
endpoint. Probe with INFO CLUSTER instead, which reliably reports
cluster_enabled:0/1 on both standalone and cluster instances.

Also log a one-line summary of the discovered master pool and its warm
connection count after cluster topology load (per-master detail at
debug level).

Verified against a live 3-master/3-replica Redis 8 cluster: seeded with
a single node, all three masters are discovered and their TCP
connections warmed up, and dialog rows load and flush across the
non-seed masters via the slot map.
DocBook admin guide covering the data model (schema/row/sequence
hashes), the single-endpoint and Redis Cluster deployment modes, the
exported parameters, usage examples and limitations, plus the generated
README and contributors file.
Adds ready-to-load schema definitions matching the canonical SQL
layouts:
- dispatcher (dispatcher table, v9)
- usrloc (location table, v1013)
- clusterer (clusterer v4, clusterer_bridge v1)

Each file is self-contained and also provisions the version-table
schema. The tm module keeps no DB table, so it has none. usrloc's
raw-query paths remain unsupported; the structured contact path works.

Verified: all schemas pass the column/type-field consistency check, and
the dispatcher table loads end-to-end through db_redis (schema parsed,
destination row read and activated).
get_domain_db_ucontacts() used a raw SQL query (the only raw_query in
usrloc) to fetch all live contacts, which excluded DB backends that do
not implement raw queries, such as db_redis.

When the bound DB module lacks DB_CAP_RAW_QUERY, build the equivalent
query through the structured DB API (column projection plus an
'expires > now' filter) and apply the contact_id modulo partitioning
client-side while iterating the rows. SQL backends keep the exact same
raw-query path as before, so there is no behaviour change for existing
setups; this also makes the code work with db_text.

Consequently DB_CAP_RAW_QUERY is no longer a hard requirement in
sql-only mode.

Verified: usrloc in sql-only mode over db_redis loads the location
schema and returns live contacts to nathelper for pinging.
Update the module limitations and the scripts/redis README now that
usrloc no longer depends on raw queries, and list dispatcher, clusterer
and usrloc among the supported consumers.
@Lt-Flash
Lt-Flash marked this pull request as ready for review July 15, 2026 05:57
Marshal DB values through the db_ut helpers (db_str2int/bigint/double,
db_int2str/bigint2str/double2str) instead of raw strtoll/snprintf, the
same way db_sqlite and the other DB drivers do. This keeps type
conversion and its error handling consistent across the DB backends.

No functional change: the memory model was already correct (per-process
pkg allocations, pool_con-compatible connection header, driver-owned
result values freed via VAL_FREE).
@Lt-Flash

Copy link
Copy Markdown
Author

Refactored the module to comply with OpenSIPS conventions after an internal review of the memory model and DB API usage:

  • Value marshalling now goes through the DB layer helpers (db_str2int/db_str2bigint/db_str2double and db_int2str/db_bigint2str/db_double2str) instead of raw strtoll/strtod/snprintf, matching how db_sqlite and the other DB backends convert values — so type conversion and its error handling are consistent across drivers.

The rest of the model was already compliant and was verified during the review:

  • All allocations use the OpenSIPS package memory API (pkg_malloc/pkg_free/pkg_realloc); no raw libc allocation. pkg is correct here since DB connections are per-worker (each process opens its own in child_init), exactly like db_mysql/db_sqlite.
  • The connection struct header matches struct pool_con field-for-field, so the core connection pool (db_do_init/db_do_close) manages it correctly.
  • Result values are handed off with VAL_FREE set so the core's db_free_row reclaims driver-allocated DB_STR/DB_BLOB buffers; schema-owned column names are (correctly) not freed.
  • Uses the core DB entry points (db_do_init, db_do_close, db_use_table) and honors CON_OR_OPERATOR/CON_OR_RESET.

Remaining libc usage is limited to non-DB-boundary work (internal numeric comparison in filter/sort, and Redis-protocol parsing such as MOVED/ASK and SCAN cursors), where the DB helpers do not apply.

Regression-tested after the change: dialog load + shutdown flush over db_redis remains clean.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant