What is a good session store for a single-host Node.js production app?

node.jsExpressConnect

node.js Problem Overview


I'm using Node's Express w/ Connect middleware. Connect's memory session store isn't fit for production:

> Warning: connection.session() MemoryStore is not designed for a production environment, as it will leak memory, and obviously only work within a single process.

For larger deployments, mongo or redis makes sense.

But what is a good solution for a single-host app in production?

node.js Solutions


Solution 1 - node.js

Spent the day looking into this. Here are the options I've discovered. Requests/second are performed via ab -n 100000 -c 1 http://127.0.0.1:9778/ on my local machine.

  • no sessions - fast (438 req/sec)
  • cookieSession: requires no external service, minor speed impact (311 req/sec) - fastest, sessions will expire with the cookie (customised by maxAge)
  • connect-redis: requires redis server, large speed impact (4 req/sec with redis2go and redisgreen) - faster than mongo, sessions will be deleted after a while (customised by ttl)
  • connect-mongo - requires mongodb server, large speed impact (2 req/sec with mongohq) - slower than redis, requires manual clear_interval to be set to cleanup sessions

Here is the coffeescript I used for cookieSession:

server.use express.cookieSession({
	secret: appConfig.site.salt
	cookie: maxAge: 1000*60*60
})

Here is the coffeescript I use for redis:

RedisSessionStore ?= require('connect-redis')(express)
redisSessionStore ?= new RedisSessionStore(
	host: appConfig.databaseRedis.host
	port: appConfig.databaseRedis.port
	db: appConfig.databaseRedis.username
	pass: appConfig.databaseRedis.password
	no_ready_check: true
	ttl: 60*60  # hour
)
server.use express.session({
	secret: appConfig.site.salt
	cookie: maxAge: 1000*60*60
	store: redisSessionStore
})

Here is my coffeescript for mongo:

server.use express.session({
	secret: appConfig.site.salt
	cookie:
		maxAge: 100*60*60
	store: new MongoSessionStore({
		db: appConfig.database.name
		host: appConfig.database.host
		port: appConfig.database.port
		username: appConfig.database.username
		password: appConfig.database.password
		auto_reconnect: appConfig.database.serverOptions.auto_reconnect
		clear_interval: 60*60  # hour
	})
})

Now of course, the remote redis and mongo databases will be slower than their local equivalents. I just couldn't get the local equivalents working, especially considering the installation and maintenance time for me was far more than what I was willing to invest when compared with hosted remote alternatives, something I feel is true for others too hence why these hosted remote database services exist in the first place!

For local database benhmarks, see @Mustafa's answer.

Happy for someone to edit this answer to add their local database benchmarks to the mix.

Solution 2 - node.js

Since the accepted answer is only connecting to remote hosts, it is obvious that it will be always slower than localhost. Even if it is the next computer in your home, it would take milliseconds to read from that computer, but local memory takes only nanoseconds. You should compare them by using locally installed servers.

Here are my results from my local pc: You see, redis is almost as fast as in-memory in under high load. You can clone my the repo that these test codes are available: https://github.com/mustafaakin/express-session-store-benchmark

