فهرست منبع

db_redis: add Redis auto expiry support by changing sets to hashes

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 mapping_struct_type, hash_expires, hash_value to control
the described feature.
Lucian Balaceanu 2 ماه پیش
والد
کامیت
0cf6471045

+ 21 - 0
src/modules/db_redis/db_redis_mod.c

@@ -42,6 +42,12 @@ MODULE_VERSION
 str redis_keys = str_init("");
 str redis_keys = str_init("");
 str redis_schema_path = str_init(SHARE_DIR "db_redis/kamailio");
 str redis_schema_path = str_init(SHARE_DIR "db_redis/kamailio");
 int db_redis_verbosity = 1;
 int db_redis_verbosity = 1;
+int mapping_struct_type = 0;
+str db_redis_hash_value = str_init("DUMMY");
+
+int db_redis_hash_expires = 0;
+str db_redis_hash_expires_str = str_init("");
+char db_redis_hash_expires_buf[20] = {0};
 
 
 static int db_redis_bind_api(db_func_t *dbb);
 static int db_redis_bind_api(db_func_t *dbb);
 static int mod_init(void);
 static int mod_init(void);
@@ -60,6 +66,9 @@ static param_export_t params[] = {
 		{"keys", PARAM_STRING | PARAM_USE_FUNC, (void *)keys_param},
 		{"keys", PARAM_STRING | PARAM_USE_FUNC, (void *)keys_param},
 		{"schema_path", PARAM_STR, &redis_schema_path},
 		{"schema_path", PARAM_STR, &redis_schema_path},
 		{"verbosity", PARAM_INT, &db_redis_verbosity},
 		{"verbosity", PARAM_INT, &db_redis_verbosity},
+		{"mapping_struct_type", PARAM_INT, &mapping_struct_type},
+		{"hash_value", PARAM_STRING, &db_redis_hash_value},
+		{"hash_expires", PARAM_INT, &db_redis_hash_expires},
 #ifdef WITH_SSL
 #ifdef WITH_SSL
 		{"opt_tls", PARAM_INT, &db_redis_opt_tls},
 		{"opt_tls", PARAM_INT, &db_redis_opt_tls},
 		{"ca_path", PARAM_STRING, &db_redis_ca_path},
 		{"ca_path", PARAM_STRING, &db_redis_ca_path},
@@ -120,6 +129,18 @@ int mod_register(char *path, int *dlflags, void *p1, void *p2)
 static int mod_init(void)
 static int mod_init(void)
 {
 {
 	LM_DBG("module initializing\n");
 	LM_DBG("module initializing\n");
+
+	if(db_redis_hash_expires && (mapping_struct_type != MS_HASH)) {
+		LM_ERR("expires parameter is only supported with mapping_struct_type "
+			   "set to 1 (hash)\n");
+		return -1;
+	}
+
+	if(db_redis_hash_expires) {
+		db_redis_hash_expires_str.s = db_redis_hash_expires_buf;
+		db_redis_hash_expires_str.len = snprintf(
+				db_redis_hash_expires_str.s, 20, "%d", db_redis_hash_expires);
+	}
 	return 0;
 	return 0;
 }
 }
 
 

+ 5 - 0
src/modules/db_redis/db_redis_mod.h

@@ -51,5 +51,10 @@
 
 
 extern str redis_keys;
 extern str redis_keys;
 extern str redis_schema_path;
 extern str redis_schema_path;
+typedef enum
+{
+	MS_SET = 0,
+	MS_HASH = 1
+} mapping_struct_type_t;
 
 
 #endif /* _DB_REDIS_MOD_H */
 #endif /* _DB_REDIS_MOD_H */

+ 93 - 5
src/modules/db_redis/doc/db_redis_admin.xml

@@ -48,11 +48,11 @@ username/string,domain/string,contact/string,received/string,path/string,expires
 		<para>
 		<para>
 			For instance, usrloc relies on a key of "username@domain", but in order to store
 			For instance, usrloc relies on a key of "username@domain", but in order to store
 			multiple contacts per AoR, it cannot be constrained to uniqueness. To
 			multiple contacts per AoR, it cannot be constrained to uniqueness. To
-			work around this, db_redis supports mapping sets in such a way as to, in the case of
-			the usrloc module, have a set with a key of "username@domain" and its entries being
-			unique keys per contact based on the ruid of a contact. Thus, one contact in usrloc
-			consists of a unique key "location:entry::example-ruid-1" being a hash with the columns like
-			username, domain, contact, path etc. In addition, this unique key is stored
+			work around this, db_redis supports mapping structures (either sets or hashes).
+			If sets are chosen, in the case of the usrloc module for example, one would have a set with a key
+			of "username@domain" and its entries being unique keys per contact based on the ruid of a contact.
+			Thus, one contact in usrloc consists of a unique key "location:entry::example-ruid-1" being a hash
+			with the columns like username, domain, contact, path etc. In addition, this unique key is stored
 			in a set "location:usrdom::exampleuser:exampledomain.org". When usrloc does
 			in a set "location:usrdom::exampleuser:exampledomain.org". When usrloc does
 			a lookup based on "username@domain", db_redis figures out via the keys/values
 			a lookup based on "username@domain", db_redis figures out via the keys/values
 			the query constructed by usrloc to look for the final entry key in the
 			the query constructed by usrloc to look for the final entry key in the
@@ -101,6 +101,29 @@ location=entry:ruid&amp;usrdom:username,domain&amp;timer:partition,keepalive;acc
 			For readability purposes, definitions of keys per table can span multiple Kamailio
 			For readability purposes, definitions of keys per table can span multiple Kamailio
 			config lines by providing multiple "keys" modparams.
 			config lines by providing multiple "keys" modparams.
 		</para>
 		</para>
+		<para>
+			When the mapping structure is selected to be hash, one contact in usrloc
+			consists of the same unique key "location:entry::example-ruid-1" as before, but now this unique
+			key is also a key in the hash "location:usrdom::exampleuser:exampledomain.org".
+			The value associated with this key is whatever module param hash_value is set to.
+			When usrloc does a lookup based on "username@domain", db_redis goes through
+			the keys in the hash "location:usrdom::exampleuser:exampledomain.org" to retrieve the associated
+			contact keys. All the other mapping structures that used to be sets are now hashes as well
+			(index::timer, location:timer). This possibility of using hashes has only been tested for usrloc and
+			was introduced because from Redis v 7.4.0 onwards the HEXPIRE command is available, which allows
+			expiring individual keys inside a hash. This makes the process of expiring contacts something that Redis
+			itself can handle, without the need for Kamailio to do it.
+		</para>
+		<para>
+			Example of structures inside Redis for the usrloc module, with mapping structure type 1 (hashes):
+			<programlisting format="linespecific">
+			HASH "location:entry::example-ruid-1" -> contact info
+			HASH "location:usrdom::exampleuser:exampledomain" -> contains (key:"location:entry::example-ruid-1" , value:"hash_value")
+			HASH "location::index::usrdom" -> contains (key:"location:usrdom::exampleuser:exampledomain", value:"hash_value")
+			HASH "location:timer::YYYY-MM-DD mm:ss:mm" -> contains (key:"location:entry::example-ruid-1", value:"hash_value")
+			HASH "location::index::timer" -> contains (key:"location:timer::YYYY-MM-DD mm:ss:mm", value:"hash_value")
+			</programlisting>
+		</para>
 	</section>
 	</section>
 
 
 	<section>
 	<section>
@@ -243,6 +266,71 @@ modparam("db_redis", "opt_tls", 1)
 			</example>
 			</example>
 		</section>
 		</section>
 
 
+		<section id="db_redis.p.mapping_struct_type">
+			<title><varname>mapping_struct_type</varname> (int)</title>
+			<para>
+				Controls the type of mapping structures to be used. Beforehand, only sets were used.
+				For the rationale behind mapping structures, see the discussion about mapping structures in the overview section.
+				Currently supported values: 0 - sets (default), 1 - hashes.
+			</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.
+			</para>
+			<para>
+				Default value: 0.
+			</para>
+			<example>
+				<title>Enabling redis hashes</title>
+				<programlisting format="linespecific">
+...
+modparam("db_redis", "mapping_struct_type", 1)
+...
+				</programlisting>
+			</example>
+		</section>
+
+		<section id="db_redis.p.hash_value">
+			<title><varname>hash_value</varname> (string)</title>
+			<para>
+				Only has sense if mapping_struct_type is set to hash (1).
+				Defines the value to be used for the hash entries in the hashes that now replace mapping sets.
+			</para>
+			<para>
+				Default value: DUMMY.
+			</para>
+			<example>
+				<title>Setting hash_value</title>
+				<programlisting format="linespecific">
+...
+modparam("db_redis", "hash_value", "DUMMY")
+...
+				</programlisting>
+			</example>
+		</section>
+
+		<section id="db_redis.p.hash_expires">
+			<title><varname>hash_expires</varname> (int)</title>
+			<para>
+				Set an expiration time in seconds for the keys in the hash data structures in the database.
+				This is useful for the usrloc module for example, for automatic contact expiry.
+				A value of 0 is interpreted as no expiration. This is only supported with
+				mapping_struct_type set to hash (1).
+			</para>
+			<para>
+				Default value: 0.
+			</para>
+			<example>
+				<title>Setting hash expires</title>
+				<programlisting format="linespecific">
+...
+modparam("db_redis", "hash_expires", 3600)
+...
+				</programlisting>
+			</example>
+		</section>
+
 		<section id="db_redis.p.db_pass">
 		<section id="db_redis.p.db_pass">
 			<title><varname>db_pass</varname> (string)</title>
 			<title><varname>db_pass</varname> (string)</title>
 			<para>
 			<para>

+ 48 - 1
src/modules/db_redis/redis_connection.c

@@ -37,6 +37,8 @@ static unsigned int MAX_URL_LENGTH = 1023;
 #endif
 #endif
 
 
 extern int db_redis_verbosity;
 extern int db_redis_verbosity;
+extern int mapping_struct_type;
+extern str db_redis_hash_expires_str;
 #ifdef WITH_SSL
 #ifdef WITH_SSL
 extern int db_redis_opt_tls;
 extern int db_redis_opt_tls;
 extern char *db_redis_ca_path;
 extern char *db_redis_ca_path;
@@ -120,6 +122,13 @@ static redis_key_t *db_redis_shift_query(km_redis_con_t *con)
 	return query;
 	return query;
 }
 }
 
 
