We have added an implementation of HMSETEX to our redis-scripts repo. The repo is a collection of scripts packaged up as a module for Node.js to make them easy to use in Node apps. Hope it helps!
/** | |
* npm install --save lodash redis node-redis-scripty | |
* | |
* Usage: | |
* let hmsetex = require('./hmsetex.js') | |
* let key = 'your-key'; | |
* let fields = { some_field: 'test', another_field: 'foo' }; | |
* hmsetex(key, fields).then(handleResult).catch(handleError); | |
*/ | |
const _ = require('lodash'); | |
let Scripty = require('node-redis-scripty'); | |
let redis = require('redis'); | |
let yourRedisClient = redis.createClient(); | |
let scripty = new Scripty(yourRedisClient); | |
const hmsetexsrc = ` | |
local key = KEYS[1] | |
local args = ARGV | |
local call = redis.call | |
if call("EXISTS", key) == 0 then | |
return nil | |
end | |
return call("HMSET", key, unpack(args)) | |
`; | |
module.exports = function(key, fields) { | |
return new Promise((resolve, reject) => { | |
scripty.loadScript('hmsetex', hmsetexsrc, (err, script) => { | |
if (err) return reject(err); | |
// Make sure any null values are set to '' for redis | |
Object.keys(fields).forEach(k => { | |
if (fields[k] === null) fields[k] = ''; | |
}); | |
let args = _.flatten(_.toPairs(fields)); | |
let numKeys = 1; | |
script.run(numKeys, key, ...args, (err, result) => err ? reject(err) : resolve(result)); | |
}); | |
}); | |
}; |
Backstory
Redis’ existence-aware commands are a useful way to atomically modify keys based on their state. For example, SET[XX|NX] is a fast and concurrency-safe way of setting keys only if they exist (XX) or don’t exist (NX).
Not all keys support this option, namely HMSET. HMSET will always create the key to set its fields if it doesn’t exist.
In cases where we only want to modify hash keys that exist (say, our app relies on ttl to clear out keys), it would be convenient to have some kind of HMSETEX so we can perform the check/set logic atomically in a single command. This is much safer and simpler than having to check/set across round trips between your app and Redis, and less prone to concurrency bugs too.