Wednesday, April 14, 2010

11:09 PM

I have recently read Cal Henderson’s book, Building Scalable Web Sites, and was inspired 6 ways from Sunday on just about every approach I take to web development. I highly recommend you purchase this book, and read through it asap… it is a must read [even though it was published back in 2006]. My friend Erik Kastner recommended it to me, and now I’m recommending it to you.

Anyways, back on topic… One of the major concepts I picked up was that of Write Through Caches. Write Through Caches are explained in depth all throughout the internetS, so I leave you with This script also solves the scalability issue you will experience when you move your web site / application to more than one web server. If you put a Memcache and MySQL daemon on one box and have all your web servers connect to it, your sessions are in one centralized place [though, this doesn't account for fail over].
I also built in a simple check to see if the session data had changed before writing it to the DB. Most of the time it doesn’t, so this should offer some more performance.
This was written for PHP5, if you’re still using PHP4, click here.
This script assumes you have already connected to your database.
class SessionHandler {
public $lifeTime;
public $memcache;
public $initSessionData;
function __construct() {
# Thanks, inf3rno
$this->memcache = new Memcache;
$this->lifeTime = intval(ini_get("session.gc_maxlifetime"));
$this->initSessionData = null;
return true;
function open($savePath,$sessionName) {
$sessionID = session_id();
if ($sessionID !== "") {
$this->initSessionData = $this->read($sessionID);
return true;
function close() {
$this->lifeTime = null;
$this->memcache = null;
$this->initSessionData = null;
return true;
function read($sessionID) {
$data = $this->memcache->get($sessionID);
if ($data === false) {
# Couldn't find it in MC, ask the DB for it
$sessionIDEscaped = mysql_real_escape_string($sessionID);
$r = mysql_query("SELECT `sessionData` FROM `tblsessions` WHERE `sessionID`='$sessionIDEscaped'");
if (is_resource($r) && (mysql_num_rows($r) !== 0)) {
$data = mysql_result($r,0,"sessionData");
# Refresh MC key: [Thanks Cal :-)]
# The default miss for MC is (bool) false, so return it
return $data;
function write($sessionID,$data) {
# This is called upon script termination or when session_write_close() is called, which ever is first.
$result = $this->memcache->set($sessionID,$data,false,$this->lifeTime);
if ($this->initSessionData !== $data) {
$sessionID = mysql_real_escape_string($sessionID);
$sessionExpirationTS = ($this->lifeTime + time());
$sessionData = mysql_real_escape_string($data);
$r = mysql_query("REPLACE INTO `tblsessions` (`sessionID`,`sessionExpirationTS`,`sessionData`) VALUES('$sessionID',$sessionExpirationTS,'$sessionData')");
$result = is_resource($r);
return $result;
function destroy($sessionID) {
# Called when a user logs out...
$sessionID = mysql_real_escape_string($sessionID);
mysql_query("DELETE FROM `tblsessions` WHERE `sessionID`='$sessionID'");
return true;
function gc($maxlifetime) {
# We need this atomic so it can clear MC keys as well...
$r = mysql_query("SELECT `sessionID` FROM `tblsessions` WHERE `sessionExpirationTS`<" . (time() - $this->lifeTime));
if (is_resource($r) && (($rows = mysql_num_rows($r)) !== 0)) {
for ($i=0;$i<$rows;$i++) {
rn true;
ret u
ini_set("session.gc_maxlifetime",60 * 30); # 30 minutes
$sessionHandler = new SessionHandler();
session_set_save_handler(array (&$sessionHandler,"open"),array (&$sessionHandler,"close"),array (&$sessionHandler,"read"),array (&$sessionHandler,"write"),array (&$sessionHandler,"destroy"),array (&$sessionHandler,"gc"));

And here’s the SQL for the DB portion of the Session Handler:
CREATE TABLE `tblsessions` (
`sessionID` VARCHAR(32) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
`sessionExpirationTS` INT(10) UNSIGNED NOT NULL,
LL, PRIMARY KEY (`sessionID`), KEY `sessionExpir
`sessionData` TEXT COLLATE utf8_unicode_ci NOT N UationTS` (`sessionExpirationTS`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

As always, I welcome any and all CONSTRUCTIVE criticism. If you have something to add, or a bug fix or any suggestions I fully welcome them here. Trolls need not apply.