Stopping auto session_start

Due to our replicated deployment environment, there is a big overhead for us in starting a session unnecessarily.

In the implementation of CWebUser::init(), it calls Yii::app()->getSession()->open() so effectively it always starts a session.

To work around this we have subclassed CWebUser and override init(). In it we call CApplicationComponent::init(); rather than parent::init(); to avoid the session being started, and we copy the implementation of CWebUser::init but with some new conditional logic around the session open call.

This creates an overhead for us because with each new Yii release, we will have to check to see if the implementation of CWebUser::init() has changed, and ensure we copy the new/changed code into our subclassed file :(

Is there a better way? Or could yii be enhanced to give more flexibility on the timing of session starting? B)

(We are currently using Yii 1.0.11. but plan to upgrade to 1.1 later in the year)

At what point the session get created without a need for it? When calling Yii::app()->user->isGuest()? Because by default the init() function of CWebUser should only trigger when you access Yii::app()->user the first time.

Are you using db for session? Memcache may be better solution.

Yes, when calling Yii::app()->user->isGuest() (which we have to do on each request) init() is called on CWebUser.

And yes, we are using memcache for shared session across servers.

Any thoughts?

Okay, well as a workaround the first thing that came into my mind is to define a dummy session component (that uses local storage), and then set the memcache session when needed. I’m not quite sure if it works but that way you preserve backward compatibility.




// config


...


'components' => array(

   'session' => array(

      'class' => 'CHttpSession',

      ...

   ),

   'sessionX' => array(

      'class' => 'CCacheHttpSession',

      ...

   ),

   'user' => array(

      'class' => 'MyWebUser',

   ),

),


...







class MyWebUser extends CWebUser

{


   private $_correctSessionApplied;


   public function getId()

   {

      if ($this->_correctSessionApplied)

      {

         return parent::getId();

      }

   }


   public function getName()

   {

      if ($this->_correctSessionApplied)

      {

         return parent::getName();

      }

      else

      {

         return $this->guestName;

      }

   }


   public function getIsGuest()

   {

      if ($this->_correctSessionApplied)

      {

         return parent::getIsGuest();

      }

      else

      {

         return true;

      }

   }


   // triggered upon login and auto-login from cookie

   protected function changeIdentity($id,$name,$states)

   {


      if (!$this->_correctSessionApplied)

      {

         Yii::app()->getSession()->close();

         Yii::app()->setComponent('session', Yii::app()->sessionX);

         Yii::app()->getSession()->open();

         $this->_correctSessionApplied = true;

      }


      return parent::changeIdentity($id,$name,$states);


   }


}




Let me know what you think.

You will need the session if you do


Yii::app()->user->isGuest()



as the current authentication status is stored in session. I see no way around that.

What do you mean by "big overhead"? Does it affect the speed of the application? Are you sure the problems rely in the session being created?

Dave, yes we have millions of users and creating a session for every user rather than just when needed would mean our shared memcache server would need a tonne more RAM and network traffic is increased too as it’s not on the same physical machine as any of our webservers.

Y!!, your solution is elegant to allow 2 cache systems to work together (and switch between them) but doesn’t solve the root problem of a session being created by the init() method of CWebUser. (We also use 2 levels of session cache - memcache and DB, but neither should be hit until they are actually required.)

Mike, we create the session only when needed based on the presence of the Yii identification/state cookie and other factors. isGuest() only calls getState() which checks for the presence of the id in the session, but (helpfully) doesn’t actually start the session.

We’d love Yii to be configurable whether or not CWebUser->init() starts a session, so we can retain complete control without the messy subclass solution I described above which duplicates a bunch of lines of code.

So what you can do is to use a local session by default (which creates session data locally). Override getIsGuest() this way:




   public function getIsGuest()

   {

      if (!isset($_COOKIE['yourSessionName']))

      {

         return true;

      }

      return parent::getIsGuest();

   }



"yourSessionName" refers to the session name you apply to the remote session (CCacheHttpSession::$sessionName).Then make sure every time the real remote session is needed, the correct session gets applied via:


Yii::app()->setComponent('session', Yii::app()->remoteSession);



It should work and is more backward compatible since it doesn’t override init(), but I don’t know if it’s really necessary since your solution works as well.

Again: How do you want to check for authentication status (in the session) if you don’t start it? I see no way. As HTTP is stateless by design you need some persistent storage to save authentication information. That’s what session represents. If you rely on cookie authentication, situation get’s even worse, because you’d have to authenticate the user on every request.

So your real question is: How can i make Sessions faster?

But if there is no session on client side that may lead to a valid authentication status (eg identified thru cookie), then there’s no real need in this case to start a session since getIsGuest would return false anyway. So what I’m saying is, use a dummy local session that will serve for CWebUser::init(), then when the session is really needed, switch to the correct session component that uses remote storge. This should work, or what I’m missing here?

My solution below:




class LazySessionStartWebUser extends CWebUser

{

	public function init()

	{

		if (!isset($_COOKIE['PHPSESSID'])) 

                {

			CApplicationComponent::init();

		} else {

			parent::init();

		}

	}

	

	public function login($identity,$duration=0)

	{

		Yii::app()->getSession()->open();

		parent::login($identity,$duration=0);

	}

	

	public function logout($destroySession=true)

	{		

		Yii::app()->request->getCookies()->remove('PHPSESSID');

		parent::logout($destroySession);

	}

	

	public function getIsGuest()

	{

    	        if (!isset($_COOKIE['PHPSESSID']))

    	        {

       		        return true;

      	        }

      	        return parent::getIsGuest();

        }

}



change the cookie name will be better:




class LazySessionStartWebUser extends CWebUser

{

	public function init()

	{

		if (!isset($_COOKIE['login'])) {

			CApplicationComponent::init();

			if($this->getIsGuest() && $this->allowAutoLogin)

				$this->restoreFromCookie();

			else if($this->autoRenewCookie && $this->allowAutoLogin)

				$this->renewCookie();

			$this->updateFlash();

		} else {

			parent::init();

		}

	}

	

	public function login($identity,$duration=0)

	{

		$cookie=new CHttpCookie('login','1');

		Yii::app()->request->getCookies()->add('login', $cookie);

		Yii::app()->getSession()->open();

		parent::login($identity,$duration=0);

	}

	

	public function logout($destroySession=true)

	{		

		Yii::app()->request->getCookies()->remove('login');

		parent::logout($destroySession);

	}

	

	public function getIsGuest()

	{

    	if (!isset($_COOKIE['login']))

    	{

       		return true;

      	}

      	return parent::getIsGuest();

   }

}