Concurrency: 1
none	   4484.86 [#/sec] 
memory	   2144.15 [#/sec] 
redis	   1891.96 [#/sec] 
mongo	   710.85 [#/sec] 
Concurrency: 10
none	   5737.21 [#/sec] 
memory	   3336.45 [#/sec] 
redis	   3164.84 [#/sec] 
mongo	   1783.65 [#/sec] 
Concurrency: 100
none	   5500.41 [#/sec] 
memory	   3274.33 [#/sec] 
redis	   3269.49 [#/sec] 
mongo	   2416.72 [#/sec] 
Concurrency: 500
none	   5008.14 [#/sec] 
memory	   3137.93 [#/sec] 
redis	   3122.37 [#/sec] 
mongo	   2258.21 [#/sec] 

The session used pages are very simple pages;

app.get("/", function(req,res){
	if ( req.session && req.session.no){
		req.session.no = req.session.no + 1;
	} else {
		req.session.no = 1;
	}
	res.send("No: " + req.session.no);
});

Redis store config:

app.use(express.session({
	store: new RedisStore({
		host: 'localhost',
		port: 6379,
		db: 2,
		}),
	secret: 'hello'
}));

Mongo store config:

app.use(express.cookieParser());
app.use(express.session({
	store: new MongoStore({
		url: 'mongodb://localhost/test-session'
	}),
	secret: 'hello'
}));
 

Solution 3 - node.js

Another good option is memcached. The session states are lost if memcached is restarted, but there is virtually never any reason to do that. You can leave the cache running all the time even when you restart your app server. Access to the session data is virtually instantaneous and memcached will run happily with whatever (appropriate) amount of memory you give it. And I've never seen memcached crash (on Linux).

https://github.com/elbart/node-memcache

Things to keep in mind about memcached generally:

  • Never have whitespace in your cache keys
  • Be aware that there is a maximum cache key length, including any namespace prefix you might use. If your cache key is too long, use a 1-way hash of it instead.

Neither of these should be an issue with session storage; just with generalized caching.

Solution 4 - node.js

I've gone with a MongoDB session store using connect-mongo.

Install with npm install connect-mongo and replace the existing MemoryStore with

app.use(express.session({ store: new MongoStore({ db: 'some-database' }) }));

It manages the database side of sessions automatically.

Solution 5 - node.js

I would still use Redis even for local development. This is helpful because it stores the session even when you restart the Node application, keeping your browser session logged in. Redis by default saves the session in memory, same as connect's memory store is simple to configure (I just run it in screen along with my node apps) can support multiple applications if you just use a different database or session value in the configuration.

Solution 6 - node.js

I'm just exploring node.js myself, but if you don't need to store a lot of information in the session object -- you might want to explore secure cookies.

Secure cookies store session information as part of the cookie that the browser stores and forwards with each request. They are encrypted to prevent a user from forging a valid cookie.

The advantage is that you don't have to maintain state at the server -- this solution scales well and is simple to implement.

The disadvantage is that you can only store up to about 4KB and that data gets sent to the server on every request (But you can have multiple fictitious domains pointing at your server so you don't impose that baggage on publicly visible static content, for example).

Searching the web it seems like there are at least two implementations of secure cookies for node.js. Not sure how production ready they are, though:

https://github.com/benadida/node-client-sessions/blob/master/lib/client-sessions.js

https://github.com/caolan/cookie-sessions

Solution 7 - node.js

I appreciate that this is an old question, but I came across it while searching for a solution to a similar problem. I had already decided to use memcached for session storage on Linux (with connect-memcached), but I also required the ability to run on Windows. I spent a while trying to find an in-memory session storage for a single-process node app. Redis and Memcached don't appear to be well-supported on Windows, and I didn't want the additional complexity of their installation.

I found session-memory-store in another Stack Overflow thread, which looks good but significantly increased the size of my dependencies.

Finally, I found memorystore in the documentation for express-session. I had missed it originally due to the similarly of its name to the default MemoryStore, but it's exactly what I was looking for:

> express-session full featured MemoryStore module without leaks!

I'm now using connect-memcached when running in a cluster (on Linux only), and memorystore when running a single process (on Linux or Windows).

I thought it worth posting this as another answer, just in case anyone else makes the mistake of missing memorystore as I initially did.

Solution 8 - node.js

Check out my benchmarks at https://github.com/llambda/express-session-benchmarks showing comparisons of different session implementations.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionNilsView Question on Stackoverflow
Solution 1 - node.jsbaluptonView Answer on Stackoverflow
Solution 2 - node.jsMustafaView Answer on Stackoverflow
Solution 3 - node.jskgilpinView Answer on Stackoverflow
Solution 4 - node.jsgreenimpalaView Answer on Stackoverflow
Solution 5 - node.jsTimothy MeadeView Answer on Stackoverflow
Solution 6 - node.jsnimrodmView Answer on Stackoverflow
Solution 7 - node.jsdavidm_ukView Answer on Stackoverflow
Solution 8 - node.jsgx0rView Answer on Stackoverflow