Lesson 16 · 22 min read

Redis: Caching and Sessions

Fast in-memory storage for sessions, rate limits and hot data — and when not to reach for it.

What you'll learn in this lesson

  1. Concept: a very fast whiteboard
  2. Why your app needs one
  3. What Redis actually is
  4. Learn it locally in five minutes
  5. Pattern 1 — caching (cache-aside)
  6. Pattern 2 — sessions
  7. Pattern 3 — rate limiting
  8. Patterns 4–6 — locks, queues, pub/sub
  9. Memory, eviction and durability
  10. Run it on AWS: ElastiCache
  11. Connect the app safely
  12. What to monitor
  13. When not to use Redis
  14. What this costs
  15. Troubleshooting table
  16. Interview corner
  17. Practice task before Lesson 17
  18. Conclusion

Concept

Redis is a database that keeps its data in memory (RAM)instead of on disk. It stores simple values under names, which are called keys. A read or a write usually takes well under a millisecond. That is much faster than a typical PostgreSQL query. One modest server can do tens of thousands of operations per second.

The analogy — the whiteboard next to the filing cabinet

Postgres is the filing cabinet. Everything is stored safely and for a long time, but fetching a folder means a walk across the room. Redis is the whiteboard on the wall. You write there what you need all the time, and everyone can read it at a glance. The whiteboard is small. If the power goes out, you may lose what is on it. So never keep the only copy of something important on the whiteboard.

Why your app needs one

After Lessons 8 and 12 you have many servers and one database. As you grow, three problems appear:

ProblemWhy it appears nowRedis answer
The database is the bottleneckEvery page runs the same queries, and the database has a limit on connections (Lesson 8)Keep the results in memory. The database is asked once, not 10,000 times
Sessions vanish between serversA login on server A is unknown to server B (Lesson 12)One shared session store that every server reads
Limits that must be global“5 login attempts per minute” means nothing if each server counts on its ownA shared counter that expires

People often call Redis the “shared memory of a group of stateless servers”. (Stateless means a server keeps no important data of its own.) It is the last piece that lets the web servers stay disposable, so you can delete and replace any of them.

What Redis actually is

Redis is a key-value store. This means you save a value under a name (the key) and read it back by that name. Redis has several value types, not only strings. Each type has atomic operations. Atomic means the operation happens fully or not at all, and nothing can interrupt it. Every key can also have an expiry time, called a TTL (time to live). When the time ends, Redis deletes the key automatically. This feature makes caches and sessions easy.

TypeExample commandUsed for
StringSET user:1:name "Asha"Cached values, counters, on/off flags, tokens
HashHSET session:abc user 42 role adminA small object with named fields, such as a session or a profile
ListLPUSH jobs "send-email"Simple queues and feeds of recent activity
SetSADD online:tenant7 42A group of unique items, such as who is online, or tags
Sorted setZADD leaderboard 980 "asha"Rankings, delayed jobs and sliding-window rate limits
StreamXADD events * type signupA durable log of events that groups of workers can read
  • Atomic. Redis runs commands one at a time on a single main thread (one line of work). So INCR counter (add 1 to a number) from a hundred servers at the same moment never loses an update. This is what makes shared counters and locks possible.
  • In memory, so size matters. Your data must fit in RAM. A 1 GB node holds roughly a million small cached objects. It cannot hold your whole database.
  • Not your system of record. A system of record is the one place that holds the true copy of your data. That place is Postgres. Redis can save data to disk, but you should plan as if any key can disappear at any time. If you lose the cache, the site should become slower, never wrong.
Redis or Valkey?

In 2024 Redis changed its licence. The community then made a copy of the code, called Valkey, under the Linux Foundation. Valkey is a drop-in replacement, which means you can swap one for the other without changing your code. AWS ElastiCache offers both, and Valkey costs less. The commands and the client libraries (such as ioredis) are the same. So everything in this lesson works for both. We use Valkey on AWS and say “Redis” for the commands and the protocol.

Learn it locally in five minutes

Never learn a new tool on your live system. Run Redis in Docker (Lesson 9) on your own computer and try it out:

On your laptop
  • docker run -d --name redis -p 6379:6379 redis:7

    A real Redis server in one command (or use the image valkey/valkey:8)

  • docker exec -it redis redis-cli

    Opens the interactive client. You should see the prompt 127.0.0.1:6379>

