Redis
an in-memory store for things that live briefly
What it is
Redis is a database server that keeps its whole dataset in memory as typed structures and lets many application processes read and change them over the network, one short command at a time. The project describes itself as a data structure server. The freely available edition is called Redis Open Source, and Redis Ltd also sells Redis Cloud and Redis Software under commercial licences.
Every key holds one value of one type. Strings are plain bytes and double as counters, hashes are records of fields and values, lists keep the order in which items arrived, sets hold unique members, and sorted sets rank their members by a score. The list of types runs on to streams, JSON documents, geospatial indexes, time series, vector sets and probabilistic structures such as Bloom filters and HyperLogLog.
Commands work on those types directly. INCR adds one to a counter and
returns the new value, SADD puts members into a set and SPOP takes one out,
and any key can carry a timeout after which Redis deletes it by itself. Redis
calls such a key volatile. When several steps have to happen as one, a Lua
script runs inside the server, and Redis guarantees that nothing else runs
while it does.
Memory is where the data lives, and disk is optional. Redis can write
point-in-time snapshots, known as RDB files, or log every write to an
append-only file (AOF) that it replays at startup, or do both, or neither.
Replicas copy a primary and can serve reads. A memory ceiling, maxmemory,
and an eviction policy decide what happens once the data outgrows the space
it was given.
The licence changed with Redis 8. From that release on, Redis Open Source is offered under a choice of three licences: RSALv2, SSPLv1 and AGPLv3. The first two are source-available licences, and AGPLv3 is the open source option.
Why it matters
A web application rarely runs as one process. Behind a load balancer sit several instances, each with its own memory, and anything kept in that memory — a count of login attempts, a session, a cached page — exists only in the process that happened to handle the request. A limit counted that way can be escaped by landing on another instance. Redis gives all the instances one shared place for that kind of state, fast enough to consult on every request.
The atomic commands matter as much as the speed. Two instances that each read
a counter, add one and write it back can both write the same number, and an
increment disappears. INCR does the read and the write inside the server as
one step, so a hundred concurrent requests leave the counter exactly a hundred
higher. A Lua script extends the same guarantee to a short sequence: a check
and a change that must not be split apart, such as confirming there is still
room and taking the place, run as one.
Expiry is the third piece. A rate-limit window, a session or a one-time code ought to disappear when its time is up, and with a timeout on the key the cleanup is the store’s job rather than a scheduled task’s.
The price is durability, and it is worth knowing in numbers before choosing. With the append-only file and its default policy of one fsync per second, a crash can cost about a second of writes. With snapshots alone, which usually run every few minutes, the documentation says you “should be prepared to lose the latest minutes of data”. Replication is asynchronous by default, so a primary can acknowledge a write and stop before any replica has received it. That makes Redis a good home for state that can be rebuilt, or lost without harm, and a poor home for the only copy of an order, a payment or a balance. The design question that matters most is what the application does when Redis is unreachable, or has forgotten something it was told.
What it looks like in practice
22 rate limiters keep their state in Redis on a public directory of companies and professionals, so that a limit holds across every instance instead of living in one process, where it would be trivially escaped. Each limiter is built for the path it protects, because a login attempt, a search and a bulk export are abusive at very different rates. The deciding choice is what happens when Redis is missing: the limiters fail closed, and traffic is refused rather than waved through. That choice has a visible cost. A Redis problem becomes a problem users can see, accepted on purpose, because the alternative is a Redis problem becoming a billing one.
Selling limited stock puts Redis in front of a database as a fast gate:
a Redis counter decremented by a Lua script
admits about as many buyers as there are units and turns everyone else away
in microseconds. The script makes the check and the decrement one step. The
gate promises less than it may seem to. Redis replicates
asynchronously, so after a failover the new primary may have forgotten some
admissions and let a few too many through. A conditional UPDATE in the
database still decides each sale, and a reconciler resets the gate from the
database’s number. When units are few and distinct, such as numbered seats,
their ids can sit in a Redis set, and SPOP hands them out one at a time with
no counter to fight over.
The most ordinary role is a plain cache. An Azure estate defined in Bicep builds each environment from the same templates, testing, staging and product alike, and each one has Redis for caching beside a PostgreSQL Flexible Server, the Container Apps that run the api and the website, and Front Door with a WAF policy. Because the cache is declared in the templates with everything else, changing it is a pull request like any other change to an environment.
Where it goes wrong
Most Redis trouble comes from treating a fast, forgetful store as a durable one, or from assuming it remembers what it was told about time.
A key that should expire stays forever. SET replaces the value and
discards any timeout the key had, so code that refreshes a cached value with a
plain SET turns a ten-minute entry into a permanent one:
> SET session:42 "v1" EX 600
OK
> SET session:42 "v2"
OK
> TTL session:42
(integer) -1
Pass the timeout on every write that needs one, or add KEEPTTL, available
since 6.0.0, to keep the old one. The same leak hides in a counter built from
INCR followed by a separate EXPIRE: when the second command never arrives,
the key never expires. Send both inside one MULTI and EXEC block, or in
one Lua script.
Writes start returning errors while reads still work. The data has reached
maxmemory, and the eviction policy is noeviction, or a volatile- policy
on a dataset where no key has a timeout, which behaves the same way. Choose
the policy for the job: an allkeys- policy for a pure cache, where any key
may go, and a volatile- one only when cached keys share an instance with
keys that must stay. For that mix, the documentation’s advice is to consider
two separate instances.
Writes that Redis acknowledged are missing after a restart or a failover. A crash landed inside the fsync window, or a replica was promoted before the write reached it. That is how Redis is designed, so the fix lies in what is kept there. Store anything that must survive in a database with durable commits, put Redis in front of it as a gate or a cache, and reconcile the two from the database’s side, as the stock note does.
Every client stalls at the same moment. A long Lua script is running, and Redis blocks all other server activity for the whole of a script’s runtime. Keep scripts short and bounded, and keep loops over large collections out of them.
A limiter waves traffic through during an outage. The client code treats an error from Redis as “limit not reached”, so the limiter opens exactly when its store is unreachable. Decide the failure mode on purpose and test it by stopping Redis: a limiter guarding a costly path should refuse, as the 22 limiters above do, with the cost that a Redis problem becomes visible to users.
Further reading
- Develop with Redis — the project’s starting point: commands to try, client libraries by language, and the data types.
- Redis persistence — snapshots, the append-only file, fsync policies and what each can lose.
- Redis replication — how replicas follow a primary, and why a window for data loss remains.
- Key eviction
—
maxmemory, every eviction policy, and advice on choosing one. - EXPIRE — key timeouts, and which commands clear them or leave them in place.
- Scripting with Lua — running a script atomically on the server, and what it blocks meanwhile.