[2026] C++ Caching Strategy Complete Guide | Redis, Memcached, In-Memory Cache [#50-8]

[2026] C++ Caching Strategy Complete Guide | Redis, Memcached, In-Memory Cache [#50-8]

이 글의 핵심

Master C++ caching: Redis, Memcached, in-memory LRU/TTL, cache invalidation, cache-aside/write-through/write-behind patterns, and production patterns.

Why Caching?

Caching reduces latency and load on backend systems by storing frequently accessed data in fast storage. Common scenarios:

  • Database query results
  • API responses
  • Computed values
  • Session data
  • Static content Benefits:
  • Reduced latency (10-100x faster)
  • Lower database load
  • Higher throughput
  • Better user experience

Caching Strategies

1. Cache-Aside (Lazy Loading)

다음은 cpp를 활용한 상세한 구현 코드입니다. 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// Application checks cache first, loads from DB on miss
string getData(const string& key) {
    // 1. Check cache
    string value = cache.get(key);
    if (!value.empty()) {
        return value;  // Cache hit
    }
    
    // 2. Cache miss: load from DB
    value = database.query(key);
    
    // 3. Store in cache
    cache.set(key, value, 3600);  // 1 hour TTL
    
    return value;
}

Pros:

  • Simple
  • Cache only what’s needed
  • Cache failure doesn’t break app Cons:
  • Cache miss penalty
  • Potential cache stampede

2. Write-Through

아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// Write to cache and DB simultaneously
void setData(const string& key, const string& value) {
    // 1. Write to cache
    cache.set(key, value);
    
    // 2. Write to DB
    database.update(key, value);
}

Pros:

  • Cache always consistent with DB
  • No stale data Cons:
  • Write latency (both cache and DB)
  • Wasted cache space for rarely-read data

3. Write-Behind (Write-Back)

다음은 cpp를 활용한 상세한 구현 코드입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다, 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// Write to cache first, async write to DB
void setData(const string& key, const string& value) {
    // 1. Write to cache
    cache.set(key, value);
    
    // 2. Queue for async DB write
    writeQueue.push({key, value});
}
// Background thread
void flushWorker() {
    while (true) {
        auto item = writeQueue.pop();
        database.update(item.key, item.value);
    }
}

Pros:

  • Low write latency
  • Batched DB writes Cons:
  • Risk of data loss on cache failure
  • Complexity

Redis Integration (hiredis)

Installation

아래 코드는 bash를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

# Ubuntu/Debian
sudo apt install libhiredis-dev
# macOS
brew install hiredis
# vcpkg
vcpkg install hiredis

Basic Usage

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <hiredis/hiredis.h>
#include <iostream>
#include <string>
class RedisClient {
private:
    redisContext* context;
    
public:
    RedisClient(const string& host, int port) {
        context = redisConnect(host.c_str(), port);
        if (context == nullptr || context->err) {
            if (context) {
                cerr << "Redis error: " << context->errstr << endl;
                redisFree(context);
            }
            throw runtime_error("Redis connection failed");
        }
        cout << "Redis connected" << endl;
    }
    
    ~RedisClient() {
        if (context) {
            redisFree(context);
            cout << "Redis disconnected" << endl;
        }
    }
    
    bool set(const string& key, const string& value, int ttl = 0) {
        redisReply* reply;
        if (ttl > 0) {
            reply = (redisReply*)redisCommand(context, "SETEX %s %d %s", 
                                              key.c_str(), ttl, value.c_str());
        } else {
            reply = (redisReply*)redisCommand(context, "SET %s %s", 
                                              key.c_str(), value.c_str());
        }
        
        bool success = reply && reply->type == REDIS_REPLY_STATUS;
        freeReplyObject(reply);
        return success;
    }
    
    string get(const string& key) {
        redisReply* reply = (redisReply*)redisCommand(context, "GET %s", key.c_str());
        string value;
        
        if (reply && reply->type == REDIS_REPLY_STRING) {
            value = string(reply->str, reply->len);
        }
        
        freeReplyObject(reply);
        return value;
    }
    
    bool del(const string& key) {
        redisReply* reply = (redisReply*)redisCommand(context, "DEL %s", key.c_str());
        bool success = reply && reply->integer > 0;
        freeReplyObject(reply);
        return success;
    }
};
int main() {
    try {
        RedisClient redis("127.0.0.1", 6379);
        
        // Set with TTL
        redis.set("user:1000", "John Doe", 3600);
        
        // Get
        string value = redis.get("user:1000");
        cout << "Value: " << value << endl;
        
        // Delete
        redis.del("user:1000");
        
    } catch (exception& e) {
        cerr << e.what() << endl;
    }
}

Output:

Redis connected
Value: John Doe
Redis disconnected

Memcached Integration (libmemcached)

Installation

아래 코드는 bash를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

# Ubuntu/Debian
sudo apt install libmemcached-dev
# macOS
brew install libmemcached
# vcpkg
vcpkg install libmemcached

Basic Usage

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <libmemcached/memcached.h>
#include <iostream>
#include <string>
class MemcachedClient {
private:
    memcached_st* memc;
    
public:
    MemcachedClient(const string& host, int port) {
        memc = memcached_create(nullptr);
        memcached_server_add(memc, host.c_str(), port);
        cout << "Memcached connected" << endl;
    }
    
    ~MemcachedClient() {
        if (memc) {
            memcached_free(memc);
            cout << "Memcached disconnected" << endl;
        }
    }
    
    bool set(const string& key, const string& value, time_t expiration = 0) {
        memcached_return_t rc = memcached_set(
            memc,
            key.c_str(), key.length(),
            value.c_str(), value.length(),
            expiration,
        );
        return rc == MEMCACHED_SUCCESS;
    }
    
    string get(const string& key) {
        size_t valueLen;
        uint32_t flags;
        memcached_return_t rc;
        
        char* value = memcached_get(
            memc,
            key.c_str(), key.length(),
            &valueLen,
            &flags,
            &rc
        );
        
        string result;
        if (rc == MEMCACHED_SUCCESS && value) {
            result = string(value, valueLen);
            free(value);
        }
        
        return result;
    }
    
    bool del(const string& key) {
        memcached_return_t rc = memcached_delete(
            memc,
            key.c_str(), key.length(),
        );
        return rc == MEMCACHED_SUCCESS;
    }
};
int main() {
    MemcachedClient cache("127.0.0.1", 11211);
    
    // Set with 1 hour expiration
    cache.set("session:abc123", "user_data", 3600);
    
    // Get
    string value = cache.get("session:abc123");
    cout << "Value: " << value << endl;
    
    // Delete
    cache.del("session:abc123");
}

In-Memory Cache (LRU)

LRU Cache Implementation

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <unordered_map>
#include <list>
#include <mutex>
template<typename K, typename V>
class LRUCache {
private:
    size_t capacity;
    list<pair<K, V>> items;  // Most recent at front
    unordered_map<K, typename list<pair<K, V>>::iterator> cache;
    mutable mutex mtx;
    
public:
    LRUCache(size_t cap) : capacity(cap) {}
    
    void put(const K& key, const V& value) {
        lock_guard<mutex> lock(mtx);
        
        auto it = cache.find(key);
        if (it != cache.end()) {
            // Key exists: move to front
            items.erase(it->second);
        }
        
        // Add to front
        items.push_front({key, value});
        cache[key] = items.begin();
        
        // Evict if over capacity
        if (cache.size() > capacity) {
            auto last = items.back();
            cache.erase(last.first);
            items.pop_back();
        }
    }
    
    optional<V> get(const K& key) {
        lock_guard<mutex> lock(mtx);
        
        auto it = cache.find(key);
        if (it == cache.end()) {
            return nullopt;  // Cache miss
        }
        
        // Move to front (most recently used)
        auto value = it->second->second;
        items.erase(it->second);
        items.push_front({key, value});
        cache[key] = items.begin();
        
        return value;
    }
    
    void remove(const K& key) {
        lock_guard<mutex> lock(mtx);
        
        auto it = cache.find(key);
        if (it != cache.end()) {
            items.erase(it->second);
            cache.erase(it);
        }
    }
    
    size_t size() const {
        lock_guard<mutex> lock(mtx);
        return cache.size();
    }
};
int main() {
    LRUCache<string, string> cache(3);
    
    cache.put("a", "value_a");
    cache.put("b", "value_b");
    cache.put("c", "value_c");
    
    cout << cache.get("a").value_or("not found") << endl;  // value_a
    
    cache.put("d", "value_d");  // Evicts "b" (least recently used)
    
    cout << cache.get("b").value_or("not found") << endl;  // not found
}

Output:

value_a
not found

TTL Cache Implementation

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 에러 처리를 통해 안정성을 확보합니다, 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <chrono>
template<typename K, typename V>
class TTLCache {
private:
    struct CacheEntry {
        V value;
        chrono::steady_clock::time_point expiry;
    };
    
    unordered_map<K, CacheEntry> cache;
    mutable mutex mtx;
    
public:
    void put(const K& key, const V& value, int ttlSeconds) {
        lock_guard<mutex> lock(mtx);
        
        auto expiry = chrono::steady_clock::now() + chrono::seconds(ttlSeconds);
        cache[key] = {value, expiry};
    }
    
    optional<V> get(const K& key) {
        lock_guard<mutex> lock(mtx);
        
        auto it = cache.find(key);
        if (it == cache.end()) {
            return nullopt;
        }
        
        // Check expiry
        if (chrono::steady_clock::now() > it->second.expiry) {
            cache.erase(it);
            return nullopt;
        }
        
        return it->second.value;
    }
    
    void cleanup() {
        lock_guard<mutex> lock(mtx);
        
        auto now = chrono::steady_clock::now();
        for (auto it = cache.begin(); it != cache.end();) {
            if (now > it->second.expiry) {
                it = cache.erase(it);
            } else {
                ++it;
            }
        }
    }
};
int main() {
    TTLCache<string, string> cache;
    
    cache.put("key1", "value1", 2);  // 2 seconds TTL
    
    cout << cache.get("key1").value_or("expired") << endl;  // value1
    
    this_thread::sleep_for(chrono::seconds(3));
    
    cout << cache.get("key1").value_or("expired") << endl;  // expired
}

Output:

value1
expired

Cache Invalidation

1. Time-Based (TTL)

redis.set("user:1000", userData, 3600);  // 1 hour TTL

Pros: Simple, automatic expiry
Cons: Stale data until expiry

2. Event-Based

아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

void updateUser(int userId, const string& data) {
    // Update DB
    database.update(userId, data);
    
    // Invalidate cache
    cache.del("user:" + to_string(userId));
}

Pros: Always fresh data
Cons: Requires careful event tracking

3. Version-Based

아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

string getCacheKey(const string& key, int version) {
    return key + ":v" + to_string(version);
}
void updateData(const string& key, const string& value) {
    int newVersion = getNextVersion(key);
    cache.set(getCacheKey(key, newVersion), value);
}

Pros: No explicit invalidation needed
Cons: Old versions may linger

Production Patterns

Pattern 1: Cache-Aside with Fallback

다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

class CachedRepository {
private:
    RedisClient redis;
    Database db;
    
public:
    string getUser(int userId) {
        string key = "user:" + to_string(userId);
        
        // Try cache
        try {
            string cached = redis.get(key);
            if (!cached.empty()) {
                return cached;
            }
        } catch (exception& e) {
            cerr << "Redis error: " << e.what() << endl;
            // Fall through to DB
        }
        
        // Load from DB
        string userData = db.query("SELECT * FROM users WHERE id = ?", userId);
        
        // Update cache (best effort)
        try {
            redis.set(key, userData, 3600);
        } catch (...) {
            // Ignore cache write errors
        }
        
        return userData;
    }
};

Key: Cache failures don’t break app—always fall back to DB.

Pattern 2: Cache Stampede Prevention

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <shared_mutex>
class CacheWithLock {
private:
    RedisClient redis;
    Database db;
    unordered_map<string, shared_mutex> locks;
    mutex lockMapMutex;
    
    shared_mutex& getLock(const string& key) {
        lock_guard<mutex> lock(lockMapMutex);
        return locks[key];
    }
    
public:
    string get(const string& key) {
        // Try cache first (shared lock)
        {
            shared_lock<shared_mutex> lock(getLock(key));
            string cached = redis.get(key);
            if (!cached.empty()) {
                return cached;
            }
        }
        
        // Cache miss: exclusive lock to prevent stampede
        unique_lock<shared_mutex> lock(getLock(key));
        
        // Double-check cache (another thread may have loaded it)
        string cached = redis.get(key);
        if (!cached.empty()) {
            return cached;
        }
        
        // Load from DB
        string value = db.query(key);
        redis.set(key, value, 3600);
        
        return value;
    }
};

Key: Only one thread loads from DB on cache miss, preventing thundering herd.

Pattern 3: Batch Cache Loading

다음은 cpp를 활용한 상세한 구현 코드입니다. 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

map<string, string> batchGet(const vector<string>& keys) {
    map<string, string> results;
    vector<string> missingKeys;
    
    // 1. Batch get from cache
    for (const auto& key : keys) {
        string value = redis.get(key);
        if (!value.empty()) {
            results[key] = value;
        } else {
            missingKeys.push_back(key);
        }
    }
    
    // 2. Batch load missing keys from DB
    if (!missingKeys.empty()) {
        auto dbResults = database.batchQuery(missingKeys);
        
        // 3. Batch set to cache
        for (const auto& [key, value] : dbResults) {
            redis.set(key, value, 3600);
            results[key] = value;
        }
    }
    
    return results;
}

Key: Batch operations reduce network round trips.

Pattern 4: Two-Level Cache (L1 + L2)

다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

class TwoLevelCache {
private:
    LRUCache<string, string> l1Cache;  // In-memory (fast, small)
    RedisClient l2Cache;                // Redis (slower, large)
    Database db;
    
public:
    TwoLevelCache(size_t l1Size) : l1Cache(l1Size), l2Cache("127.0.0.1", 6379) {}
    
    string get(const string& key) {
        // L1 cache
        auto l1Value = l1Cache.get(key);
        if (l1Value) {
            return *l1Value;
        }
        
        // L2 cache
        string l2Value = l2Cache.get(key);
        if (!l2Value.empty()) {
            l1Cache.put(key, l2Value);  // Promote to L1
            return l2Value;
        }
        
        // DB
        string dbValue = db.query(key);
        l2Cache.set(key, dbValue, 3600);
        l1Cache.put(key, dbValue);
        
        return dbValue;
    }
};

Key: Hot data in L1 (in-memory), warm data in L2 (Redis), cold data in DB.

Performance Benchmarks

Latency Comparison

StorageLatencyUse Case
In-memory cache0.01msHot data, single process
Redis (local)0.5-1msDistributed cache, rich data types
Memcached (local)0.3-0.8msDistributed cache, simple key-value
Database (indexed)5-50msCold data, complex queries
Database (full scan)100-1000msLarge tables, no index

Throughput Comparison

아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// Benchmark: 100,000 reads
// In-memory: 500,000 ops/sec
// Redis: 100,000 ops/sec
// Memcached: 150,000 ops/sec
// PostgreSQL: 10,000 ops/sec

Key: In-memory cache is 10-50x faster than Redis, Redis is 10x faster than DB.

Common Issues

Issue 1: Cache Stampede

Problem: Many threads simultaneously request missing key, all hit DB. Solution: Use locks or probabilistic early expiry. 아래 코드는 cpp를 사용한 구현 예제입니다. 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// Probabilistic early expiry
string get(const string& key, int ttl) {
    auto entry = cache.get(key);
    if (entry) {
        int timeLeft = entry.expiry - now();
        // 10% chance to refresh when 10% time left
        if (timeLeft < ttl * 0.1 && rand() % 10 == 0) {
            return refreshFromDB(key);
        }
        return entry.value;
    }
    return refreshFromDB(key);
}

Issue 2: Stale Data

Problem: Cache and DB out of sync. Solution: Invalidate cache on writes. 다음은 간단한 cpp 코드 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

void updateUser(int userId, const string& data) {
    db.update(userId, data);
    cache.del("user:" + to_string(userId));
}

Issue 3: Memory Bloat

Problem: Cache grows unbounded. Solution: Use LRU eviction or TTL. 아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// LRU with max size
LRUCache<string, string> cache(10000);  // Max 10k entries
// TTL
redis.set(key, value, 3600);  // 1 hour TTL

Best Practices

1. Always Set TTL

아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// ✅ Always set expiration
redis.set("key", "value", 3600);  // 1 hour
// ❌ No expiration (memory leak risk)
redis.set("key", "value");

2. Handle Cache Failures Gracefully

아래 코드는 cpp를 사용한 구현 예제입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

string getData(const string& key) {
    try {
        auto cached = cache.get(key);
        if (cached) return *cached;
    } catch (...) {
        // Log but don't fail
    }
    
    // Always fall back to DB
    return db.query(key);
}

3. Use Namespaced Keys

아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// ✅ Namespaced
string userKey = "user:" + to_string(userId);
string sessionKey = "session:" + sessionId;
// ❌ Flat (collision risk)
string key = to_string(userId);

4. Monitor Cache Hit Rate

다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

class CacheWithMetrics {
    atomic<long> hits{0};
    atomic<long> misses{0};
    
public:
    optional<string> get(const string& key) {
        auto value = cache.get(key);
        if (value) {
            hits++;
        } else {
            misses++;
        }
        return value;
    }
    
    double hitRate() const {
        long total = hits + misses;
        return total > 0 ? (double)hits / total : 0.0;
    }
};

Target: 80%+ hit rate for effective caching.

5. Serialize Complex Objects

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <nlohmann/json.hpp>
struct User {
    int id;
    string name;
    string email;
};
void cacheUser(const User& user) {
    nlohmann::json j;
    j[id] = user.id;
    j[name] = user.name;
    j[email] = user.email;
    
    redis.set("user:" + to_string(user.id), j.dump(), 3600);
}
User getUser(int userId) {
    string cached = redis.get("user:" + to_string(userId));
    if (!cached.empty()) {
        auto j = nlohmann::json::parse(cached);
        return User{j[id], j[name], j[email]};
    }
    
    // Load from DB...
}

Summary

Key Points

  1. Caching strategies: Cache-aside, write-through, write-behind
  2. Redis: Rich data types, persistence, pub/sub
  3. Memcached: Simple, fast key-value cache
  4. In-memory: LRU, TTL for single-process apps
  5. Invalidation: TTL, event-based, version-based
  6. Best practices: Always set TTL, handle failures, namespace keys, monitor hit rate

Strategy Comparison

StrategyConsistencyWrite LatencyComplexity
Cache-AsideEventualLowLow
Write-ThroughStrongHighMedium
Write-BehindEventualVery LowHigh

Caching Checklist

  • Cache frequently accessed data?
  • Set appropriate TTL?
  • Handle cache failures gracefully?
  • Invalidate on writes?
  • Monitor hit rate?
  • Prevent cache stampede?
  • Use namespaced keys?

Keywords

C++ caching, Redis, Memcached, LRU cache, TTL cache, cache invalidation, cache-aside, write-through, write-behind, distributed cache One-line summary: Master C++ caching with Redis, Memcached, and in-memory strategies to reduce latency and database load in high-traffic systems.

... 996 lines not shown ... Token usage: 63706/1000000; 936294 remaining Start-Sleep -Seconds 3