10. Cache-Aside 읽기 재채움 vs 쓰기 무효화 경합(stale 부활) (C#)
난이도 최상 해설 보기 →
결함을 모두 찾고 원인·수정안·더 나은 설계를 제시하라. 마커
(A)(B) 는 주목 위치 힌트다.
결함 코드 · C#
// 시나리오: 캐시 어사이드(Cache-Aside / lazy loading) 패턴으로 플레이어 프로필을 읽는다.
// 읽기는 캐시(Redis 류)를 먼저 보고, 미스면 DB 에서 읽어 캐시에 채운다.
// 쓰기는 DB 를 갱신한 뒤 캐시를 무효화(삭제)한다.
//
// 요구사항:
// - GetProfile: 캐시 히트면 그 값을, 미스면 DB 값을 읽어 캐시에 채우고 반환한다.
// - UpdateProfile: DB 를 갱신하고 캐시를 무효화한다.
// - 읽기와 쓰기는 서로 다른 요청 스레드에서 동시에 들어올 수 있다.
// - 캐시는 결국 DB 와 수렴해야 한다(오래도록 옛 값을 주면 안 됨).
//
// 과제: (A)(B) 지점을 검토하라. 읽기와 쓰기가 특정 순서로 겹치면 캐시에 옛 값이
// "부활"해 TTL 이 만료될 때까지 남을 수 있다. 어떤 인터리빙에서 그런지 밝히고 고쳐라.
using System;
public interface ICache
{
bool TryGet(string key, out string val);
void Set(string key, string val, TimeSpan ttl);
void Delete(string key);
}
public interface IDb
{
string Read(string key);
void Write(string key, string val);
}
public sealed class ProfileService
{
private readonly ICache _cache;
private readonly IDb _db;
private static readonly TimeSpan Ttl = TimeSpan.FromMinutes(10);
public ProfileService(ICache cache, IDb db) { _cache = cache; _db = db; }
public string GetProfile(string playerId)
{
if (_cache.TryGet(playerId, out var cached))
return cached;
string v = _db.Read(playerId); // 캐시 미스 → DB 조회
_cache.Set(playerId, v, Ttl); // (A) 읽은 값을 캐시에 채움
return v;
}
public void UpdateProfile(string playerId, string val)
{
_db.Write(playerId, val); // DB 먼저 갱신
_cache.Delete(playerId); // (B) 그 다음 캐시 무효화
}
} 결함 코드 · C++
// 시나리오: 캐시 어사이드(Cache-Aside / lazy loading) 패턴으로 플레이어 프로필을 읽는
// C++ 서비스. 읽기는 캐시(Redis 류)를 먼저 보고 미스면 DB 에서 읽어 캐시에 채운다.
// 쓰기는 DB 를 갱신한 뒤 캐시를 무효화(삭제)한다.
//
// 요구사항:
// - getProfile: 캐시 히트면 그 값을, 미스면 DB 값을 읽어 캐시에 채우고 반환한다.
// - updateProfile: DB 를 갱신하고 캐시를 무효화한다.
// - 읽기·쓰기는 서로 다른 워커 스레드에서 동시에 들어올 수 있다.
// - 캐시는 결국 DB 와 수렴해야 한다(오래도록 옛 값을 주면 안 됨).
//
// 과제: (A)(B) 지점을 검토하라. 읽기와 쓰기가 특정 순서로 겹치면 캐시에 옛 값이
// "부활"해 TTL 만료까지 남을 수 있다. 어떤 인터리빙에서 그런지 밝히고 고쳐라.
#include <string>
#include <chrono>
struct Cache {
bool get(const std::string& key, std::string& out); // 히트면 true
void set(const std::string& key, const std::string& val,
std::chrono::seconds ttl);
void del(const std::string& key);
};
struct Db {
std::string read(const std::string& key);
void write(const std::string& key, const std::string& val);
};
class ProfileService {
public:
ProfileService(Cache* c, Db* d) : cache_(c), db_(d) {}
std::string getProfile(const std::string& playerId) {
std::string cached;
if (cache_->get(playerId, cached))
return cached;
std::string v = db_->read(playerId); // 캐시 미스 → DB 조회
cache_->set(playerId, v, std::chrono::seconds(600)); // (A) 읽은 값을 캐시에 채움
return v;
}
void updateProfile(const std::string& playerId, const std::string& val) {
db_->write(playerId, val); // DB 먼저 갱신
cache_->del(playerId); // (B) 그 다음 캐시 무효화
}
private:
Cache* cache_;
Db* db_;
}; 내 리뷰 · C#
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.
내 리뷰 · C++
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.