Use Cases

Session Management

What is session state?

Session state is data that captures the current status of user interaction with applications such as a website or a game.

A typical web application keeps a session for each connected user, for as long as the user is logged in. Session state is how apps remember user identity, login credentials, personalization information, recent actions, shopping cart, and more.

Reading and writing session data at each user interaction must be done without hurting the user experience. Behind the scenes, the session state is cached data for a specific user or application that allows fast response to user actions. Therefore, while the user session is live, no round-trip to the central database should be needed.

The last step in the session state life cycle occurs when the user is disconnected. Some data will be persisted in the database for future use, but transient information can be discarded after the session ends.

Challenges and best practices for session state

Understanding session state best practices is key to assessing and solving common sessions-related problems such as isolation, volatility and persistence.

While the session is live, the application reads from and writes to the in-memory session store exclusively. This means faster writing operations, but also no tolerance to data loss. Since session store data isn’t a simple snapshot from another database, it must be highly durable and always available.

Session state is similar to a cache, but it has different read/write life cycles: a cache is data-loss tolerant, and can be restored at any time from a primary database. Writing to a cache also requires writing to the underlying database. In contrast, the session state can be restored only from the primary data source when the user session starts and is persisted back to the source only when the session ends.

Session state can be volatile or permanent, meaning that data can be either discarded or persisted to disk storage when the user session ends. An example of volatile session data might be page-navigation history in a corporate intranet—there’s little need to retain it. In contrast, a durable shopping cart in an e-commerce app is critical to the business and must be saved on a permanent store.

Session state is stored as a key-value pair with user identifier as the key and session data as the value. This ensures that user sessions don’t access each others’ information.

Storing session state in a fast in-memory cache allows some online analytical scenarios that would otherwise penalize a transactional database. These applications include real-time analysis and dashboards, recommendation engines and fraud detection.

How Redis Enterprise makes it fast

  • Redis Enterprise is based on a shared-nothing, symmetric architecture that lets dataset sizes grow linearly and seamlessly without requiring changes to the application code.
  • Redis Enterprise offers multiple models of high availability and geographic distribution enabling local latencies for your users when needed.
  • Multiple persistence options (AOF per write or per second and snapshots) that don’t impact performance ensures that you don’t have to rebuild your database servers after failures.
  • Support for extremely large datasets with the use of intelligent tiered access to memory (RAM, persistent memory or Flash) ensures that you can scale your data sets with the demands of your users without any significant impact to performance.

How to use Redis Enterprise for session management

Consider a text chat application using MySQL as the relational database, Node.js as the backend server technology, and Redis Enterprise for session management. The frontend is comprised of two pages: a home page, where users log in, and a chat page, in which users type in and send messages.

For the sake of simplicity, we will show only the server code here. It will explain how to implement the session state lifecycle in Node.js. We are also omitting the HTML view pages and the rest of the application.

First, the application loads dependencies, including session, Redis objects, and MySQL client:

var express = require("express");
var session = require('express-session');
var mysql = require("mysql");
var redis = require("redis");
var redisStore = require('connect-redis')(session);
var redisClient = redis.createClient();
// more dependencies are loaded here...

The declarations above create objects for managing web routing, session, database, caching and session Node.js libraries. Then set up Redis as the session store:

app.use(session({
    secret: 'mysecret',
    // create new redis store.
    store: new redisStore({ host: 'localhost', port: 6379, client: redisClient }),
    saveUninitialized: false,
    resave: false
}));

Next, configure the Node.js express routes for the home and chat page, along with support for AJAX requests coming from the client, including login, logout and send comments.

When a user requests the home page, the server redirects them to the chat.html page or displays the login page.html, depending on whether the user is logged in or not. The snippet below shows the handler code for the /get web route:

app.get('/',function(req,res){
    // create new session object.
    if(req.session.key) {
        // user is already logged in
        res.redirect('/chat');
    } else {
        // no session found, go to login page
        res.render("login.html");
    }
});

When the user submits the login form data (with email and password), the client JavaScript AJAX sends form data to the server. In this example, it calls the executeLoginDbCommand function (not shown here), which executes a SQL query against the MySQL database and returns an object containing the user’s previously saved session data.

If the login succeeds, the user data coming from MySQL is saved to the web session backed by the Redis session store, and the client JavaScript code redirects the user to the chat page:

app.post('/login',function(req, res){
    // SQL Query will compare login and password
    // from HTTP request body to user table data
    executeLoginDbCommand(req.body.Email, req.body.Password, function(dbResult){
        //
        if(!dbResult) {
            res.json({"success" : false,"message" : "Login failed ! Please register"});
        } else {
            req.session.key = dbResult;
            res.json({"success" : true,"message" : "Login success."});
        }
    });
});

The application chat page lets users read and post messages to other people logged in to the application. Since users see only their own message interactions with others, the data returned by the server for the chat page requests change from user to user. Most importantly, access to this page is restricted to logged-in users. Checking the session key reveals whether the user is logged in or not:

app.get('/chat',function(req,res){
    if(req.session.key) {
        //user is already logged in,
        //so let's render the chat page with the user email
        res.render("chat.html",
            {
                email : req.session.key["UserEmail"],
                name : req.session.key["UserName"]
            });
    } else {
        // no session found, go to login page
        res.redirect("/");
    }
});

When the user submits a new comment from the chat page, the client JavaScript AJAX sends form data to the server. If the user is logged in, the comments are inserted in the MySQL UserComments table. We do this by invoking the executeSendCommmentDbCommand function (not shown here).

app.post("/sendComment",function(req,res){
    // This SQL command will insert a new comment in
    // users table
    if(req.session.key) {
        executeSendCommmentDbCommand(req.body.Email, req.body.Recipient, req.body.Comment, function(dbResult){
            if(!dbResult) {
                res.json({"success" : true, "message" : "Comment has been sent successfully."});
            } else {
                res.json({"success" : false, "message" : "SendComment failed!"});
            }
        });
    } else {
        res.json({"success" : false, "message" : "Please login first."});
    }
});

When the user logs out, the session object is destroyed and the user is redirected to the login page. But first, the executePersistSessionDbCommand (not shown here) saves the in-memory user session to the MySQL database:

app.get('/logout',function(req,res){
    // user is logged in, so let's destroy the session
    // and redirect to login page.
    if(req.session.key) {
        executePersistSessionDbCommand(req.session, function(dbResult){
            if(!dbResult) {
                req.session.destroy(function(){
                    res.redirect('/');
                } else {
                    res.json({"success" : false, "message" : "Session persistence failed!"});
                }
            });
        });
    } else {
        res.redirect('/');
    }
});

These snippets only scratch the surface of a real application using Redis as a session store. But they illustrate how Redis can manage in-memory session state lifecycle in combination with permanent database storage like MySQL.