<?xml version="1.0" encoding='ISO-8859-1'?>
<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN"
"http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd" [

<!-- Include general documentation entities -->
<!ENTITY % docentities SYSTEM "../../../../doc/docbook/entities.xml">
%docentities;

]>
<!-- Module User's Guide -->

<chapter>
	<title>&adminguide;</title>
	<section id="db_redis.sec.overview">
		<title>Overview</title>
		<para>
			This module provides a DB APIv1 connector for the Redis server (<ulink url="https://www.redis.io">https://www.redis.io</ulink>).

		</para>
		<para>
			It can be used as a replacement for other database modules such as
			db_mysql and db_postgres. Not all the specs of DB APIv1 are
			implemented, thus the usage of this module might be restricted to
			specific modules. Also, for proper performance, this module needs
			particular configuration tailored to the modules that make use of it.
		</para>
		<para>
			Since Redis does not provide a schema by itself, db_redis ships with
			schema files. The path to these has to be defined using the module parameter
			"schema_path". The schema definition is defined in one file per table, such that
			the file name corresponds to the table name, and each file is composed of a
			comma-separated list of column definitions in the format
			&lt;column-name&gt;/&lt;type&gt;[,&lt;column-name&gt;/&lt;type&gt; ...]
			in one line, followed by a line holding the table version.
		</para>
		<para>
			Example definition for the "location" table (from the usrloc module):
			<programlisting format="linespecific">
username/string,domain/string,contact/string,received/string,path/string,expires/timestamp,q/double,callid/string,cseq/int,last_modified/timestamp,flags/int,cflags/int,user_agent/string,socket/string,methods/int,ruid/string,reg_id/int,instance/string,server_id/int,connection_id/int,keepalive/int,partition/int
8
			</programlisting>
		</para>
		<para>
			Because Redis is a key-value store, it requires unique keys. This means
			that tables and rows from a relational SQL database, e.g. from MySQL, can
			not be ported one a 1:1 basis to Redis.
		</para>
		<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 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
			mapping set first. It then query the actual entries from there, avoiding full
			table scans. For usrloc, the same holds true for expired contacts, requiring
			a different kind of mapping. There is a certain balance of read performance
			vs. write performance to consider, because inserts and deletes also have to
			maintain the mappings, though this yields much faster selects. The mappings can be
			freely defined, so even though other kamailio modules don't require a specific
			mapping to be in place for proper performance, mappings could be defined
			for external applications to read faster (for instance letting the acc module
			also write mappings besides the actual records for billing systems to
			correlate start and stop records faster).
		</para>
		<para>
			The key is always prefixed with 'tablename:entry::'. For example the record in
			'subscriber' table for user 'alice@sip.com' has the key:
			'subscriber:entry::alice:sip.com'. If all the records are just loaded at
			startup (or all reloaded at runtime), the key can just be made unique using whatever
			values added after 'tablename:entry::' prefix. For example, keys for 'address' table
			records can be: 'address:entry::1', address:entry::2', address:entry::3', ... No
			'keys' modparam of 'db_redis' for 'address' table needs to be defined.
		</para>
		<para>
			Important Note: at this moment the module requires at least one 'keys'
			parameter, but it does not need to be related to the table loaded
			from Redis server -- for example, if used only for permissions module with
			'address' table, then the 'keys' parameter can be specified for
			'version' table. However, if it used for a module that inserts or
			updates the records in database table, the key for entry must be defined for
			that table.
		</para>
		<para>
			The mappings can be freely defined in the "keys" module parameter, which is
			composed of a semi-colon separated list of definitions in the format
			&lt;table-name&gt;=&lt;entry&gt;:&lt;column-name&gt;[&amp;&lt;map-name&gt;:&lt;column-name&gt;,&lt;column-name&gt;...].
			Each table must at least have an "entry" key for db_redis to be able to store data.
		</para>
		<para>
			Example:
			<programlisting format="linespecific">
location=entry:ruid&amp;usrdom:username,domain&amp;timer:partition,keepalive;acc=entry:callid,time_hires&amp;cid:callid
			</programlisting>
		</para>
		<para>
			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>
		<title>Limitations</title>
		<itemizedlist>
			<listitem>
				<para>
					This module has implemented equivalent underlying Redis operations for INSERT,
					UPDATE, DELETE and SELECT. The ORDER BY clause for SELECT is not implemented.
					Raw querying is not implemented inside this module; for sending literal commands
					to the Redis server, use ndb_redis.
				</para>
			</listitem>
		</itemizedlist>
	</section>

	<section>
		<title>Dependencies</title>
		<section>
			<title>&kamailio; Modules</title>
			<para>
				The following modules must be loaded before this module:
				<itemizedlist>
					<listitem>
						<para>
							<emphasis>none</emphasis>.
						</para>
					</listitem>
				</itemizedlist>
			</para>
		</section>
		<section>
			<title>External Libraries or Applications</title>
			<para>
				The following libraries or applications must be installed before running
				&kamailio; with this module loaded:
				<itemizedlist>
					<listitem>
						<para>
						<emphasis>hiredis</emphasis> - available at
						<ulink url="https://github.com/redis/hiredis">https://github.com/redis/hiredis</ulink>
						</para>
					</listitem>
				</itemizedlist>
			</para>
			<para>
				The following library is an optional dependency to support redis cluster protocol:
				<itemizedlist>
					<listitem>
						<para>
						<emphasis>hiredis-cluster</emphasis> - available at
						<ulink url="https://github.com/Nordix/hiredis-cluster">https://github.com/Nordix/hiredis-cluster</ulink>
						</para>
					</listitem>
				</itemizedlist>
			</para>
		</section>
	</section>

	<section>
		<title>Parameters</title>
		<section id="db_redis.p.schema_path">
			<title><varname>schema_path</varname> (string)</title>
			<para>
				The path to the table schemas.
			</para>
			<para>
				Default value: "/usr/share/kamailio/db_redis".
			</para>
			<example>
				<title>Setting schema_path module parameter</title>
				<programlisting format="linespecific">
...
modparam("db_redis", "schema_path", "/usr/local/share/kamailio/db_redis/kamailio")
...
				</programlisting>
			</example>
		</section>

		<section id="db_redis.p.keys">
			<title><varname>keys</varname> (string)</title>
			<para>
				The entry and mapping keys of tables.
			</para>
			<para>
				Default value: "" (empty).
			</para>
			<example>
				<title>Setting keys module parameter</title>
				<programlisting format="linespecific">
...
modparam("db_redis", "keys", "version=entry:table_name;location=entry:ruid&amp;usrdom:username,domain&amp;timer:partition,keepalive")
...
				</programlisting>
			</example>
		</section>

		<section id="db_redis.p.verbosity">
			<title><varname>verbosity</varname> (int)</title>
			<para>
				Control the verbosity of debug messages printed by the module.
				If set to 1, the module prints schema details for all tables
				on each connect operation to Redis server.
			</para>
			<para>
				Default value: 1.
			</para>
			<example>
				<title>Setting verbosity module parameter</title>
				<programlisting format="linespecific">
...
modparam("db_redis", "verbosity", 0)
...
				</programlisting>
			</example>
		</section>

		<section id="db_redis.p.opt_tls">
			<title><varname>opt_tls</varname> (int)</title>
			<para>
				Controls TLS usage while connecting to a remote DB.
				If set to 1, TLS is used to connect to the DB.
			</para>
			<para>
				If TLS is enabled, the module will validate the Redis server certificate against the
				ca_path. There is currently no way to connect with a specified client certificate, the
				<ulink url="https://redis.io/docs/management/security/encryption/#client-certificate-authentication">corresponding configuration</ulink>
				to check client certificates in the Redis server must therefore be turned off.
			</para>
			<para>
				Default value: 0.
			</para>
			<example>
				<title>Enabling TLS connection</title>
				<programlisting format="linespecific">
...
modparam("db_redis", "opt_tls", 1)
...
				</programlisting>
			</example>
		</section>

		<section id="db_redis.p.max_key_length">
			<title><varname>max_key_length</varname> (int)</title>
			<para>
				Controls the max possible key length to be used when adding/prepending strings into the redis key.
				If used will be always compared against the given string key length.
			</para>
			<para>
				Default value: 0. Means disabled.
			</para>
			<example>
				<title>Limiting the max key length</title>
				<programlisting format="linespecific">
...
modparam("db_redis", "max_key_length", 4096)
...
				</programlisting>
			</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>
				Sets the password to connect to the DB.
			</para>
			<para>
				Default value: "" (empty).
			</para>
			<example>
				<title>Setting a password</title>
				<programlisting format="linespecific">
...
modparam("db_redis", "db_pass", "r3d1sPass")
...
				</programlisting>
			</example>
		</section>

		<section id="db_redis.p.ca_path">
			<title><varname>ca_path</varname> (string)</title>
			<para>
				Sets the path where Certificates Authorities certs for the Redis server certificate are stored.
			</para>
			<para>
				Default value: "" (empty).
			</para>
			<example>
				<title>Setting CA path</title>
				<programlisting format="linespecific">
...
modparam("db_redis", "ca_path", "/etc/ssl/certs")
...
				</programlisting>
			</example>
		</section>

		<section id="db_redis.p.with_sentinels">
			<title><varname>with_sentinels</varname> (int)</title>
			<para>
				Enables the use of Redis sentinels for high availability.
				If set to 1, the module will connect to the Redis server using
				the sentinels defined in the "sentinels_config" modparam.
			</para>
			<para>
				Default value: 0.
			</para>
			<example>
				<title>Enabling sentinels</title>
				<programlisting format="linespecific">
...
modparam("db_redis", "with_sentinels", 1)
...
				</programlisting>
			</example>
		</section>

		<section id="db_redis.p.sentinels_config">
			<title><varname>sentinels_config</varname> (string)</title>
			<para>
				Sets the sentinels to connect to the Redis server.
				The format is semi-colon separated list of key-value pairs: password, sentinels.
				Sentinels is a pipe separated list of host:port pairs. Password applies to all sentinels.
			</para>
			<para>
				Default value: "" (empty).
			</para>
			<example>
				<title>Setting sentinels</title>
				<programlisting format="linespecific">
...
modparam("db_redis", "sentinels_config", "password=password;sentinels=host1:port1|host2:port2|host3:port3")
...
				</programlisting>
			</example>
		</section>

	<section id="db_redis.p.redis_master_name">
			<title><varname>redis_master_name</varname> (string)</title>
			<para>
				Sets the name of the Redis master. This is needed when building the
				SENTINEL get-master-addr-by-name command.
				The name should match the name used in the Redis Sentinel configuration.
			</para>
			<para>
				Default value: "mymaster" (empty).
			</para>
			<example>
				<title>Setting Redis master name</title>
				<programlisting format="linespecific">
...
modparam("db_redis", "redis_master_name", "mymaster")
...
				</programlisting>
			</example>
		</section>

		<section id="db_redis.p.use_replicas">
			<title><varname>use_replicas</varname> (int)</title>
			<para>
				Enables connecting to Redis replicas instead of the master when using sentinels. Replicas are selected randomly.
				This helps distribute load across the Redis setup (i.e.: read from replicas instead of master). If for whatever
				reason no replicas are available, the module will fall back to connecting to the master (this cannot be toggled off).
			</para>
			<para>
				Default value: 0 (connect to master).
			</para>
			<example>
				<title>Setting use_replicas</title>
				<programlisting format="linespecific">
...
modparam("db_redis", "use_replicas", 1)
...
				</programlisting>
			</example>
		</section>

		<section id="db_redis.p.recheck_replicas_interval">
			<title><varname>recheck_replicas_interval</varname> (int)</title>
			<para>
				The parameter sets the interval	in seconds to recheck for available replicas so that we don't
				remain connected to the master. Because of implementation details, the first check can exceptionally
				happen after half of this interval.

				If set to 0, no rechecks will be performed and the module will remain connected to the master until
				a new connection is established (i.e.: Redis server goes down and comes back up).
			</para>
			<para>
				Default value: 120 seconds.
			</para>
			<example>
				<title>Setting use_replicas</title>
				<programlisting format="linespecific">
...
modparam("db_redis", "recheck_replicas_interval", 120)
...
				</programlisting>
			</example>
		</section>

	</section>

	<section id="db_redis.sec.usage">
		<title>Usage</title>
		<para>
			Load the module and set the "db_url" modparam for specific modules to:
			'redis://[username]@host:port/database'. Username is optional.
			The database portion must be a valid Redis database number.
		</para>
		<para>
			For cluster support you need to set the "db_url" modparam with a comma separated list of cluster hosts:
			'redis://host1:port1,host2:port2/'. The database portion is not supported in cluster mode.
		</para>
		<para>
			If accessed DB requires TLS connections, you need to enable TLS support setting the "opt_tls" parameter to 1.
			In case the DB requires a password, that should be set using the "db_pass" parameter.
		</para>
		<example>
			<title>Usage</title>
			<programlisting format="linespecific">
...
loadmodule "db_redis.so"
...
#!define DBURL_USRLOC "redis://127.0.0.1:6379/5"
#!define DBURL_ACC    "redis://127.0.0.1:6379/6"
#!define DBURL_AUTH   "redis://127.0.0.1:6379/7"
#!define DBURL_PERM   "redis://127.0.0.1:6379/8"
#!define DBURL_DLG    "redis://127.0.0.1:6379/9"
...
modparam("db_redis", "schema_path", "/usr/share/kamailio/db_redis/kamailio")
modparam("db_redis", "keys", "version=entry:table_name")
modparam("db_redis", "keys", "location=entry:ruid&amp;usrdom:username,domain&amp;timer:partition,keepalive")
modparam("db_redis", "keys", "acc=entry:callid,time_hires&amp;cid:callid")
modparam("db_redis", "keys", "subscriber=entry:username,domain")
modparam("db_redis", "keys", "dialog=entry:hash_entry,hash_id&amp;cid:callid")
modparam("db_redis", "keys", "dialog_vars=entry:hash_entry,hash_id,dialog_key&amp;dialog:hash_entry,hash_id")
...
modparam("usrloc", "db_url", DBURL_USRLOC)
...
modparam("acc_db", "db_url", DBURL_ACC)
...
modparam("auth_db", "db_url", DBURL_AUTH)
...
modparam("permissions", "db_url", DBURL_PERM)
...
modparam("dialog", "db_url", DBURL_DLG)
...
			</programlisting>
		</example>
		<para>
		Samples adding records for address table using 'redis-cli':
		</para>
		<example>
			<title>Usage</title>
			<programlisting format="linespecific">
...
SELECT 8
HMSET address:entry::1 id 1 grp 1 ip_addr "127.0.0.1" mask 32 port 0
HMSET address:entry::2 id 2 grp 1 ip_addr "127.0.0.2" mask 32 port 0
HMSET address:entry::3 id 3 grp 2 ip_addr "127.0.0.3" mask 32 port 0
HMSET address:entry::4 id 4 grp 2 ip_addr "127.0.0.4" mask 32 port 0 tag "test"
...
			</programlisting>
		</example>
		<para>
		Note that is some cases, the optional values in database tables can be
		omitted. For 'address' table, the 'tag' value may be omitted. To avoid
		any issues, set unused fields to their default values as defined by database
		schema. When definition allows 'NULL', that field can be unset.
		</para>
	</section>

	<section>
		<title>Module Specific Considerations</title>
		<section>
			<title>usrloc</title>
			<para>
				If you set "expires_type" to "1" in order to use BIGINT instead of DATETIME,
				make sure to update your location schema file and change the type of "expires"
				and "last_modified" from "time" to "int".
			</para>
		</section>
	</section>

</chapter>