inside redis-cli — type these
SET greeting "hello"            # OK
GET greeting                    # "hello"
SET otp:9876 "482913" EX 60     # a value that deletes itself after 60 seconds
INCR page:views                 # 1, then 2, and so on. Atomic: safe when many servers do it at once
# KEYS *                        # NEVER on a live system: it blocks the server while it scans every key

Redis also has hashes (one key that holds several fields, perfect for a session) and sets (a group of unique members, like “who is online”). It also has a safe way to look through the keys a few at a time, called SCAN. You will meet these in the patterns below. A “pattern” is a common way to solve a problem.

Pattern 1 — caching (cache-aside)

This is the most common use. Your code checks Redis first. If the answer is there (a hit), it returns it at once. If not (a miss), it asks the database, stores the answer in Redis with a TTL, and returns it. The “aside” in the name means the cache sits beside the database, and your code manages both.

lib/cache.ts
import Redis from "ioredis";

// One shared client. In Next.js, keep it on globalThis so hot reload (the dev server reloading code
// when you save) does not open a new connection on every edit.
const g = globalThis as unknown as { redis?: Redis };
export const redis =
  g.redis ??
  new Redis(process.env.REDIS_URL!, {   // rediss://:TOKEN@host:6379 in production (TLS = an encrypted connection)
    maxRetriesPerRequest: 2,
    connectTimeout: 2000,
    commandTimeout: 500,                // a slow cache is worse than no cache, so give up fast
  });
if (process.env.NODE_ENV !== "production") g.redis = redis;

/** Return the cached value, or compute it, cache it, and return it. */
export async function cached<T>(key: string, ttlSeconds: number, load: () => Promise<T>): Promise<T> {
  try {
    const hit = await redis.get(key);
    if (hit !== null) return JSON.parse(hit) as T;
  } catch {
    // Redis is down or slow: FAIL OPEN. This means the site gets slower, but it does not break.
  }

  const value = await load();                       // the slow database query

  try {
    await redis.set(key, JSON.stringify(value), "EX", ttlSeconds);
  } catch {}
  return value;
}
using it
// A dashboard that runs four heavy queries and is requested thousands of times a minute
export async function getDashboard(tenantId: string) {
  return cached(`app:v1:tenant:${tenantId}:dashboard`, 60, () => buildDashboardFromDatabase(tenantId));
}

// When the data changes, remove the old copy at once
export async function updateSettings(tenantId: string, data: Settings) {
  await db.settings.update(tenantId, data);
  await redis.del(`app:v1:tenant:${tenantId}:dashboard`);   // invalidate (remove the old copy) on write
}

The four decisions that make or break a cache

  • Key design. Use a clear, structured name such as app:v1:tenant:<id>:thing. In a multi-tenant app (one app that serves many customers), the tenant ID must be in the key. Otherwise one customer can receive another customer's data. That is a serious data leak, not just a bug. The v1 lets you throw away all old entries at once. Change it to v2 after the data format changes.
  • TTL. The TTL is the safety net. Even if you forget to remove a key somewhere, old data lives only this long. For data that changes often, use 10 to 60 seconds. For data that changes slowly, use minutes to hours. Always set one.
  • Invalidation. Invalidation means removing or updating a cached copy when the real data changes. It is known to be hard. Programmers joke that there are only two hard things in computer science: cache invalidation and naming things. Delete or update the key in the same code that changes the data. Let the TTL clean up the cases you missed.
  • What to cache. Cache data that is slow to produce, read often, changes rarely and is the same for many users. Examples are dashboards, product lists, settings and pieces of rendered pages. Do not cache data that is different for every request or very personal.
Cache stampede — the busy-key problem

A popular key expires. In the next millisecond, 500 requests all miss the cache. All of them run the same slow query, and the database fails, exactly when the cache was supposed to protect it. This is a stampede. Two cheap defences help. First, add jitter, a small random extra time, to the TTLs (for example ttl + random(0..10%)), so keys do not all expire together. Second, let only one request rebuild the value. Use a short lock with SET key NX EX 10. The other requests wait, or they use the slightly old copy.

Pattern 2 — sessions

A session is how the app remembers that a user is logged in across requests. There are two designs, and each has trade-offs:

Signed cookie / JWTServer-side session in Redis
Where the data livesIn the cookie itselfIn Redis; the cookie holds only a random session ID
Extra infrastructureNoneRedis
Log out / revoke instantlyHard. It stays valid until it expires, unless you keep a block list (which you would store in Redis)Delete the key: gone
“Log out everywhere”, ban a userHardEasy
Size limitAbout 4 KB (the cookie limit)Practically none
Cost per requestNone (the server only checks the signature)One Redis read (about 1 ms)

