DEV Community

loading...
Cover image for Caching with Redis or Memcached and Node

Caching with Redis or Memcached and Node

kevin_odongo35 profile image Kevin Odongo ・6 min read

Hey Dev's

In this tutorial let us discuss caching, we once talked about jasonwebtoken, imagine you want to track user session. You can use Redis or Memcached for your application.

Caching is the process of storing copies of files in a cache, or temporary storage location so that they can be accessed more quickly. Technically, a cache is any temporary storage location for copies of files or data, but the term is often used in reference to Internet technologies.

Redis and Memcached are very, very fast in-memory data structure stores, so they are perfect for the volatile storage of small and non-complex data as well as very fast retrieval.

They are not used for storing large amounts of data. Things like tokens, fast-changing data, etc are quite ideal. You need to also be very careful because without proper setup you can always serve stale data to your customers.

Let us see some scenarios to understand the logic behind using Redis and Memcached.

Scenario A

In your application, you always don't want to fetch everything at once. Let say a user wants to fetch 100 content you will not fetch all at once you will be doing it in batches.

With AWS DynamoDB you need to always provide LastEvaluatedKey in case you want to paginate your database. This LastEvaluatedKey can always be saved in our Redis or Memcached. Our browsers always cache our request so the logic is we only want to fetch what we don't have and hence we will use the LastEvaluatedKey. So we will always evaluate before fetching.

This will reduce the cost of API fetches for your application. It is quite costly if you don't handle large application API requests correctly.

Scenario B

Let us assume you have a SAAS product and you want to offer limited use of a free version of your product. Your site is hosted on Cloudflare. You can trace user details and temporarily save them so that any retry of use after expiration they won't be able to access your product.

Copy this and paste it into your browser

https://www.cloudflare.com/cdn-cgi/trace
Enter fullscreen mode Exit fullscreen mode

This is the response you will get

fl=4f422
h=www.cloudflare.com
ip=64.119.22.100
ts=1575967108.245
visit_scheme=https
uag=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36 Hypothesis-Via
colo=SJC
http=http/1.1
loc=US
tls=TLSv1.3
sni=plaintext
warp=off
Enter fullscreen mode Exit fullscreen mode

With this response, you can temporarily save the IP address to track usage of your SAAS free version.

Scenario C

Let assume you want to track the total visits to your site. You can save the total visits to your site and display them in your application.

I have given you different scenarios but there are many scenarios that will require caching in your application.

Redis

Install Redis in your application.

yarn add redis
Enter fullscreen mode Exit fullscreen mode

.env

Add the Redis configurations in your .env file.

REDIS_HOST=redis- // using the server labs
REDIS_PORT=6379
REDIS_PASSWORD=Cn*****************
...
Enter fullscreen mode Exit fullscreen mode

To get a free 30MB register here with https://redislabs.com/try-free/ OR Get it as a container https://hub.docker.com/_/redis/

For practical purposes, I will give an example with DynamoDB.

For every request, our JSON response will be as follows. Note the LastEvaluatedKey. In case there won't be any more items in the database this LastEvaluatedKey won't be available in the query request.

{
   "ConsumedCapacity": { 
      "CapacityUnits": number,
      "GlobalSecondaryIndexes": { 
         "string" : { 
            "CapacityUnits": number,
            "ReadCapacityUnits": number,
            "WriteCapacityUnits": number
         }
      },
      "LocalSecondaryIndexes": { 
         "string" : { 
            "CapacityUnits": number,
            "ReadCapacityUnits": number,
            "WriteCapacityUnits": number
         }
      },
      "ReadCapacityUnits": number,
      "Table": { 
         "CapacityUnits": number,
         "ReadCapacityUnits": number,
         "WriteCapacityUnits": number
      },
      "TableName": "string",
      "WriteCapacityUnits": number
   },
   "Count": number,
   "Items": [ 
      { 
         "string" : { 
            "B": blob,
            "BOOL": boolean,
            "BS": [ blob ],
            "L": [ 
               "AttributeValue"
            ],
            "M": { 
               "string" : "AttributeValue"
            },
            "N": "string",
            "NS": [ "string" ],
            "NULL": boolean,
            "S": "string",
            "SS": [ "string" ]
         }
      }
   ],
   "LastEvaluatedKey": { 
      "string" : { 
         "B": blob,
         "BOOL": boolean,
         "BS": [ blob ],
         "L": [ 
            "AttributeValue"
         ],
         "M": { 
            "string" : "AttributeValue"
         },
         "N": "string",
         "NS": [ "string" ],
         "NULL": boolean,
         "S": "string",
         "SS": [ "string" ]
      }
   },
   "ScannedCount": number
}
Enter fullscreen mode Exit fullscreen mode

Install aws-sdk and update your credentials in the .env file.

yarn add aws-sdk 
Enter fullscreen mode Exit fullscreen mode

.env file

AWS_REGION=your_region
AWS_ACCESS_KEY_ID=your_access_key_id
AWS_SECRET_ACCESS_KEY=your_secret_access_key
Enter fullscreen mode Exit fullscreen mode

In my request when my API is fetched we will scan my database and return all the data. Ideally, we want to limit what the user can get from our database in a single request.

Below is a request before we added a limit. We currently have 6 items.

Alt Text

When we add our limit to only 5 items.

Alt Text

You will note there is a LastEvaluatedKey indicating the last item scanned. This means in our next request we will get items from the LastEvaluatedKey.

