C++ Project Recommendation – A KV Storage Project Comparable to Redis – Including Performance Optimization Techniques

Overview

This tutorial will guide you step by step to implement a high-performance Mini-Redis from scratch, covering core technologies such as RESP protocol parsing, event-driven network programming, data structure implementation, persistence, and master-slave replication.

This KV storage project is a C++ project that can truly be added to your resume. Unlike other KV storage projects available online, which can only achieve over a thousand QPS after adding persistence, this project can reach over 55,000 QPS, making its performance comparable to Redis.

C++ Project Recommendation - A KV Storage Project Comparable to Redis - Including Performance Optimization Techniques

Video explanation and source code access:

https://www.bilibili.com/video/BV12oemzEEjQ/

Project Goals

  • Protocol Compatibility: Supports the standard RESP protocol, compatible with redis-cli tools

  • High Performance: Single-machine QPS reaches over 50,000, maintaining high performance even with AOF enabled

  • Complete Functionality: Data structures, persistence, expiration, master-slave replication

  • Educational Focus: Clear code, detailed documentation, suitable for learning

Technology Stack

  • Language: C++17

  • Networking: epoll event-driven

  • Protocol: RESP (Redis Serialization Protocol)

  • Data Structures: unordered_map + skip list

  • Persistence: AOF + RDB

  • Build System: CMake

Chapter 1: Project Architecture Design

1.1 Overall Architecture

C++ Project Recommendation - A KV Storage Project Comparable to Redis - Including Performance Optimization Techniques

1.2 Request Processing Flow

A complete request goes through the following stages:

  1. Network Reception: epoll listens for client connections and data

  2. Protocol Parsing: Parses commands in RESP format

  3. Command Execution: Executes operations in the KV storage

  4. Persistence: AOF records commands, RDB takes periodic snapshots

  5. Master-Slave Replication: Synchronizes commands to slave nodes

  6. Response Return: Serializes the result back to RESP format

1.3 Module Division

Module File Responsibilities
Network Layer server.cpp epoll event loop, TCP connection management
Protocol Layer resp.hpp/cpp RESP protocol parsing and serialization
Storage Layer kv.hpp/cpp Data structure implementation, expiration management
Persistence aof.hpp/cpp, rdb.hpp/cpp AOF/RDB persistence
Replication replica_client.hpp/cpp Master-slave replication
Configuration config.hpp, config_loader.cpp Configuration parsing

Chapter 2: Environment Preparation and Project Setup

2.1 Environment Requirements

# Ubuntu/Debian
sudo apt install build-essential cmake pkg-config

# CentOS/RHEL
sudo yum install gcc-c++ cmake make

# Check versions
g++ --version  # Needs to support C++17
cmake --version  # Recommended 3.15+

2.2 Project Structure Creation

mkdir mini-redis && cd mini-redis
mkdir -p include/mini_redis src docs tools build

Project directory structure:

mini-redis/
├── CMakeLists.txt          # CMake build file
├── include/mini_redis/     # Header files
│   ├── config.hpp         # Configuration structure
│   ├── resp.hpp           # RESP protocol
│   ├── kv.hpp             # KV storage
│   ├── aof.hpp            # AOF persistence
│   ├── rdb.hpp            # RDB persistence  
│   └── replica_client.hpp # Master-slave replication
├── src/                   # Source files
│   ├── main.cpp          # Program entry
│   ├── server.cpp        # Main server logic
│   ├── resp.cpp          # RESP implementation
│   ├── kv.cpp            # KV storage implementation
│   ├── aof.cpp           # AOF implementation
│   ├── rdb.cpp           # RDB implementation
│   ├── replica_client.cpp # Replication implementation
│   └── config_loader.cpp # Configuration loading
├── docs/                 # Documentation
├── tools/                # Utility scripts
└── build/               # Build directory

2.3 CMake Configuration File

Create <span>CMakeLists.txt</span>:

