Optimistic Transactions with BoostRedis

Introduction

Welcome back! In the previous lesson, you learned how to batch multiple commands into a single request using Boost.Redis to reduce network round trips. Batching improves performance, but it is not atomic — other clients may still change data between your batched reads and writes.

This lesson introduces WATCH together with MULTI/EXEC, which you use to build conditional, optimistic-locking transactions. The idea is:

  • MULTI/EXEC makes a group of writes execute atomically.
  • WATCH monitors one or more keys and causes EXEC to abort if any of those keys change after WATCH and before EXEC.

In short: batching is non-atomic; atomicity requires MULTI/EXEC. WATCH ensures the keys stay unchanged between your read and your EXEC.

What You'll Learn

By the end of this lesson, you will be able to:

  • Send WATCH for one or more keys using request.push().
  • Combine WATCH with MULTI/EXEC to perform conditional updates.
  • Detect conflicts via EXEC's reply (nil/aborted), not via exceptions.
  • Follow the typical optimistic sequence (on the same connection):
    1. WATCH
    2. read
    3. compute
    4. MULTI
    5. write
    6. EXEC (retry if aborted)

Remember: request batching by itself is not atomic. Transactions require MULTI/EXEC, and WATCH ensures the keys you care about didn't change.

The Problem: Race Conditions in Read-Modify-Write

Imagine you're building a banking application. You want to update a user's balance by adding money to their account. The naive approach looks like this:

  1. GET balance:1 → returns "100"
  2. Calculate in C++: 100 + 50 = 150
  3. SET balance:1 150

What's the problem? Between steps 1 and 3, another client might also read the balance, compute a new value, and write it back. You might lose updates:

  • Client A reads 100, adds 50 → wants to write 150
  • Client B reads 100, adds 30 → wants to write 130
  • Client B writes 130 first
  • Client A writes 150 second
  • Result: Client B's +30 is lost! The balance should be 180, not 150.

This is called a race condition. Even if you batch the commands, they're not atomic — other clients can still interleave their operations.

How WATCH Solves This

WATCH is Redis's optimistic locking mechanism. Here's how it works:

  1. Before reading, you tell Redis: "Watch this key for changes"
  2. Redis remembers which keys you're watching
  3. You read the key and compute your new value
  4. When you execute MULTI/EXEC, Redis checks: "Did any watched key change since WATCH?"
    • If yes: EXEC returns nil (aborted), and nothing is written
    • If no: Your transaction executes atomically

This is called optimistic locking because you optimistically assume no conflict will occur. If one does, you detect it and retry.

Key insight: WATCH doesn't lock the key or block other clients. It just monitors for changes. If a change happens, you find out during EXEC.

Connection-specific (reliability) insight: WATCH is stored on the server per connection. That means:

  • You must run the whole sequence (WATCH → read → MULTI/EXEC) on the same Redis connection.
  • If the connection drops or is reset, the watched state is lost. In practice, you must treat a disconnect as “transaction attempt failed” and retry from the beginning after reconnecting.
  • Don’t share one connection across concurrent optimistic transactions unless you strictly serialize them; otherwise one transaction’s WATCH can interfere with another’s.
Sign up

Join the 1M+ learners on CodeSignal

Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal