Skip to content

Using Memcached for Session Lookups

Fabian Wenzelmann edited this page Apr 2, 2017 · 3 revisions

Using Memcached for Session Lookups

New in version v0.2

When dealing with sessions we have the problem that we will do the same lookup again and again (user clicking on a link, reloading certain information...). Querying the database every time can be very expensive. So there is a memcached handler to cache keys in memory. This handler wraps another handler. When looking up a key we first check memcache, if the lookup fails we query the real handler (like the database). goauth uses bradfitz/gomemcache to connect to memcached.

Quickstart Example

...
import "github.com/bradfitz/gomemcache/memcache"

postgreHandler = goauth.NewPostgresSessionHandler(db, "", "")
memcachedHandler := goauth.NewMemcachedSessionHandler(postgreHandler, memcache.New("localhost:11211"))
controller := goauth.NewSessionController(memcachedHandler)

How it Works / Settings

You can configure the memcached client via bradfitz/gomemcache. A key k gets stored as "skey<SOME-RANDOM-INT>:k" in memcached. Note that the max length for keys in memcached is 250, so don't set the session key length to something too big. The data associated with the key is stored as a json string.

An important note: The user information is of type interface{} s.t. you can use whatever type you want. When storing the user information the user key is transformed to a string with the String() method. You want to make sure that when retrieving the data your key gets transformed to the correct data type. The default implementation assumes that the key type is uint64. If you want to changed that you must change the ConvertUser method of your MemcachedSessionHandler!

Lookup works as follows:

  1. We store the current random integer value (session key identifier)
  2. We do a lookup in memcached for the value "skey<RANDOM-INT>:k"
  3. If the entry was found in memcached the value is returned, if the lookup fails we lookup the real handler and if that returns a valid result we add it to memcached
  4. If you call a method to invalidate a key (for example DeleteEntriesForUser) ALL entries in memcached become invalid by creating a new random prefix number

Of course this could become problematic if the prefix generates a number previously used. But I think that is very very unlikely, especially with a small expiration value. And a small value makes sense here because we're looking up sessions, a database lookup from time to time won't hurt that much. The default expiration time of memcached entries is one hour.

To change this value change the Expiration field on your MemcachedSessionHandler. This is of type int32 and has the following meaning (according to memcached): "Expiration times are specified in unsigned integer seconds. They can be set from 0, meaning "never expire", to 30 days (60 * 60 * 24 * 30). Any time higher than 30 days is interpreted as a unix timestamp date. If you want to expire an object on january 1st of next year, this is how you do that."

Note that because the way we invalidate keys (new random integer) we never again look at the old values, so they never get removed from memcached. So you should start memcached with a reasonable -m limit.

Error Handling

Memcached errors are not returned as real errors, they're reported to the log.

Time Comparison

I was interested to know if memcached helps much after all, so I ran some tests and here are my results. Important: These were very simple tests and have nothing in common with real-life scenarios. Even though it might be of interested. For the tests I created 10,000 keys and inserted them. In one version I used only postgres, in the other postgres with memcached. I then ran tests to lookup 10.000 keys were 0, 2,500, 5,000, 7,500 and 10,000 keys were in the storage. For each of those keys I did three lookups (to give posgre some time to start caching maybe, don't know if that made sense). The max memory for object storage for memcached was set to 32MB. So no deletes, key invalidation and so on! Insertion time of all keys: postgres 42.54s, postgres with memcached 45.38s.

match / mismatch postgres postgres with memcached
0 / 10000 5.53s 7.73s
2500 / 7500 5.63s 6.33s
5000 / 5000 5.71s 4.87s
7500 / 2500 5.79s 3.49s
10000 / 0 5.93s 2.07s

So as we might expect: It takes a bit more time to add all the entries because both memcached and postgres must store the data. If we have many misses memcached is not efficient: First the lookup on memcached fails and then we ask the database as well. But with more matches memcached more than halves the time. I would say that most lookups are successful (user loading another page) so yes, imho memcached is useful here.

Clone this wiki locally

⌂ 换网址