17. 읽기 복제본 지연과 read-your-writes 일관성 위반 (C#)

난이도 하 해설 보기 →

결함을 모두 찾고 원인·수정안·더 나은 설계를 제시하라. 마커 (A)(B) 는 주목 위치 힌트다.

결함 코드 · C#
// 시나리오: 게임 서버 — 읽기 부하 분산을 위한 읽기 복제본(read replica)
//
// DB 는 쓰기 전용 프라이머리 1대 + 읽기 전용 복제본 여러 대로 구성된다.
// 복제는 비동기(수십~수백 ms 지연 가능). 쓰기는 프라이머리로, 읽기는 부하를 나누기
// 위해 복제본으로 라우팅한다.
//
// 아래는 "재화 충전 후 잔액 표시" 흐름이다.
//  - ChargeGold: 결제 후 골드를 더한다(프라이머리에 UPDATE).
//  - GetBalanceForResponse: 클라이언트에게 돌려줄 최신 잔액을 조회한다.
//
// 요구사항:
//  - 사용자가 방금 충전한 뒤 보는 잔액은 반드시 충전이 반영된 값이어야 한다.
//  - 일반적인 조회는 복제본으로 분산해 프라이머리 부하를 줄인다.
//
// 아래 코드를 검토하라.

using System.Threading.Tasks;

public interface IDb
{
    Task ExecuteAsync(string sql, params object[] args);
    Task<long> QueryScalarAsync(string sql, params object[] args);
}

public sealed class GoldService
{
    private readonly IDb _primary;   // 쓰기 전용
    private readonly IDb _replica;   // 읽기 전용(비동기 복제, 지연 있음)

    public GoldService(IDb primary, IDb replica)
    {
        _primary = primary;
        _replica = replica;
    }

    public async Task ChargeGold(long userId, long amount)
    {
        await _primary.ExecuteAsync(
            "UPDATE users SET gold = gold + @amt WHERE id = @id", amount, userId); // (A)
    }

    // 충전 직후 클라이언트에게 보여줄 잔액
    public async Task<long> GetBalanceForResponse(long userId)
    {
        return await _replica.QueryScalarAsync(
            "SELECT gold FROM users WHERE id = @id", userId);   // (B)
    }

    // 결제 처리 후 응답까지 한 번에
    public async Task<long> ChargeAndReturnBalance(long userId, long amount)
    {
        await ChargeGold(userId, amount);            // (C) 프라이머리에 반영
        return await GetBalanceForResponse(userId);  // (D) 복제본에서 조회
    }
}
내 리뷰 · C#
내 답안 · 자동 저장

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