1. 글로벌 거래소를 여러 지역 서버가 공유할 때의 정합성
난이도 최상 해설 보기 →
결함을 모두 찾고 원인·수정안·더 나은 설계를 제시하라. 마커
(A)(B) 는 주목 위치 힌트다.
결함 코드 · C#
// ============================================================================
// [코드리뷰 문제] C# - 글로벌 거래소를 여러 지역 서버가 공유할 때의 정합성
// ----------------------------------------------------------------------------
// 시나리오 (서버-서버 / 분산):
// - 하나의 "글로벌 거래소"를 여러 지역(region) 게임서버가 공유한다. 같은 매물(listing)을
// 서로 다른 지역의 플레이어가 거의 동시에 구매할 수 있다.
// - 각 지역 서버는 지연을 줄이려고 매물 재고(remainingQty)를 로컬에 캐시해 두고, 구매
// 가능 여부를 로컬 캐시로 먼저 판단한 뒤 중앙 거래소(CentralExchange)에 차감을 요청한다.
// - 중앙 거래소는 모든 지역의 차감을 받는 단일 권위(authority)다.
//
// 요구사항:
// - 한 매물의 재고는 여러 지역에서 동시에 구매가 들어와도 절대 초과 판매(oversell)되면 안 된다.
// - "구매 가능 검사 → 재고 차감 → 대금/아이템 지급"은 매물 단위로 원자적이어야 한다.
// - 네트워크 지연/지역 캐시 불일치가 있어도 총 판매량은 재고를 넘지 않아야 한다.
//
// 과제:
// 이 코드의 잠재적 문제를 모두 찾고, 왜/어떻게 oversell·구매 유실·이중 차감이 분산 환경에서
// 발생하는지 설명하고, 수정안과 더 나은 설계를 제시하라.
// (먼저 직접 리뷰를 적은 뒤 answer.md 와 대조할 것)
// ============================================================================
using System;
using System.Collections.Generic;
// 중앙 거래소(단일 권위) — 모든 지역이 RPC 로 호출한다고 가정
public class CentralExchange
{
private readonly Dictionary<long, int> _remaining = new(); // listingId -> 남은 수량
public int GetRemaining(long listingId)
=> _remaining.TryGetValue(listingId, out var q) ? q : 0;
// 재고 차감 요청 (여러 지역에서 동시 호출)
public bool Decrement(long listingId, int qty)
{
int cur = _remaining.TryGetValue(listingId, out var q) ? q : 0;
// (A) 검사와 차감이 분리된 비원자 연산 (네트워크 RPC 경계 너머 동시 호출)
if (cur < qty) return false;
_remaining[listingId] = cur - qty;
return true;
}
}
// 지역 서버 — 로컬 재고 캐시로 먼저 판단
public class RegionExchangeProxy
{
private readonly CentralExchange _central;
private readonly Dictionary<long, int> _localCache = new(); // listingId -> 캐시된 남은 수량
public RegionExchangeProxy(CentralExchange central) { _central = central; }
public bool Buy(long buyerId, long listingId, int qty)
{
// (B) 로컬 캐시 재고로 구매 가능 판단
int cached = _localCache.TryGetValue(listingId, out var q) ? q : RefreshCache(listingId);
if (cached < qty) return false;
// (C) 캐시를 먼저 줄이고 대금/아이템 지급까지 진행한 뒤, 중앙엔 사후 차감 요청
_localCache[listingId] = cached - qty;
GrantItemAndCharge(buyerId, listingId, qty);
_central.Decrement(listingId, qty); // (C) 반환값 무시 — 실패해도 이미 지급됨
return true;
}
private int RefreshCache(long listingId)
{
int r = _central.GetRemaining(listingId);
_localCache[listingId] = r;
return r;
}
private void GrantItemAndCharge(long buyerId, long listingId, int qty) { /* 지급/과금 */ }
} 결함 코드 · C++
// ============================================================================
// [코드리뷰 문제] C++ - 글로벌 거래소를 여러 지역 서버가 공유할 때의 정합성
// ----------------------------------------------------------------------------
// 시나리오 (서버-서버 / 분산):
// - 하나의 "글로벌 거래소"를 여러 지역(region) 게임서버가 공유한다. 같은 매물을 서로
// 다른 지역의 플레이어가 거의 동시에 구매할 수 있다.
// - 각 지역 서버는 지연을 줄이려고 매물 재고를 로컬에 캐시하고, 구매 가능 여부를 로컬
// 캐시로 먼저 판단한 뒤 중앙 거래소(CentralExchange)에 차감을 요청한다.
// - 중앙 거래소는 모든 지역의 차감을 받는 단일 권위다.
//
// 요구사항:
// - 한 매물의 재고는 여러 지역 동시 구매에도 초과 판매(oversell)되면 안 된다.
// - "구매 가능 검사 → 재고 차감 → 지급"은 매물 단위로 원자적이어야 한다.
// - 네트워크 지연/지역 캐시 불일치가 있어도 총 판매량은 재고를 넘지 않아야 한다.
//
// 과제:
// 이 코드의 잠재적 문제를 모두 찾고, 왜/어떻게 oversell·이중 지급이 분산 환경에서 발생하는지
// 설명하고, 수정안과 더 나은 설계를 제시하라.
// (먼저 직접 리뷰를 적은 뒤 answer.md 와 대조할 것)
// ============================================================================
#include <cstdint>
#include <unordered_map>
// 중앙 거래소(단일 권위) — 모든 지역이 RPC 로 호출한다고 가정
class CentralExchange {
public:
int getRemaining(int64_t listingId) {
auto it = remaining_.find(listingId);
return it == remaining_.end() ? 0 : it->second;
}
// 재고 차감 요청 (여러 지역에서 동시 호출)
bool decrement(int64_t listingId, int qty) {
int cur = getRemaining(listingId);
// (A) 검사와 차감이 분리된 비원자 연산 (락 없음, 동시 호출)
if (cur < qty) return false;
remaining_[listingId] = cur - qty;
return true;
}
private:
std::unordered_map<int64_t, int> remaining_;
};
// 지역 서버 — 로컬 재고 캐시로 먼저 판단
class RegionExchangeProxy {
public:
explicit RegionExchangeProxy(CentralExchange& central) : central_(central) {}
bool buy(int64_t buyerId, int64_t listingId, int qty) {
// (B) 로컬 캐시 재고로 구매 가능 판단
int cached;
auto it = localCache_.find(listingId);
cached = (it != localCache_.end()) ? it->second : refreshCache(listingId);
if (cached < qty) return false;
// (C) 캐시를 먼저 줄이고 지급까지 한 뒤, 중앙엔 사후 차감 요청(반환 무시)
localCache_[listingId] = cached - qty;
grantItemAndCharge(buyerId, listingId, qty);
central_.decrement(listingId, qty); // (C) 반환값 무시 — 실패해도 이미 지급됨
return true;
}
private:
int refreshCache(int64_t listingId) {
int r = central_.getRemaining(listingId);
localCache_[listingId] = r;
return r;
}
void grantItemAndCharge(int64_t, int64_t, int) { /* 지급/과금 */ }
CentralExchange& central_;
std::unordered_map<int64_t, int> localCache_;
}; 내 리뷰 · C#
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.
내 리뷰 · C++
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.