A signed cookie or JWT (JSON Web Token) is a cookie that holds the login data itself, with a signature so nobody can change it. For a SaaS with admin panels and paying customers, you want to cancel a session at once. That is usually worth one extra millisecond.

The whole Redis-session design is three small functions:

  • Create: make 32 random bytes as the session ID, so nobody can guess it. Store userId and tenantId under sess:<id> with a 7-day expiry. Set a cookie that holds only that ID. Give the cookie three flags. httpOnly means JavaScript in the page cannot read it. secure means it is sent only over HTTPS. sameSite: lax reduces CSRF risk. CSRF (cross-site request forgery) is an attack where another website makes your browser send a request that you did not intend.
  • Read: look up the key named in the cookie. If it exists, move its expiry forward by another 7 days. This keeps active users logged in.
  • Destroy: delete the key. The user is logged out on every server at once. Also keep a set of each user's session IDs. Then “log out everywhere” is just one loop over that set.
Sessions are the exception to “fail open”

If Redis is down and you cannot read sessions, treat everyone as logged out, never as logged in. A cache failure should “fail open”, which means the app keeps working without the cache. A login failure should fail closed, which means the app denies access. This is also why, in production, the session store needs a replica (a live copy) and automatic failover (switching to the copy when the main one fails). See below.

Pattern 3 — rate limiting

Rate limiting means capping how many requests one client can make in a period of time. In Lesson 10, Nginx limited each server. With five servers, a client gets five times the allowance. A counter in Redis is shared by all servers. You can also base it on the user or the API key, not only the IP address:

lib/rate-limit.ts — fixed window
import { redis } from "./cache";

/** Allow `limit` calls per `windowSeconds` for this identity. Returns true if this call is allowed. */
export async function allow(identity: string, limit: number, windowSeconds: number) {
  const window = Math.floor(Date.now() / 1000 / windowSeconds);
  const key = `rl:${identity}:${window}`;

  // MULTI runs INCR and EXPIRE together as one unit, so a key is never left without a TTL if the process dies in between
  const [[, count]] = (await redis.multi().incr(key).expire(key, windowSeconds * 2).exec()) as [
    [null, number],
    [null, number],
  ];
  return count <= limit;
}

// in a route handler:
//   if (!(await allow(`login:${ip}`, 5, 60))) return new Response("Too many attempts", { status: 429 });

The fixed window is simple, but a client can send a burst at the edge between two windows (5 requests at 11:59:59 and 5 more at 12:00:00). If that matters, use a sliding window, which is a sorted set of timestamps. Or use a token-bucket library such as rate-limiter-flexible. Also decide what happens when Redis is down. For a login limiter, many teams fail closed (block the request). For a generous API limiter, they fail open (allow the request).

Patterns 4–6 — locks, queues, pub/sub

4. A lock so only one server does a job

A lock is a marker that says “I am doing this job, so nobody else should”.

run a job on exactly one server
// SET key value NX PX 30000  means "set only if the key does not exist, and expire in 30 s". Atomic. Exactly one caller gets OK.
const got = await redis.set("lock:nightly-report", serverId, "PX", 30_000, "NX");
if (got === "OK") {
  try { await runNightlyReport(); }
  finally { await redis.del("lock:nightly-report"); }   // (release it only if you still own it. In real code use a small Lua script, a short program that Redis runs as one atomic step, to check this)
}

This fixes the problem from Lesson 12, where a cron job (a task that runs on a schedule) on every server runs N times. If a second run would cause real harm, as with payments, do not rely on a lock alone. Also make the operation idempotent. Idempotent means that running it twice has the same result as running it once. Locks across many servers are a big topic. Treat this lock as a convenience, not a guarantee.

5. Background job queues

Some jobs should not make a user wait. Examples are sending email, making PDFs and resizing images. The web request puts a job into a queue in Redis and returns at once. A queue is a line of jobs that are handled in order. A separate worker process takes jobs from the queue and runs them. It can retry a failed job or delay a job. In Node the standard tool is BullMQ, which is built on Redis lists and sorted sets. A worker is just another container from your image (Lesson 9) that runs a different command. You can scale workers separately from the web servers.

Queues need durability

A queue that silently loses jobs when Redis evicts keys is a bug. Give queues their own Redis instance with maxmemory-policy noeviction (next section). Also consider Amazon SQS instead. SQS is a fully managed queue service that stores jobs safely and costs only cents. It cannot run out of memory.

