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++
// 시나리오: 게임 서버 — 읽기 부하 분산을 위한 읽기 복제본(read replica) (C++)
//
// DB 는 쓰기 전용 프라이머리 1대 + 읽기 전용 복제본 여러 대로 구성된다.
// 복제는 비동기(수십~수백 ms 지연 가능). 쓰기는 프라이머리로, 읽기는 부하 분산을
// 위해 복제본으로 라우팅한다.
//
// 아래는 "재화 충전 후 잔액 표시" 흐름이다.
// - ChargeGold: 결제 후 골드를 더한다(프라이머리 UPDATE).
// - GetBalanceForResponse: 클라이언트에게 돌려줄 최신 잔액을 조회한다.
//
// 요구사항:
// - 방금 충전한 사용자가 보는 잔액은 반드시 충전이 반영된 값이어야 한다.
// - 일반 조회는 복제본으로 분산해 프라이머리 부하를 줄인다.
//
// 아래 코드를 검토하라.
#include <cstdint>
#include <string>
struct IDb {
virtual void Execute(const std::string& sql, int64_t a, int64_t b) = 0;
virtual int64_t QueryScalar(const std::string& sql, int64_t id) = 0;
virtual ~IDb() = default;
};
class GoldService {
public:
GoldService(IDb& primary, IDb& replica) : primary_(primary), replica_(replica) {}
void ChargeGold(int64_t userId, int64_t amount) {
primary_.Execute("UPDATE users SET gold = gold + ? WHERE id = ?", // (A)
amount, userId);
}
int64_t GetBalanceForResponse(int64_t userId) {
return replica_.QueryScalar("SELECT gold FROM users WHERE id = ?", // (B)
userId);
}
int64_t ChargeAndReturnBalance(int64_t userId, int64_t amount) {
ChargeGold(userId, amount); // (C) 프라이머리 반영
return GetBalanceForResponse(userId); // (D) 복제본 조회
}
private:
IDb& primary_;
IDb& replica_;
}; 내 리뷰 · C#
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.
내 리뷰 · C++
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.