AWS ElastiCache with Memcached and Node.js

A practical walkthrough of using ElastiCache to reduce database load, improve response time, and integrate a Memcached-backed caching layer into an application workflow.

After setting up the private backend layers for the vProfile project, the next useful service to understand was AWS ElastiCache. This step focused on using Memcached to improve performance by reducing how often the application needs to query the database.

The core idea is simple: instead of repeatedly fetching the same data from a slower backend database, the application can keep frequently used data in memory for much faster access.

Main idea: cache data that is requested often so the application responds faster and the database does less work.

Why Do We Need a Cache Layer?

Without a cache, every repeated request can hit the database directly. That works at small scale, but it becomes less efficient as traffic increases.

Without cache: User -> App -> Database With cache: User -> App -> Cache -> Database

In practice, the application first checks the cache. If the data is already there, the result comes back quickly. If not, the app fetches the data from the database, returns it, and stores it in cache for the next request.

Why ElastiCache Matters in a Real Architecture

In a layered cloud setup, caching sits between the application logic and the database layer. This helps reduce read pressure on the database and improves user-facing response time.

User -> Application -> ElastiCache -> Database

This is especially useful for repeated reads, session-like data, or values that do not change every second.

Step 1: Create an ElastiCache Memcached Cluster

In this setup, the instructor used the Memcached engine. Memcached is lightweight, simple, and good for fast key-value caching.

The core settings included:

  • Engine: Memcached
  • Version: 1.6
  • Node type: cache.t2.micro
  • Port: 11211
  • Security group: backend security group
  • Subnet group: private subnet group
Important: the cache layer should stay private. Just like the database, it should not be publicly reachable from the internet.

Step 2: Keep Networking and Access Private

ElastiCache works best as an internal backend service. That means placing it inside the correct private subnets and attaching the correct security group so only trusted application resources can connect.

For this project, the key access rule is the same design idea used in the earlier steps:

  • Backend services stay in private networking
  • The backend security group controls trusted communication
  • The application layer is allowed to reach the cache later
Beanstalk application security group -> backend security group -> ElastiCache

Step 3: Store the Cache Endpoint as Configuration

Once the cluster is created, the application needs the cache endpoint and port number so it knows where to connect.

CACHE_HOST=your-cache-endpoint.amazonaws.com CACHE_PORT=11211

These values are typically passed as environment variables so the application code stays cleaner and easier to move between environments.

Step 4: Connect from Node.js

To show how an application can use the cache, this step used the memcached package in Node.js.

npm install memcached

A simple connection example looks like this:

const Memcached = require("memcached"); const CACHE_HOST = "your-cache-endpoint.amazonaws.com"; const CACHE_PORT = 11211; const memcached = new Memcached(`${CACHE_HOST}:${CACHE_PORT}`);

This creates the client connection that the application can use for reads and writes to the cache.

Step 5: Build Reusable Cache Functions

The next step is creating helper functions for storing and reading cached values. That keeps the caching logic separate from the main business logic.

Set a Cache Value

function setCache(key, value, ttl = 60) { return new Promise((resolve, reject) => { memcached.set(key, value, ttl, (err) => { if (err) return reject(err); resolve(); }); }); }

Get a Cache Value

function getCache(key) { return new Promise((resolve, reject) => { memcached.get(key, (err, data) => { if (err) return reject(err); resolve(data); }); }); }

The TTL value controls how long data stays in cache before expiring.

Step 6: Use Cache Before Querying the Database

This is where caching becomes useful. The application first checks whether the requested value is already stored. If it is, the database query can be skipped.

async function getUser(userId) { const cacheKey = `user:${userId}`; const cached = await getCache(cacheKey); if (cached) { console.log("Cache HIT"); return cached; } console.log("Cache MISS -> Fetching from DB"); const userFromDB = await fakeDBCall(userId); await setCache(cacheKey, userFromDB, 120); return userFromDB; }

This pattern is simple, but it is one of the most practical ways to speed up read-heavy workflows.

Step 7: Test the Performance Difference

A small test shows the benefit clearly. The first request takes longer because it fetches from the database. The second request is much faster because the result already exists in cache.

Cache MISS -> Fetching from DB First call: ~1000ms Cache HIT Second call: ~2ms

That is the practical value of caching: less repeated backend work and a much faster response path for common requests.

Common Mistakes to Avoid

Things to watch carefully:
  • Do not use the wrong port; Memcached uses 11211
  • Do not attach the wrong security group
  • Do not make the cache publicly accessible
  • Do not confuse Redis and Memcached when the project expects Memcached

What I Learned from This Step

  • ElastiCache improves performance by reducing repeated database reads
  • Memcached is simple and fast for key-value caching
  • Private networking still matters for cache services
  • Security groups control who can reach the cache layer
  • Application integration becomes cleaner with reusable cache functions

Conclusion

This step made the architecture more realistic by adding a proper caching layer between the application and the database. Instead of hitting the database for every repeated request, the application can now return commonly used data much faster.

It also showed that performance is not only about faster servers. Good design choices, such as using a private cache layer with the right integration pattern, can make a big difference in how an application behaves under load.