cmake_minimum_required(VERSION 3.15)
project(mini_redis)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# Compilation options
set(CMAKE_CXX_FLAGS "-Wall -Wextra -g")
set(CMAKE_BUILD_TYPE Release)
set(CMAKE_CXX_FLAGS_RELEASE "-O3 -march=native")

# Header file paths
include_directories(include)

# Source files
set(SOURCES
    src/main.cpp
    src/server.cpp
    src/resp.cpp
    src/kv.cpp
    src/aof.cpp
    src/rdb.cpp
    src/replica_client.cpp
    src/config_loader.cpp
)

# Executable file
add_executable(mini_redis ${SOURCES})

# Link libraries
find_package(Threads REQUIRED)
target_link_libraries(mini_redis PRIVATE Threads::Threads)

2.4 Build and Use

2.4.1 Build Project

cd mini-redis
cmake -S . -B build
cmake --build build -j

2.4.2 Standalone Startup Mode

Mini-Redis supports three persistence configuration modes:

1. No Persistence Mode (none.conf)

Suitable for: Testing, caching scenarios, no data persistence required

# Start server
./build/mini_redis --config build/none.conf

# The server will start on port 6388, with no AOF and RDB

2. Every Second Sync Mode (everysec.conf)

Suitable for: Production environment, balancing performance and data safety

# Start server
./build/mini_redis --config build/everysec.conf

# The server will start on port 6388, AOF syncs every second

3. Immediate Sync Mode (always.conf)

Suitable for: Scenarios with extremely high data consistency requirements

# Start server  
./build/mini_redis --config build/always.conf

# The server will start on port 6388, syncing every write operation to disk immediately

Configuration File Details

none.conf
port=6388
aof.enabled=false
rdb.enabled=false
everysec.conf
port=6388
aof.enabled=true
aof.mode=everysec
aof.dir=./data
aof.filename=appendonly.aof
rdb.enabled=false
aof.batch_bytes=262144
aof.batch_wait_us=2000
aof.prealloc_bytes=67108864
aof.sync_interval_ms=250
always.conf
port=6388
aof.enabled=true
aof.mode=always
aof.dir=./data
aof.filename=appendonly.aof
rdb.enabled=false

2.4.3 Master-Slave Replication Mode

Master Node Startup

Create master node configuration file <span>master.conf</span>:

port=6379
bind_address=0.0.0.0

# AOF Persistence
aof.enabled=true
aof.mode=everysec
aof.dir=./data-master
aof.filename=appendonly.aof

# RDB Snapshot
rdb.enabled=true
rdb.dir=./data-master
rdb.filename=dump.rdb

Start master node:

./build/mini_redis --config master.conf

Slave Node Startup

Create slave node configuration file <span>replica.conf</span>:

port=6380
bind_address=0.0.0.0

# RDB for receiving master node snapshots
rdb.enabled=true
rdb.dir=./data-replica
rdb.filename=dump.rdb

# Slave nodes generally do not enable AOF
aof.enabled=false

# Replication configuration
replica.enabled=true
replica.master_host=127.0.0.1
replica.master_port=6379

Start slave node:

./build/mini_redis --config replica.conf

2.4.4 Testing with redis-cli

Connection Test

# Connect to standalone mode
redis-cli -p 6388

# Connect to master node
redis-cli -p 6379

# Connect to slave node
redis-cli -p 6380

Basic Command Testing

Connection and Status
# Test connection
redis-cli -p 6388 PING

# Get server information
redis-cli -p 6388 INFO

# Echo test
redis-cli -p 6388 ECHO "Hello Mini-Redis"
String Operations
# Set key value
redis-cli -p 6388 SET mykey "Hello World"

# Get value
redis-cli -p 6388 GET mykey

# Delete key
redis-cli -p 6388 DEL mykey

# Set expiration time (seconds)
redis-cli -p 6388 SET tempkey "temporary"
redis-cli -p 6388 EXPIRE tempkey 60

