Redis: cache and shared state
What Redis is, why you cache in memory, how to store sessions, and why TTL and invalidation matter.
Why another storage system
Postgres stores data reliably, but reaching it costs time: a query, disk, network. If the same data gets read a thousand times a second, hitting the database every time is wasteful. Redis is an in-memory key-value store. Reading from memory is orders of magnitude faster than from the database, which is why Redis sits in front of Postgres as a cache.
A second use case, from the 12-factor lesson: stateless applications move shared state (sessions) into external storage, and Redis is a good fit for that.
Redis keeps data in memory. It can save snapshots to disk (RDB) and a command log (AOF), but when it's used as a cache, those are often turned off. So the rule is simple: the source of truth stays in Postgres, and Redis holds whatever can be lost and rebuilt.
Basic operations
Redis works by keys. Put, read, delete:
SET user:42:name "Ivan"
GET user:42:name -- "Ivan"
DEL user:42:name
EXISTS user:42:name -- 0 or 1
A colon in a key name means nothing to Redis. It's a convention so
keys read like a path: user:42:name, cache:product:15.
Commands can be typed in redis-cli, the command-line client. In
interactive mode it prints the type of the reply: "Ivan" for a
string, (integer) 1 for a number, (nil) for an empty reply.
Besides strings there are structures: lists, sets, hashes, counters. A counter, for instance, increments atomically with one command, which is handy for limits and metrics:
INCR page:views -- atomically +1, returns the new value
If the key doesn't exist, INCR treats it as zero and returns 1. If
the key holds something that isn't a number, you'll get the error
ERR value is not an integer or out of range.
A hash stores a set of fields under one key. That's a convenient shape for a session: user, role, login time.
HSET session:demo user_id 7 role admin -- create or add fields
HGETALL session:demo -- all fields and values
TYPE session:demo -- hash
TTL: data with an expiration
A key can be given a time to live (TTL). When it expires, Redis deletes the key on its own. That's the basis of a cache: you put a query result in for five minutes, and after that it refreshes itself.
SET weather:moscow "{...}" EX 300 -- lives for 300 seconds
TTL weather:moscow -- how much is left
For a key that already exists, EXPIRE key seconds sets the TTL.
That's how it's done with a hash: HSET has no parameter of its own
for a time to live, so EXPIRE follows it.
TTL doesn't only reply with seconds. -1 means the key exists but
lives forever. -2 means the key doesn't exist: it expired, or it was
never created.
Without a TTL, a cache grows and fills up memory. That's why cache keys almost always get a TTL.
When memory runs out
How much memory Redis can use is set by the maxmemory parameter. By
default it's 0, meaning no limit, and Redis grows until it hits the
machine's memory or the container's limit. In production, the limit
is set explicitly.
What to do when the limit is hit is decided by maxmemory-policy. By
default it's noeviction: Redis doesn't evict anything, reads keep
working, and writes are rejected with an error:
OOM command not allowed when used memory > 'maxmemory'.
Other policies evict keys on their own. allkeys-lru evicts the
least recently used keys from all keys, volatile-lru only from
keys that have a TTL. If the same Redis holds sessions without a TTL,
an allkeys-* policy can evict those too and log users out.
You can see the current values like this:
CONFIG GET maxmemory
INFO memory -- the used_memory line: how much is used right now
Cache and invalidation
A typical cache pattern is called cache-aside:
- the application asks Redis.
- the value exists (cache hit) → return it.
- it doesn't (cache miss) → go to Postgres, put the result in Redis with a TTL, return it.
The hardest part of caching is invalidation: the data changed in
the database, but the cache still holds the old value. There are two
approaches. Rely on the TTL (the old value goes stale on its own after
N seconds, fine for non-critical data), or delete the key on change
(DEL after UPDATE, when you need freshness). The choice depends on
how much freshness matters compared to simplicity.
Redis, Valkey and licenses
There's one more reason why the word Valkey shows up more and more often in job postings and cloud consoles.
In March 2024, the company Redis announced that starting with version 7.4, Redis ships under a pair of licenses, RSALv2 / SSPLv1. Both restrict anyone selling Redis as a service, and neither counts as open source (OSI). Cloud providers and some contributors responded by forking the last open version, 7.2.4. They named the fork Valkey and handed it to the Linux Foundation. AWS and Google Cloud launched their managed services on top of it, so a "Redis-compatible cache" in the cloud today often turns out to be Valkey.
In May 2025, Redis 8 added the open AGPLv3 license alongside those, but by then Valkey had already taken hold.
For you the difference is purely nominal right now: the same commands, the same protocol, the same client libraries. Everything from this lesson works in both. It's worth knowing just so you're not surprised by the name in a cloud console.
Common mistakes
- No TTL on the cache. Keys pile up until memory runs out. With
the
noevictionpolicy, every write then fails, including sessions. SETover a key with a TTL. A plainSETresets the time to live, and the key becomes permanent (TTLreturns-1). You need to either passEXagain or addKEEPTTLto keep the old TTL.KEYS *in production. The command walks every key in one call and blocks Redis for that whole time. The documentation advises using it with extreme caution. For walking keys there'sSCAN, and inredis-clithe flag--scan --pattern 'cache:*'.FLUSHALLas a fix. It clears the whole database, sessions and counters included. Delete only what's actually the cache, by key pattern.- Redis as the only place data lives. If a write exists only in Redis, a restart will lose it unless disk persistence is configured.
What to remember
Redis stores data in memory by keys and reads orders of magnitude faster than a database, which is why it's used as a cache in front of Postgres and as session storage for stateless applications.
Cache keys get a TTL, otherwise memory fills up. The cache-aside pattern: a miss goes to the database and puts the result in Redis with a TTL.
The hardest part is invalidating stale data: you rely either on the TTL or on deleting the key on change, depending on the freshness requirements.
Valkey is a fork of Redis under the Linux Foundation, created after the license change in 2024. Commands and protocol are compatible.
What you'll need for the tasks
- The client inside the lab environment's container:
docker exec -it <container> redis-cli. - Commands from the lesson:
SET ... EX,GET,DEL,INCR,HSET,HGETALL,EXPIRE,TTL,TYPE. - Memory:
INFO memory,CONFIG GET maxmemory,CONFIG GET maxmemory-policy. - Walking keys by pattern:
redis-cli --scan --pattern '<pattern>'.
The next lesson covers message queues: how to decouple services and smooth out spikes.
Done reading?
Mark the lesson as read to move your track progress forward.
Lesson marked as done.
Go through it with an AI agent
The agent explains anything unclear in plain words, gives you 2 labs for this lesson and drills you on interview questions. $49 one-time, lifetime access.
Tell the agent: use MCP izi, give me lesson data-basics-04-redis-basics
Progress saved in this browser
Clear your history or open this on your phone, and the marks will be gone. Leave your email and they'll move to your account. No password needed.
Done. moved to your account. You can see your progress on any device. My progress