Redis Cache in Nodejs

Redis Cache in Nodejs

A cache is a fast, in-memory data store that can be used on top of an existing database. Caching reduces the load on your database and significantly improves the performance of read operations.

Redis cache is an in-memory data store. Redis also provides other services to help you create a complete full stack application using only Redis.

In this blog we will be implemeting a cache on top of a MongoDB database and understand how it can be used to reduce read times.

Installation

Install locally on windows

For windows you will need to set up WSL2 in order to work with Redis. Refer to the following documentation -

https://redis.io/docs/getting-started/installation/install-redis-on-windows/

You can also install the docker image for the redis stack server to work with redis locally.

Redis CLI basics

To start redis server run the following command-

sudo service redis-server start

This will run redis locally on port 6379.

To use the redis-cli use the following command-

redis-cli

If redis-server is running you will see the following, you can also type ping and it would respond with PONG.

127.0.0.1:6379> ping
PONG

Now that redis is running successfully at port 6379, let's see what we can do with the CLI.

Creating a Database

In redis databases are numbered starting from 0. By default Redis starts with one database (which is 0). But you can create multiple databases.

127.0.0.1:6379> SELECT 3
OK

Using the SELECT command, you can select the database which you want to interact with, if the database doesn't exist it will create it.

After creating database 3, let's add some data to it.

Setting and getting key-value data

127.0.0.1:6379[3]> SET name "Aditya Bisht"
OK
127.0.0.1:6379[3]> GET name
"Aditya Bisht"

Whitespaces automatically delimit the arguments, hence if you want to add a string with spaces use "". Doubly quoted strings also support escape sequences such as "/n" (newline), "/t" (tab) etc.

Another thing to note is if you set a key that already exists, it will replace the old value with the new one, this is how data is updated in the key-value store.

127.0.0.1:6379[3]> GET name
"Aditya Bisht"
127.0.0.1:6379[3]> SET name "ADI"
OK
127.0.0.1:6379[3]> GET name
"ADI"

Getting DB size

127.0.0.1:6379[3]> DBSIZE
(integer) 2

Remember we are currently in DB 3, let's switch back to DB 0 and check its size.

127.0.0.1:6379[3]> SELECT 0
OK
127.0.0.1:6379> DBSIZE
(integer) 0

As expected the size is 0 since we didn't add any data.

Display all keys in a DB

127.0.0.1:6379[3]> SET name2 "Adi Bisht"
OK
127.0.0.1:6379[3]> keys *
1) "name2"
2) "name"

Delete a key

127.0.0.1:6379[3]> DEL name2
(integer) 1
127.0.0.1:6379[3]> keys *
1) "name"

Cache Staleness

When the data in your cache becomes stale (meaning the data is outdated and needs to be updated or deleted to prevent outdated data from being returned everytime), the condition is called cache staleness.

Staleness can be dealt with through regular updation or putting a TTL(time to live) on keys. Using a TTL, the keys would expire after a given time.

Implementing Redis cache with a Database in Express

We will be coding this in expressjs, to install node redis run-

npm i redis

While working with the CLI, we created simple key-value pairs, here we will create a key-map structure.

Note that by default all entries are made to the default database 0.

Write operation

Write operation would be done directly to MongoDB, however if the key is present in the cache, we will need to update the value of that key to ensure data isn't outdated.

async function writeToCache(req, res, next) {
  const { username, age, city } = req.body;
  //check if that key is contained in cache
  const userData = await redis.hGetAll(username);
  if (userData) {
    //update the values
    const response = await redis.hSet(username, {
      age: age,
      city: city,
    });
  }
  next();
}
app.post("/register", writeToCache, async (req, res) => {
  const { username, age, city } = req.body;
  var newUser = new User({
    username,
    age,
    city,
  });
  //save to mongoDB
  const savedUser = await newUser.save();
  return res.json({ success: true, message: savedUser });
});

Here, writeToCache middleware is called to update the cache if necessary.

Let's register a user aditya -

aditya was not present in cache so there was no entry made to cache as visible below:

Read operation

async function readCache(req, res, next) {
  const { username } = req.body;
  //check if user exists in the cache,if yes return
  const userData = await redis.hGetAll(username);
  if (userData) {
    return res.json({
      success: true,
      message: username + " found in cache!!",
    });
  }
  //go to the next middleware
  next();
}
app.get("/get-user", readCache, async (req, res) => {
  //if not in cache read from mongoDB
  const { username } = req.body;
  const users = await User.find({ username });

  //set key and map in cache to ensure faster read times next time
  const response = await redis.hSet(username, {
    age: users[0].age,
    city: users[0].city,
  });
  return res.json({
    success: true,
    message: users[0] + " found in MongoDB",
  });
});

Here if the cache contains the key, data is directly returned from the cache resulting in faster read times. Else, data is fetched from Mongo and added to the cache for future.

aditya is now added to the cache, and for future requests would be fetched from the cache.

That's all for this one, do let me know if you learnt something !!