Repository navigation
Conversation
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.
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).
|
Refactored the module to comply with OpenSIPS conventions after an internal review of the memory model and DB API usage:
The rest of the model was already compliant and was verified during the review:
Remaining Regression-tested after the change: dialog load + shutdown flush over |
|
Any updates here? No progress has been made in the last 30 days, marking as stale. |
|
Waiting for review by mainteiner |
|
Any updates here? No progress has been made in the last 30 days, marking as stale. |
|
Still waiting for review |
|
I am wondering what is the real benefit of using this module, instead of Best regards, |
|
Hi Răzvan, Thank you for the review. The separation was deliberate: I meant this as a plain On that specific combination: unless I am misreading master, it cannot run today - And I think it is the right question. The part I cannot argue with is duplication: a separate driver means the Redis connection, cluster topology and Three shapes, and I am happy with any:
You know the module layout far better than I do, so I will gladly follow whichever you prefer - including dropping this in favour of integration. Just say which and I will rework it. Best regards, |
|
Hi, Yuri! Thanks a lot for your work! If possible, I would go with option 2, as that brings more flexibility to whatever Best regards, |
|
Hi Răzvan, Thanks - that is a clear steer, and I am happy to reshape it into option 2. I will rework this PR in place rather than open a new one. Two things I ran into while planning it, both in
Otherwise the port looks straightforward: the row/schema mapping and query tiers move across as the five callbacks, and Best regards, |
rdb_apply_update() declared its HSET argv/argvlen pair as variable-length arrays sized from _un, the caller's SET column count: 32 bytes of stack per column, from a number the function's contract does not bound. The sibling rdb_store_row() has always pkg_malloc'd the identical pair, so the heap is this file's own convention. The arrays are allocated once in db_redis_update(), next to the existing numbufs and sharing its failure path and free, because the row-scan path calls rdb_apply_update() once per matched row. This is not a crash that can be triggered today: through sqlops the column count is limited by pv_print_buf_size to a few thousand columns, and every other dbf.update() caller in the tree passes a fixed column array. It is fixed because an unbounded stack allocation in a driver entry point is wrong regardless of today's callers.
|
I pushed d9e527f (
It is not a crash anyone can trigger today: through sqlops, The module builds with |
Overview
This PR adds
db_redis, a new optional OpenSIPS DB API backend that stores data in a Redis server or a Redis Cluster. Unlikecachedb_redis(which exposes Redis through the key-value NoSQL interface),db_redispresents Redis as a relational-style backend behind the standarddb_urlmechanism: a module is switched to Redis simply by pointing itsdb_urlat aredis://URL — no code change in the consuming module.The module is excluded from the default build (it depends on
hiredis) and is enabled by removingdb_redisfromexclude_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 onetype[,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>—INCRcounter backing auto-increment primary keys andlast_inserted_id.versiontable is provisioned like any other table.Ready-to-load provisioning files ship under
scripts/redis/.Structured DB API implemented —
query(primary-key fast path + full-table scan-and-filter, all six operators= < > <= >= !=combined with AND/OR, single-columnORDER BY),insert(HSETNXrow reservation for SQL-insert uniqueness),update(HSET/HDEL),delete(incl. bulkORprimary-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:-READONLYreplies (a demoted master after failover).CLUSTER SHARDS, falling back toCLUSTER 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});MOVEDis served from the warm pool with a deferred topology refresh,ASKis followed with a one-shotASKING, andTRYAGAINis 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
modemodparam (auto|single|cluster), turning a misconfiguration into a clear startup error instead of silently wrong behaviour.Modules supported in this PR
dialogdialog(v12)dispatcherdispatcher(v9)usrloclocation(v1013)sql-onlymode)clustererclusterer(v4),clusterer_bridge(v1)usrloc change
usrlochad 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 lacksDB_CAP_RAW_QUERY, the query is built through the structured API (column projection +expires > now) and thecontact_idmodulo 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 makesusrlocwork withdb_text. ConsequentlyDB_CAP_RAW_QUERYis no longer a hard requirement insql-onlymode.Roadmap
db_redis module
RediSearch ON HASHwhere available),FETCHresult cursors, per-node pipelining for bulk operations.raw_query, adbschemageneration target to emitschema:<table>files fromdb/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 serve the NoSQL cache interface rather than the structured DB API, so they are consumers ofcachedb_redis, not of this module. One exception:cachedb_perf(cachedb_perf: high-performance local memory cache built on modern kernel features #4118) also persists whole collections through the structured DB API (perf_save/perf_load,db_mode), so it is a genuinedb_redisconsumer — verified end to end against this module, with itscachedb_perftable (collection,pkey,pvalue,expires) provisioned like any other. Note Redis takes no transaction, so that snapshot is not atomic the way it is on an SQL backend.db_virtual,db_cachedb,cachedb_sql,sql_cacher— DB-layer plumbing, not end consumers.tm— keeps no database table.Testing
db_redisbuilds cleanly; module loads and binds viadb_bind_api.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;dialogrows load and flush across non-seed masters via the slot map.dispatcherandusrlocload their tables end-to-end throughdb_redis.Notes
modules/db_redis/with DocBook documentation and generatedREADME.scripts/redis/.db_redisadded toexclude_modulesinMakefile.conf.template(optional, off by default).