6. Pub/sub across servers

Pub/sub means publish and subscribe. One part sends messages to a named channel, and every part that has subscribed to the channel receives them. Here is an example. A WebSocket is a connection that stays open so the server and browser can send messages at any time. With WebSockets behind a load balancer, user A may be connected to server 1 and user B to server 2. When A sends a chat message, server 1 PUBLISHes it on a Redis channel. Every server has used SUBSCRIBE, so each one receives the message and sends it to its own connected users. The Redis adapter of Socket.IO (a real-time messaging library) does exactly this.

Memory, eviction and durability

RAM is limited. When Redis reaches its maxmemory setting, the eviction policy decides what happens. Eviction means removing keys to make room:

PolicyBehaviourRight for
allkeys-lruDelete the least recently used key (LRU), whichever key it isA pure cache
volatile-lruDelete only keys that have a TTL (the ElastiCache default)Mixed use, if every cache key has a TTL
noevictionRefuse new writes with an error when memory is fullQueues and anything you cannot afford to lose
The mistake: one Redis for everything

Suppose you put the cache, sessions and job queues in one instance with allkeys-lru. A jump in memory use will quietly delete user sessions (everyone is logged out) and queued jobs. You have two choices. Use separate instances, which are cheap. Or keep one instance, give every cache key a TTL and use volatile-lru, so only keys that expire can be evicted. Sessions have TTLs too, so protect them with spare memory and monitoring.

Durability. Durability means data survives a failure. ElastiCache Multi-AZ with a replica survives the failure of a node. The switch to the replica (the failover) usually takes under a minute. But a replica is asynchronous. This means it copies data a moment after the main node, so the last writes can be lost. That is acceptable for caches, sessions (users just log in again) and rate limits. It is not acceptable for data that exists only in Redis. That is why we said you should not have any such data.

Run it on AWS: ElastiCache

ElastiCache is Redis or Valkey run by AWS. It is a “managed” service, like RDS. AWS does the patching, replaces failed nodes, monitors it and can add replicas. Like the database, it lives in the private subnets. Only the app's Security Group can reach it.

  1. The firewall and the subnet group

    A Security Group is a virtual firewall for an AWS resource. It lists which network traffic may come in. Create a security group named cache-sg that accepts port 6379 only from web-sg. Then go to ElastiCache, then Subnet groups, and create one named myapp-private with the two private subnets. This is the same idea as the RDS subnet group in Lesson 8.

  2. Create the cluster — with TLS and a password

    Go to ElastiCache and choose Create. Pick Valkey (the open-source copy of Redis that AWS recommends; same commands). Choose a node-based cluster with cluster mode off. Use the myapp-private subnet group and cache-sg. These settings matter most:

    • Replicas: 0 (a single node) is fine for practice and for a small cache. For sessions in production, use 1 replica in another Availability Zone (a separate data centre in the same AWS Region), with Multi-AZ (the replica lives in a different zone) and automatic failover turned on.
    • Encryption in transit: on. This means the traffic between your app and Redis is encrypted with TLS, so the URL starts with rediss:// (two s). Turn it on when you create the cluster. On recent engine versions you can turn it on later, but it takes extra steps and a change on every client, so it is easier to choose it now.
    • Access control: AUTH token. This is a long random password. It must have 16 to 128 printable characters. Besides letters and digits it may use only ! & # $ ^ < > -, so avoid @, /, % and quotes. It adds protection on top of the Security Group. Turn encryption at rest on too. (For teams, ElastiCache also supports users and ACLs. An ACL is an access control list with permissions for each command.)
    • Node type cache.t4g.micro: about 0.5 GB of memory. That is more than enough to start. There is also ElastiCache Serverless. It scales by itself and bills for what you use. For Valkey its minimum is about $6 a month, so it can be cheap for tiny or uneven workloads. A steady, busy cache is often cheaper on a fixed node. Compare the prices for your case.
  3. Prove it from the server (not your laptop, because it is private)

    On the EC2 server
    • redis-cli -h <REDIS_HOST> --tls -a "$REDIS_TOKEN" ping

      You should see PONG (install redis-tools first). If it hangs, check the security group. If the connection resets, you forgot --tls. If you see NOAUTH or WRONGPASS, check the token

Connect the app safely

Add the URL to the SSM parameter that your servers read (Lesson 12). Then roll out the new setting:

