4. 지역 간 캐시 무효화 전파의 순서·유실·재채움 경합
난이도 최상 해설 보기 →
결함을 모두 찾고 원인·수정안·더 나은 설계를 제시하라. 마커
(A)(B) 는 주목 위치 힌트다.
결함 코드 · C#
// ============================================================================
// [코드리뷰 문제] C# - 지역 서버 간 캐시 무효화 전파 (pub/sub)
// ----------------------------------------------------------------------------
// 시나리오 (서버-서버 · 분산/캐시 일관성):
// - 플레이어 프로필을 여러 지역(region) 서버가 각자 로컬 캐시에 들고 읽는다.
// - 한 지역에서 프로필이 바뀌면, 무효화 메시지를 pub/sub 으로 발행해 다른 지역들이
// 자기 로컬 캐시에서 그 항목을 지우게 한다(다음 읽기 때 DB 에서 다시 채움).
// - DB 는 지역별 읽기 복제본을 두고 있어 복제에 지연이 있을 수 있다.
// - pub/sub 은 best-effort(메시지 유실/순서 역전 가능).
//
// 과제:
// 이 코드의 잠재적 문제를 모두 찾고, 한 지역에서 프로필을 바꾼 직후 다른 지역의 캐시가
// 어떤 값을 갖게 되는지(특히 무효화·재읽기·캐시 채움의 순서) 설명하라. 무효화 메시지가
// 유실되거나 순서가 뒤바뀌면 어떻게 되는지도 논하라. 수정안과 더 나은 설계를 제시하라.
// (먼저 직접 리뷰를 적은 뒤 answer.md 와 대조할 것)
// ============================================================================
using System.Threading.Tasks;
public class Profile { public string Name; /* ... */ }
public interface ILocalCache
{
bool TryGet(long playerId, out Profile p);
void Set(long playerId, Profile p);
void Remove(long playerId);
}
public interface IProfileDb
{
Task UpdateProfileAsync(long playerId, Profile p); // 마스터에 쓰기(커밋)
Task<Profile> GetProfileAsync(long playerId); // 지역 복제본에서 읽기
}
public interface IPubSub
{
void Publish(string channel, long playerId);
void Subscribe(string channel, System.Action<long> handler);
}
public class RegionCache
{
private readonly ILocalCache _local;
private readonly IProfileDb _db;
private readonly IPubSub _bus;
public RegionCache(ILocalCache local, IProfileDb db, IPubSub bus)
{
_local = local; _db = db; _bus = bus;
_bus.Subscribe("profile.invalidate", OnInvalidate);
}
public async Task UpdateProfileAsync(long playerId, Profile p)
{
_bus.Publish("profile.invalidate", playerId); // (A) 다른 지역에 무효화 알림
await _db.UpdateProfileAsync(playerId, p); // (B) 마스터 커밋
_local.Remove(playerId); // (C) 내 로컬 캐시 제거
}
public async Task<Profile> GetProfileAsync(long playerId)
{
if (_local.TryGet(playerId, out var hit)) // (D) 로컬 히트
return hit;
var fromDb = await _db.GetProfileAsync(playerId);// (E) 복제본에서 읽기
_local.Set(playerId, fromDb); // (F) 캐시 채움
return fromDb;
}
private void OnInvalidate(long playerId)
{
_local.Remove(playerId); // (G) 무효화 수신 → 제거
}
} 결함 코드 · C++
// ============================================================================
// [코드리뷰 문제] C++ - 지역 서버 간 캐시 무효화 전파 (pub/sub)
// ----------------------------------------------------------------------------
// 시나리오 (서버-서버 · 분산/캐시 일관성):
// - 플레이어 프로필을 여러 지역(region) 서버가 각자 로컬 캐시에 들고 읽는다.
// - 한 지역에서 프로필이 바뀌면, 무효화 메시지를 pub/sub 으로 발행해 다른 지역들이
// 자기 로컬 캐시에서 그 항목을 지우게 한다(다음 읽기 때 DB 에서 다시 채움).
// - DB 는 지역별 읽기 복제본을 두고 있어 복제에 지연이 있을 수 있다.
// - pub/sub 은 best-effort(메시지 유실/순서 역전 가능).
//
// 과제:
// 이 코드의 잠재적 문제를 모두 찾고, 한 지역에서 프로필을 바꾼 직후 다른 지역의 캐시가
// 어떤 값을 갖게 되는지(무효화·재읽기·캐시 채움의 순서) 설명하라. 무효화 메시지가
// 유실되거나 순서가 뒤바뀌면 어떻게 되는지도 논하라. 수정안과 더 나은 설계를 제시하라.
// (먼저 직접 리뷰를 적은 뒤 answer.md 와 대조할 것)
// ============================================================================
#include <cstdint>
#include <string>
#include <unordered_map>
#include <mutex>
#include <functional>
#include <optional>
struct Profile { std::string name; /* ... */ };
class IProfileDb {
public:
virtual void updateProfile(int64_t playerId, const Profile& p) = 0; // 마스터 커밋
virtual Profile getProfile(int64_t playerId) = 0; // 복제본 읽기
virtual ~IProfileDb() = default;
};
class IPubSub {
public:
virtual void publish(const std::string& ch, int64_t playerId) = 0;
virtual void subscribe(const std::string& ch, std::function<void(int64_t)> h) = 0;
virtual ~IPubSub() = default;
};
class RegionCache {
public:
RegionCache(IProfileDb& db, IPubSub& bus) : db_(db), bus_(bus) {
bus_.subscribe("profile.invalidate", [this](int64_t id){ onInvalidate(id); });
}
void updateProfile(int64_t playerId, const Profile& p) {
bus_.publish("profile.invalidate", playerId); // (A) 다른 지역에 무효화 알림
db_.updateProfile(playerId, p); // (B) 마스터 커밋
std::lock_guard<std::mutex> lk(mtx_);
cache_.erase(playerId); // (C) 내 로컬 캐시 제거
}
Profile getProfile(int64_t playerId) {
{
std::lock_guard<std::mutex> lk(mtx_);
auto it = cache_.find(playerId);
if (it != cache_.end()) return it->second; // (D) 로컬 히트
}
Profile fromDb = db_.getProfile(playerId); // (E) 복제본 읽기
std::lock_guard<std::mutex> lk(mtx_);
cache_[playerId] = fromDb; // (F) 캐시 채움
return fromDb;
}
private:
void onInvalidate(int64_t playerId) {
std::lock_guard<std::mutex> lk(mtx_);
cache_.erase(playerId); // (G) 무효화 수신 → 제거
}
IProfileDb& db_;
IPubSub& bus_;
std::mutex mtx_;
std::unordered_map<int64_t, Profile> cache_;
}; 내 리뷰 · C#
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.
내 리뷰 · C++
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.