16. 존 간 플레이어/아이템 이전 (서버-서버 분산 트랜잭션)
난이도 최상 해설 보기 →
결함을 모두 찾고 원인·수정안·더 나은 설계를 제시하라. 마커
(A)(B) 는 주목 위치 힌트다.
결함 코드 · C#
// ============================================================================
// [코드리뷰 문제] C# - 존 간 플레이어/아이템 이전 (서버-서버 분산 트랜잭션)
// ----------------------------------------------------------------------------
// 시나리오 (서버-서버 / 분산):
// - 플레이어가 존 서버 A 에서 존 서버 B 로 이동(심리스 경계/포탈/채널이동)할 때,
// 캐릭터 상태(골드, 인벤토리 아이템)를 A 에서 B 로 이전한다.
// - A 는 자기 메모리/DB 에서 플레이어를 제거하고, B 는 그 상태로 플레이어를
// 생성한다. 두 서버는 네트워크 메시지로만 통신하며, 메시지는 유실/지연/중복
// 될 수 있고 어느 한쪽이 중간에 죽을 수 있다.
// - 같은 골드/아이템이 두 서버에 동시에 존재하면 복제(dupe) 버그가 되고,
// 이전 중 실패로 양쪽 모두에서 사라지면 자산 유실이 된다.
//
// 요구사항:
// - 이전은 원자적이어야 한다: 골드/아이템은 정확히 한 곳에만 존재(복제·유실 금지).
// - 메시지 유실/중복/서버 다운 어느 경우에도 자산이 복제되거나 사라지면 안 된다.
// - 같은 이전 요청이 재시도돼도 결과는 한 번 적용된 것과 같아야 한다(멱등).
//
// 과제:
// 이 코드의 잠재적 문제를 모두 찾고, 왜/어떻게 복제·유실·중복이 발생하는지
// 설명하고, 수정안과 더 나은 설계를 제시하라.
// (먼저 직접 리뷰를 적은 뒤 answer.md 와 대조할 것)
// ============================================================================
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
public class PlayerState
{
public long PlayerId;
public long Gold;
public List<int> Items = new List<int>();
}
public interface IZoneLink
{
// 다른 존 서버로 "플레이어 받아라" 메시지 전송 (네트워크). fire-and-forget.
Task SendTransfer(int targetZoneId, PlayerState state);
}
// 존 서버 A 측: 플레이어를 B 로 떠나보내는 로직
public class ZoneTransferOut
{
private readonly Dictionary<long, PlayerState> _players;
private readonly IZoneLink _link;
public ZoneTransferOut(Dictionary<long, PlayerState> players, IZoneLink link)
{
_players = players;
_link = link;
}
// 플레이어가 B 존으로 이동 요청
public async Task TransferTo(long playerId, int targetZoneId)
{
PlayerState st = _players[playerId]; // (A) 현재 상태 읽기
// (B) 대상 서버로 상태 전송
await _link.SendTransfer(targetZoneId, st);
// (C) 로컬에서 플레이어 제거 (B 로 넘어갔으니)
_players.Remove(playerId);
}
}
// 존 서버 B 측: 이전 메시지를 받아 플레이어를 생성
public class ZoneTransferIn
{
private readonly Dictionary<long, PlayerState> _players;
public ZoneTransferIn(Dictionary<long, PlayerState> players)
{
_players = players;
}
// A 로부터 SendTransfer 수신 시 호출
public void OnTransferReceived(PlayerState state)
{
// (D) 받은 상태로 플레이어 생성
_players[state.PlayerId] = state;
}
} 결함 코드 · C++
// ============================================================================
// [코드리뷰 문제] C++ - 존 간 플레이어/아이템 이전 (서버-서버 분산 트랜잭션)
// ----------------------------------------------------------------------------
// 시나리오 (서버-서버 / 분산):
// - 플레이어가 존 서버 A 에서 존 서버 B 로 이동할 때, 캐릭터 상태(골드,
// 인벤토리 아이템)를 A 에서 B 로 이전한다.
// - A 는 자기 메모리/DB 에서 플레이어를 제거하고, B 는 그 상태로 플레이어를
// 생성한다. 두 서버는 네트워크 메시지로만 통신하며, 메시지는 유실/지연/중복
// 될 수 있고 어느 한쪽이 중간에 죽을 수 있다.
// - 같은 골드/아이템이 두 서버에 동시에 존재하면 복제(dupe) 버그가 되고,
// 이전 중 실패로 양쪽 모두에서 사라지면 자산 유실이 된다.
//
// 요구사항:
// - 이전은 원자적이어야 한다: 골드/아이템은 정확히 한 곳에만 존재(복제·유실 금지).
// - 메시지 유실/중복/서버 다운 어느 경우에도 자산이 복제되거나 사라지면 안 된다.
// - 같은 이전 요청이 재시도돼도 결과는 한 번 적용된 것과 같아야 한다(멱등).
//
// 과제:
// 이 코드의 잠재적 문제를 모두 찾고, 왜/어떻게 복제·유실·중복이 발생하는지
// 설명하고, 수정안과 더 나은 설계를 제시하라.
// (먼저 직접 리뷰를 적은 뒤 answer.md 와 대조할 것)
// ============================================================================
#include <cstdint>
#include <unordered_map>
#include <vector>
#include <string>
struct PlayerState {
int64_t playerId;
int64_t gold;
std::vector<int> items;
};
// 다른 존 서버로 "플레이어 받아라" 메시지 전송 (네트워크). fire-and-forget.
class IZoneLink {
public:
virtual ~IZoneLink() = default;
virtual void SendTransfer(int targetZoneId, const PlayerState& state) = 0;
};
// 존 서버 A 측: 플레이어를 B 로 떠나보내는 로직
class ZoneTransferOut {
public:
ZoneTransferOut(std::unordered_map<int64_t, PlayerState>& players, IZoneLink& link)
: players_(players), link_(link) {}
// 플레이어가 B 존으로 이동 요청
void TransferTo(int64_t playerId, int targetZoneId) {
PlayerState& st = players_[playerId]; // (A) 현재 상태 참조
// (B) 대상 서버로 상태 전송
link_.SendTransfer(targetZoneId, st);
// (C) 로컬에서 플레이어 제거 (B 로 넘어갔으니)
players_.erase(playerId);
}
private:
std::unordered_map<int64_t, PlayerState>& players_;
IZoneLink& link_;
};
// 존 서버 B 측: 이전 메시지를 받아 플레이어를 생성
class ZoneTransferIn {
public:
explicit ZoneTransferIn(std::unordered_map<int64_t, PlayerState>& players)
: players_(players) {}
// A 로부터 SendTransfer 수신 시 호출
void OnTransferReceived(const PlayerState& state) {
// (D) 받은 상태로 플레이어 생성
players_[state.playerId] = state;
}
private:
std::unordered_map<int64_t, PlayerState>& players_;
}; 내 리뷰 · C#
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.
내 리뷰 · C++
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.