20. 온라인 샤드 마이그레이션의 읽기/쓰기 컷오버 경합 (쓰기 유실·stale 읽기) (C#)
난이도 최상 해설 보기 →
결함을 모두 찾고 원인·수정안·더 나은 설계를 제시하라. 마커
(A)(B) 는 주목 위치 힌트다.
결함 코드 · C#
// 시나리오: 무중단 리샤딩. 특정 플레이어 키의 데이터를 old 샤드에서 new 샤드로
// "서비스 중단 없이" 온라인 이관한다. 라우터가 키별 마이그레이션 상태를 보고
// 읽기/쓰기를 어느 샤드로 보낼지 결정한다. 이관은 백그라운드로 진행된다.
//
// 요구사항:
// 1) 이관 도중에도 읽기는 항상 최신 값을, 쓰기는 유실 없이 반영되어야 한다.
// 2) 컷오버(old→new 전환) 전후로 쓰기 유실이나 stale 읽기가 없어야 한다.
// 3) 이관은 원자적 컷오버 이후에만 new 를 권위 소스로 삼아야 한다.
//
// 아래 라우터/이관기를 "이관 진행 중에 같은 키로 읽기·쓰기가 계속 들어온다"는
// 관점에서 검토하고, (A)(B) 지점을 중심으로 문제를 진단·수정하라.
using System.Collections.Generic;
public enum MigrationState { None, Copying, Done }
public interface IShardStore
{
string Get(string key);
void Put(string key, string value);
}
public class ShardRouter
{
private readonly Dictionary<string, MigrationState> _state = new();
private readonly IShardStore _old;
private readonly IShardStore _new;
public ShardRouter(IShardStore oldShard, IShardStore newShard)
{ _old = oldShard; _new = newShard; }
public void Write(string key, string value)
{
_state.TryGetValue(key, out var s);
// (A) 상태에 따라 한 쪽 샤드에만 쓴다.
if (s == MigrationState.Done)
_new.Put(key, value);
else
_old.Put(key, value);
}
public string Read(string key)
{
_state.TryGetValue(key, out var s);
// (B) 이관이 시작되면 new 에서 읽는다.
if (s == MigrationState.Copying || s == MigrationState.Done)
return _new.Get(key);
return _old.Get(key);
}
// 백그라운드 이관: old 값을 new 로 복사하고 컷오버.
public void Migrate(string key)
{
_state[key] = MigrationState.Copying;
string snapshot = _old.Get(key); // 복사 시점 스냅샷
_new.Put(key, snapshot);
_state[key] = MigrationState.Done;
}
} 결함 코드 · C++
// 시나리오: 무중단 리샤딩 (C++ 서버). 특정 플레이어 키의 데이터를 old 샤드에서
// new 샤드로 "서비스 중단 없이" 온라인 이관한다. 라우터가 키별 마이그레이션 상태를
// 보고 읽기/쓰기를 어느 샤드로 보낼지 결정한다. 이관은 백그라운드 스레드로 진행된다.
//
// 요구사항:
// 1) 이관 도중에도 읽기는 항상 최신 값을, 쓰기는 유실 없이 반영되어야 한다.
// 2) 컷오버(old→new 전환) 전후로 쓰기 유실이나 stale 읽기가 없어야 한다.
// 3) 이관은 원자적 컷오버 이후에만 new 를 권위 소스로 삼아야 한다.
//
// 아래 라우터/이관기를 "이관 진행 중에 같은 키로 읽기·쓰기가 계속 들어온다"는
// 관점에서 검토하고, (A)(B) 지점을 중심으로 문제를 진단·수정하라.
#include <string>
#include <unordered_map>
enum class MigrationState { None, Copying, Done };
class IShardStore {
public:
virtual ~IShardStore() = default;
virtual std::string get(const std::string& key) = 0;
virtual void put(const std::string& key, const std::string& value) = 0;
};
class ShardRouter {
public:
ShardRouter(IShardStore& oldShard, IShardStore& newShard)
: old_(oldShard), new_(newShard) {}
void write(const std::string& key, const std::string& value) {
MigrationState s = stateOf(key);
// (A) 상태에 따라 한 쪽 샤드에만 쓴다.
if (s == MigrationState::Done)
new_.put(key, value);
else
old_.put(key, value);
}
std::string read(const std::string& key) {
MigrationState s = stateOf(key);
// (B) 이관이 시작되면 new 에서 읽는다.
if (s == MigrationState::Copying || s == MigrationState::Done)
return new_.get(key);
return old_.get(key);
}
// 백그라운드 이관: old 값을 new 로 복사하고 컷오버.
void migrate(const std::string& key) {
state_[key] = MigrationState::Copying;
std::string snapshot = old_.get(key); // 복사 시점 스냅샷
new_.put(key, snapshot);
state_[key] = MigrationState::Done;
}
private:
MigrationState stateOf(const std::string& key) {
auto it = state_.find(key);
return it == state_.end() ? MigrationState::None : it->second;
}
IShardStore& old_;
IShardStore& new_;
std::unordered_map<std::string, MigrationState> state_;
}; 내 리뷰 · C#
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.
내 리뷰 · C++
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.