Introduction

Effective caching strategies are vital for high-performance applications. While data fetching from the data store is performed by the client application on lazy loading, with the read through pattern the client application only interacts with the caching layer. Indeed the caching layer is reponsible for calling the data store in case of cache miss.

Mechanics of Read Through

Sequence diagram

Lazy loading Sequence diagram

Steps

Here is a breakdown of the steps involved in read through :

  1. User Request: A request is made for data by the client application.
  2. Cache Lookup: The application queries the cache first.
  3. Cache Hit: If data is present (cache hit), it’s returned immediately to the client.
  4. Cache Miss: If the data isn’t in the cache (cache miss), the cache layer itself automatically fetches the data from the primary store.
  5. Data Caching: Once retrieved, the data is stored in the cache for future access.
  6. Serving Data: The data is returned to the client, now stored in the cache for quicker subsequent retrieval.

Implementation

In this example, the cache server abstracts the interaction with the data store and the cache from the client application.

Read Through Implementation Sequence diagram

Here’s a Node.js implementation of the cache server:

const express = require('express');

const app = express();
const port = 3000;

// Simulated data store 
const dataStore = {
  '1': { id: 1, name: 'Item 1' },
  '2': { id: 2, name: 'Item 2' },
  // ... other items
};

// Simulated cache
let cache = {};

// Middleware to simulate read-through cache behavior
function readThroughCache(req, res, next) {
  const { id } = req.params;
  if (cache[id]) {
    console.log('Returning from cache');
    res.send(cache[id]);
  } else {
    console.log('Cache miss, loading from data store');
    const item = dataStore[id];
    if (item) {
      cache[id] = item; // Add to cache
      res.send(item);
    } else {
      res.status(404).send('Item not found');
    }
  }
}

app.get('/data/:id', readThroughCache);

app.listen(port, () => {
  console.log(`Server running on port ${port}`);
});

Here is the client application code that consumes the abstraction provided by the cache server.

const http = require('http');

// Function to get data by ID from the server
function getDataById(id) {
  http.get(`http://localhost:3000/data/${id}`, (res) => {
    let data = '';

    res.on('data', (chunk) => {
      data += chunk;
    });

    res.on('end', () => {
      if (res.statusCode === 200) {
        console.log('Data retrieved:', data);
      } else {
        console.log('Error:', res.statusCode, data);
      }
    });

  }).on('error', (e) => {
    console.error(`Got error: ${e.message}`);
  });
}

// Example usage
getDataById('1'); // Should retrieve the item with ID '1'

Read Through on AWS

Before discussing read through on AWS let’s view in more details DAX, and DynamoDB.

DynamoDB

Amazon DynamoDB is a fully managed NoSQL database service provided by Amazon Web Services (AWS)

DAX

Amazon DynamoDB Accelerator (DAX) is an in-memory caching service for DynamoDB

Read through pattern

The client application interacts only with DAX. It does not query DynamoDB directly. In case of cache miss, DAX is responsible for fetching the data from the dynamoDB and returning the data to the client application.

The following diagram illustrates the interaction between the client application, DAX, and DynamoDB:

Read Through DAX DynamoDB

Note that CDNs like Cloudfront operate using a read-through pattern as well.

Conclusion

Read-through caching enhances application performance by automatically loading and refreshing cache entries on demand. It abstracts the complexity of direct data store access from the client applications.

Updated: