Sessions in Rails are not black and white. Please stop treating them as such. This sort of thinking is deprecated and will be removed in 1.2.
Most of you know better, I’m sure, but humor me: there is a meme going around whereby sessions are thought to fill one of two roles in your Rails app: on or off. This is not the case. Sessions are wonderfully flexible and incredibly deep.
Real quick, there’s a really long paper about sessions on the web you can read if you want to know some non-Rails, general overview type stuff. Basically a session is two things: a cookie with a unique ID and some sort of data persisted by the app server and referenced by the cookie’s unique ID. As long as you’ve got that same ID in your temporary cookie, you’re in the same session and the app knows who you are. When you close your browser (or enough time passes), that temporary cookie goes away and you’re in a new session. That’s that. Here’s a really basic intro to using sessions in Rails if you’re unfamiliar.
Agile Sessions
Let’s dive right in. So once you disable sessions they’re off for good? Not true. You can disable and re-enable sessions all over the place. But why?
Well, maybe you don’t need sessions. Maybe you’ve got it all figured out with cookies and memcached on your big million dollar site, and sessions just weren’t needed. So you turn them off:
class ApplicationController < ActionController::Base session :off end
First, this isn’t fullproof. Any routing errors bypass ApplicationController and will still create sessions. To really turn off sessions you need a catch-all route, which we won’t go into here. The curious can check out this trac ticket to get all the details.
Back to your million dollar app, where you’ve successfully semi-disabled sessions completely. You go live and suddenly you’re getting all kinds of comment spam. Hastily you scaffold an admin backend to the site so you can CRUD around with comments. flash and other sessiony goodness is required. What to do?
There’s no session :on, but you can un-disable (!) sessions just for that secret part of your site:
class Admin::Comments < ApplicationController session :disabled => false end
That’s great, and efficient. No session overhead where it isn’t needed.
Quickly you decide you want to be ultra efficient. You only need sessions on four admin methods: delete, edit, create, and save. You try this, but it doesn’t work:
class Admin::Comments < ApplicationController session :off, :except => %w[delete edit create save] end
The sessions are already turned off everywhere because of ApplicationController. What you need to do is turn them on for these specific methods:
class Admin::Comments < ApplicationController session :disabled => false, :only => %w[delete edit create save] end
Turn them on by un-disabling sessions only for your special methods. Makes sense, right?
You don’t have to set session :off in ApplicationController, of course. You can keep sessions on throughout your whole app and only turn them off where they’re not needed by using :only and :except liberally:
class StoriesController < ApplicationController session :off, :only => %w[rss atom] end
Just keep in mind who inherits from whom and you’ll be okay.
Like before_filter and friends, you can also pass a proc to :if to control whether sessions are enabled or disabled:
class StoriesController < ApplicationController session :off, :if => proc { |request| request.server_name == 'localhost' } end
Something like that. Your proc is passed the request object we see all over the place. Plan accordingly.
There’s more to it than that, I’m afraid. Your big app is getting hundreds of hits and suddenly you realize it’s slowing down. Disk space is waning. Sessions are to blame. But there’s a solution.
ActiveRecordStore
The default storage for sessions in Rails is what’s known as PStore. Think of your Rails app as a bicycle and PStore as training wheels. Training wheels are really helpful and easy, but you’ll never get a date using them in public. Same goes for PStore.
There are better session storage options in Rails, for a number of reasons. Files stored locally on a server’s disk, for instance, are the definition of “doesn’t scale.” What happens when you add another server? I don’t even know. PStore is also pretty slow, according to the Internet.
One viable option is ActiveRecordStore. This comes with Rails and you can start using it right now.
Do this: rake db:sessions:create
This’ll give you a new migration for creating a sessions table ActiveRecordStore will be happy with. So, migrate.
Next go into environment.rb and uncomment this line:
config.action_controller.session_store = :active_record_store
You see, it’s basically done for you. Why even bother with file sessions? Get rid of them right away. This is share nothing at its finest, people.
You can easily clear out your sessions table during development, too:
rake db:sessions:clear.
If you’re feeling like a spelunker you can check out lib/action_controller/session/active_record_store.rb in ActionPack. Not only does that file house the ActiveRecordStore code, but also skeleton code upon which you may build your own DB session stores (thanks, core!).
SQLSessionStore
Speaking of custom DB session stores, the all-knowing Stefan Kaes has posted extensively on sessions. He’s got a SQLSessionStore that, he claims, will knock your socks off (if you’re on MySQL). Download the tarball, extract it into RAILS_ROOT/lib/, then add this code to the bottom of your environment.rb file (after commenting out the other session_store line higher up):
require 'sql_session_store' require 'mysql_session' ActionController::Base.session_options[:database_manager] = SQLSessionStore SQLSessionStore.session_class = MysqlSession
Unfortunately for us, Mr. Kaes’ code can not use the database table generated by db:sessions:create without modification. You need to use this migration file to create the correct session table. But it’ll be worth it. Bare bones SQL, no AR overhead. Hot metal on metal action.
An important thing to remember when using a database session store is you need to clear out the session table. This won’t happen automatically, you know? Luckily both Stefan and Rails’ solutions provide a created_at column. With the ActiveRecordStore it’s as easy as writing a cron job.
memcached
The ol’ chestnut, memcached. Getting this session store to work is less straight forward: you need to install memcached and its dependencies first. Topfunky has a good memcached basics article which should help get you going. Namely, check out his sh script.
Once you have memcache-client and memcached itself up and running, the session store part is just a bit of code:
config.action_controller.session_store = :mem_cache_store
Make sure you setup the CACHE constant and memcache-client in your environment.rb file as per topfunky’s article. Something like this:
require 'memcache' memcache_options = { :c_threshold => 10_000, :compression => true, :debug => false, :namespace => :app-#{RAILS_ENV}", :readonly => false, :urlencode => false } CACHE = MemCache.new memcache_options CACHE.servers = 'localhost:11211' ActionController::Base.session_options[:expires] = 1800 ActionController::Base.session_options[:cache] = CACHE
Memcached is nice because it has its own expiration system. We don’t have to worry about expiring sessions, it will take care of that as long as we set a timeout.
DRbStore
Uh, does anyone use this? I’m not sure they do. Seriously, Google is convinced I’m looking for some kind of Rails drugstore. It’s fun, though. Here’s how you do it.
Open environment.rb and uncomment the session_store line. Change it thus:
config.action_controller.session_store = :drb_store
Now I couldn’t really figure out how to start the server cleanly. What I had to do was copy actionpack/lib/action_controller/session/drb_server.rb to my RAILS_ROOT/script directory, then start it:
ruby script/drb_server.rb.
Black screen, but that’s okay. It seems to work just fine and is very basic. No real options or anything. No word on session expirations, for instance. Maybe something cool will come out of this one day. I imagine it’s possible to have some sort of distribute system setup ala memcached, but I also imagine memcached will always be faster.
Memory Store
Not memcached and not DRbStore, there’s also Memory Store. The simplest:
config.action_controller.session_store = :memory_store
Nothing else to it. Fast. Uses RAM. Expiration? Who needs it! And like PStore, it doesn’t scale.
Finally
There’s still a lot more to be said about sessions. The Rails wiki is just overflowing. A huge page on session options alone. I hope you’re up for it. Just be sure to watch out for gotchas when using sessions promiscuously, but don’t let that stop you.
Memory is what makes us who we are.
Nice work; I am one of those who mostly ignores session management. But I can change…
Yet another cool thing to check
Thanx !
Nice article! Thanks man!
Great article Chris. Keep it up.
Nice Article. I would request you to extend the article by covering the session management in load balanced servers
There’s nothing to say about load balanced sessions. If you’re using memcached or a database session store, just add more app servers and go. As long as you’re keeping your sessions in a centralized location (like memcached or a db) there’s no issue.
“Scaling Rails is easy.”
There’s no real need to set an expiry time for memcached sessions, they’ll just fall off the LRU.
Note that if you have too much cache pressure sessions in memcached may expire prematurely. Use a cookie with an HMAC to keep login credentials alive.
When i use db to store session, i often get some error “mysql connection lost”. any idea???
I’m using DRb Session management. It’s pretty easy to set up and it just works. But you’re right, you have to write your own server if you want expiration. Here’s the code for my server. I start it through an init.d script. This is the server:
Then you need the client. You put the client in your Rails application’s lib directory as drb_store.rb. This is the client code:
Finally, you put this line in config/envronments/production.rb:
Or if you want sessions for all environments then put the above line in environment.rb
Long live (!)
Can’t figure out to change the session id while keeping the rest of the session data intact. Anyone?
/Sven
I am interested in the possibility of shared sessions between different Rails applications being used by the same user as a suite or whatever. Single sign on I guess. Are there any good approaches in Rails for this? The active record session, while very good and simple, is tied into the application database by default (the database.yml bit) which prevents two or three applications from converging in session terms. So I wondered about some kind of single sign on session token that would enable the second application to be accessed to scaffold a new session transparently without going to the login screen.
Has anyone actually measured the performance increases gained by moving up the list of session stores? I mean how much faster is memcahced vs AR store? And how does drb store compare with memchached performance-wise?
Wow, thanks tomek!
“Fast. Uses RAM. Expiration? Who needs it!”
Wouldn’t that end up using more and more memory?
Chris, is there any way of calling session :off based certain conditions? For instance I’m trying to create a new component for the DHH browser_filters plugin which detects some common search engines and disables sessions for these. For example (this doesn’t work!):
Any tips?
Nice writeup. I have released SqlSessionStore as a plugin. It now uses the default table layout generated by rake db:sessions:create. If you have an old version of SQLSessionStore, the plugin provides a migration generator to rename sessid into session_id.
Akhil, I have the same problem on my PC, but not on my host. I think MySql is configured differently on the host. I think this link might provide the answer. It says that the client and server both have to have large packet sizes. I was able to change the setting for the server, but I don’t know how to set the max_allowed_packet for the client (Rails). Let me know if you figure this out.
- Tad se6 at tadlamb.com
Can any bodu tell me how to clear the value of session if user close the browser directlly
pls reply me soon, i m in gr8 demand of this. thnx
ActiveRecordStore Session Expirations
While not the most agile way of handling things, I have an app that requires a cron job every minute for some db maintenance. I included something to this effect for session expirations:it gets the job done for me.
hi friends i had a serious issue for my project actualy as u all know whenever we login a entry is added in the sessions tableof our database and it re3amins there till we log off
but in case the user clicks the close icon nof the browser (eg, IE or mozilla )then in that case the session entry reamins intact
my issue was to delete the entry in either case whether the browser is closed or the user logs off
or to delete the entry when the user types the url again, the session entry should get regenerated
if u have the solution to my query plz do help me and mail me at mba.piyushgupts@gmail.com
i am eagerly waiting ur reply
really useful how to!!!
thanks
Here is a snippet of code that will stop ActiveRecordStore from needlessly writing unchanged sessions back to the database:
http://pastie.caboo.se/53086
Obviously, this changes the behavior of updated_at, so you may need to revise your session gc algorithm, but it helps keeps excessive writes away from your database. Useful if you are write bound on the DB.
Wonderful article. Looking at deployment now and trying to figure out the best option to handle sessions so that it can scale correctly in the future and not overwhelm the disk. The better SQL store seems like an appealing solution.
Landed here searching for changes in Rail2.0 for preventing XSS attacks. Thanks for the info here, really useful stuff.
FastSession plugin was released today – check it out at http://blog.kovyrin.net/2008/02/06/fastsessions-rails-plugin-released/
Chime in.