# Check remaining expiration time
redis-cli -p 6388 TTL tempkey

# Check if key exists
redis-cli -p 6388 EXISTS mykey
Hash Operations
# Set Hash field
redis-cli -p 6388 HSET user:1 name "Alice"
redis-cli -p 6388 HSET user:1 age "25"
redis-cli -p 6388 HSET user:1 city "Beijing"

# Get Hash field
redis-cli -p 6388 HGET user:1 name

# Get all fields and values
redis-cli -p 6388 HGETALL user:1

# Check if field exists
redis-cli -p 6388 HEXISTS user:1 email

# Delete field
redis-cli -p 6388 HDEL user:1 age

# Get field count
redis-cli -p 6388 HLEN user:1
ZSet (Sorted Set) Operations
# Add members and scores
redis-cli -p 6388 ZADD leaderboard 100 "player1"
redis-cli -p 6388 ZADD leaderboard 85 "player2"
redis-cli -p 6388 ZADD leaderboard 92 "player3"

# Query by score range (default ascending)
redis-cli -p 6388 ZRANGE leaderboard 0-1

# Query by score range and display scores
redis-cli -p 6388 ZRANGE leaderboard 0-1 WITHSCORES

# Get member score
redis-cli -p 6388 ZSCORE leaderboard "player2"

# Delete member
redis-cli -p 6388 ZREM leaderboard "player2"
Other Operations
# List all keys
redis-cli -p 6388 KEYS "*"

# Clear all data
redis-cli -p 6388 FLUSHALL

# Trigger RDB snapshot save
redis-cli -p 6388 BGSAVE

Master-Slave Replication Test

1. Write data on master node
# Connect to master node and write
redis-cli -p 6379 SET repl:test "master-data"
redis-cli -p 6379 HSET repl:hash field1 "value1"
redis-cli -p 6379 ZADD repl:zset 90 "item1"
2. Read data on slave node
# Connect to slave node and read
redis-cli -p 6380 GET repl:test
redis-cli -p 6380 HGETALL repl:hash  
redis-cli -p 6380 ZRANGE repl:zset 0-1 WITHSCORES

Batch Operation Test

Using Pipeline Mode
# Create test data file
echo -e "SET key1 value1\nSET key2 value2\nSET key3 value3" > test-commands.txt

# Execute through pipeline
redis-cli -p 6388 --pipe < test-commands.txt
Performance Testing
# Basic performance test
redis-benchmark -h 127.0.0.1 -p 6388 -n 10000 -c 50

# Test SET operation
redis-benchmark -h 127.0.0.1 -p 6388 -t set -n 10000 -d 100

# Test GET operation  
redis-benchmark -h 127.0.0.1 -p 6388 -t get -n 10000

Chapter 3: RESP Protocol Implementation

Chapter 4: Network Programming and Event Loop

Chapter 5: KV Storage Engine Implementation

Chapter 6: Persistence Implementation

Chapter 7: Master-Slave Replication Implementation

These chapters need to be viewed alongside the source code, and detailed learning documents and source code are provided in this video explanation, which you can watch to access.

https://www.bilibili.com/video/BV12oemzEEjQ/

Chapter 8: Performance Optimization and Tuning

8.1 Performance Testing Benchmark

Use <span>redis-benchmark</span> for performance testing, comparing the effects before and after optimization:

# Benchmark test command
redis-benchmark -h 127.0.0.1 -p 6379 -n 100000 -c 50 -P 10 -t set,get
redis-benchmark -h 127.0.0.1 -p 6379 -n 50000 -c 10 -P 1 -d 100 -t set,get

8.1.1 Performance Optimization Comparison

Optimization Item QPS Before Optimization QPS After Optimization Improvement Factor Remarks
Basic Implementation 15k Blocking I/O + Synchronous AOF
Non-blocking I/O + epoll 15k 45k 3x Event-driven
Edge Triggered (EPOLLET) 45k 52k 1.15x Reduced system calls
writev Batch Sending 52k 58k 1.12x Reduced network system calls
AOF Asynchronous Writing 1.4k 55k 39x Significant improvement in AOF mode
Group Commit 55k 48k 0.87x Trade-off in always mode

