Implementing Redis hmsetnx Command Using Lua Scripts and Jedis
TL;DR Batch initializes Hash fields without overwriting existing data, completing the task in a single network round-trip instead of multiple HSETNX calls.
In Redis’s Hash operations, HSETNX ensures that a field is only written when it does not already exist. However, HMSET (which sets multiple fields in batch) has no corresponding HMSETNX command. If you need to batch-initialize a cache without overwriting existing data, issuing N HSETNX commands would require N network round-trips — terrible performance in batch scenarios.
This can be solved elegantly with a Lua script — pushing the logic to the Redis server side and requiring only a single network round-trip.
Problem Background
Imagine a user configuration caching scenario: when a new user accesses the system for the first time, a set of default configurations needs to be initialized in a Redis Hash. However, if the user already has some configurations, those should not be overwritten.
Using HSETNX one by one would certainly work, but if there are 20 configuration items, that means 20 Redis round-trips. Under high concurrency, this becomes a bottleneck.
HMSET can set multiple fields at once, but it unconditionally overwrites existing values — which doesn’t meet the requirement.
Lua Script Solution
1 | local key |
Script Logic:
The ARGV format is identical to HMSET: [field1, value1, field2, value2, ...]. The script iterates through the parameter list — odd positions (1, 3, 5…) are stored as key, while even positions (2, 4, 6…) trigger an HSETNX call.
Since Redis executes Lua scripts atomically, the entire process cannot be interrupted by other commands.
Java Invocation (Jedis)
1 | // Script string (can be stored in a constant) |
Parameter Explanation:
keys: Contains only one element — the key of the Hash table itselfargs: Arranged infield1, value1, field2, value2...ordereval()has two overloads:Stringversion andbyte[]version — the difference is whether parameters need serialization
Performance Optimization: EVALSHA
Every EVAL call transmits the full script content. For fixed scripts, EVALSHA can significantly reduce network overhead:
1 | // Compute the script's SHA1 |
EVALSHA only transmits a few dozen bytes (the SHA1 value). The script itself is only transmitted once during the initial SCRIPT LOAD.
Hmsetnx vs Per-field Hsetnx: Performance Comparison
| Approach | Network Round-trips | Atomicity | Complexity |
|---|---|---|---|
Per-field HSETNX |
N | No (non-transactional) | Lowest |
Lua script EVAL |
1 | Yes (Lua atomic execution) | Medium |
Lua script EVALSHA |
1 | Yes | Medium |
For a scenario with 20 fields, the Lua approach eliminates 19 network round-trips — the effect is dramatic under high concurrency.
Notes
- Cluster Mode: If Redis is deployed in Cluster mode, ensure
KEYS[1](the Hash key) falls in the same slot. Use hash tags to guarantee this (e.g.,{user:1001}:config) - Big Key: If the Hash contains a huge number of fields,
HSETNXitself is O(1) per operation, but be mindful of the total key size - Script Length: Redis enforces a time limit on Lua script execution (
lua-time-limit), but this script just loops through O(n)HSETNXcalls and won’t trigger a timeout
Source: https://lichuanyang.top/en/posts/63756/









