Upgrading to Redis 8.6 is worth doing, but target 8.6.5 or later rather than 8.6.0 — the 8.6.3 and 8.6.5 patches fix several flaws that may lead to remote code execution. On the feature side, the change that matters most is idempotent stream production: XADD now accepts IDMP and IDMPAUTO arguments so a producer that reconnects or restarts can resend a message without creating a duplicate entry. Redis 8.6 also adds least-recently-modified eviction, a HOTKEYS command for finding hot keys, and TLS certificate-based client authentication. This guide covers patch priority first, then each new capability, then the upgrade gates.
Patch level before features
Start here, because it changes what "upgrade to 8.6" means operationally. The 8.6 patch series is not a set of optional performance fixes. Per the official 8.6 release notes:
| Version | Date | Urgency | What it fixes |
|---|---|---|---|
| 8.6.1 | Feb 2026 | SECURITY | A user could manipulate data read by a connection by injecting \r\n sequences into an error reply |
| 8.6.2 | Mar 2026 | SECURITY | Potential use-after-free in the reply copy-avoidance path for module strings; also several IDMP correctness fixes |
| 8.6.3 | May 2026 | SECURITY | CVE-2026-23479 (UAF in the unblock client flow), CVE-2026-25243, CVE-2026-25588 and CVE-2026-25589 (invalid memory access in RESTORE), CVE-2026-23631 (Lua UAF) — all may lead to RCE |
| 8.6.4 | Jun 2026 | HIGH | Redis fails to start on AArch64; XREADGROUP consumer replication inconsistency; Sentinel config injection via SENTINEL SET; integer overflow in SCAN's COUNT; potential TCP stalls and deadlocks |
| 8.6.5 | Jul 2026 | SECURITY | A crafted stream RESTORE payload can make two consumers share the same NACK, leading to a use-after-free that may result in RCE; crafted RedisBloom and TDigest RESTORE payloads may trigger out-of-bounds writes |
Two of these deserve to be called out separately.
The AArch64 startup failure fixed in 8.6.4 is a hard deployment blocker, not a degradation. If your images are built for Graviton, Ampere, or Apple Silicon, an 8.6.0 through 8.6.3 target will fail at process start.
And notice how many of the critical items involve RESTORE. That makes any path accepting serialized payloads from a source you do not fully control part of your attack surface — migration tooling, backup restore flows, and any admin endpoint reachable from outside. While you are scheduling the upgrade, auditing who is permitted to call RESTORE is cheap insurance.
What 8.6 adds over 8.4
The release notes summarize the major changes as substantial performance improvements, substantial memory reduction for hashtable-encoded hashes and skiplist-encoded sorted sets, XADD idempotency, the new volatile-lrm and allkeys-lrm eviction policies, hot key detection via HOTKEYS, TLS certificate-based automatic client authentication, and time series support for NaN values with new COUNTNAN and COUNTALL aggregators. Redis 8.6.0 reached general availability in February 2026.
Idempotent stream production
The idempotent message processing documentation frames the problem in terms of two failure scenarios, and both should look familiar.
A network interruption between producer and Redis: if the disconnect happens after XADD executes but before the reply arrives, the producer cannot know whether the message landed. A producer crash and restart: same window, same ambiguity — the process died after calling XADD but before recording the message as delivered.
In both cases the producer must resend to guarantee delivery. Before 8.6, resending meant duplicates, and deduplication was entirely the consumer's problem. Redis 8.6 moves that responsibility to the server.
The mechanism pairs a producer ID (pid) with an idempotent ID (iid) to form a deduplication key.
IDMP versus IDMPAUTO
The full command syntax:
XADD key [NOMKSTREAM] [KEEPREF | DELREF | ACKED]
[IDMPAUTO producer-id | IDMP producer-id idempotent-id]
[<MAXLEN | MINID> [= | ~] threshold [LIMIT count]] <* | id>
field value [field value ...]
Both modes in practice:
XADD mystream IDMP producer-1 iid-1 * field value
XADD mystream IDMPAUTO producer-2 * field value
IDMP is manual mode. You supply both the pid and the iid, and the iid can be an identifier the message already carries — a transaction ID, a monotonic counter, a UUID. It is faster because no hash is computed, and you retain full control over uniqueness. If that (pid, iid) pair has been seen before, the command returns the ID of the original entry instead of creating a duplicate.
IDMPAUTO is automatic mode. You supply only the pid and Redis derives the iid from the field-value content, so identical content yields an identical iid. The documented trade-off is that it is slightly slower due to the hash calculation.
The decision rule is straightforward: if your messages already carry a stable business identifier, use IDMP. Order IDs, event IDs, and CDC log sequence numbers are all natural iids. Reserve IDMPAUTO for messages with no reliable identifier whose content genuinely defines uniqueness — and note the trap in that semantics. Content-derived iids break the moment your payload contains a timestamp, a random field, or a retry counter, because a resend no longer produces the same content.
Two constraints apply to both modes. They can only be used when the entry ID is * (auto-generated), and each producer application must reuse the same pid after it restarts. A pid derived from a container instance ID or a fresh UUID per process makes idempotency purely decorative: nothing will ever deduplicate. This is the single most common implementation mistake.
Sizing the deduplication window
Tracked iids are not retained forever. Configure retention per stream with XCFGSET:
XCFGSET mystream IDMP-DURATION 300 IDMP-MAXSIZE 1000
IDMP-DURATION— how long, in seconds, to retain iids. Range 1 to 86400, default 100.IDMP-MAXSIZE— the maximum number of per-producer iids to track. Range 1 to 10,000, default 100.
Server-level stream-idmp-duration and stream-idmp-maxsize supply the defaults for streams that have not been configured explicitly.
The two limits are disjunctive — an iid is dropped when either is hit — and the documentation is explicit that IDMP-MAXSIZE is stronger than IDMP-DURATION: Redis never keeps more than IDMP-MAXSIZE iids per pid regardless of elapsed time.
The documented sizing logic is worth following rather than guessing at.
IDMP-DURATION is an operational guarantee: Redis will not discard a previously seen iid for that duration unless MAXSIZE forces it out. So it should cover the longest realistic time between a producer crashing and resuming. The documentation's example: if recovery can take up to 1,000 seconds, set IDMP-DURATION to 1000. Setting it higher than needed just wastes memory retaining iids nobody will resend.
IDMP-MAXSIZE depends on your mark-delay — the time between receiving an XADD reply and durably recording the message as delivered. The documented formula:
IDMP-MAXSIZE = mark-delay [in msec] * (messages / msec) + some margin
The worked example: a producer sending 1K msgs/sec (1 msg/msec) that takes up to 80 msec to mark each message delivered should set IDMP-MAXSIZE to 1 * 80 + margin = 100. The docs add that this number is usually very small, and often even one is enough.
If your producer marks synchronously — reply, write to the transaction log, continue — mark-delay is near zero and the default of 100 is generous. Asynchronous or batched marking is where the arithmetic matters.
Producers are isolated
Each producer tracks independently, so reusing an iid across different pids is fine:
XADD mystream IDMP producer-1 iid-1 * field value
XADD mystream IDMP producer-2 iid-1 * field value
That means iids only need to be unique within a pid, not globally. Sharded producers can each run a local counter without introducing a distributed ID service.
Verifying it works
XINFO STREAM returns additional fields once idempotency is in use:
idmp-durationandidmp-maxsize— the active configurationpids-tracked— how many producers are currently trackediids-tracked— total iids currently trackediids-added— lifetime count of messages carrying idempotent IDsiids-duplicates— lifetime count of duplicates prevented
iids-duplicates is the metric that tells you whether the feature is doing anything. If it sits at zero while your producers demonstrably reconnect, the likely cause is unstable pids rather than an absence of retries. An unusually high value points the other way — at aggressive resends caused by network problems or a too-tight client timeout.
Watch pids-tracked for monotonic growth. Steady growth means new producer identities keep appearing, which is the "new pid on every restart" antipattern showing up in telemetry.
Three edges to know about
8.6.0 ships a documented limitation. The release notes state that you should avoid XADD with IDMP or IDMPAUTO when running appendonly yes together with the non-default aof-use-rdb-preamble no, and note the limitation will be removed in the next patch. If that is your persistence configuration, confirm your target patch level clears it or move aof-use-rdb-preamble back to its default.
Reconfiguring clears the tracking map. Executing XCFGSET with an IDMP-DURATION or IDMP-MAXSIZE different from the current value for a key clears that key's IDMP map. Retuning at runtime opens a deduplication gap, so do it during a traffic trough or accept the momentary duplicate risk.
The overhead is small but real. The documented figures are a 2-5% throughput reduction, under 1.5% additional memory, and negligible per-operation latency impact, with manual mode slightly faster than automatic. Acceptable almost everywhere, but worth confirming in a load test if you run a latency-critical write path.
Persistence has no gap: RDB and AOF both save all producer/idempotent-ID pairs, tracking stays active across restarts, and the IDMP-DURATION and IDMP-MAXSIZE settings persist.
Does this make Streams a task queue?
It makes Streams a more credible one. Producer-side duplication is now a server concern, leaving consumers to handle only their own idempotency. It still does not give you retry policies, dependency ordering, or an operator-facing view of what ran and what failed. We compared the reliability boundaries of in-process tasks, queues, and durable workflows in FastAPI Background Tasks, and the same decision criteria apply when the transport is Redis Streams.
When the actual requirement is scheduled jobs with dependencies and retries rather than high-throughput messaging, a purpose-built scheduler is usually less work. Cronova is our take on that shape: a single self-hosted binary where DAGs are defined in YAML, with embedded SQLite, a web console, a REST API, and real-time logs, keeping retries and notifications in the scheduling layer instead of the message layer.
LRM eviction
Redis 8.6 adds Least Recently Modified eviction. Per the key eviction documentation, LRM differs from LRU in exactly one respect:
- LRU updates the timestamp on both reads and writes.
- LRM updates the timestamp only on writes.
That yields two new maxmemory-policy values: allkeys-lrm and volatile-lrm.
The documented guidance is to use allkeys-lrm when you want to preserve frequently read data but evict data that has not been modified recently — useful for read-heavy workloads where the meaningful distinction is between actively updated data and data that is merely being read.
The contrast is concrete. Under allkeys-lru, a configuration value read thousands of times an hour but never written will never be evicted. Under allkeys-lrm, it ages out as stale and yields memory to data that is actually changing. Which behavior you want depends on cache semantics: if cached values are immutable, LRU fits; if you are caching derived data that can go stale, LRM clears old copies faster.
Implementation-wise LRM is approximate like LRU — random sampling, the same maxmemory-samples directive applies.
Remember the shared caveat for the volatile-* family: with no keys carrying a TTL they behave like noeviction, and writes start erroring.
Choosing a policy matters less than getting invalidation right in the first place. How publish state and cache versions stay consistent — including what to do when the database commit succeeded and the cache invalidation did not — is a separate problem we worked through in Building an Atomic Bilingual Publishing System.
Finding hot keys with HOTKEYS
HOTKEYS, added in 8.6.0, is a container command for identifying hot keys during a tracking period. It defines "hot" along two axes: the percentage of CPU time spent on the key out of total time during the tracking period, and the percentage of network bytes (input plus output) attributable to the key out of all network bytes Redis used.
The workflow is to start tracking, let it run, then fetch the top K. Metrics are recorded in a probabilistic data structure. Four subcommands: HOTKEYS START begins tracking with the specified metrics, HOTKEYS STOP halts tracking while preserving data, HOTKEYS GET returns results and metadata, and HOTKEYS RESET releases the resources used.
The two axes point at different problems. High CPU share usually means expensive commands against large collections — wide ZRANGE scans, unbounded HGETALL. High network share usually means large values or very high call frequency. The first calls for command and data-structure changes; the second for cache granularity or a client-side cache layer.
Call HOTKEYS RESET when you are done rather than leaving the tracking structure resident.
TLS certificate authentication
Redis 8.6 adds TLS certificate-based automatic client authentication via the tls-auth-clients-user configuration parameter, along with an acl_access_denied_tls_cert metric counting failed certificate-based authentication attempts.
The operational value is moving client identity off a shared password and onto per-client certificates. Rotating a shared password requires coordinating the server and every client at once; certificates can be issued and revoked per client, and a problem points at one client rather than "someone who knows the password."
Wire acl_access_denied_tls_cert into monitoring before rollout. A sudden rise almost always means an expired certificate or a misconfigured issuing chain.
Upgrade sequence and rollback gates
Redis 8.6 is tested on Ubuntu 22.04 and 24.04, Rocky Linux 8.10 and 9.5, AlmaLinux 8.10, 9.5 and 10.1, Debian 12 and 13, and macOS 14 and 15, with binary distributions covering Docker images, snap, brew, RPM, and Debian APT.
A workable order:
- Fix the target at 8.6.5 or later. Planning around 8.6.0 with patches "to follow" means knowingly deploying published RCE exposure.
- Validate startup in staging, especially on AArch64, confirming the 8.6.4 fix applies to your build.
- Check persistence settings against the 8.6.0 IDMP limitation before enabling idempotent production.
- Do not change eviction policy in the same window as the version bump. Upgrade, observe a full business cycle, then evaluate LRM separately — changing both makes hit-rate movement unattributable.
- Roll out idempotency incrementally. Enable
IDMPon one non-critical stream, confirmiids-duplicatesandpids-trackedbehave as expected, then widen. - Define rollback triggers in advance — connection error rate, unexpected growth in
evicted_keys, hit-rate drop threshold, p99 latency ceiling. - Audit who can call
RESTORE, given where the critical fixes cluster.
For a hit-rate baseline, INFO stats gives keyspace_hits and keyspace_misses; the documented calculation is keyspace_hits / (keyspace_hits + keyspace_misses) * 100. Capture it before and after instead of relying on impressions.
FAQ
Does 8.6 require client changes? Only to use the new capabilities. IDMP and IDMPAUTO are optional XADD arguments, LRM is configuration, and HOTKEYS is an operator command. Existing clients keep working unchanged.
Is this exactly-once delivery? No. The documentation calls it at-most-once production. It prevents duplicate writes caused by producer resends; consumer-side idempotency remains yours to implement.
Do iids need to be globally unique? No — only unique within a pid. Different producers using the same iid is explicitly supported.
Can IDMPAUTO handle messages containing timestamps? Not reliably. It derives the iid from field-value content, so any random or time-varying field changes the content on resend and defeats deduplication. Use IDMP with a stable identifier for those.
Does LRM replace LRU? No, it is an additional option. Read-heavy workloads where staleness tracks writes suit LRM; the classic hot-subset access pattern still suits LRU.
Will upgrading lose tracked idempotency state? A normal upgrade will not — RDB and AOF persist the pid/iid pairs. Changing IDMP-DURATION or IDMP-MAXSIZE at runtime does clear a key's map, which is a distinct operation from upgrading.