I had a look for this and it turns out it's slightly mis-described there - it's not a window counter, it's a "GCRA (Generic Cell Rate Algorithm)" - a leaky bucket algorithm. Code here: https://github.com/redis/redis/blob/unstable/src/gcra.c
The code comments say it was heavily influenced by https://github.com/brandur/redis-cell by Brandur Leach.
It's a neat algorithm (I just learned about it today) - it only needs to store a single integer for each rate-limited key, which is the "Theoretical Arrival Time" when the bucket would next be empty.
This is awesome!
And arrays look great too. Lots to play with.
The website looks like openclaw's website.
One, it would be cool to be able to embed it, similar to sqlite, directly into applications.
Two, the HA story is so much more complicated than it should be. I totally acknowledge that concurrency and distributed computing is hard, but it should not require reading heaps of documentation and understanding two entirely separate multi-node approaches only to figure out there are lots of subtle strings attached that make it impractical for many applications.
https://redis.io/blog/diving-deep-into-rediss-new-array-data...
I've found myself wanting this on several occasions too. I.e. wanting all my rust backend processes (k8s pods) to have some minimal shared state, without having to spin up a Redis cluster. I've talked to Claude about it a couple of times, and it descends into something like, "you gotta use Raft or CRDTs, and pick 2 out of 3 from CAP". Which honestly seems pretty fair, and indicates to me that I'm dreaming for something magical.
Nonetheless, it is nice to hear someone else asking for this. If this is indeed feasible (even if simple/limited), then I'd be interested to try it.
But most of the cloud providers now offer Valkey because of the license changes. Of course, cloud providers not offering Redis was the intention of the license change from the Redis point of view. So mission accomplished for Redis.
But the flip side of course is that if you want to deploy on standard infrastructure rather than self hosting Redis, Valkey is now the easy, low risk path that probably should be the default for most companies that target AWS, Azure, GCP, etc. Same with Elasticsearch vs. Opensearch and a few other products where the community forked because of license changes.
Mentioning Elasticsearch because I know people in both communities and I'm deeply familiar with the stack. A few years on, Opensearch has taken a lot of the momentum from Elasticsearch.
I'm asking as a non-webdev who never quite got what Redis actually does, but would love to learn.
This is entirely different than what Redis is and tries to solve.
Sqlite is embedded. It's not a distributed SQL. Redis is a distributed data structure store and concurrency primitive. These are worlds apart.
> HA story is so much more complicated than it should be
It is precisely as complicated as it needs to be. You don't want data loss.
If you're in the business of high available fault tolerance, you read the manual and learn how to Redis.
https://aws.amazon.com/blogs/database/reduce-your-amazon-ela...
I feel like we're using about 1% of its features at this point - really just as a fast K/V store - so it would be easy to switch if needed, but I can't see a case where we would.
It would also be useful because of the ability to switch modalities. When running a multi node service, you can use Redis to share data between nodes and use Redis pubsub as a communication bus. If you wanted to support a simple single node configuration too, then it wouldn't need to be a special case, it could just go through the same mechanism but with an embedded Redis instance.
It's pretty similar to SQLite: being able to embed more or less a complete storage engine into your app can be very convenient and powerful.
Embedding would make local dev/CI integration testing convenient.
Embedding replicated Redis with each application instance would give you HA benefits while infra-management complexity.
Embedded redis (even via local RPC) is still going to be faster than a lot of languages or frameworks’ built-in data structures. Large array operations in, say, Python are gonna slower than RPCing to Redis (assuming that the data structures are built gradually and not built all at once); to beat Redis you’d have to use numpy or something—-which is definitely preferable, but is extra work if your app already uses Redis for other things.
Just like choosing SQLite over e.g. LMDB or RocksDB, embedded Redis would be a nice future proofing option for small apps during the prototype phase; less would have to be changed to move Redis out of the app than if a different cache or persistence service were chosen.
It’s the same use case with a different api.
A typical (meaningful) example might be communication between threads or actors in a single process, or idempotent tests.
As with SQLite, an external xxx that does this for you is certainly better, etc. but it’s convenient sometimes, to have an application that doesn’t go “now before you run this install Postgres…”.
It’s seldom useful for a web app where you control everything.
If you haven't come across Kvrocks yet, it may be worth a look: https://github.com/apache/kvrocks https://kvrocks.apache.org/ . It's a database with a Redis-compatible wire protocol, but the database is stored on disk. This means your working set is not limited by RAM and can be a few orders of magnitude larger! On modern SSDs this is still very fast. I think it improves the durability story as well. But the big win is the orders of magnitude larger database space.
As I've been improving my side project https://totalrealreturns.com/ recently I've ended up using both Redis and Kvrocks together. Redis is great for small global state that needs to be super fast. Kvrocks is great for larger bulk data storage (large precomputed datasets), but also supports a lot of the Redis data structures as well as Lua scripts.
So we’ve stayed with Valkey.
For example if you use it for session storage, you can't have your application read from a random instance that may or may not contain the session.
Will millions of users, high availability is critical for this functionality.
A high availability protocol should not leak into the client. It should be able to discover other nodes. It should not land in broken states so easily. It should not limit the number of writers. It should not error during failover.
Are these hard problems? Yes. Should we just accept that things are hard because that’s how the gods have given them to us? No.
It's similar in effect to what busybox does to shell utilities, though the motives are different.
- HyperLogLog, bloom filter, other probabilistic data structures
- Geospatial operations on stored points and polygons
- Expiring keys, for creating caches
These aren't in most standard libraries, and the Redis implementations tend to be fast, robust and well understood.
A key-value database, or key-value store, is a data storage paradigm designed for storing, retrieving, and managing associative arrays, a data structure more commonly known today as a dictionary.
it's not a relational database.
Now you could solve this specific case by sharding by prefix, or by querying all instances, but then you still do not have high availability: if the instance a specific session is on is down, these users cannot authenticate. At that point you’re better off with a single instance.
The app would look up in both databases. If it exists in any, there would be a session.
Thisnis strictly different from partitioning which I think you are mixing it up with.
Paritioning is for performance not HA
You, obviously, don't commit important data only to a session that you can loose, if the application does not allow it.
We use redis as infrastructure. To route events and as a cache.
For us redis could go down and we would merely see a degradation of our service with no data loss.
I recommend using redis like that. And then use a database that supports transactions for real data problems.
But we are different. And that's OK.
Just because it works for your use case right now doesn’t mean there isn’t room for improvements to support others too.
Oh good, then you don't need to do any of the stuff that you suggested to do
Redis 8.8 in Redis Open Source is now available, bringing performance improvements alongside a set of powerful new features. Highlights include array - a new general-purpose data structure, a window counter rate limiter, streams message NACKing, subkey notifications for hash fields, explicit control over JSON numeric array storage, multiple aggregators in a single time series query, and a new COUNT aggregator for sorted sets union and intersection.
Redis 8.8 introduces significant end-to-end throughput improvements:
| Data type | Operations | End-to-end throughput improvements |
|---|---|---|
| Strings | MGET (pipelined, with I/O-threads) | Up to 68% |
| MGET (pipelined, single thread) | Up to 50% | |
| MSET | Up to 8% | |
| Hash | HGETALL | Up to 25% (1K+ fields) |
| Streams | XREADGROUP | Up to 83% (COUNT 100) |
| Sorted set | ZADD, ZINCRBY, ZRANGEBYSCORE | Up to 74% |
| Bitmap | Bitmap operations | Up to 28% (x86) |
| HyperLogLog | PFCOUNT | Up to 18% (x86) |
| (several) | SCAN, HSCAN, SSCAN, ZSCAN | Up to 40% |
In addition, persistence and replication (full synchronization) is now up to 60% faster.
Redis has always been about choosing the right data structure for the job. In Redis 8.8, we introduce a new general-purpose data structure: array. An array is an index-addressable collection of string values. Each array element is stored at a numeric index, and can be accessed extremely fast. Arrays are dynamic, sparse-friendly, and compute-aware containers, enabling new use cases and better flexibility and efficiency for existing use cases (by @antirez).
Rate limiting is one of the most common Redis use cases. Traditionally, users implemented rate limiters using server-side Lua scripts combined with client logic. In Redis 8.8, we introduce a window counter rate limiter (by @raffertyyu, together with the Redis team).
Our investment in improving Redis Streams continues.
Building on this momentum, Redis 8.8 adds support for message NACKing, allowing consumers to explicitly release pending messages so they become immediately available and prioritized for consumption by other consumers.
In Redis 7.4 we introduced hash field expiration – a capability that saw strong adoption. A frequent follow-up request was for field-level notifications, similar to existing key-level notifications. Redis 8.8 delivers this with subkey notifications for hash fields, allowing clients to subscribe to events such as field expiration and deletion. These notifications include the key, the subkey (field name), and the event type.
Retrieving multiple time series aggregators is a common operation. For example, candlestick charts rely on MIN, MAX, FIRST, and LAST aggregations. Prior to Redis 8.8, this required multiple commands. Redis 8.8 now supports multiple aggregators in a single time series command, reducing round trips and simplifying client logic.
Redis 8.4 introduced support for homogeneous numeric arrays in JSON, delivering up to 92% memory reduction – especially valuable for AI workloads. In Redis 8.8, users can now explicitly control how numeric arrays are stored (BF16, FP16, FP32, or FP64), enabling better alignment with source data, vector indexing needs, and memory/precision tradeoffs.
Finally, Redis 8 extends sorted set union and intersection operations with a new COUNT aggregator. This allows the score of each element to reflect either the number of input sets it appears in or the weighted sum across those sets, unlocking new use cases in ranking, scoring, and analytics.
Redis has always been about choosing the right data structure for the job. Redis traditionally provides several core data structures, including lists, hashes, sets, and sorted sets. In Redis 8.8, we introduce a new general-purpose data structure: array.
What is an array?
An array is an index-addressable collection of string values. Each element is stored at a numeric index, and can be accessed extremely fast.
Arrays go far beyond basic indexed storage. They are flexible, memory-efficient, and compute-aware. Arrays have some capabilities that enable new use cases and better flexibility and efficiency for many existing use cases:
SUM, MIN, and MAX. When the values are binary flags, Boolean aggregators (AND, OR, XOR) are supported as well.In summary, an array is a dynamic, flexible, high-performance, index-addressable, compute-aware container that combines aspects of:
Random element access: Array vs list vs hash
Benchmarking arrays against the closest list and hash equivalents under random access at large element counts, the advantages of array show up clearly:
| Operation (100K elements; 1 KB values) | Array | List | Hash |
|---|---|---|---|
| Read random element | 675K ops/sec | 133K ops/sec | 626K ops/sec |
| Write random element | 757K ops/sec | 137K ops/sec | 689K ops/sec |
| Delete random element | 841K ops/sec | — | 730K ops/sec |
* Redis 8.8, single instance on an Intel Sapphire Rapids m7i.metal-24xl machine
For random-element operations, array provides 8-15% better throughput than Hashes and are at least 5 times faster than Lists.
Memory wise, lists are the most compact. Arrays require ~18% more memory per element, while hashes require 30-46% more memory than lists, depending on the size of the elements:
| Element size (100K elements) | Array | List | Hash |
|---|---|---|---|
| 100 bytes | 122 bytes/element | 104 bytes/element | 151 bytes/element |
| 1 Kbyte | 1290 bytes/element | 1035 bytes/element | 1337 bytes/element |
Ring buffer: Array vs list
A common pattern in Redis is using a list as a bounded ring buffer: clients push new entries with RPUSH and trim list back with LTRIM to keep a constant number of elements. Arrays expose ARRING, which performs the same operation in a single atomic command.
| Ring size; element size | Array (ARRING) | List (RPUSH+LTRIM) | Array’s advantage |
|---|---|---|---|
| 1K elements; 100 bytes | 1.11M inserts/sec | 512K inserts/sec | × 2.2 |
| 100K elements; 100 bytes | 1.12M inserts/sec | 528K inserts/sec | × 2.1 |
| 1K elements; 1 Kbyte | 840K inserts/sec | 424K inserts/sec | × 2.0 |
| 100K elements; 1 Kbyte | 837K inserts/sec | 413K inserts/sec | × 2.0 |
* Redis 8.8, single instance on an Intel Sapphire Rapids m7i.metal-24xl machine
ARRING delivers twice the throughput (inserts/sec) compared to the equivalent RPUSH+LTRIM idiom, independent of ring size. Memory footprint is the same as above: Arrays require ~18% more memory than lists.
When should arrays be used?
Arrays are extremely useful when:
What arrays are not suitable for?
Arrays are not a replacement for other data structures.Use lists if you need push/pop operations, or inserting elements between others.
Use hashes if you need field name-based access instead of numeric indices.
Where can I learn more?
Array documentation: https://redis.io/docs/staging/DOC-6334/develop/data-types/arrays/
Array commands: https://redis.io/docs/latest/commands/?group=array
Diving deep into Redis’s new array data type: https://redis.io/blog/diving-deep-into-rediss-new-array-data-type/
Window counter rate limiters, including fixed window, fixed window with lazy reset, and sliding window counter variants, use one or more fixed-duration time windows. Each window maintains a counter initialized to 0 when the window is created, along with a maximum capacity representing the number of tokens allowed during that window’s lifetime.
Before Redis 8.8, implementing a Window counter rate limiter required Lua scripting. In 8.8, we introduce a new command for working with window counters:
The idea is simple: each window has a duration (specified via EX or PX) and a token capacity (specified with UBOUND). The number of tokens requested can be specified with BYINT increment (default is 1). INCREX attempts to increment the counter by the requested number of tokens. The key is created if it does not already exist.
To make this command suitable for rate limiter use cases, beyond basic increment semantics, INCREX introduces three new capabilities compared to the existing INCR family of commands:
INCREX returns both the new counter value and the actual increment applied, allowing the caller to immediately determine whether the request should be allowed or rejected.ENX is specified, expiration is set only if the key does not already have one. This ensures that the window’s TTL is set only when a window is created and not modified on subsequent requests during its lifetime.SATURATE, the request may be “partially accepted” with the counter clamped to the specified bounds (“saturated”) .Beyond rate limiting, INCREX can be seen as a generalized form of INCR, INCRBY, INCRBYFLOAT, as well as DECR and DECRBY (via negative increments), with added support for bounds and expiration control.
In real-world applications, stream consumers don’t always successfully process the messages they consume. Failures can happen for many reasons:
Before Redis 8.8, consumers had no way to explicitly reject (NACK) a message. They could either acknowledge it or leave it pending. In practice, this meant other consumers in the consumer group had to recover these messages using XREADGROUP … CLAIM, XPENDING+XCLAIM or XAUTOCLAIM.
This approach introduces delays, since messages remain idle in the Pending Entries List (PEL) until another consumer claims them – an issue for time-sensitive systems.
Redis 8.8 introduces a new command to address this directly:
XNACK key group [SILENT|FAIL|FATAL] IDS numids id [id ...]
This command allows consumers to explicitly release messages back to the stream, making them immediately available for re-delivery.
XNACK supports three modes, each designed for a different real-world scenario:
SILENT - Used when the failure is unrelated to the message (e.g., shutdown or transient internal errors). The delivery counter is decremented by 1, effectively undoing the increment that occurred when the message was added to the PEL.FAIL - Used when the message cannot be processed by this consumer but may succeed elsewhere (e.g., requires more resources). The delivery counter remains unchanged (it was already incremented by 1 when added to the group's PEL).FATAL - used for malformed, poison, or potentially malicious messages. The delivery counter is set to LLONG_MAX, making it easy to detect and route to a dead-letter queue.These modes map naturally to production scenarios: graceful shutdowns or transient failures, resource-based failures, and poison message handling.
When a message is NACKed, it is:
The head of the PEL is reserved for all NACKed messages, ordered FIFO among themselves, followed by pending messages that were neither ACKed nor NACKed in their existing order. This guarantees that NACKed messages are always prioritized over idle pending messages.
The delivery order on XREADGROUP is updated accordingly:
CLAIM min-idle-time is specified:CLAIM is not specified:Redis key-level notifications let clients subscribe to key-related events in real time via pub/sub channels. There are two types of channels:
In Redis 7.4, we introduced hash field expiration. This feature saw strong adoption, and a common request followed: support for hash field-level notifications, since key-level notifications do not include field names.
Redis 8.8 introduces subkey-level notifications. Starting with Hashes, clients can now subscribe to events at the field level, such as field updates, deletions, and expirations.
Subkey notifications include the key, subkeys (for hashes, these are field names), and the event type.
Redis 8.8 adds four new channel types:
These mirror the flexibility of keyspace notifications while extending visibility down to the field level.
The following events are emitted for hash fields: hset, hdel, hexpire, hexpired, hpersist, hincrby, and hincrbyfloat.
The TS.RANGE, TS.REVRANGE, TS.MRANGE, and TS.MREVRANGE commands support an optional AGGREGATION parameter which allows grouping samples into time buckets and applying an aggregation function.
Users can choose from 15 supported aggregators (such as AVG, SUM, MIN, MAX, FIRST, and LAST), and the results are computed accordingly.
In many real-world scenarios, however, multiple aggregations are needed simultaneously. A common example is candlestick charts, which require MIN (low), MAX (high), FIRST (open), and LAST (close).
Before Redis 8.8, this required issuing multiple commands - one per aggregator - resulting in additional latency and client-side complexity.
Redis 8.8 introduces support for multiple aggregators in a single command, allowing all required aggregations to be computed in one request.
The command syntax remains unchanged. Users can now specify multiple aggregators as a comma-separated list:
TS.RANGE key from to AGGREGATION MIN,MAX,FIRST,LAST bucketDuration
Note that aggregators are comma-separated, with no spaces between them.
The JSON specification defines a generic “number” type, without enforcing a specific representation such as IEEE-754 FP16, FP32, or FP64 for non-integers. As a result, each implementation must choose how to represent numeric values internally.
Starting with Redis 8.4, JSON numeric arrays (such as vector embeddings) are stored using efficient binary representations, significantly reducing memory usage. Redis automatically selects the most appropriate numeric type, but for non-integers, and without additional hints, this usually defaults to FP64 to preserve precision.
For example, the decimal value 0.3 cannot be represented exactly in binary (similar to how 1/3 cannot be represented exactly in decimal). To avoid loss of precision, Redis typically uses FP64. In practice, this means that many floating-point arrays end up being stored as FP64, even when such high precision is not required.
In many real-world scenarios, the original data was already generated using lower-precision formats. Redis 8.8 addresses this by allowing users to explicitly control how floating-point arrays are stored. Users can now choose between BF16, FP16, FP32, and FP64, enabling better alignment with source data, vector indexing requirements, and memory/precision tradeoffs.
The JSON.SET command includes a new optional parameter:
JSON.SET key path value [NX | XX] **[FPHA BF16|FP16|FP32|FP64]**
Sorted sets support set operations via ZUNION, ZUNIONSTORE, ZINTER, and ZINTERSTORE. For all four commands, users can control how element scores are computed in the result using the SUM, MIN, or MAX aggregators, optionally applying weights to each input set.
In some use cases, however, the original scores are not relevant. Instead, users may want the resulting score to reflect how many input sets contain each element, or, when weights are provided, the sum of the weights of the sets that contain it.
In Redis 8.8, we introduce a new COUNT aggregator to support this directly.
With COUNT:
1 + 1 + ...)weight₁ + weight₂ + ...)This effectively ignores the original element scores and focuses only on set membership.
The COUNT aggregator enables patterns such as:
All without requiring additional client-side logic.
All these enhancements are generally available on Redis 8.8 today. You can start using the new commands by downloading Redis 8.8 and experimenting with them in your existing workflows.
Have feedback or questions? Join the discussion on our Discord server or reach out to your account manager.