/myapp/env parameter — add one line
REDIS_URL=rediss://:THE-TOKEN@myapp-cache.abc123.aps1.cache.amazonaws.com:6379
  • One client for each process, reused (as in lib/cache.ts). Opening a new connection for every request wastes time and uses up the server's connection limit.
  • Short timeouts. A stuck cache must not make your whole site stuck. Use a command timeout of 200 to 500 ms, a short connect timeout and few retries.
  • Decide fail open or fail closed for each use. Cache: fail open. Sessions and login: fail closed. Rate limit: your choice, but choose on purpose.
  • Never expose it. Give it no public IP and no 0.0.0.0/0 rule. Redis is not built to face the open internet, and exposed instances are often attacked within minutes. To reach it from your laptop, use an SSH tunnel, as you did for the database. An SSH tunnel sends your traffic through an SSH connection to the server.
  • Do not run FLUSHALL or KEYS * on your live system. The first deletes every key, including everyone's sessions. The second blocks the server. Use an ACL user for the app that is not allowed to run them.

What to monitor

CloudWatch metricHealthyWorry when
CacheHitRateAbove about 80 to 90% for a cacheIt falls. Causes: TTLs too short, a key design mistake or too little memory
DatabaseMemoryUsagePercentageBelow about 75%It nears 100%. Evictions or write errors are coming
EvictionsNear zeroIt rises. The cache is too small, or it is being used as a database
EngineCPUUtilizationBelow about 70%It is high. Look for slow commands or a key with too much traffic. Redis uses one core for commands
CurrConnectionsSteadyIt keeps growing. This is a connection leak (a new client for each request)

The hit rate is the share of requests answered from the cache. It tells you whether the cache is worth its cost. A cache with a 20% hit rate costs money and does almost nothing.

When not to use Redis

Every extra part is something you must secure, monitor, pay for and fix at 3 a.m. Do not add Redis only because the diagram looks incomplete:

You think you need Redis for…Try first
Speeding up a slow pageFind the slow query. Add an index (a lookup structure that makes searches fast) or fix an N+1 (one query that runs again for every row). A 2-second query is a database problem, not a reason to cache
Sessions on a single serverSigned cookie sessions need no extra service
A job queue with a few jobs a dayA Postgres table that workers read with FOR UPDATE SKIP LOCKED (it skips rows that another worker has locked), or SQS
Storing important data quicklyPostgres. Data that matters should not live only in RAM
Large files or blobsS3 and CloudFront (Lesson 11)
Measure, then cache

The right order is this. First measure, using the slow-query log or Performance Insights from Lesson 8. Then fix the query. Then cache what is still slow and popular. If you cache a bad query, you hide the problem until the cache is empty (cold).

What this costs

ItemApproximate cost per month (Mumbai)
cache.t4g.micro (Valkey), single nodeabout $10 to $12 (about ₹900)
cache.t4g.small (about 1.4 GB)about $20 to $24
Adding a replica for failoverplus the node price again
Backups (snapshots) within the free allowanceFree up to the cluster size
Data transfer within the same AZFree (traffic between Availability Zones has a small charge)
Running Redis in Docker on the app server instead$0 extra, but there is no failover, and it is lost if the instance dies

A single micro node costs roughly a fifth of the load-balancer layer, and it can greatly reduce the load on your database. Check the current prices. Valkey costs less than Redis OSS.

Troubleshooting table

SymptomLikely causeFix
Connection hangs, then times outcache-sg does not allow port 6379 from web-sg, or the client is in the wrong subnet or VPCUse the Lesson 6 checklist. Test with nc -vz <endpoint> 6379 from the server
ECONNRESET or “Connection closed” immediatelyThe cluster requires TLS but the client connects without TLS (or the other way round)Use tls: {} in ioredis, or rediss:// in the URL
NOAUTH Authentication required / WRONGPASSThe AUTH token or user is missing or wrongPass the token as the password. Check for extra spaces or characters changed by the shell
OOM command not allowed when used memory > 'maxmemory'Memory is full and the eviction policy is noevictionAdd memory, set TTLs on keys, or choose an eviction policy. Also stop mixing cache data with critical data
Cache hit rate is lowThe TTL is too short, the keys contain something unique for each request, or the data you use often does not fit in memoryCheck the key design. Raise the TTL. Look at the Evictions metric
Users see stale dataThe key is not removed when data changes, or the TTL is too longDelete or update the key when the data changes. Use a shorter TTL for data that changes often
Latency spikesSlow commands (KEYS, big lists), huge values, or the single main thread is fully busyUse SCAN, keep values small, watch EngineCPUUtilization, and split keys that get too much traffic
Site goes down when Redis doesThe app treats Redis as required for every requestFail open for caches (fall back to the database) and set short command timeouts

