Redis Lua Scripting Transactions

Introduction

Redis Lua scripting is a game-changer for building robust, high-performance applications. It allows you to execute multiple commands as a single atomic operation directly on the Redis server. In this lesson, you'll discover how to harness the power of Lua scripts from C++ using Boost.Redis to create atomic transactional logic that's both efficient and elegant.

Understanding Lua Scripting in Redis

Think of Lua scripts as stored procedures for Redis. When you execute a Lua script, Redis:

  1. Blocks other commands - Your script runs without interruption
  2. Executes atomically - All operations succeed or fail together
  3. Runs server-side - Eliminates network round-trips between commands
  4. Returns results - Sends back computed values to your application

This means you can implement complex conditional logic that would otherwise require multiple round-trips and careful transaction management.

Real-World Use Case: Atomic Counter with Initialization

Let's build a counter that intelligently handles both initialization and incrementation in a single atomic operation. This pattern is common in:

  • Rate limiting systems
  • Request counters
  • Session management
  • Distributed locks with timeouts

Our script will:

  • Check if a counter exists
  • Initialize it if absent
  • Increment it if present
  • Return the new value

The Complete Implementation

C++
#include <boost/redis.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/consign.hpp>
#include <iostream>
#include <string>

#include <boost/redis/src.hpp>

namespace net = boost::asio;
using boost::redis::connection;
using boost::redis::config;
using boost::redis::logger;
using boost::redis::request;
using boost::redis::response;

int main() {
    try {
        // Set up the event loop
        net::io_context ioc;

        // Create a shared Redis connection
        auto conn = std::make_shared<connection>(ioc.get_executor());

        // Configure and start the connection asynchronously
        config cfg;
        conn->async_run(cfg, logger{logger::level::disabled},
                        net::consign(net::detached, conn));

        // Define our Lua script for atomic counter operations
        const std::string lua_script = R"(
            local current = redis.call('get', KEYS[1])
            local inc = tonumber(ARGV[1])
            if current then
                current = tonumber(current)
                local newval = current + inc
                redis.call('set', KEYS[1], newval)
                return newval
            else
                redis.call('set', KEYS[1], inc)
                return inc
            end
        )";

        // Build the EVAL command request
        request req;
        req.push("EVAL", lua_script, "1", "counter", "5");

        // Prepare to receive an integer response
        response<std::int64_t> resp;

        // Execute the script asynchronously
        conn->async_exec(req, resp,
            [conn, &resp](boost::system::error_code ec, std::size_t) {
                if (!ec) {
                    const auto& eval_res = std::get<0>(resp);
                    if (eval_res) {
                        std::cout << "New counter value: " << eval_res.value() << "\n";
                    } else {
                        std::cout << "Script returned no value.\n";
                    }
                } else {
                    std::cerr << "Error executing Lua script: " << ec.message() << "\n";
                }
                conn->cancel();
            });

        // Run the event loop
        ioc.run();
    } catch (const std::exception& e) {
        std::cerr << "Exception: " << e.what() << "\n";
        return 1;
    }
    return 0;
}
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