deniz.in

Markets

Weather

Loading weather

· via dev.to (home feed)

Valkey 9.2 adds INCREX: one atomic command for the INCR-plus-EXPIRE rate-limit pattern

A hands-on test of Valkey 9.2.0-rc1 finds the new INCREX command combines an increment and its expiry atomically, closing the crash window that strands rate-limit counters without a TTL.

Valkey 9.2 adds INCREX: one atomic command for the INCR-plus-EXPIRE rate-limit pattern

Valkey 9.2, currently available as the 9.2.0-rc1 release candidate on Docker Hub, introduces INCREX, a command that increments a key and assigns its time-to-live in a single atomic call. According to hands-on testing published on dev.to, the command targets the two-step INCR-plus-EXPIRE idiom that backs a large share of rate limiters and quota counters, and its most important benefit turns out to be correctness rather than speed: it removes a failure state that has always existed between the two calls.

What the command does

The grammar is INCREX key [NX|XX] [EX seconds|PX ms|EXAT ts|PXAT ts] [BYINT n|BYFLOAT n]; leaving out the BYINT or BYFLOAT clause increments by one. The server's own command documentation marks INCREX as available since 9.2.0, and the dev.to author confirmed it is entirely absent from the previous stable 9.1.2 image. One operational wrinkle: the INFO server output reports candidate builds as plain 9.2.0 with no -rc1 suffix, so there is no way to distinguish a release candidate from a final build just by querying the server.

The test setup ran both container images on the same host, driven from a Python script using redis-py 8.1.0 over localhost TCP.

Where the speedup actually comes from

The benchmark compared 2,000 operations on distinct keys, three runs each. The traditional pattern, a blocking INCR followed by a conditional EXPIRE once the counter reads 1, averaged roughly 0.46 to 0.57 ms per operation, against 0.26 to 0.29 ms for INCREX, a repeatable 1.6x to 2x improvement.

The dev.to author then pipelined the old pattern, sending INCR and EXPIRE as one batch, and the gap collapsed to between 9 and 21 percent. The headline number, in other words, reflects the cost of a second network round trip, not a faster server-side command. The author's observation is that few teams actually pipeline this code, because the conditional logic that expires a key only on the first increment makes batching awkward, so most real deployments would still see the full saving.

The failure mode it removes

The stronger argument for switching is the window between the two calls. If a request times out, the process dies, or a deploy kills it after INCR lands but before EXPIRE is sent, the counter exists with no expiry and will never reset. The simulation in the dev.to piece ran 500 requests with a 2 percent chance of crashing immediately after the first command succeeded: the old pattern left 11 keys permanently without a TTL, while INCREX left zero.

With a single command there is no intermediate state to crash inside: either nothing has happened yet, or the new value and its TTL are both already set. A separate contention check, with 20 threads each performing 100 increments on one shared key, arrived at exactly 2,000, and the TTL set by the first caller was left intact by the other 1,999, confirming no lost updates.

Porting gotchas

Two behaviors surprised the author. First, INCREX's NX and XX flags follow SET's convention, testing whether the key exists, rather than EXPIRE's convention of testing whether a TTL is already present. Calling INCREX with NX on a key that already exists refuses the entire operation: the increment is skipped and the second element of the reply, the amount actually applied, reads 0. Code that relied on EXPIRE's NX to mean "never reset a window that is already running" should port to INCREX with no condition at all, since the command leaves an existing TTL untouched when no expiration clause is supplied.

Second, overflow behaves differently from INCR. Pushing INCR past the signed 64-bit ceiling raises an explicit error, but INCREX never throws: the value simply stops moving and the applied amount returns 0. A caller wrapping INCR in exception handling would silently miss a stuck counter under INCREX and must inspect the second reply element on every call. Type errors match INCR's exactly, and combining BYINT with BYFLOAT in one call is a syntax error.

Why it matters

Increment-with-expiry is one of the most widely replicated patterns in backend infrastructure, and every counter stranded without a TTL is both a permanent memory leak and, in a rate limiter, potentially a user throttled forever. INCREX fixes that class of bug by construction rather than by discipline. The caveats, including its release-candidate status, the SET-style flag semantics and the silent overflow behavior, mean porting takes care. But for anyone running Valkey-backed rate limiters or quota counters, this is the most practically useful change in the 9.2 release.

  • #valkey
  • #rate-limiting
  • #redis
  • #databases
  • #open-source

Related posts