duhaime
duhaime

Reputation: 27594

Redis: Atomic get and conditional set

I'd like to perform an atomic GET in Redis, and if the value returned is equal to some expected value, I'd like to do a SET, but I want to chain all of this together as one atomic operation. (I'm trying to set a flag that indicates whether any process is writing data to disk, as only one process may be permitted to do so.)

Is it possible to accomplish this with Redis?

I have seen documentation on MULTI operations but I haven't seen conditional operations i MULTI operations. Any suggestions others can offer with this would be greatly appreciated!

Upvotes: 7

Views: 17185

Answers (5)

Yehezkel
Yehezkel

Reputation: 135

You can do this by SET command, with these 2 arguments, which according to the docs here:

GET - return the old string stored at key, or nil if key did not exist.

NX - Only set the key if it does not already exist.

Since Redis doesn't execute any command while another command is running - you have the 2 operations in an atomic manner.

Upvotes: 1

Kenneth Wilke
Kenneth Wilke

Reputation: 4669

I came across this post looking for a similar type of function, but I didn't see any options that appealed to me. I opted instead to write a small module in Rust that provides this exact type of operation:

https://github.com/KennethWilke/redis-setif

With this module you would do this via:

SETIF <key> <expected> <new>
HSETIF <key> <field> <expected> <new>

Upvotes: 1

Vitaly
Vitaly

Reputation: 3460

redis-if - lua script for "conditional transactions". More convenient than WATCH + MULTY.

You can pass any combination of conditions & followed commands as json object:

const Redis = require('ioredis')

const redis = new Redis()

redis.defineCommand('transaction', { lua: require('redis-if').script, numberOfKeys: 0 })

await redis.set('custom-state', 'initialized')
await redis.set('custom-counter', 0)

// this call will change state and do another unrelated operation (increment) atomically
let success = await redis.transaction(JSON.stringify({
  if: [
    // apply changes only if this process has acquired a lock
    [ 'initialized', '==', [ 'sget', 'custom-state' ] ]
  ],
  exec: [
    [ 'set', 'custom-state', 'finished' ],
    [ 'incr', 'custom-counter' ]
  ]
}))

With this script we removed all custom scripting from our projects.

Upvotes: 0

duhaime
duhaime

Reputation: 27594

I ended up using redlock-py, an implementation of the redlock algorithm that the Redis docs recommend for creating write locks: https://redis.io/topics/distlock. The linked article is fantastic reading for anyone looking to create similar write locks in Redis.

Upvotes: 1

Chris Tanner
Chris Tanner

Reputation: 1650

You can do both the GET and set operations on the redis server itself using Lua scripts. They're atomic and allow you to add logic too.

Upvotes: 6

Related Questions