This is what we want to save in our Redis or Memcached so we can know what to retrieve that our customer requires.

Once we have confirmed the LastEvaluatedKey then our next query we will use what we have saved and set on ExclusiveStartKey to retrieve more items

I hope we are on the same page.

index.js

Import Redis and configure it in your application.

const express = require("express");
const bodyParser = require("body-parser");
const cors = require("cors");
require('dotenv').config()
const app = express();

// parse application/json
app.use(bodyParser.json())

// parse application/x-www-form-urlencoded
app.use(bodyParser.urlencoded({ extended: true }))

// use cors options
app.use(cors())

// aws configuration
var AWS = require("aws-sdk");
AWS.config.update({
  region: process.env.AWS_REGION,
  accessKeyId: process.env.AWS_ACCESS_KEY_ID,
  secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY
});


// redis
const redis = require("redis");
const client = redis.createClient({
   host: process.env.REDIS_HOST,
   port: process.env.REDIS_PORT,
});
// i am using redis from redis server labs
client.auth(process.env.REDIS_PASSWORD)
// let us have a default value
client.set("LastEvaluatedKey", 0)

// catch redis error
client.on("error", function(error) {
  console.error(error);
});

// home route
app.get("/", async (req, res) => {  
    client.get("LastEvaluatedKey", async(err, data) => {
        if(err) console.log(err)
        if(parseInt(data) === 0){
          let item = { id: 0}
          const response = await scanTable(item)
          client.set("LastEvaluatedKey", JSON.stringify(response.LastEvaluatedKey))
          res.send(JSON.stringify(response))
        } else {
          const response = await scanTable(JSON.parse(data))
          client.set("LastEvaluatedKey", JSON.stringify(response.LastEvaluatedKey))
          res.send(JSON.stringify(response))
        }
    }) 
})

const scanTable = async (item) => {
  var params = {
    TableName : 'redis_memcached',
    Limit: 5,
    ExclusiveStartKey: item 
  };
  var db = new AWS.DynamoDB.DocumentClient();
  const results = await db.scan(params).promise()
  return results
}

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
    console.log(`Server is running on port ${PORT}.`);
});

Enter fullscreen mode Exit fullscreen mode

Memcached

To install Memcached on Linux

wget http://memcached.org/latest
tar -zxvf memcached-1.x.x.tar.gz
cd memcached-1.x.x
./configure && make && make test && sudo make install
Enter fullscreen mode Exit fullscreen mode

Or use a Docker container to host one.

yarn add Memcached
Enter fullscreen mode Exit fullscreen mode

Import memcahed in your index.js and configure it.

const express = require("express");
const bodyParser = require("body-parser");
const cors = require("cors");
require('dotenv').config()
const app = express();

// parse application/json
app.use(bodyParser.json())

// parse application/x-www-form-urlencoded
app.use(bodyParser.urlencoded({ extended: true }))

// use cors options
app.use(cors())

// aws configuration
var AWS = require("aws-sdk");
AWS.config.update({
  region: process.env.AWS_REGION,
  accessKeyId: process.env.AWS_ACCESS_KEY_ID,
  secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY
});


// memcached
var Memcached = require('memcached');
var memcached = new Memcached(process.env.MEMCACHED_HOST);

// let us have a default value
memcached.set("LastEvaluatedKey", 0)

// home route
app.get("/", async (req, res) => {  
    memcached.get("LastEvaluatedKey", async(err, data) => {
        if(err) console.log(err)
        if(parseInt(data) === 0){
          let item = { id: 0}
          const response = await scanTable(item)
          memcached.set("LastEvaluatedKey", JSON.stringify(response.LastEvaluatedKey))
          res.send(JSON.stringify(response))
        } else {
          const response = await scanTable(JSON.parse(data))
          memcached.set("LastEvaluatedKey", JSON.stringify(response.LastEvaluatedKey))
          res.send(JSON.stringify(response))
        }
    }) 
})

const scanTable = async (item) => {
  var params = {
    TableName : 'redis_memcached',
    Limit: 5,
    ExclusiveStartKey: item 
  };
  var db = new AWS.DynamoDB.DocumentClient();
  const results = await db.scan(params).promise()
  return results
}

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
    console.log(`Server is running on port ${PORT}.`);
});
Enter fullscreen mode Exit fullscreen mode

That is all you have to know about getting started with Memcached and Redis for more please go through their documentation.

Caching will improve your application performance and a better user experience.

BONUS

You can use Amazon ElastiCache to get your Redis Server or Memcached. AWS Supports both.

**
Amazon ElastiCache allows you to seamlessly set up, run, and scale popular open-source compatible in-memory data stores in the cloud. Build data-intensive apps or boost the performance of your existing databases by retrieving data from high throughput and low latency in-memory data stores. Amazon ElastiCache is a popular choice for real-time use cases like Caching, Session Stores, Gaming, Geospatial Services, Real-Time Analytics, and Queuing.
**

There are two options to choose from:

  1. Amazon ElastiCache for Redis
  2. Amazon ElastiCache for Memcached

Alt Text

Pricing

The pricing is based on node types.
https://aws.amazon.com/elasticache/pricing/?nc=sn&loc=5

If you are building an application that will require in memory then AWS will offer the best scalable solution.

Redis documentation https://redis.io/documentation

Memcached documentation https://memcached.org/.

Thank you

Discussion (0)

pic
Editor guide