Interview corner

Explain the cache-aside pattern and its main risks.

The app checks the cache first. On a miss, it loads the data from the source of truth (the main database), writes the result to the cache with a TTL, and returns it. There are three main risks. One is stale data, which I handle with invalidation and TTLs. Another is a stampede when a popular key expires, which I handle with jitter and a lock so only one request rebuilds it. The third is wrong or leaked data because of bad key design, for example a key without the tenant or user in it.

Why is Redis fast, and what are its limits?

The data is in RAM, the data structures are simple and well tuned, and the main thread runs one command at a time, so there is no cost for locking. The limits are these. The data must fit in memory. One main thread for each shard (one part of the data) limits how much heavy or slow work it can do. And saving to disk is best effort, so it should not be the main store.

Where should session state live in a horizontally scaled app?

Not in the memory of one server. There are two good choices. One is to put it in the client as a signed or encrypted token. That needs no server state, but it is hard to cancel. The other is to keep it on the server side in a shared store such as Redis, with a TTL and a random session ID in an httpOnly, secure cookie. That can be cancelled at once. Sticky sessions (always sending a user to the same server) are only a temporary fix.

How do you implement a distributed rate limiter?

I use a shared atomic counter in Redis for each user and each time window. I run INCR and EXPIRE together inside MULTI or a Lua script. Or I use a sliding window made with a sorted set. When the limit is passed, I return HTTP 429 (Too Many Requests). For a Redis outage, I choose fail open or fail closed, depending on how risky the endpoint is.

What happens when Redis runs out of memory?

It follows the maxmemory-policy setting. It either evicts keys (allkeys-lru or volatile-lru) or rejects writes with an OOM (out of memory) error (noeviction). I choose the policy for each workload. Caches may evict, and queues must not. I do not mix them. I also monitor memory, evictions and the hit rate.

Why is KEYS * dangerous in production?

It scans every key in one blocking command. Its cost grows with the number of keys (O(N)), and it runs on the single main thread, so all clients freeze. SCAN is better. It goes through the keys a few at a time, using a cursor (a bookmark for where it stopped).

Cache or database index — which do you reach for first?

I diagnose first. If a query is slow because of a missing index or an N+1 problem, I fix that. A cache would only hide the problem and add the work of invalidation. I add a cache after the query is as fast as it can be and is still expensive or very frequent.


Practice task before Lesson 17

  1. Run Redis in Docker and work through the redis-cli session above. Watch the TTL count down until a key disappears.
  2. Use cached() around one real slow query. Log the hits and misses. Measure the response time with an empty cache (cold) and a full cache (warm).
  3. Add the invalidation call to the code that updates that data. Check that users see the change at once.
  4. Move sessions to Redis. Log in. Then delete the key in redis-cli. Check that the user is logged out on the next request.
  5. Add the rate limiter to your login route. Try six wrong passwords in a minute. Look for the 429 response.
  6. Create the ElastiCache cluster. Reach it from EC2 with redis-cli --tls. Then change the app's REDIS_URL and roll out.
  7. Stop Redis (or block the port) and load the site. It must still work, only more slowly. If it crashes, fix the fail-open handling.
Optional stretch

Add a debug header such as x-cache: hit or x-cache: miss on cached routes. Watch CacheHitRate in CloudWatch while you click around, and get it above 80%. Then add the lock so that one expiring popular key cannot cause a stampede.

Conclusion

Redis is the shared, very fast whiteboard for a group of disposable servers. It holds cached answers, sessions, counters, locks and short queues. It should never hold the only copy of anything you would miss.

  • Cache-aside with structured keys that include the tenant, a TTL on everything, invalidation when data is written, and fail-open behaviour.
  • Sessions in Redis can be cancelled at once. They must fail closed, and in production they need a replica.
  • Shared counters make rate limits work across all servers.
  • Match the eviction policy to the job. Caches evict, queues do not. Do not mix them.
  • Keep it private, with TLS and AUTH. Add it only when measurements show that you need it.

The system now has all its features. But how do you know it is healthy right now? How will you find out at 3 a.m. that it is not? That is monitoring.


End of Lesson 16. Next: Lesson 17 — Monitoring and Logging with CloudWatch.

Published by Jainik · Last updated · How these lessons are made

Keep learning

Related series