Introducing Dragonfly Cloud! Learn More

Redis Conditional Update in Node.js (Detailed Guide w/ Code Examples)

Use Case(s)

  • Implementing optimistic locking to prevent race conditions.
  • Updating a value in the cache only if it meets specific criteria.
  • Maintaining consistency between the cache and a backing store.

Code Examples

Example 1: Conditional Update Using Watch and Multi

This example demonstrates how to use the watch and multi commands to perform a conditional update. The value is updated only if it has not been modified by another client during the transaction.

const redis = require('redis'); const client = redis.createClient(); client.on('error', (err) => { console.error('Error:', err); }); const key = 'user:1000'; const newValue = { score: 200 }; client.watch(key, (watchErr) => { if (watchErr) throw watchErr; client.get(key, (getErr, result) => { if (getErr) throw getErr; const parsedResult = JSON.parse(result); if (parsedResult.score < newValue.score) { const multi = client.multi(); multi.set(key, JSON.stringify(newValue)); multi.exec((execErr, replies) => { if (execErr) throw execErr; if (replies === null) { console.log('Transaction aborted due to concurrent modification.'); } else { console.log('Transaction successful:', replies); } client.quit(); }); } else { console.log('Condition not met, no update performed.'); client.quit(); } }); });

Explanation:

  • watch(key): Starts watching the key for changes.
  • get(key): Retrieves the current value of the key.
  • Condition check if (parsedResult.score < newValue.score): Ensures that the new value is only set if the existing score is lower than the new score.
  • multi(): Initiates a transaction.
  • exec(): Executes the transaction if the key hasn't been modified since watch().

Example 2: Conditional Increment with Lua Script

Using a Lua script ensures atomic execution of the conditional operation.

const redis = require('redis'); const client = redis.createClient(); client.on('error', (err) => { console.error('Error:', err); }); const key = 'counter:1000'; const incrementBy = 10; const condition = 50; // Only increment if current value is less than this const script = ` local current = tonumber(redis.call('get', KEYS[1])) if current < tonumber(ARGV[1]) then return redis.call('incrby', KEYS[1], ARGV[2]) else return current end `; client.eval(script, 1, key, condition, incrementBy, (err, result) => { if (err) throw err; console.log('Result:', result); client.quit(); });

Explanation:

  • eval(script, 1, key, condition, incrementBy): Runs a Lua script atomically.
  • The Lua script checks if the current value is less than a specified condition before performing an increment.

Best Practices

  • Use Lua scripts for atomic operations that involve multiple commands or complex logic.
  • Implement proper error handling to handle cases where transactions are aborted due to concurrent modifications.

Common Mistakes

  • Forgetting to call watch() before starting a transaction can lead to lost updates.
  • Not checking the result of exec() for a null value, which indicates that the transaction was aborted.

FAQs

Q: What happens if the watched key is modified by another client before exec()? A: The transaction will fail (exec() returns null), and you should retry the operation.

Q: Why use Lua scripts for conditional updates? A: Lua scripts ensure atomicity and can perform more complex logic than simple Redis commands, reducing the risk of race conditions.

Was this content helpful?

Start building today 

Dragonfly is fully compatible with the Redis ecosystem and requires no code changes to implement.