Login | Register

Documentation

Documentation -> Tutorials -> Memory Caching

This page has been visited 108489 times.


1.  OpenSIPS MemCache

The Memory Caching support from OpenSIPS wants to provide a way of caching at runtime different kind of data. These data will globally available (anywhere in the routing script) and shared between all OpenSIPS processes.

The idea is to be able to store (cache) custom values for later usage. The main purpose of this MemCache support is to reduce the number of DB queries by caching data that does not need constant update from DB. DB queries are known to be one of bottle necks in the current design (see design considerations) and MemCache support will help in avoiding unnecessary DB hits.


2.  OpenSIPS MemCache Design

The Memory Caching support in OpenSIPS has a flexible and expendable design. The idea is to have a simple and transparent way of operating (in the same time) with multiple implementations / forms of memory caching. A memory caching system can be via the local shared memory (see Local Cache module) or via the System V shared memory (mem cache shared across independent applications) or via the MemCache server.

The OpenSIPS core offers an API for operating with any memory caching system from the script. This API is composed out of three functions that allow the basic operations with a memory cache:

The MemCache design from OpenSIPS allow to set a lifetime (timeout) to an attribute when inserting it in the cache. This provides support for auto-removal of attributes (for re-fetching or clean-up purposes) from the cache.

The implementations for the memory caching support are provided by the OpenSIPS modules. Right now there is a single implementation (see Local Cache module) that caches the data into the shared memory of OpenSIPS. In the future release, more implementations are planned to be added.


3.  MemCache usage - Password caching for DB authentication

The best way of describing the MemCache way of usage is to provide a real example - how to use memcache support for reducing the DB queries due user authentication.

3.1  The idea - how to

The idea is, after performing a DB authentication (authentication by fetching the password from DB) to store the password in memcache; the password is stored with a certain lifetime to ensure that from time to time the password is read from DB again.

When a new authentication is needed, first we check if the password is available in memcache (previously stored and not yet expired); if so, we use the value from there to perform authentication without a DB hit.

As we have to store in the same time the passwords of more than one users, we need to use different names for the attributes -> the attribute name will contain the user name, something like "passwd_username".

3.2  The logic - diagram

  1. REGISTER request received
  2. is password stored in "passwd_$tu" attribute?
    1. NO -> perform DB authentication
    2. store the used password into "passwd_$tu" with s lifetime (1200 seconds - 10 minutes)
    3. DONE
  3. YES -> use the value from the memcache do to authentication from script variables.
  4. DONE

3.3  Script

Current format of authentication script for REGISTER is:

	if (!www_authorize("", "subscriber")) {
		www_challenge("", "0");
		exit;
	};

By adding memcache support, the script looks like:

....
loadmodule "modules/db_mysql/db_mysql.so"
loadmodule "modules/auth/auth.so"
loadmodule "modules/auth_db/auth_db.so"
loadmodule "modules/localcache/localcache.so"
....
modparam("auth","username_spec","$avp(i:54)")
modparam("auth","password_spec","$avp(i:55)")
modparam("auth","calculate_ha1",1)

modparam("auth_db", "calculate_ha1", yes)
modparam("auth_db", "password_column", "password")
modparam("auth_db", "db_url","mysql://opensips:opensipsrw@localhost/opensips_1_2")
modparam("auth_db", "load_credentials", "$avp(i:55)=password")
....

....
route[x]{
	.....
	# do we have the password cached ?
	if(cache_fetch("local","passwd_$tu",$avp(i:55))) {
		$avp(i:54) = $tU;
		xlog("SCRIPT: stored password is $avp(i:55)\n");
		# perform auth from variables
		# $avp(i:54) contains the username
		# $avp(i:55) contains the password
		if (!pv_www_authorize("")) {
			# authentication failed -> do challenge			
			www_challenge("", "0");
			exit;
		};
	} else {
		# perform DB authentication ->
		# password will be loaded from DB automatically
		if (!www_authorize("", "subscriber")) {
			# authentication failed -> do challenge		
			www_challenge("", "0");
			exit;
		};
		# after DB authentication, the password is available
		# in $avp(i:55) because of the "load_credentials"
		# module parameter.
		xlog("SCRIPT: storing password <$avp(i:55)>\n");
		# use a 20 minutes lifetime for the password;
		# after that, it will erased from cache and we do
		# db authentication again (refresh the passwd from DB)
		cache_store("local","passwd_$tu","$avp(i:55)",1200);
	}

	....
}

References:

  1. auth module - see functions and parameters
  2. auth_db module - see functions and parameters

3.4  Improvements

You can easily add 2 improvements to the previous script.

Force password re-fetching

When PV authentication (based on cached password) fails because of invalid password (see the error codes of the pv_www_authorize() function). So, if the function returns "-2" (invalid password) it may mean that the password may changed (in DB), so you need to re-fresh.

In such a case, you can simple remove the cached password and try to do DB authentication (that will use the DB password). Of course, after such a re-load, store into the cache the new value.

WWW and PROXY authentication

You can use the same cached password for both WWW (REGISTER) and PROXY (INVITE, MESSAGE, etc) authentication. The logic is the same in both blocks (WWW and PROXY) and you can share the same cached password for both blocks. This will dramatically reduce the number of DB queries.

NOTE: for WWW authentication the authentication name is extracted from TO hedear, but for PROXY authentication the auth name is extracted from FROM header, so when operating with the cache, build the attribute name as "passwd_$tU" for WWW auth and as "passwd_$fU" for PROXY auth.

3.5  Performance estimation

Assuming a 30 minute registration period and placing a call each 20 minutes, without any caching, there will be 5 authentication queries to DB per hour per user.

This means, for 1000 users, per day : 1000 * 3 * 24 = 72000 queries per day for auth.

If you set a 2 hours lifetime for cached data, you have one query per user at each 2 hours.

So, for the same 1000 users, per day, you have : 1000 * 0.5 * 24 = 12000 queries per day for auth

So, this gives you a reduction to with 83% (to 17%) of the numbers of queries!


aidanna?13 August 2010, 04:37

Thanks guys for the tutorial on the caching API. Using a layered localcache -> memcache -> db scheme (where our application controls the last two), we've been able to see almost a 95% performance gain after tuning compared to a straight Opensips 1.4.5 db only scheme.

Manas?08 February 2011, 14:59

How do the timeout value specified in the cache_store method interact with the cache_clean_period specified at the module level?

 		cache_store("local","passwd_$tu","$avp(i:55)",1200);

modparam("localcache", "cache_clean_period", 600)


For example: at 10:00 - I insert a record with timeout "1200" - so the record is set to expire at 10:20.

For the sake of the example - if the module setting was 1800 - the above record wont be purged till 10:30.

Am I correct?

Cleide?18 March 2012, 05:03

Did you test this patch, i personnaly tisnalled because i need to use distributed cache for apache ssl cache but it could not retreive the session from cache, session is setted successly but when retriving it it tells memcache scache get_session' CORRUPT'when i look in memcache it seems that the session is bad because it contains lines of logthank you for reply

Hydrosine?16 December 2016, 10:00

There are some clients which do not use the same $tU as $au (username in authorization header). I would suggest to save your keys/passwords on the $tU(or $tu), but set the username_spec variable to the $au when an authorization header is available. if(is_present_hf(Authorization))

So that when pv_authorize hits it is authorized against the username from the authorization header instead of against the to-user.

This makes it in line with what www_authorize does, as this also uses the $au.

Add Comment 
Sign as Author 
Enter code 641


Page last modified on December 16, 2016, at 11:03 AM