+inline int redis_supports_expires(int major, int minor, int patch)
+{
+	if(!(major > 7 || (major == 7 && minor >= 4)))
+		return 0;
+	return 1;
+}
+
 int db_redis_connect(km_redis_con_t *con)
 int db_redis_connect(km_redis_con_t *con)
 {
 {
 	struct timeval tv;
 	struct timeval tv;
@@ -290,7 +299,11 @@ int db_redis_connect(km_redis_con_t *con)
 	LM_DBG("connection opened to %.*s\n", con->id->url.len, con->id->url.s);
 	LM_DBG("connection opened to %.*s\n", con->id->url.len, con->id->url.s);
 
 
 #ifndef WITH_HIREDIS_CLUSTER
 #ifndef WITH_HIREDIS_CLUSTER
-	reply = redisCommand(con->con, "SCRIPT LOAD %s", SREM_KEY_LUA);
+	if(mapping_struct_type == MS_HASH) {
+		reply = redisCommand(con->con, "SCRIPT LOAD %s", HDEL_KEY_LUA);
+	} else {
+		reply = redisCommand(con->con, "SCRIPT LOAD %s", SREM_KEY_LUA);
+	}
 	if(!reply) {
 	if(!reply) {
 		LM_ERR("failed to load LUA script to server %.*s: %s\n",
 		LM_ERR("failed to load LUA script to server %.*s: %s\n",
 				con->id->url.len, con->id->url.s, con->con->errstr);
 				con->id->url.len, con->id->url.s, con->con->errstr);
@@ -315,6 +328,40 @@ int db_redis_connect(km_redis_con_t *con)
 	strcpy(con->srem_key_lua, reply->str);
 	strcpy(con->srem_key_lua, reply->str);
 	freeReplyObject(reply);
 	freeReplyObject(reply);
 	reply = NULL;
 	reply = NULL;
+
+	if(db_redis_hash_expires_str.len) {
+		char *version_str = NULL;
+		int major = 0, minor = 0, patch = 0;
+
+		reply = redisCommand(con->con, "INFO server");
+		if(!reply) {
+			LM_ERR("failed to get INFO from Redis server\n");
+			goto err;
+		}
+
+		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
+		if(sscanf(version_str, "%d.%d.%d", &major, &minor, &patch) < 3) {
+			LM_ERR("Error parsing the version string: %s\n", version_str);
+			goto err;
+		}
+
+		if(!redis_supports_expires(major, minor, patch)) {
+			LM_ERR("HEXPIRE (used by redis_expire parameter) is not "
+				   "implemented in Redis version %d.%d.%d\n.",
+					major, minor, patch);
+			goto err;
+		}
+
+		LM_DBG("Redis server version is: %d.%d.%d\n", major, minor, patch);
+		freeReplyObject(reply);
+		reply = NULL;
+	}
 #endif
 #endif
 	LM_DBG("connection opened to %.*s\n", con->id->url.len, con->id->url.s);
 	LM_DBG("connection opened to %.*s\n", con->id->url.len, con->id->url.s);
 
 

+ 342 - 50
src/modules/db_redis/redis_dbase.c

@@ -33,6 +33,9 @@
 
 
 #define TIMESTAMP_STR_LENGTH 19
 #define TIMESTAMP_STR_LENGTH 19
 
 
+extern int mapping_struct_type;
+extern str db_redis_hash_expires_str;
+
 static void db_redis_dump_reply(redisReply *reply)
 static void db_redis_dump_reply(redisReply *reply)
 {
 {
 	int i;
 	int i;
@@ -53,9 +56,6 @@ static void db_redis_dump_reply(redisReply *reply)
 	}
 	}
 }
 }
 
 
-// TODO: utilize auto-expiry? on insert/update, also update expire value
-// of mappings
-
 /*
 /*
  * Initialize database module
  * Initialize database module
  * No function should be called before this
  * No function should be called before this
@@ -722,11 +722,12 @@ static int db_redis_build_query_keys(km_redis_con_t *con, const str *table_name,
 			}
 			}
 			if(key_found) {
 			if(key_found) {
 				redis_key_t *query_v = NULL;
 				redis_key_t *query_v = NULL;
-				char *prefix = "SMEMBERS";
+				char *prefix =
+						(mapping_struct_type == MS_HASH) ? "HKEYS" : "SMEMBERS";
 
 
 				if(db_redis_key_add_string(&query_v, prefix, strlen(prefix))
 				if(db_redis_key_add_string(&query_v, prefix, strlen(prefix))
 						!= 0) {
 						!= 0) {
-					LM_ERR("Failed to add smembers command to query\n");
+					LM_ERR("Failed to add smembers/hkeys command to query\n");
 					db_redis_key_free(&query_v);
 					db_redis_key_free(&query_v);
 					goto err;
 					goto err;
 				}
 				}
@@ -1262,10 +1263,18 @@ static int db_redis_scan_query_keys(km_redis_con_t *con, const str *table_name,
 				set_key->key.s);
 				set_key->key.s);
 
 
 		redis_key_t *query_v = NULL;
 		redis_key_t *query_v = NULL;
-		if(db_redis_key_add_string(&query_v, "SMEMBERS", 8) != 0) {
-			LM_ERR("Failed to add smembers command to query\n");
-			db_redis_key_free(&query_v);
-			goto out;
+		if(mapping_struct_type == MS_HASH) {
+			if(db_redis_key_add_string(&query_v, "HKEYS", 5) != 0) {
+				LM_ERR("Failed to add hkeys command to query\n");
+				db_redis_key_free(&query_v);
+				goto out;
+			}
+		} else {
+			if(db_redis_key_add_string(&query_v, "SMEMBERS", 8) != 0) {
+				LM_ERR("Failed to add smembers command to query\n");
+				db_redis_key_free(&query_v);
+				goto out;
+			}
 		}
 		}
 		if(db_redis_key_add_str(&query_v, &set_key->key) != 0) {
 		if(db_redis_key_add_str(&query_v, &set_key->key) != 0) {
 			LM_ERR("Failed to add key name to smembers query\n");
 			LM_ERR("Failed to add key name to smembers query\n");
@@ -2020,9 +2029,16 @@ static int db_redis_perform_delete(const db1_con_t *_h, km_redis_con_t *con,
 				type_key = type_key->next, set_key = set_key->next) {
 				type_key = type_key->next, set_key = set_key->next) {
 
 
 #ifdef WITH_HIREDIS_CLUSTER
 #ifdef WITH_HIREDIS_CLUSTER
-			if(db_redis_key_add_string(&query_v, "SREM", 4) != 0) {
-				LM_ERR("Failed to add srem command to post-delete query\n");
-				goto error;
+			if(mapping_struct_type == MS_HASH) {
+				if(db_redis_key_add_string(&query_v, "HDEL", 4) != 0) {
+					LM_ERR("Failed to add hdel command to post-delete query\n");
+					goto error;
+				}
+			} else {
+				if(db_redis_key_add_string(&query_v, "SREM", 4) != 0) {
+					LM_ERR("Failed to add srem command to post-delete query\n");
+					goto error;
+				}
 			}
 			}
 			if(db_redis_key_add_str(&query_v, &type_key->key) != 0) {
 			if(db_redis_key_add_str(&query_v, &type_key->key) != 0) {
 				LM_ERR("Failed to add key to delete query\n");
 				LM_ERR("Failed to add key to delete query\n");
@@ -2037,9 +2053,17 @@ static int db_redis_perform_delete(const db1_con_t *_h, km_redis_con_t *con,
 			db_redis_check_reply(con, reply, error);
 			db_redis_check_reply(con, reply, error);
 			db_redis_free_reply(&reply);
 			db_redis_free_reply(&reply);
 
 
-			if(db_redis_key_add_string(&query_v, "SCARD", 5) != 0) {
-				LM_ERR("Failed to add scard command to post-delete query\n");
-				goto error;
+			if(mapping_struct_type == MS_HASH) {
+				if(db_redis_key_add_string(&query_v, "HLEN", 4) != 0) {
+					LM_ERR("Failed to add hlen command to post-delete query\n");
+					goto error;
+				}
+			} else {
+				if(db_redis_key_add_string(&query_v, "SCARD", 5) != 0) {
+					LM_ERR("Failed to add scard command to post-delete "
+						   "query\n");
+					goto error;
+				}
 			}
 			}
 			if(db_redis_key_add_str(&query_v, &type_key->key) != 0) {
 			if(db_redis_key_add_str(&query_v, &type_key->key) != 0) {
 				LM_ERR("Failed to add key to delete query\n");
 				LM_ERR("Failed to add key to delete query\n");
@@ -2054,9 +2078,16 @@ static int db_redis_perform_delete(const db1_con_t *_h, km_redis_con_t *con,
 			if(scard != 0)
 			if(scard != 0)
 				continue;
 				continue;
 
 
-			if(db_redis_key_add_string(&query_v, "SREM", 4) != 0) {
-				LM_ERR("Failed to add srem command to post-delete query\n");
-				goto error;
+			if(mapping_struct_type == MS_HASH) {
+				if(db_redis_key_add_string(&query_v, "HDEL", 4) != 0) {
+					LM_ERR("Failed to add srem command to post-delete query\n");
+					goto error;
+				}
+			} else {
+				if(db_redis_key_add_string(&query_v, "SREM", 4) != 0) {
+					LM_ERR("Failed to add srem command to post-delete query\n");
+					goto error;
+				}
 			}
 			}
 			if(db_redis_key_add_str(&query_v, &set_key->key) != 0) {
 			if(db_redis_key_add_str(&query_v, &set_key->key) != 0) {
 				LM_ERR("Failed to add key to delete query\n");
 				LM_ERR("Failed to add key to delete query\n");
@@ -2248,15 +2279,15 @@ static int db_redis_perform_update(const db1_con_t *_h, km_redis_con_t *con,
 	/*
 	/*
     key_value = (str*)pkg_malloc(_nu * sizeof(str));
     key_value = (str*)pkg_malloc(_nu * sizeof(str));
     if (!key_value) {
     if (!key_value) {
-        LM_ERR("Failed to allocate memory for key buffer\n");
-        goto error;
+	LM_ERR("Failed to allocate memory for key buffer\n");
+	goto error;
     }
     }
     memset(key_value, 0, _nu * sizeof(str));
     memset(key_value, 0, _nu * sizeof(str));
 
 
     col_value = (str*)pkg_malloc(_nu * sizeof(str));
     col_value = (str*)pkg_malloc(_nu * sizeof(str));
     if (!col_value) {
     if (!col_value) {
-        LM_ERR("Failed to allocate memory for column buffer\n");
-        goto error;
+	LM_ERR("Failed to allocate memory for column buffer\n");
+	goto error;
     }
     }
     memset(col_value, 0, _nu * sizeof(str));
     memset(col_value, 0, _nu * sizeof(str));
     */
     */
@@ -2440,9 +2471,18 @@ static int db_redis_perform_update(const db1_con_t *_h, km_redis_con_t *con,
 
 
 				// add to new set key and delete from old
 				// add to new set key and delete from old
 
 
-				if(db_redis_key_add_string(&query_v, "SADD", 4) != 0) {
-					LM_ERR("Failed to set sadd command to post-update query\n");
-					goto error;
+				if(mapping_struct_type == MS_HASH) {
+					if(db_redis_key_add_string(&query_v, "HSET", 4) != 0) {
+						LM_ERR("Failed to set hset command to post-update "
+							   "query\n");
+						goto error;
+					}
+				} else {
+					if(db_redis_key_add_string(&query_v, "SADD", 4) != 0) {
+						LM_ERR("Failed to set sadd command to post-update "
+							   "query\n");
+						goto error;
+					}
 				}
 				}
 				if(db_redis_key_add_str(&query_v, &new_type_key->key) != 0) {
 				if(db_redis_key_add_str(&query_v, &new_type_key->key) != 0) {
 					LM_ERR("Failed to add map key to post-update query\n");
 					LM_ERR("Failed to add map key to post-update query\n");
@@ -2452,6 +2492,13 @@ static int db_redis_perform_update(const db1_con_t *_h, km_redis_con_t *con,
 					LM_ERR("Failed to set entry key to post-update query\n");
 					LM_ERR("Failed to set entry key to post-update query\n");
 					goto error;
 					goto error;
 				}
 				}
+				if(mapping_struct_type == MS_HASH) {
+					if(db_redis_key_add_string(&query_v, "DUMMY", 5) != 0) {
+						LM_ERR("Failed to set entry key to post-update "
+							   "query\n");
+						goto error;
+					}
+				}
 
 
 				update_queries++;
 				update_queries++;
 				if(db_redis_append_command_argv(con, query_v, 1) != REDIS_OK) {
 				if(db_redis_append_command_argv(con, query_v, 1) != REDIS_OK) {
@@ -2461,9 +2508,60 @@ static int db_redis_perform_update(const db1_con_t *_h, km_redis_con_t *con,
 
 
 				db_redis_key_free(&query_v);
 				db_redis_key_free(&query_v);
 
 
-				if(db_redis_key_add_string(&query_v, "SADD", 4) != 0) {
-					LM_ERR("Failed to set sadd command to post-update query\n");
-					goto error;
+				if(db_redis_hash_expires_str.len) {
+					if(db_redis_key_add_string(&query_v, "HEXPIRE", 7) != 0) {
+						LM_ERR("Failed to add hexpire command to query\n");
+						goto error;
+					}
+					if(db_redis_key_add_str(&query_v, &new_type_key->key)
+							!= 0) {
+						LM_ERR("Failed to add map key to post-update query\n");
+						goto error;
+					}
+
+					if(db_redis_key_add_str(
+							   &query_v, &db_redis_hash_expires_str)
+							!= 0) {
+						LM_ERR("Failed to add expiry time to post-update "
+							   "query\n");
+						goto error;
+					}
+					if(db_redis_key_add_string(&query_v, "FIELDS", 6) != 0) {
+						LM_ERR("Failed to add fields suffix to query\n");
+						goto error;
+					}
+					if(db_redis_key_add_string(&query_v, "1", 1) != 0) {
+						LM_ERR("Failed to add fields suffix to query\n");
+						goto error;
+					}
+					if(db_redis_key_add_str(&query_v, &key->key) != 0) {
+						LM_ERR("Failed to set entry key to post-update "
+							   "query\n");
+						goto error;
+					}
+
+					update_queries++;
+					if(db_redis_append_command_argv(con, query_v, 1)
+							!= REDIS_OK) {
+						LM_ERR("Failed to append redis command\n");
+						goto error;
+					}
+
+					db_redis_key_free(&query_v);
+				}
+
+				if(mapping_struct_type == MS_HASH) {
+					if(db_redis_key_add_string(&query_v, "HSET", 4) != 0) {
+						LM_ERR("Failed to set hset command to post-update "
+							   "query\n");
+						goto error;
+					}
+				} else {
+					if(db_redis_key_add_string(&query_v, "SADD", 4) != 0) {
+						LM_ERR("Failed to set sadd command to post-update "
+							   "query\n");
+						goto error;
+					}
 				}
 				}
 				if(db_redis_key_add_str(&query_v, &set_key->key) != 0) {
 				if(db_redis_key_add_str(&query_v, &set_key->key) != 0) {
 					LM_ERR("Failed to add map key to post-update query\n");
 					LM_ERR("Failed to add map key to post-update query\n");
@@ -2473,6 +2571,13 @@ static int db_redis_perform_update(const db1_con_t *_h, km_redis_con_t *con,
 					LM_ERR("Failed to set entry key to post-update query\n");
 					LM_ERR("Failed to set entry key to post-update query\n");
 					goto error;
 					goto error;
 				}
 				}
+				if(mapping_struct_type == MS_HASH) {
+					if(db_redis_key_add_string(&query_v, "DUMMY", 5) != 0) {
+						LM_ERR("Failed to set entry key to post-update "
+							   "query\n");
+						goto error;
+					}
+				}
 
 
 				update_queries++;
 				update_queries++;
 				if(db_redis_append_command_argv(con, query_v, 1) != REDIS_OK) {
 				if(db_redis_append_command_argv(con, query_v, 1) != REDIS_OK) {
@@ -2482,10 +2587,60 @@ static int db_redis_perform_update(const db1_con_t *_h, km_redis_con_t *con,
 
 
 				db_redis_key_free(&query_v);
 				db_redis_key_free(&query_v);
 
 
+				if(db_redis_hash_expires_str.len) {
+					if(db_redis_key_add_string(&query_v, "HEXPIRE", 7) != 0) {
+						LM_ERR("Failed to add hexpire command to query\n");
+						goto error;
+					}
+					if(db_redis_key_add_str(&query_v, &set_key->key) != 0) {
+						LM_ERR("Failed to add map key to post-update query\n");
+						goto error;
+					}
+					if(db_redis_key_add_str(
+							   &query_v, &db_redis_hash_expires_str)
+							!= 0) {
+						LM_ERR("Failed to add expiry time to post-update "
+							   "query\n");
+						goto error;
+					}
+					if(db_redis_key_add_string(&query_v, "FIELDS", 6) != 0) {
+						LM_ERR("Failed to add fields suffix to query\n");
+						goto error;
+					}
+					if(db_redis_key_add_string(&query_v, "1", 1) != 0) {
+						LM_ERR("Failed to add fields suffix to query\n");
+						goto error;
+					}
+					if(db_redis_key_add_str(&query_v, &new_type_key->key)
+							!= 0) {
+						LM_ERR("Failed to set entry key to post-update "
+							   "query\n");
+						goto error;
+					}
+
+					update_queries++;
+					if(db_redis_append_command_argv(con, query_v, 1)
+							!= REDIS_OK) {
+						LM_ERR("Failed to append redis command\n");
+						goto error;
+					}
+
+					db_redis_key_free(&query_v);
+				}
+
 #ifdef WITH_HIREDIS_CLUSTER
 #ifdef WITH_HIREDIS_CLUSTER
-				if(db_redis_key_add_string(&query_v, "SREM", 4) != 0) {
-					LM_ERR("Failed to add srem command to post-delete query\n");
-					goto error;
+				if(mapping_struct_type == MS_HASH) {
+					if(db_redis_key_add_string(&query_v, "HDEL", 4) != 0) {
+						LM_ERR("Failed to add hdel command to post-delete "
+							   "query\n");
+						goto error;
+					}
+				} else {
+					if(db_redis_key_add_string(&query_v, "SREM", 4) != 0) {
+						LM_ERR("Failed to add srem command to post-delete "
+							   "query\n");
+						goto error;
+					}
 				}
 				}
 				if(db_redis_key_add_str(&query_v, &type_key->key) != 0) {
 				if(db_redis_key_add_str(&query_v, &type_key->key) != 0) {
 					LM_ERR("Failed to add key to delete query\n");
 					LM_ERR("Failed to add key to delete query\n");
@@ -2500,10 +2655,18 @@ static int db_redis_perform_update(const db1_con_t *_h, km_redis_con_t *con,
 				db_redis_check_reply(con, reply, error);
 				db_redis_check_reply(con, reply, error);
 				db_redis_free_reply(&reply);
 				db_redis_free_reply(&reply);
 
 
-				if(db_redis_key_add_string(&query_v, "SCARD", 5) != 0) {
-					LM_ERR("Failed to add scard command to post-delete "
-						   "query\n");
-					goto error;
+				if(mapping_struct_type == MS_HASH) {
+					if(db_redis_key_add_string(&query_v, "HLEN", 4) != 0) {
+						LM_ERR("Failed to add hlen command to post-delete "
+							   "query\n");
+						goto error;
+					}
+				} else {
+					if(db_redis_key_add_string(&query_v, "SCARD", 5) != 0) {
+						LM_ERR("Failed to add scard command to post-delete "
+							   "query\n");
+						goto error;
+					}
 				}
 				}
 				if(db_redis_key_add_str(&query_v, &type_key->key) != 0) {
 				if(db_redis_key_add_str(&query_v, &type_key->key) != 0) {
 					LM_ERR("Failed to add key to delete query\n");
 					LM_ERR("Failed to add key to delete query\n");
@@ -2518,9 +2681,18 @@ static int db_redis_perform_update(const db1_con_t *_h, km_redis_con_t *con,
 				if(scard != 0)
 				if(scard != 0)
 					continue;
 					continue;
 
 
-				if(db_redis_key_add_string(&query_v, "SREM", 4) != 0) {
-					LM_ERR("Failed to add srem command to post-delete query\n");
-					goto error;
+				if(mapping_struct_type == MS_HASH) {
+					if(db_redis_key_add_string(&query_v, "HDEL", 4) != 0) {
+						LM_ERR("Failed to add hdel command to post-delete "
+							   "query\n");
+						goto error;
+					}
+				} else {
+					if(db_redis_key_add_string(&query_v, "SREM", 4) != 0) {
+						LM_ERR("Failed to add srem command to post-delete "
+							   "query\n");
+						goto error;
+					}
 				}
 				}
 				if(db_redis_key_add_str(&query_v, &set_key->key) != 0) {
 				if(db_redis_key_add_str(&query_v, &set_key->key) != 0) {
 					LM_ERR("Failed to add key to delete query\n");
 					LM_ERR("Failed to add key to delete query\n");
@@ -2540,12 +2712,22 @@ static int db_redis_perform_update(const db1_con_t *_h, km_redis_con_t *con,
 							   "query\n");
 							   "query\n");
 						goto error;
 						goto error;
 					}
 					}
-					if(db_redis_key_add_string(
-							   &query_v, SREM_KEY_LUA, strlen(SREM_KEY_LUA))
-							!= 0) {
-						LM_ERR("Failed to add srem command to post-delete "
-							   "query\n");
-						goto error;
+					if(mapping_struct_type == MS_HASH) {
+						if(db_redis_key_add_string(
+								   &query_v, HDEL_KEY_LUA, strlen(HDEL_KEY_LUA))
+								!= 0) {
+							LM_ERR("Failed to add hdel command to post-delete "
+								   "query\n");
+							goto error;
+						}
+					} else {
+						if(db_redis_key_add_string(
+								   &query_v, SREM_KEY_LUA, strlen(SREM_KEY_LUA))
+								!= 0) {
+							LM_ERR("Failed to add srem command to post-delete "
+								   "query\n");
+							goto error;
+						}
 					}
 					}
 					if(db_redis_key_add_string(&query_v, "3", 1) != 0) {
 					if(db_redis_key_add_string(&query_v, "3", 1) != 0) {
 						LM_ERR("Failed to add srem command to post-delete "
 						LM_ERR("Failed to add srem command to post-delete "
@@ -2876,6 +3058,26 @@ int db_redis_insert(const db1_con_t *_h, const db_key_t *_k, const db_val_t *_v,
 	db_redis_check_reply(con, reply, error);
 	db_redis_check_reply(con, reply, error);
 	db_redis_free_reply(&reply);
 	db_redis_free_reply(&reply);
 
 
+	if(db_redis_hash_expires_str.len) {
+		if(db_redis_key_add_string(&query_v, "EXPIRE", 6) != 0) {
+			LM_ERR("Failed to add expire command to query\n");
+			goto error;
+		}
+		if(db_redis_key_add_str(&query_v, &key->key) != 0) {
+			LM_ERR("Failed to add key to insert query\n");
+			goto error;
+		}
+		if(db_redis_key_add_str(&query_v, &db_redis_hash_expires_str) != 0) {
+			LM_ERR("Failed to add expiry time to post-update query\n");
+			goto error;
+		}
+
+		reply = db_redis_command_argv(con, query_v);
+		db_redis_key_free(&query_v);
+		db_redis_check_reply(con, reply, error);
+		db_redis_free_reply(&reply);
+	}
+
 	for(k = type_keys, set_key = set_keys; k;
 	for(k = type_keys, set_key = set_keys; k;
 			k = k->next, set_key = set_key->next) {
 			k = k->next, set_key = set_key->next) {
 		str *type_key = &k->key;
 		str *type_key = &k->key;
@@ -2883,9 +3085,16 @@ int db_redis_insert(const db1_con_t *_h, const db_key_t *_k, const db_val_t *_v,
 		LM_DBG("inserting entry key '%.*s' to type map '%.*s'\n", key->key.len,
 		LM_DBG("inserting entry key '%.*s' to type map '%.*s'\n", key->key.len,
 				key->key.s, type_key->len, type_key->s);
 				key->key.s, type_key->len, type_key->s);
 
 
-		if(db_redis_key_add_string(&query_v, "SADD", 4) != 0) {
-			LM_ERR("Failed to set sadd command to post-insert query\n");
-			goto error;
+		if(mapping_struct_type == MS_HASH) {
+			if(db_redis_key_add_string(&query_v, "HSET", 4) != 0) {
+				LM_ERR("Failed to set hset command to post-insert query\n");
+				goto error;
+			}
+		} else {
+			if(db_redis_key_add_string(&query_v, "SADD", 4) != 0) {
+				LM_ERR("Failed to set sadd command to post-insert query\n");
+				goto error;
+			}
 		}
 		}
 		if(db_redis_key_add_str(&query_v, type_key) != 0) {
 		if(db_redis_key_add_str(&query_v, type_key) != 0) {
 			LM_ERR("Failed to add map key to post-insert query\n");
 			LM_ERR("Failed to add map key to post-insert query\n");
@@ -2895,15 +3104,60 @@ int db_redis_insert(const db1_con_t *_h, const db_key_t *_k, const db_val_t *_v,
 			LM_ERR("Failed to set entry key to post-insert query\n");
 			LM_ERR("Failed to set entry key to post-insert query\n");
 			goto error;
 			goto error;
 		}
 		}
+		if(mapping_struct_type == MS_HASH) {
+			if(db_redis_key_add_string(&query_v, "DUMMY", 5) != 0) {
+				LM_ERR("Failed to set entry key to post-insert query\n");
+				goto error;
+			}
+		}
 
 
 		reply = db_redis_command_argv(con, query_v);
 		reply = db_redis_command_argv(con, query_v);
 		db_redis_key_free(&query_v);
 		db_redis_key_free(&query_v);
 		db_redis_check_reply(con, reply, error);
 		db_redis_check_reply(con, reply, error);
 		db_redis_free_reply(&reply);
 		db_redis_free_reply(&reply);
 
 
-		if(db_redis_key_add_string(&query_v, "SADD", 4) != 0) {
-			LM_ERR("Failed to set sadd command to post-insert query\n");
-			goto error;
+		if(db_redis_hash_expires_str.len) {
+			if(db_redis_key_add_string(&query_v, "HEXPIRE", 7) != 0) {
+				LM_ERR("Failed to add hexpire command to query\n");
+				goto error;
+			}
+			if(db_redis_key_add_str(&query_v, type_key) != 0) {
+				LM_ERR("Failed to add map key to post-update query\n");
+				goto error;
+			}
+			if(db_redis_key_add_str(&query_v, &db_redis_hash_expires_str)
+					!= 0) {
+				LM_ERR("Failed to add expiry time to post-update query\n");
+				goto error;
+			}
+			if(db_redis_key_add_string(&query_v, "FIELDS", 6) != 0) {
+				LM_ERR("Failed to add fields suffix to query\n");
+				goto error;
+			}
+			if(db_redis_key_add_string(&query_v, "1", 1) != 0) {
+				LM_ERR("Failed to add fields suffix to query\n");
+				goto error;
+			}
+			if(db_redis_key_add_str(&query_v, &key->key) != 0) {
+				LM_ERR("Failed to set entry key to post-update query\n");
+				goto error;
+			}
+			reply = db_redis_command_argv(con, query_v);
+			db_redis_key_free(&query_v);
+			db_redis_check_reply(con, reply, error);
+			db_redis_free_reply(&reply);
+		}
+
+		if(mapping_struct_type == MS_HASH) {
+			if(db_redis_key_add_string(&query_v, "HSET", 4) != 0) {
+				LM_ERR("Failed to set hset command to post-insert query\n");
+				goto error;
+			}
+		} else {
+			if(db_redis_key_add_string(&query_v, "SADD", 4) != 0) {
+				LM_ERR("Failed to set sadd command to post-insert query\n");
+				goto error;
+			}
 		}
 		}
 		if(db_redis_key_add_str(&query_v, &set_key->key) != 0) {
 		if(db_redis_key_add_str(&query_v, &set_key->key) != 0) {
 			LM_ERR("Failed to add map key to post-insert query\n");
 			LM_ERR("Failed to add map key to post-insert query\n");
@@ -2913,11 +3167,49 @@ int db_redis_insert(const db1_con_t *_h, const db_key_t *_k, const db_val_t *_v,
 			LM_ERR("Failed to set entry key to post-insert query\n");
 			LM_ERR("Failed to set entry key to post-insert query\n");
 			goto error;
 			goto error;
 		}
 		}
+		if(mapping_struct_type == MS_HASH) {
+			if(db_redis_key_add_string(&query_v, "DUMMY", 5) != 0) {
+				LM_ERR("Failed to set entry key to post-insert query\n");
+				goto error;
+			}
+		}
 
 
 		reply = db_redis_command_argv(con, query_v);
 		reply = db_redis_command_argv(con, query_v);
 		db_redis_key_free(&query_v);
 		db_redis_key_free(&query_v);
 		db_redis_check_reply(con, reply, error);
 		db_redis_check_reply(con, reply, error);
 		db_redis_free_reply(&reply);
 		db_redis_free_reply(&reply);
+
+		if(db_redis_hash_expires_str.len) {
+			if(db_redis_key_add_string(&query_v, "HEXPIRE", 7) != 0) {
+				LM_ERR("Failed to add hexpire command to query\n");
+				goto error;
+			}
+			if(db_redis_key_add_str(&query_v, &set_key->key) != 0) {
+				LM_ERR("Failed to add map key to post-update query\n");
+				goto error;
+			}
+			if(db_redis_key_add_str(&query_v, &db_redis_hash_expires_str)
+					!= 0) {
+				LM_ERR("Failed to add expiry time to post-update query\n");
+				goto error;
+			}
+			if(db_redis_key_add_string(&query_v, "FIELDS", 6) != 0) {
+				LM_ERR("Failed to add fields suffix to query\n");
+				goto error;
+			}
+			if(db_redis_key_add_string(&query_v, "1", 1) != 0) {
+				LM_ERR("Failed to add fields suffix to query\n");
+				goto error;
+			}
+			if(db_redis_key_add_str(&query_v, type_key) != 0) {
+				LM_ERR("Failed to set entry key to post-update query\n");
+				goto error;
+			}
+			reply = db_redis_command_argv(con, query_v);
+			db_redis_key_free(&query_v);
+			db_redis_check_reply(con, reply, error);
+			db_redis_free_reply(&reply);
+		}
 	}
 	}
 
 
 	db_redis_key_free(&key);
 	db_redis_key_free(&key);

+ 3 - 0
src/modules/db_redis/redis_dbase.h

@@ -30,6 +30,9 @@
 #define SREM_KEY_LUA                                                         \
 #define SREM_KEY_LUA                                                         \
 	"redis.call('SREM', KEYS[1], KEYS[3]); if redis.call('SCARD', KEYS[1]) " \
 	"redis.call('SREM', KEYS[1], KEYS[3]); if redis.call('SCARD', KEYS[1]) " \
 	"== 0 then redis.call('SREM', KEYS[2], KEYS[1]) end"
 	"== 0 then redis.call('SREM', KEYS[2], KEYS[1]) end"
+#define HDEL_KEY_LUA                                                        \
+	"redis.call('HDEL', KEYS[1], KEYS[3]); if redis.call('HLEN', KEYS[1]) " \
+	"== 0 then redis.call('HDEL', KEYS[2], KEYS[1]) end"
 
 
 
 
 /*
 /*