Session management for the cloud

Hi everyone.

I’m unfortunately new to this really excellent framework, but we started to build our extensive gaming backend on top of this framework now. What we need is to be able to deploy our backend across multiple servers with a load balancer in front of them.

[b]Our Setup looks like this:

[/b]

  1. Load Balancer

  2. Multiple application server which handle all PHP requests

  3. Master / Slave Mysql Cluster with 1 Master for write queries and several Slaves for all read queries.

As like everyone dealing with such a configuration, we also needed to find a way to handle the PHP session data across all application servers.

As described here:

http://programmersnotes.info/2009/03/05/speeding-up-yii-or-why-should-you-use-db-sessions/

and here

http://www.yiiframework.com/forum/index.php?/topic/6667-slow-db-session-table-reason-found

It would be a very cool solution to use the Database as session data holder, so all data is replicated to all db slaves and therefor could be used easily. But as the OP found out, there is a reasonable amount of performance lost, due to writing and reading session data from/to a database server. So the OP deciced to use CCacheHttpSession instead of CDbHttpSession to gain performance again and be able to use multiple application servers.

But this way the session data can very easily be destroyed, if the memcached server crashes.

Why not just use the best of both worlds?

We could try to query a memcached server for the session data. If there is no memcached server running or the server hasn’t got our session data set, we would then query the database and in return save the result in the memcached server for later use. So the next requests to the session would be served with the data from the memcached. If the memcached crashes, and after some time comes up and running again, it will automatically will be filled with the session data again.

Isn’t this the perfect solution?

I really can’t find a implementation of this in the forums, neither it seams to be implemented already. So I built my own session handler class name “CCachingDbHttpSession”, which does exactly what I described above.

I would be very happy if someone could enlighten me with the downsides of this solution, if there are any…




class CCachingDbHttpSession extends CDbHttpSession {

        public $initSessionData = null;

        public $memCacheConfig = null;

        public $memCache = null;




	/**

	 * Session open handler.

	 * Do not call this method directly.

	 * @param string session save path

	 * @param string session name

	 * @return boolean whether session is opened successfully

	 */

	public function openSession($savePath,$sessionName)

	{

            if (!$this->memCache && isset($this->memCacheConfig))

            {

                $memCacheConfig = $this->memCacheConfig;

                $this->memCache = Yii::app()->$memCacheConfig;

            }

            

            $result = parent::openSession($savePath, $sessionName);


            if ($result)

            {

                $sessionID = session_id();

                if ($sessionID !== "") {

                    $this->initSessionData = $this->readSession($sessionID);

                }

            }


            return $result;

	}




	/**

	 * Session close handler.

	 * This method should be overridden if {@link useCustomStorage} is set true.

	 * Do not call this method directly.

	 * @return boolean whether session is closed successfully

	 */

        

	public function closeSession()

	{

            $result = parent::closeSession();

            

            $this->initSessionData = null;


            return $result;

	}




        

	/**

	 * Session read handler.

	 * Do not call this method directly.

	 * @param string session ID

	 * @return string the session data

	 */

        

	public function readSession($id)

	{

            if ($this->memCache) $data = $this->memCache->get("session_".$id);

            

            if ($data === false) {


                $data = parent::readSession($id);


                if ($this->memCache) $this->memCache->set ("session_".$id, $data, $this->getTimeout());

            }


            return $data;

	}


	/**

	 * Session write handler.

	 * Do not call this method directly.

	 * @param string session ID

	 * @param string session data

	 * @return boolean whether session write is successful

	 */

        

	public function writeSession($id,$data)

	{

            $result = false;


            if ($this->memCache) $result = $this->memCache->set("session_".$id, $data, $this->getTimeout());


            if ($this->initSessionData !== $data)

            {

                $result = parent::writeSession($id, $data);

            }


            return $result;

	}

  

	/**

	 * Session destroy handler.

	 * Do not call this method directly.

	 * @param string session ID

	 * @return boolean whether session is destroyed successfully

	 */

        

	public function destroySession($id)

	{

            if ($this->memCache) $this->memCache->delete("session_".$id);


            return parent::destroySession($id);

	}

         


	/**

	 * Session GC (garbage collection) handler.

	 * Do not call this method directly.

	 * @param integer the number of seconds after which data will be seen as 'garbage' and cleaned up.

	 * @return boolean whether session is GCed successfully

	 */

        

	public function gcSession($maxLifetime)

	{

            if ($this->memCache)

            {

                $db=$this->getDbConnection();

                $db->setActive(true);

                $sql="SELECT id FROM {$this->sessionTableName} WHERE expire<".time();

                $list = $db->createCommand($sql)->query();

                foreach ($list as $item)

                {

                    $this->memCache->delete("session_".$item["id"]);

                }

            }

            return parent::gcSession($maxLifetime);

	}

         




}



Configuration:




                'session' => array(

                        'class' => 'CCachingDbHttpSession',

                        'connectionID' => 'db',

                        'autoCreateSessionTable' => false,

                        'sessionTableName' => 'session_data',

                        'memCacheConfig' => 'globalcache'

                ),

               'globalcache' => array(

                        'class' => 'CMemCache',

                        'servers' => array(

                            array('host' => '127.0.0.1')

                        ),

                ),



What do you think about it?

Best regards,

Human

Just an idea: Why don’t you set up a new machine with a MySQL server instance entirely dedicated to session management? Surely, this isn’t very cloudy, but it would save you from the hassle with replication to all slaves.

The cached db session only helps as long as only reading session data. Whenever you write session data, you have to persist the data in the DB and write it into the cache. Since memcache is a distributed cache, this means another network request.

Maybe you could reconfigure your load balancer. If it guarantees, that one single users request will always be handled by the same application server, you didn’t need to deal with session data sharing between application servers.