1. 마스터-읽기복제본 복제 지연(read-your-writes 위반)
난이도 상 해설 보기 →
결함을 모두 찾고 원인·수정안·더 나은 설계를 제시하라. 마커
(A)(B) 는 주목 위치 힌트다.
결함 코드 · C#
// ============================================================================
// [코드리뷰 문제] C# - 마스터-읽기복제본 복제 지연(read-your-writes 위반)
// ----------------------------------------------------------------------------
// 시나리오 (영속화/DB/캐시 · 서버-서버):
// - DB는 쓰기 전용 마스터(master) 1대 + 읽기 전용 복제본(read replica) N대로 구성된다.
// 쓰기는 마스터로, 읽기는 부하 분산을 위해 복제본으로 보낸다. 복제는 비동기라
// 마스터→복제본 사이에 수십~수백 ms 의 "복제 지연(replication lag)" 이 있다.
// - 플레이어가 닉네임/프로필/장착을 변경(마스터 write)한 직후, 같은 요청 흐름에서
// 그 값을 다시 읽어 클라에 응답하거나 후속 처리를 한다.
// - 읽은 값은 잠시 캐시에 담아 재사용한다.
//
// 요구사항:
// - "방금 자신이 쓴 값"은 직후 읽기에서 반드시 보여야 한다(read-your-writes 보장).
// - 복제 지연으로 인한 stale(옛) 값을 캐시에 채워 한동안 재사용하면 안 된다.
// - 일반 읽기는 복제본으로 분산하되, 방금-쓴-경우만 정확성을 지켜야 한다.
//
// 과제:
// 이 코드의 잠재적 문제를 모두 찾고, 왜/어떻게 방금 쓴 값을 못 읽거나(stale) 그 stale
// 값이 캐시에 굳어버리는지 설명하고, 수정안과 더 나은 설계를 제시하라.
// (먼저 직접 리뷰를 적은 뒤 answer.md 와 대조할 것)
// ============================================================================
using System;
using System.Collections.Generic;
public interface IDb
{
void WriteMaster(long playerId, string key, string value); // 마스터에 쓰기
string ReadReplica(long playerId, string key); // 복제본에서 읽기(지연 가능)
string ReadMaster(long playerId, string key); // 마스터에서 읽기(항상 최신)
}
public class ProfileService
{
private readonly IDb _db;
private readonly Dictionary<long, Dictionary<string, string>> _cache = new();
public ProfileService(IDb db) { _db = db; }
// 프로필 변경 후 변경된 값을 다시 읽어 응답에 사용
public string UpdateAndEcho(long playerId, string key, string value)
{
// 마스터에 쓰기
_db.WriteMaster(playerId, key, value);
// (A) 쓰자마자 복제본에서 다시 읽음 — 복제 지연으로 옛 값일 수 있음
string readBack = _db.ReadReplica(playerId, key);
// (B) 읽어온 값을 캐시에 저장(이후 GetProfile 이 재사용)
if (!_cache.TryGetValue(playerId, out var map))
{
map = new Dictionary<string, string>();
_cache[playerId] = map;
}
map[key] = readBack;
return readBack;
}
// 일반 조회: 캐시 우선, 없으면 복제본
public string GetProfile(long playerId, string key)
{
if (_cache.TryGetValue(playerId, out var map) && map.TryGetValue(key, out var v))
return v;
// (C) 캐시 미스 시 복제본에서 읽어 캐시 채움 — 그 사이 쓰기와 경합 시 stale 고착
string fromReplica = _db.ReadReplica(playerId, key);
return fromReplica;
}
} 결함 코드 · C++
// ============================================================================
// [코드리뷰 문제] C++ - 마스터-읽기복제본 복제 지연(read-your-writes 위반)
// ----------------------------------------------------------------------------
// 시나리오 (영속화/DB/캐시 · 서버-서버):
// - DB는 쓰기 전용 마스터 1대 + 읽기 전용 복제본 N대. 쓰기는 마스터로, 읽기는 부하
// 분산을 위해 복제본으로 보낸다. 복제는 비동기라 수십~수백 ms 의 복제 지연이 있다.
// - 플레이어가 프로필을 변경(마스터 write)한 직후, 같은 흐름에서 그 값을 다시 읽어
// 응답/후속 처리에 쓴다. 읽은 값은 잠시 캐시에 담아 재사용한다.
//
// 요구사항:
// - "방금 자신이 쓴 값"은 직후 읽기에서 반드시 보여야 한다(read-your-writes 보장).
// - 복제 지연으로 인한 stale 값을 캐시에 채워 재사용하면 안 된다.
// - 일반 읽기는 복제본으로 분산하되, 방금-쓴-경우만 정확성을 지켜야 한다.
//
// 과제:
// 이 코드의 잠재적 문제를 모두 찾고, 왜/어떻게 stale 읽기/캐시 고착이 생기는지 설명하고,
// 수정안과 더 나은 설계를 제시하라. (먼저 직접 리뷰를 적은 뒤 answer.md 와 대조할 것)
// ============================================================================
#include <string>
#include <cstdint>
#include <unordered_map>
struct IDb {
virtual ~IDb() = default;
virtual void writeMaster(int64_t playerId, const std::string& key, const std::string& value) = 0;
virtual std::string readReplica(int64_t playerId, const std::string& key) = 0; // 지연 가능
virtual std::string readMaster (int64_t playerId, const std::string& key) = 0; // 항상 최신
};
class ProfileService {
public:
explicit ProfileService(IDb& db) : db_(db) {}
// 프로필 변경 후 변경된 값을 다시 읽어 응답에 사용
std::string updateAndEcho(int64_t playerId, const std::string& key, const std::string& value) {
db_.writeMaster(playerId, key, value);
// (A) 쓰자마자 복제본에서 다시 읽음 — 복제 지연으로 옛 값일 수 있음
std::string readBack = db_.readReplica(playerId, key);
// (B) 읽어온 값을 캐시에 저장(이후 getProfile 이 재사용)
cache_[playerId][key] = readBack;
return readBack;
}
// 일반 조회: 캐시 우선, 없으면 복제본
std::string getProfile(int64_t playerId, const std::string& key) {
auto pit = cache_.find(playerId);
if (pit != cache_.end()) {
auto kit = pit->second.find(key);
if (kit != pit->second.end()) return kit->second;
}
// (C) 캐시 미스 시 복제본에서 읽어 캐시 채움 — 쓰기와 경합 시 stale 고착
std::string fromReplica = db_.readReplica(playerId, key);
return fromReplica;
}
private:
IDb& db_;
std::unordered_map<int64_t, std::unordered_map<std::string, std::string>> cache_;
}; 내 리뷰 · C#
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.
내 리뷰 · C++
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.