Handling Transactions with Pipelines in C++

Welcome back! We're advancing in our Redis-based backend system project by tackling transactions using pipelines. This enhancement allows us to execute multiple Redis commands as a single atomic operation. You should already be comfortable with managing user data and leaderboards. This unit will optimize these operations using pipelines.

What You'll Build

Before we proceed, let's outline the primary focus of this unit. The key tasks include:

  1. Adding user data with expiration using pipelines: We will group multiple commands into one pipeline to add user data more efficiently.
  2. Adding scores to a leaderboard using pipelines: Using pipelines ensures that these operations are executed atomically.
  3. Executing the pipeline: We'll execute the grouped commands within the pipeline as a single operation.

These tasks demonstrate how pipelines can enhance performance and consistency in our Redis operations.

Example for Pipelines

Below is an example of how to implement pipelines in C++ using the hiredis library:

#include <iostream>
#include <hiredis/hiredis.h>
#include <vector>

// Structure to hold user data
struct User {
    std::string username;
    std::string name;
    int age;
    std::string email;
};

int main() {
    // Connect to the Redis server
    redisContext* context = redisConnect("127.0.0.1", 6379);
    if (context == nullptr || context->err) {
        if (context) {
            std::cerr << "Connection error: " << context->errstr << std::endl;
        } else {
            std::cerr << "Connection error: can't allocate Redis context" << std::endl;
        }
        return 1;
    }

    // Prepare users data
    std::vector<User> users = {
        {"alice", "Alice", 30, "alice@example.com"},
        {"bob", "Bob", 25, "bob@example.com"}
    };

    // Add user data using pipeline
    for (const auto& user : users) {
        redisAppendCommand(context, "SETEX user:%s 86400 %s:%d:%s", 
                           user.username.c_str(), user.name.c_str(), user.age, user.email.c_str());
    }

    // Execute all commands in the pipeline
    for (size_t i = 0; i < users.size(); ++i) {
        redisReply* reply;
        if (redisGetReply(context, (void**)&reply) == REDIS_OK && reply) {
            std::cout << "Pipeline result: " << reply->str << std::endl;
            freeReplyObject(reply);
        } else {
            std::cerr << "Pipeline execution failed." << std::endl;
        }
    }

    // Verify by getting one user's data
    redisReply* reply = (redisReply*)redisCommand(context, "GET user:alice");
    if (reply->type == REDIS_REPLY_STRING) {
        std::cout << "Stored string in Redis: " << reply->str << std::endl;
    } else {
        std::cout << "Failed to retrieve the value." << std::endl;
    }
    freeReplyObject(reply);

    // Free the context
    redisFree(context);

    return 0;
}

In this code, all commands appended to the pipeline are sent to the Redis server in one batch, which optimizes network usage and ensures atomic execution when redisGetReply() is called.

Let's start building efficient backend systems with this approach!

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