8.2 Key Optimization Techniques

8.2.1 Core AOF Performance Optimization

The most critical optimization is the asynchronous writing mechanism of AOF:

// Before optimization: synchronous writing (performance killer)
void appendAOF_slow(const std::string &cmd) {
    std::ofstream file(aof_path_, std::ios::app);
    file << cmd;
    file.flush();  // Immediate flush, QPS plummets
}

// After optimization: asynchronous batch writing
void appendAOF_fast(const std::string &cmd) {
    {
        std::lock_guard <std::mutex> lock(queue_mutex_);
        aof_queue_.push(cmd);
        pending_bytes_ += cmd.size();
    }
    cv_.notify_one();  // Wake up background writing thread
}

8.2.2 Network I/O Optimization

// Optimization Technique 1: TCP_NODELAY to avoid Nagle's algorithm delay
int opt = 1;
setsockopt(client_fd, IPPROTO_TCP, TCP_NODELAY, &opt, sizeof(opt));

// Optimization Technique 2: writev batch sending
struct iovec iov[64];  // Increase to 64 iovec
int count = 0;
for (const auto &chunk : out_chunks) {
    iov[count].iov_base = (void *)chunk.data();
    iov[count].iov_len = chunk.size();
    if (++count >= 64) break;
}
writev(fd, iov, count);

// Optimization Technique 3: Edge-triggered read all at once
while (true) {
    ssize_t n = read(fd, buf, sizeof(buf));
    if (n <= 0) {
        if (errno == EAGAIN) break;  // Finished reading
        // Handle error
    }
    process_data(buf, n);
}

8.3 Memory and Algorithm Optimization

8.3.1 Adaptive Storage for ZSet

// Small sets use vector, large sets use skip list
class ZSetRecord {
    static constexpr size_t THRESHOLD = 128;
    
    std::vector <std::pair <double, std::string>> small_set;  // <128 elements
    std::unique_ptr <Skiplist> skiplist;                     // >=128 elements
    
    void checkAndConvert() {
        if (!use_skiplist && small_set.size() >= THRESHOLD) {
            convertToSkiplist();  // Automatic upgrade
        }
    }
};

8.3.2 Pre-allocation and Object Reuse

// Pre-allocate AOF file space to avoid frequent expansion
void preallocateAOF(int fd, size_t size) {
    if (posix_fallocate(fd, 0, size) == 0) {
        printf("Preallocated %zu bytes for AOF\n", size);
    }
}

// Connection object reuse
class ConnectionPool {
    std::vector <std::unique_ptr <Conn>> free_conns_;
    
    std::unique_ptr <Conn> acquire() {
        if (!free_conns_.empty()) {
            auto conn = std::move(free_conns_.back());
            free_conns_.pop_back();
            conn->reset();  // Reset state
            return conn;
        }
        return std::make_unique <Conn>();
    }
};

Conclusion

Technical Highlights

  • High Performance: Asynchronous AOF, batch I/O, edge-triggered

  • Complete Architecture: Separation of network layer, protocol layer, and storage layer

  • Data Structures: Skip list, adaptive ZSet, hash table

  • Persistence: Dual assurance of AOF + RDB

  • Replication: Master-slave synchronization, partial resynchronization

  • Optimization: Performance improvement from 1.4k to 55k QPS (this is for SET), GET can be comparable to Redis.

Learning Value

  • System Design Capability: Understanding high-performance server architecture

  • Network Programming Skills: Mastering epoll, non-blocking I/O

  • Storage System Knowledge: Learning to design persistence schemes

  • Performance Tuning Experience: Mastering common optimization techniques

  • Interview Preparation: Covering high-frequency interview questions related to Redis

Leave a Comment