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

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