Using MySQL memcached API for Moodle session storage

Using MySQL memcached API for Moodle session storage

by Chris Fryer -
Number of replies: 3

Here's a method to provide highly-available session storage for your users.  

It's well known that keeping Moodle sessions in the database can be a performance bottleneck.  Moreover, it can lead to unintentional denial of service because of MDL-42435. Someone falls asleep with their nose on the F5 key and uses up all the available database connections, locking everyone else out of your site (you included).

Memcache is the go-to session storage mechanism, but using a naked memcached server for session storage is a rotten way to treat your users.  If you evict because you're out of memory, your oldest session is destroyed; and every time you restart your memcached daemon, everyone gets logged out. 

An answer, if you're using MySQL 5.6 or newer, is to use the InnoDB Memcached Plugin.  This exposes a memcached interface on TCP port 11211, but uses InnoDB for persistent storage.  User session state is preserved following restarts of your MySQL server, and you can make it part of your replication topology to provide resilience in the event of hardware failure.

How to install

Add the following text to a temporary file, e.g. /tmp/innodb_memcached_moodle.sql

CREATE DATABASE IF NOT EXISTS innodb_memcache;
USE innodb_memcache;
CREATE TABLE IF NOT EXISTS `cache_policies` (
  `policy_name` varchar(40) NOT NULL,
  `get_policy` enum('innodb_only','cache_only','caching','disabled') NOT NULL,
  `set_policy` enum('innodb_only','cache_only','caching','disabled') NOT NULL,
  `delete_policy` enum('innodb_only','cache_only','caching','disabled') NOT NULL,
  `flush_policy` enum('innodb_only','cache_only','caching','disabled') NOT NULL,
  PRIMARY KEY (`policy_name`)
) ENGINE=InnoDB;
INSERT INTO `cache_policies` VALUES ('cache_policy','innodb_only','innodb_only','innodb_only','innodb_only');
CREATE TABLE IF NOT EXISTS `config_options` (
  `name` varchar(50) NOT NULL,
  `value` varchar(50) DEFAULT NULL,
  PRIMARY KEY (`name`)
) ENGINE=InnoDB;
INSERT INTO `config_options` VALUES ('separator','|'),('table_map_delimiter','.');
CREATE TABLE IF NOT EXISTS `containers` (
  `name` varchar(50) NOT NULL,
  `db_schema` varchar(250) NOT NULL,
  `db_table` varchar(250) NOT NULL,
  `key_columns` varchar(250) NOT NULL,
  `value_columns` varchar(250) DEFAULT NULL,
  `flags` varchar(250) NOT NULL DEFAULT '0',
  `cas_column` varchar(250) DEFAULT NULL,
  `expire_time_column` varchar(250) DEFAULT NULL,
  `unique_idx_name_on_key` varchar(250) NOT NULL,
  PRIMARY KEY (`name`)
) ENGINE=InnoDB;
INSERT INTO `containers` VALUES ('default','moodlesessions','sessions','key','sessdata','0','0','0','PRIMARY');
CREATE DATABASE IF NOT EXISTS moodlesessions DEFAULT CHARACTER SET utf8 DEFAULT COLLATE utf8_unicode_ci;
USE moodlesessions;
CREATE TABLE IF NOT EXISTS `sessions` (
  `key` varchar(128) NOT NULL DEFAULT '',
  `sessdata` longtext,
  PRIMARY KEY (`key`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

Now log in to MySQL as root, prepare the tables for memcached and activate the plug-in:

mysql> source /tmp/innodb_memcached_moodle.sql
mysql> install plugin daemon_memcached soname "libmemcached.so";

Exit MySQL and check that mysqld is listening on TCP 11211. In your firewall, ensure TCP port 11211 is available to the Moodle web servers (and only to them; memcached has no security).

Your MySQL database is now ready to act as a session store for Moodle.

All the above can be done without an outage.

Be aware that mysqld will now be listening on 0.0.0.0:11211. If you want to bind it to a specific address, you need to use the -l option in my.cnf, e.g.

daemon_memcached_option = '-l192.168.0.1'

Configure your web servers

This cannot be done without trashing all the existing Moodle user sessions, so schedule downtime for this:

Add the following to config.php on each app server:

$CFG->session_handler_class = '\core\session\memcached';
$CFG->session_memcached_save_path = '<ip_of_moodle_db_server>:11211';
$CFG->session_memcached_prefix = 'memc.sess.key.';
$CFG->session_memcached_acquire_lock_timeout = 120;
$CFG->session_memcached_lock_expire = 7200;       // Ignored if memcached extension <= 2.1.0

Ensure you change "ip_of_moodle_db_server" to an IP address!

Restart your web server or PHP-FPM daemon, and release the application to the users.

Average of ratings: Useful (7)
In reply to Chris Fryer

Re: Using MySQL memcached API for Moodle session storage

by Howard Miller -
Picture of Core developers Picture of Documentation writers Picture of Particularly helpful Moodlers Picture of Peer reviewers Picture of Plugin developers

Good stuff. Have you thought about adding this to the documentation wiki though? wink

In reply to Howard Miller

Re: Using MySQL memcached API for Moodle session storage

by Helen Foster -
Picture of Core developers Picture of Documentation writers Picture of Moodle HQ Picture of Particularly helpful Moodlers Picture of Plugin developers Picture of Testers Picture of Translators

Many thanks Chris for your informative post. As Howard mentions, it would be great if you could add the info to Moodle Docs, maybe here: Session handling. In the meantime, I've added a link to your post under 'See also'.

In reply to Howard Miller

Re: Using MySQL memcached API for Moodle session storage

by Chris Fryer -

I'd be glad to. It'd be good to get independent verification of the procedure first. Has anyone had a go at doing this yet?