diff --git a/content/blog/2026-09-15-valkey-9.2-new-commands/index.md b/content/blog/2026-09-15-valkey-9.2-new-commands/index.md new file mode 100644 index 00000000..0f22b0e2 --- /dev/null +++ b/content/blog/2026-09-15-valkey-9.2-new-commands/index.md @@ -0,0 +1,472 @@ ++++ +title = "Valkey 9.2: Fewer Reasons to Reach for Lua" +date = 2026-09-20 +draft = true +description = "How several of Valkey 9.2's new commands and options let application developers replace patterns that used to need WATCH, manual bookkeeping, or a Lua script, with real, practical examples." +authors = ["dragosandriciuc"] +[taxonomies] +blog_type = ["Technical Deep Dive"] ++++ + +A lot of application logic built on top of Valkey ends up in one of two places: either scattered across multiple round trips in your client code, or pushed into a Lua script so the server can do it atomically in one call. +Both work, but they have different costs: extra round trips add latency and race windows, while scripts add something to write, ship, cache, and get right. + +Several of Valkey 9.2's changes exist specifically to shrink that second category: giving plain commands the conditional logic that used to mean either extra round trips or a script. + +If you want the background on how Lua scripting works in Valkey before diving in, the [Introduction to Eval Scripts](https://valkey.io/topics/eval-intro/) documentation page is a good starting point. + +Let's look at where 9.2 actually changes things. + +## `SET IFNE` to set a key when its value doesn't match a specified value + +Sometimes your application wants to update a value only if it hasn't already been set to something specific, to replace a stale default or placeholder, for example, without touching it if another process has already moved it on. + +Before 9.2, doing this safely meant a `GET` first, checking the value in your application code, then conditionally issuing a `SET` which is an extra round trip, and a race window between the `GET` and the `SET` where another client could write in between. +A Lua script can close that race window in one round trip. However, it's still a script you have to write for a pattern that comes up constantly. + +With Valkey 9.2, the `IFNE` option lets `SET` do that check itself. +It sets a key only if the current value does **not** equal the comparison value, in a single call: + +```bash +127.0.0.1:6379> SET foo hello +OK +127.0.0.1:6379> SET foo world IFNE hello +(nil) +127.0.0.1:6379> GET foo +"hello" +127.0.0.1:6379> SET foo world IFNE goodbye +OK +127.0.0.1:6379> GET foo +"world" +``` + +In this example, `SET foo world IFNE goodbye` means "Set foo to world, but only if foo's current value is **NOT** goodbye." +Since `foo` is `"hello"`, which isn't `"goodbye"`, the condition passes and the write goes through. + +Beyond avoiding a stale overwrite, `IFNE` has a second, sharper use: suppressing idempotent writes. +A plain `SET` always counts as a write, even when you're writing the exact value that's already there such as a heartbeat key refreshed every few seconds with the same status string. + +Here's the difference in practice, one client `WATCH`es a key while another writes to it: + +```bash +# Case A: plain SET with the same value +Client A: WATCH heartbeat -> OK +Client B: SET heartbeat alive -> OK #(same value as before) +Client A: MULTI -> OK +Client A: GET heartbeat -> QUEUED +Client A: EXEC -> (nil) #<- WATCH was invalidated anyway + +# Case B: SET ... IFNE with the same value +Client A: WATCH heartbeat -> OK +Client B: SET heartbeat alive IFNE alive -> (nil) #(write skipped, values match) +Client A: MULTI -> OK +Client A: GET heartbeat -> QUEUED +Client A: EXEC -> ["alive"] #<- WATCH still valid, transaction succeeds +``` + +Because `IFNE` skips the write entirely when the value hasn't changed, it never touches the key so it never triggers replication, AOF writes, or `WATCH` invalidation for other clients either. +For keys written frequently with values that often don't change, that's a real reduction in unnecessary churn, and it's built into the command instead of requiring anyone to think to script it. + +For more information, see the [SET command](https://valkey.io/commands/set/). + +## Conditional `EXEC` for optimistic-locking workflows: an alternative to `WATCH` and Lua + +Say your application tracks account balances, and a transfer needs to update two keys together, but only if the account hasn't been touched by another transfer since your application last read it. +This is a classic optimistic-locking pattern: the database reads a version number, then writes only if it hasn't changed. + +Before 9.2, Valkey gave you two ways to do this atomically. +You could `WATCH` the version key before opening `MULTI`, at the cost of an extra round trip before you've even queued a command: + +```bash +127.0.0.1:6599> WATCH ver{foo} +127.0.0.1:6599> GET ver{foo} +127.0.0.1:6599> MULTI +127.0.0.1:6599> SET ver{foo} 2 +127.0.0.1:6599> SET mykey{foo}1 111 +127.0.0.1:6599> SET mykey{foo}2 222 +127.0.0.1:6599> EXEC +``` + +Or you could write a Lua script and let the server do the check and the writes in a single round trip: + +```lua +-- KEYS[1] = version key, ARGV[1] = expected value, ARGV[2] = new value +-- KEYS[2], KEYS[3] = the other keys, ARGV[3], ARGV[4] = their new values +local current = server.call('GET', KEYS[1]) +if current ~= ARGV[1] then + return nil +end +server.call('SET', KEYS[1], ARGV[2]) +server.call('SET', KEYS[2], ARGV[3]) +server.call('SET', KEYS[3], ARGV[4]) +return {'OK', 'OK', 'OK'} +``` + +```bash +127.0.0.1:6999> EVAL "