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#
내 답안 · 자동 저장

작성 후 위 해설 보기에서 모범 해설과 대조하세요.