Kudos go to Pawel Kuzak.
Redis v7.4, hashes support auto-expiry for individual keys. By transforming sets into hashes we can support auto-expiry.
Adding module parameters use_redis_hashes and expires to control the described feature.
<!-- Kamailio Pull Request Template -->
<!-- IMPORTANT: - for detailed contributing guidelines, read: https://github.com/kamailio/kamailio/blob/master/.github/CONTRIBUTING.md - pull requests must be done to master branch, unless they are backports of fixes from master branch to a stable branch - backports to stable branches must be done with 'git cherry-pick -x ...' - code is contributed under BSD for core and main components (tm, sl, auth, tls) - code is contributed GPLv2 or a compatible license for the other components - GPL code is contributed with OpenSSL licensing exception -->
#### Pre-Submission Checklist <!-- Go over all points below, and after creating the PR, tick all the checkboxes that apply --> <!-- All points should be verified, otherwise, read the CONTRIBUTING guidelines from above--> <!-- If you're unsure about any of these, don't hesitate to ask on sr-dev mailing list --> - [x] Commit message has the format required by CONTRIBUTING guide - [x] Commits are split per component (core, individual modules, libs, utils, ...) - [ ] Each component has a single commit (if not, squash them into one commit) - [ ] No commits to README files for modules (changes must be done to docbook files in `doc/` subfolder, the README file is autogenerated)
#### Type Of Change - [ ] Small bug fix (non-breaking change which fixes an issue) - [x] New feature (non-breaking change which adds new functionality) - [ ] Breaking change (fix or feature that would change existing functionality)
#### Checklist: <!-- Go over all points below, and after creating the PR, tick the checkboxes that apply --> - [ ] PR should be backported to stable branches - [x] Tested changes locally - [ ] Related to issue #XXXX (replace XXXX with an open issue number)
#### Description <!-- Describe your changes in detail -->
You can view, comment on, or merge this pull request online at:
https://github.com/kamailio/kamailio/pull/4306
-- Commit Summary --
* db_redis: add Redis auto expiry support by changing sets to hashes
-- File Changes --
M src/modules/db_redis/db_redis_mod.c (18) M src/modules/db_redis/doc/db_redis_admin.xml (46) M src/modules/db_redis/redis_connection.c (47) M src/modules/db_redis/redis_dbase.c (385) M src/modules/db_redis/redis_dbase.h (3)
-- Patch Links --
https://github.com/kamailio/kamailio/pull/4306.patch https://github.com/kamailio/kamailio/pull/4306.diff
@miconda commented on this pull request.
@@ -42,6 +42,11 @@ MODULE_VERSION
str redis_keys = str_init(""); str redis_schema_path = str_init(SHARE_DIR "db_redis/kamailio"); int db_redis_verbosity = 1; +int db_redis_use_hashes = 0; +int db_redis_expires = 0; + +str expires = str_init(""); +char expires_buf[20] = {0};
These should be also prefixed with the module name, `expires` is quite common name in SIP and other libs, being declared with `extern` in other files, the symbol can conflict and be resolved to other globals. Like:
``` str db_redis_expires_str = str_init(""); char db_redis_expires_buf[20] = {0}; ```
@@ -315,6 +328,38 @@ int db_redis_connect(km_redis_con_t *con)
strcpy(con->srem_key_lua, reply->str); freeReplyObject(reply); reply = NULL; + + if(expires.len) { + reply = redisCommand(con->con, "INFO server"); + if(!reply) { + LM_ERR("failed to get INFO from Redis server\n"); + goto err; + } + + char *version_str = strstr(reply->str, "redis_version:");
It is recommended that variables are declared at the beginning of the function or of the block to work fine with older C versions.
+ if(expires.len) { + reply = redisCommand(con->con, "INFO server"); + if(!reply) { + LM_ERR("failed to get INFO from Redis server\n"); + goto err; + } + + char *version_str = strstr(reply->str, "redis_version:"); + if(!version_str) { + LM_ERR("Redis version not found in INFO reply\n"); + goto err; + } + + version_str += strlen("redis_version:"); // Skip past the field name + int major = 0, minor = 0, patch = 0;
It is recommended that variables are declared at the beginning of the function or of the block to work fine with older C versions.
@@ -243,6 +243,52 @@ modparam("db_redis", "opt_tls", 1)
</example> </section>
+ <section id="db_redis.p.use_redis_hashes"> + <title><varname>use_redis_hashes</varname> (int)</title> + <para> + Controls whether to use Redis hashes for storing the records in the database. + If 0, sets are used (which is the default). + </para> + <para> + Motivation of hashes is the implementation of HEXPIRE command in Redis, + available since Redis v 7.4.0 onwards, which allows expiring individual + keys inside. Hashes do bring the disadvantage of storing DUMMY values, + since only the keys are useful for us.
Is it a reason to store exactly `DUMMY`, or it was just a random pick? Maybe this value should be made a mod param (e.g., `hash_value`)?
</para>
+ <para> + Default value: 0. + </para> + <example> + <title>Enabling redis hashes</title> + <programlisting format="linespecific"> +... +modparam("db_redis", "use_redis_hashes", 1) +... + </programlisting> + </example> + </section> + + <section id="db_redis.p.expires"> + <title><varname>expires</varname> (int)</title>
To be more clear that is related to the hash table, I would propose to name this modparam `hash_expires`.
@@ -243,6 +243,52 @@ modparam("db_redis", "opt_tls", 1)
</example> </section>
+ <section id="db_redis.p.use_redis_hashes"> + <title><varname>use_redis_hashes</varname> (int)</title> + <para>
I am not sure if in the future would be another need for a different value, so it is just a suggestion to name this modparam `hash_mode`, so another value (e.g., `2`) can be considered instead of adding a new parameter in the future.
@lbalaceanu commented on this pull request.
@@ -243,6 +243,52 @@ modparam("db_redis", "opt_tls", 1)
</example> </section>
+ <section id="db_redis.p.use_redis_hashes"> + <title><varname>use_redis_hashes</varname> (int)</title> + <para> + Controls whether to use Redis hashes for storing the records in the database. + If 0, sets are used (which is the default). + </para> + <para> + Motivation of hashes is the implementation of HEXPIRE command in Redis, + available since Redis v 7.4.0 onwards, which allows expiring individual + keys inside. Hashes do bring the disadvantage of storing DUMMY values, + since only the keys are useful for us.
Hi @miconda, this "DUMMY" name was just a random pick, the value is currently not used. Sure, we will make it configurable.
@lbalaceanu commented on this pull request.
</para>
+ <para> + Default value: 0. + </para> + <example> + <title>Enabling redis hashes</title> + <programlisting format="linespecific"> +... +modparam("db_redis", "use_redis_hashes", 1) +... + </programlisting> + </example> + </section> + + <section id="db_redis.p.expires"> + <title><varname>expires</varname> (int)</title>
Sure.
@lbalaceanu commented on this pull request.
@@ -243,6 +243,52 @@ modparam("db_redis", "opt_tls", 1)
</example> </section>
+ <section id="db_redis.p.use_redis_hashes"> + <title><varname>use_redis_hashes</varname> (int)</title> + <para>
Hi, these hash data structures refer to the mapping structures that db_redis uses to implement uniqueness. use_redis_hashes means in fact use_redis_hashes_for_mapping_structures, maybe renaming the parameter to `mapping_struct_type` would be clearer ?!
lbalaceanu left a comment (kamailio/kamailio#4306)
Hi @miconda,
Thank you for the comments, I will implement them.
@miconda commented on this pull request.
@@ -243,6 +243,52 @@ modparam("db_redis", "opt_tls", 1)
</example> </section>
+ <section id="db_redis.p.use_redis_hashes"> + <title><varname>use_redis_hashes</varname> (int)</title> + <para>
Fine for me for the suggested name, sounds better for future extensibility that the previous one.
miconda left a comment (kamailio/kamailio#4306)
A few questions to understand the design for the new struct storage model: did I understand correctly that a DB record (ie., row values) are now keys in a dedicated hash table (since the value is `DUMMY`)? If yes, they are prefixed with the column name, or how is the mapping column-name to hash-key (the value) is done? Afaik, uasualy the order of the items/keys in a hash table is not guaranteed, or does Redis guarantees that for the same list of keys, it is the same order of the items?
Where per-item expiration helps (e.g., for usrloc), isn't the entire hash table that has to be deleted on expire? Or is not like each DB record is a dedicated htable?
Sorry for the laziness of not going through the code to understand from there some of the questions ...
lbalaceanu left a comment (kamailio/kamailio#4306)
Sorry for not making this clearer.
Until now, when using usrloc + redis, when 497xxxxxxxxx @ domain would register, then the following data structs would be created:
HASH "location:entry::uloc-686296ea-5773d-1" -> contact info SET "location:usrdom::497xxxxxxxxx:domain" -> contains "location:entry::uloc-686296ea-5773d-1" , but might have more entries SET "location::index::usrdom" -> contains "location:usrdom::497xxxxxxxxx:domain" etc SET "location:timer::2025-06-30 17:54:03" "location:entry::uloc-686296ea-5773d-1" SET "location::index::timer" "location:timer::2025-06-30 17:54:03"
This commit changes all the sets to hashes, the keys being the previous entries in the hashes. We do this because from v7.4 on, Redis allows auto-expiry on key level. With this patch we set expires on location:entry::uloc-686296ea-5773d-1 itself and on its corresponding keys in "location:usrdom::497xxxxxxxxx:domain", "location::index::usrdom", "location:timer::2025-06-30 17:54:03" , "location::index::timer".
We saw this also as a relatively low hanging fruit since there was a todo comment in the db_redis code stating: // TODO: utilize auto-expiry? on insert/update, also update expire value // of mappings
miconda left a comment (kamailio/kamailio#4306)
Thanks for the details!
This commit changes all the sets to hashes, the keys being the previous entries in the hashes.
I guess that a typo was made and the above should be: `... the keys being the previous entries in the sets`
lbalaceanu left a comment (kamailio/kamailio#4306)
You are right about the typo.
@lbalaceanu pushed 0 commits.
Closed #4306.
Reopened #4306.
lbalaceanu left a comment (kamailio/kamailio#4306)
I am still doing some changes so please don't close this PR.
@lbalaceanu pushed 1 commit.
63730ff4e0595b7880e9f645bf0ce2679e34bd81 db_redis: add Redis auto expiry support by changing sets to hashes
lbalaceanu left a comment (kamailio/kamailio#4306)
Hi, I removed the work-in-progress label, I have nothing more to add to these changes. If anyone wants to suggest anything, please do.
miconda left a comment (kamailio/kamailio#4306)
Fine for me to merge, if there are no other comments.
Merged #4306 into master.