Dragonfly Cloud announces new enterprise security features - 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?

Switch & save up to 80% 

Dragonfly is fully compatible with the Redis ecosystem and requires no code changes to implement. Instantly experience up to a 25X boost in performance and 80% reduction in cost