[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
| Storage | Latency | Use Case |
|---|---|---|
| In-memory cache | 0.01ms | Hot data, single process |
| Redis (local) | 0.5-1ms | Distributed cache, rich data types |
| Memcached (local) | 0.3-0.8ms | Distributed cache, simple key-value |
| Database (indexed) | 5-50ms | Cold data, complex queries |
| Database (full scan) | 100-1000ms | Large 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
- Caching strategies: Cache-aside, write-through, write-behind
- Redis: Rich data types, persistence, pub/sub
- Memcached: Simple, fast key-value cache
- In-memory: LRU, TTL for single-process apps
- Invalidation: TTL, event-based, version-based
- Best practices: Always set TTL, handle failures, namespace keys, monitor hit rate
Strategy Comparison
| Strategy | Consistency | Write Latency | Complexity |
|---|---|---|---|
| Cache-Aside | Eventual | Low | Low |
| Write-Through | Strong | High | Medium |
| Write-Behind | Eventual | Very Low | High |
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?
Related Articles
- C++ Redis Integration Guide
- C++ Performance Optimization Guide
- C++ Query Optimization Complete Guide
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.