Redis 8, data structures and the licence question
Redis keeps data in working memory and serves it over the network, so a read takes a fraction of a millisecond rather than several. The current release from late July 2026 carries version 8.10.
The biggest misconception about this tool is reducing it to a cache. Caching is the most common use, while the real value lies in data structures doing things that take several queries and a lock in an ordinary database.
The licence, the thing to settle first
The matter is tangled and worth untangling, since it comes up in every conversation about this tool.
For years the project shipped under the BSD licence. In March 2024 the owner changed it to a variant that the body assessing open licences does not recognise as open. A fork named Valkey appeared then, taken from the last release under the old licence, run under a foundation and backed by several large cloud providers.
Since version 8.0 in May 2025 the project offers a choice among three licences, AGPLv3 among them, which is recognised as open. Formally the return happened, though under a different licence from the original.
What that means in practice. For the overwhelming majority of users nothing, since AGPL imposes obligations on whoever offers a modified version as a service rather than on whoever connects to the database over a network from their application. It starts mattering in two situations: when you modify the server code and distribute it, and when your company's legal department holds a rule rejecting that licence family without examining the details.
Both projects are alive and release at a similar pace, so the choice between them is real rather than academic. Valkey remains on the old BSD licence and is the default option at some cloud providers, which is sometimes the deciding argument when you use their managed service anyway.
The structures that make the difference
Worth going beyond storing and reading strings, since that is the least interesting part.
A sorted set holds elements with an assigned value and lets you query ranges. That is a ready leaderboard: recording a player's score and fetching the top ten are two operations, with no sorting and no lock.
A list works as a queue from both ends. Appending a job at one end and taking from the other, with the option of waiting for an element to appear, gives a job queue without standing up a separate system.
An approximate cardinality set answers the question of how many unique elements exist, occupying a fixed, small amount of memory regardless of scale. Counting unique visitors on a site with millions of visits fits in a dozen or so kilobytes at the price of a few percent accuracy.
A stream is an event log with consumer groups and acknowledgement. That is a sensible route for event processing where a full queueing system would be overkill.
A newer addition in the eighth version is the vector set. It lets you hold embeddings and search by similarity without standing up a separate vector database, which for small collections is often sufficient and operationally far simpler.
Caching and where it breaks
Since caching is the most common use, it deserves describing where it usually fails.
def get_product(id: str) -> dict:
key = f"product:{id}"
data = r.get(key)
if data:
return json.loads(data)
product = db.get_product(id)
r.set(key, json.dumps(product), ex=300)
return productThat code is correct and holds three problems that surface only under load.
The first is many keys expiring at once. If a thousand products entered the cache in the same second after a deployment, they all expire in the same second five minutes later, and the database receives a thousand queries at once. The answer is adding random variation to the lifetime.
The second is a race on one hot key. A key fetched by five hundred parallel requests expiring produces five hundred database queries, since every request sees an empty cache. The answer is a lock letting one request refresh the value while the rest wait or receive the previous one.
The third is invalidation. Changing a product in the database changes nothing in the cache, so users see the old price for five minutes. Deleting the key on write is the right answer and requires every place that changes data to remember it, which in a larger application is harder than it looks.
Remember the general principle too: a cache should speed things up rather than be the source of truth. An application that stops working after this server restarts is not using a cache but storing data in it.
Durability and what happens on failure
The data sits in memory, so the question of what survives a power loss is fundamental.
Two mechanisms exist. The first writes a state snapshot periodically; the second appends every operation to a log. A snapshot is cheap and loses everything since the last write. A log loses less and costs more on writes and at startup.
The key thing to understand: even the variant appending every operation synchronises to disk once a second by default, so a failure loses up to a second of writes. Setting synchronisation on every operation removes that window and heavily reduces throughput.
The practical conclusion is simple and often skipped. This is not a database for data whose loss is unacceptable. Sessions, caches, counters, job queues that can be retried, yes. Account balances and orders, no.
Replication solves a different problem from durability: availability and spreading reads. Worth remembering, since replication is asynchronous, so a write acknowledgement does not mean a replica already holds it.
Vector sets and when they suffice
The new data type from the eighth version deserves separate treatment, since it solves a problem usually handled by standing up a separate database.
The point is storing embeddings, vectors of numbers representing the meaning of text, and finding those most similar to a query. That is the foundation of applications answering questions from documents.
The advantage is operational rather than qualitative. If you already run this server for caching and sessions, adding similarity search requires no further service to maintain, no further backup set, and no further place where something can fail.
The boundary is equally clear. The data sits in memory, so the embedding collection has to fit there alongside everything else, and embeddings are large: a hundred thousand text fragments at a typical dimension is several hundred megabytes. Across millions of documents the right choice is a database built for it, holding its index on disk.
The practical rule reads: up to a few tens of thousands of fragments this is a good answer and saves a whole infrastructure layer. Above that, compute the memory before discovering that search evicts the cache that previously worked.
Remember durability too. An embedding collection built at the cost of many model calls and stored only here has to be rebuilt after a failure, and that costs again. Keeping the source fragments in a relational database and treating search as a reproducible layer is cheaper than guaranteeing durability in this place.
Wiring it into an application
A few connection related things save trouble on a first deployment.
A connection pool is mandatory. Opening a connection per request wastes time on handshakes and, under heavier traffic, exhausts the descriptor limit. Client libraries ship a pool and using it beats creating a client inside a request handler.
Set timeouts explicitly. A server that stopped responding hangs a request when no timeout exists, and with a cache that is particularly bad, since a layer meant to speed things up halts the application. The sensible route is a short timeout and a fall through to the data source when the cache stays silent.
Settle the behaviour on unavailability deliberately. An application that reaches the database when the cache is missing survives an outage slowly and running. An application that returns an error falls over entirely because of a layer that was meant to be optional.
For agent memory and session data it pays to consider where the boundary sits between this server and a durable layer, the one described in memory in LangGraph for instance. The current conversation's state fits here; what should survive weeks belongs in a database.
Redis against the alternatives
| Option | Model | Durability | Pick it when |
|---|---|---|---|
| Redis | In memory structures | Configurable, with a loss window | Cache, sessions, queues, counters |
| Valkey | The same, BSD licence | Identical | The licence matters, or your cloud offers it |
| Upstash | Compatible interface, per request billing | On the vendor's side | Uneven traffic, serverless environments |
| A relational database | Data on disk | Full | Data whose loss is unacceptable |
The first two rows are compatible at the protocol level, so moving between them usually comes down to changing an address. The difference lies in the licence and in what your cloud provider offers rather than in capability.
The third row deserves consideration with uneven traffic. A classic deployment pays for held memory regardless of use, while per request billing suits an application idle most of the day. At steady high traffic the economics reverse.
The last row is not an alternative but a reminder. Reaching for this tool for data requiring durability guarantees is a common architectural mistake, arising from it being fast and convenient.
Uses beyond caching
Three cases where this tool clearly beats the alternatives.
Rate limiting requests. A counter with a lifetime, or a sliding window built on a sorted set, gives a mechanism working correctly across many application instances, which a counter in process memory does not.
User sessions. A shared place visible to every instance, with automatic expiry, and no need to pin a user to a particular server.
Distributed locks. Setting a key only when it does not exist, together with a lifetime, gives a mechanism ensuring a scheduled job runs once rather than on every instance.
Know the boundary on that last one. A correct lock requires a random owner identifier and release only by whoever acquired it, otherwise a slow job releases a lock another process already holds. That is a place to use an existing library rather than write your own.
Daily practice
A few things save trouble over longer maintenance.
Set a memory limit and an eviction policy. Without them the server grows until the machine runs out of memory, and then behaviour depends on the system and tends to be unpleasant. A policy evicting the least recently used keys suits a cache; rejecting writes suits data that must not disappear.
Give a lifetime to everything that is a cache. A key without one stays forever, and after a key schema change the old ones remain as rubbish nobody will clear.
Do not use the command listing all keys in production. It blocks the server while it walks the whole set, and across millions of keys that is noticeable to everyone. Iterating in batches is what the scanning variant exists for.
Batch operations across many calls. A hundred separate queries mean a hundred network round trips; the same sent as one batch takes one. On reads inside a loop the difference is tens of times.
The last item is scripts executed server side. They let several operations run indivisibly, which on counters and locks removes races that cannot be removed on the client.
Common mistakes
The first is no random variation in key lifetimes. A mass expiry within one second moves the entire load onto the database.
The second is treating this as the source of truth. An application that stops working after this server restarts is not caching but storing data in a place with no guarantees.
The third is no memory limit and no eviction policy. The server grows until the machine's memory runs out, and the symptoms appear first in other services.
The fourth is listing all keys with the blocking command. On a large set it halts the server for every client.
The fifth is a distributed lock with no owner identifier. A slow job then releases a lock already belonging to another process.
The sixth is assuming a replica holds data immediately after a write acknowledgement. Replication is asynchronous, so a read from a replica can return a slightly stale state.
FAQ
Is Redis open source?
Since version 8.0 in May 2025 a choice of licences is available, AGPLv3 among them, which is recognised as open. Earlier, from March 2024, a variant not recognised as open applied, and that is what led to the Valkey fork.
How does Valkey differ?
In licence and governance. Valkey came out of the last release under the old BSD licence, runs under a foundation, and is backed by several large cloud providers. Both projects remain compatible at the protocol level, so moving usually comes down to changing an address.
Will data survive a restart?
That depends on the durability configuration and always carries a loss window. The default variant appending operations synchronises to disk once a second, so a failure loses up to a second of writes. For data whose loss is unacceptable, a relational database is the right choice.
Does this work as a job queue?
For simple cases yes, since a list with a blocking read gives a queue with no extra infrastructure. Where guaranteed delivery, retries, and job visibility are required, a system built for that deserves consideration, since here some of those have to be built yourself.
When should I choose a managed option?
With uneven traffic and in serverless environments, where per request billing suits better than paying for held memory. Upstash is the typical pick there, while at steady high traffic a classic deployment comes out cheaper.
Documentation sits on the project site, and releases in the GitHub repository.