Your agents aren't failing. Their context is.

See how we fix it
Platform
Solutions
Resources
Partners

Blog

Announcing Redis 8.8: New array data structure, rate limiter, performance improvements, & more

June 02, 202614 minute read
Lior Kogan
Lior Kogan

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.

Summary of performance improvements in 8.8

Redis 8.8 introduces significant end-to-end throughput improvements:

Data typeOperationsEnd-to-end throughput improvements
StringsMGET (pipelined, with I/O-threads)Up to 68%
MGET (pipelined, single thread)Up to 50%
MSETUp to 8%
HashHGETALLUp to 25% (1K+ fields)
StreamsXREADGROUPUp to 83% (COUNT 100)
Sorted setZADD, ZINCRBY, ZRANGEBYSCOREUp to 74%
BitmapBitmap operationsUp to 28% (x86)
HyperLogLogPFCOUNTUp to 18% (x86)
(several)SCAN, HSCAN, SSCAN, ZSCANUp to 40%

In addition, persistence and replication (full synchronization) is now up to 60% faster.

Summary of new features in 8.8

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.

  • Redis 8.2 simplified message acknowledgment and deletion across multiple consumer groups
  • Redis 8.4 made it easier for consumers to read both new and idle pending messages
  • Redis 8.6 introduced idempotent production

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.

The new features explained

Array: A new general-purpose data structure

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:

  • An array doesn’t need to have a fixed size
    Arrays grow and shrink dynamically. Elements can be set at any index (0 to 2⁶⁴−1), and the array grows efficiently as needed. Elements can be deleted and the array shrinks accordingly.
  • An array can be dense or sparse
    The used indices don’t have to be consecutive, and yet the memory footprint is proportional to the number of elements, and access by index remains extremely fast.
    For example: an array index can represent a product ID and the values hold product names or details. Similarly, the index can represent timestamps and the values hold log events.
  • An array can be used as a ring buffer (Sliding Window)
    Arrays can act as a bounded rolling buffer: retain the last n elements, maintain insertion order, and automatically overwrite older entries.
    Think of a log file or a stream of events, where you want to efficiently keep the last n log entries, events, or measurements, and frequently fetch the last [up to] n values. This is especially useful if you need to feed a rule engine, process a fraud detection window, continuously update a chart, or execute security validations.
  • Arrays can aggregate data
    When the values are numeric (for example, real-time sensor reports or stock quotes), arrays support server-side computation over index ranges, including SUM, MIN, and MAX. When the values are binary flags, Boolean aggregators (AND, OR, XOR) are supported as well.
    Server-side aggregators are Ideal for sensor data, financial ticks, and real-time metrics. When combined with ring buffer semantics, Arrays enable sliding window analytics such as real-time anomaly detection.
  • An array can be searched
    An array can represent a textual file (e.g., .txt, .csv, .log), where each element is indexed by the line-number and holds a single text line. Users can iterate over these lines for analysis, and can search for specific lines using an exact or partial string, a glob-style pattern, or a regular expression.
    With ring buffer semantics, arrays can constantly hold the last n log-lines allowing users and agents to contextualize or enrich incoming events based on recent ones.

In summary, an array is a dynamic, flexible, high-performance, index-addressable, compute-aware container that combines aspects of:

  • List (ordered data)
  • Time-series (sliding windows)
  • Sparse map (non-contiguous indices)
  • Analytical engine (aggregation and search)

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)ArrayListHash
Read random element675K ops/sec133K ops/sec626K ops/sec
Write random element757K ops/sec137K ops/sec689K ops/sec
Delete random element841K ops/sec730K 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)ArrayListHash
100 bytes122 bytes/element104 bytes/element151 bytes/element
1 Kbyte1290 bytes/element1035 bytes/element1337 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 sizeArray (ARRING)List (RPUSH+LTRIM)Array’s advantage
1K elements; 100 bytes1.11M inserts/sec512K inserts/sec × 2.2
100K elements; 100 bytes1.12M inserts/sec528K inserts/sec× 2.1
1K elements; 1 Kbyte840K inserts/sec424K inserts/sec× 2.0
100K elements; 1 Kbyte837K inserts/sec413K 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:

  • You need extremely fast access by index or by index-range
  • You need a sliding window over recent data
  • You need server-side aggregation
  • You want to search for matching elements

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 limiter

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:

  1. 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.
  2. When 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.
  3. Boundary enforcement: the request is rejected if it would exceed the defined bounds. With 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.

Streams: NACKing messages

In real-world applications, stream consumers don’t always successfully process the messages they consume. Failures can happen for many reasons:

  • A consumer may encounter internal issues unrelated to the message itself. For example, failing to reach an external service it needs for processing the message.
  • A consumer may need to shut down and release unprocessed messages.
  • A resource-constrained consumer (CPU, memory) may be unable to process certain messages (at least, in a timely manner).
  • A message may be malformed, poisoned, or even malicious.

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:

  • Marked as unowned (its last consumer is set to an empty string)
  • Assigned a last delivery time of 0
  • Placed at the end of the NACKed portion of the PEL

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:

  • When CLAIM min-idle-time is specified:
    • NACKed messages (new behavior)
    • Messages pending for at least min-idle-time
    • Never-delivered messages
  • If CLAIM is not specified:
    • Only never-delivered messages are returned (unchanged behavior)

Hash: Subkey notifications

Redis key-level notifications let clients subscribe to key-related events in real time via pub/sub channels. There are two types of channels:

  • Keyspace notifications: clients subscribe to a specific key; each message contains an event type.
  • Keyevent notifications: clients subscribe to specific events; each message contains a key name.

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:

  • Subkeyspace notifications: Clients subscribe to a specific key; each message contains an event type and field names.
  • Subkeyevent notifications: Clients subscribe to a specific event type; each message contains a key name and field names.
  • Subkeyspaceitem notifications: Clients subscribe to a specific key+field combination; each message contains an event type.
  • Subkeyspaceevent notifications: Clients subscribe to a specific event+key combination; each message contains field names.

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.

Time series: Multiple aggregators in a single command

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.

JSON: Explicitly declaring floating-point array types

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]

  • FPHA stands for Floating-Point Homogeneous Array
  • It forces Redis to store any floating-point array in value using the specified format
  • For large arrays, such as embeddings with hundreds or thousands of elements, the difference becomes substantial, often reducing memory usage by multiple times.

Sorted sets: Union and intersection - COUNT aggregator

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:

  • If no weights are specified, the score becomes the number of input sets containing the element (i.e., 1 + 1 + ...)
  • If weights are specified, the score becomes the sum of the weights of the sets containing the element (i.e., weight₁ + weight₂ + ...)

This effectively ignores the original element scores and focuses only on set membership.

The COUNT aggregator enables patterns such as:

  • Ranking items by popularity across multiple sources
  • Finding elements that appear in many datasets
  • Implementing voting or scoring systems based on presence rather than value

All without requiring additional client-side logic.

Getting started

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.

Découvrez Redis dès aujourd’hui.

Échangez avec un expert Redis et découvrez dès aujourd’hui notre solution Redis Entreprise.