Przeglądaj źródła

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 miesięcy temu
rodzic
commit
0cf6471045

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

@@ -42,6 +42,12 @@ MODULE_VERSION
 str redis_keys = str_init("");
 str redis_schema_path = str_init(SHARE_DIR "db_redis/kamailio");
 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 mod_init(void);
@@ -60,6 +66,9 @@ static param_export_t params[] = {
 		{"keys", PARAM_STRING | PARAM_USE_FUNC, (void *)keys_param},
 		{"schema_path", PARAM_STR, &redis_schema_path},
 		{"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
 		{"opt_tls", PARAM_INT, &db_redis_opt_tls},
 		{"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)
 {
 	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;
 }
 

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

@@ -51,5 +51,10 @@
 
 extern str redis_keys;
 extern str redis_schema_path;
+typedef enum
+{
+	MS_SET = 0,
+	MS_HASH = 1
+} mapping_struct_type_t;
 
 #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>
 			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
-			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
 			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
@@ -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
 			config lines by providing multiple "keys" modparams.
 		</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>
@@ -243,6 +266,71 @@ modparam("db_redis", "opt_tls", 1)
 			</example>
 		</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">
 			<title><varname>db_pass</varname> (string)</title>
 			<para>

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

@@ -37,6 +37,8 @@ static unsigned int MAX_URL_LENGTH = 1023;
 #endif
 
 extern int db_redis_verbosity;
+extern int mapping_struct_type;
+extern str db_redis_hash_expires_str;
 #ifdef WITH_SSL
 extern int db_redis_opt_tls;
 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;
 }
 
+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)
 {
 	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);
 
 #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) {
 		LM_ERR("failed to load LUA script to server %.*s: %s\n",
 				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);
 	freeReplyObject(reply);
 	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
 	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
 
+extern int mapping_struct_type;
+extern str db_redis_hash_expires_str;
+
 static void db_redis_dump_reply(redisReply *reply)
 {
 	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
  * 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) {
 				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))
 						!= 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);
 					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);
 
 		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) {
 			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) {
 
 #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) {
 				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_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) {
 				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)
 				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) {
 				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));
     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));
 
     col_value = (str*)pkg_malloc(_nu * sizeof(str));
     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));
     */
@@ -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
 
-				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) {
 					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");
 					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++;
 				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);
 
-				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) {
 					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");
 					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++;
 				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);
 
+				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
-				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) {
 					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_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) {
 					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)
 					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) {
 					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");
 						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) {
 						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_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;
 			k = k->next, set_key = set_key->next) {
 		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,
 				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) {
 			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");
 			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);
 		db_redis_key_free(&query_v);
 		db_redis_check_reply(con, reply, error);
 		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) {
 			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");
 			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);
 		db_redis_key_free(&query_v);
 		db_redis_check_reply(con, reply, error);
 		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);

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

@@ -30,6 +30,9 @@
 #define SREM_KEY_LUA                                                         \
 	"redis.call('SREM', KEYS[1], KEYS[3]); if redis.call('SCARD', KEYS[1]) " \
 	"== 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"
 
 
 /*