17. 분산 락 lease 만료와 fencing token 부재 (C#)
난이도 상 해설 보기 →
결함을 모두 찾고 원인·수정안·더 나은 설계를 제시하라. 마커
(A)(B) 는 주목 위치 힌트다.
결함 코드 · C#
// 시나리오: 분산 게임 서버 — 던전 인스턴스 상태를 한 워커만 갱신하도록 분산 락 사용
//
// 던전 인스턴스의 권위 있는 상태(보스 HP, 페이즈 등)는 동시에 한 워커 노드만
// 갱신해야 한다. 이를 위해 분산 락(예: Redis)에 TTL(lease)을 걸어 소유권을 얻는다.
// 락은 만료 시각(lease)이 지나면 자동 해제되어 다른 노드가 획득할 수 있다.
//
// 요구사항:
// - 어떤 순간에도 인스턴스 상태를 갱신하는 노드는 하나뿐이어야 한다.
// - 락을 잡은 노드가 GC 정지·네트워크 지연 등으로 오래 멈춰 lease 가 만료되면,
// 그 노드는 더 이상 유효한 소유자가 아니다.
//
// 아래 워커 처리 루프를 검토하라.
using System.Threading.Tasks;
public interface IDistributedLock
{
// 성공 시 true. ttlMs 후 자동 만료.
Task<bool> TryAcquire(string key, int ttlMs);
Task Release(string key);
}
public interface IStateStore
{
Task Write(string key, byte[] state); // 인스턴스 상태 저장
Task<byte[]> Read(string key);
}
public sealed class DungeonWorker
{
private readonly IDistributedLock _lock;
private readonly IStateStore _store;
public DungeonWorker(IDistributedLock l, IStateStore s) { _lock = l; _store = s; }
public async Task ProcessTick(string instanceId)
{
string lockKey = $"lock:dungeon:{instanceId}";
if (!await _lock.TryAcquire(lockKey, ttlMs: 5000)) // (A) 5초 lease
return; // 다른 노드가 소유
try
{
byte[] state = await _store.Read(instanceId); // (B) 현재 상태 로드
byte[] next = Simulate(state); // (C) 무거운 시뮬레이션(가변 시간)
await _store.Write(instanceId, next); // (D) 갱신 저장
}
finally
{
await _lock.Release(lockKey); // (E)
}
}
private static byte[] Simulate(byte[] state) => state; // 자리표시자
} 결함 코드 · C++
// 시나리오: 분산 게임 서버 — 던전 인스턴스 상태를 한 워커만 갱신 (C++)
//
// 던전 인스턴스의 권위 상태는 동시에 한 워커 노드만 갱신해야 한다. 분산 락(예:
// Redis)에 TTL(lease)을 걸어 소유권을 얻는다. lease 만료 시 다른 노드가 획득 가능.
//
// 요구사항:
// - 어떤 순간에도 상태를 갱신하는 노드는 하나뿐.
// - lease 만료 후의 노드는 유효한 소유자가 아니다.
//
// 워커는 락 획득 시 만료 시각을 로컬에 저장해두고, 쓰기 직전 "아직 안 만료됐는지"를
// 로컬에서 점검한 뒤 저장소에 쓴다. 아래 코드를 검토하라.
#include <cstdint>
#include <string>
#include <vector>
#include <chrono>
struct IDistributedLock {
virtual bool TryAcquire(const std::string& key, int ttlMs) = 0;
virtual void Release(const std::string& key) = 0;
virtual ~IDistributedLock() = default;
};
struct IStateStore {
virtual void Write(const std::string& key, const std::vector<uint8_t>& s) = 0;
virtual std::vector<uint8_t> Read(const std::string& key) = 0;
virtual ~IStateStore() = default;
};
class DungeonWorker {
public:
DungeonWorker(IDistributedLock& l, IStateStore& s) : lock_(l), store_(s) {}
void ProcessTick(const std::string& instanceId) {
std::string lockKey = "lock:dungeon:" + instanceId;
if (!lock_.TryAcquire(lockKey, 5000)) // (A) 5초 lease
return;
// 로컬에 만료 시각 기록(벽시계)
auto leaseEnd = std::chrono::system_clock::now()
+ std::chrono::milliseconds(5000); // (B)
std::vector<uint8_t> state = store_.Read(instanceId); // (C)
std::vector<uint8_t> next = Simulate(state); // (D) 무거운 시뮬(가변 시간)
if (std::chrono::system_clock::now() < leaseEnd) { // (E) 아직 유효?
store_.Write(instanceId, next); // (F) 갱신 저장
}
lock_.Release(lockKey); // (G)
}
private:
static std::vector<uint8_t> Simulate(const std::vector<uint8_t>& s) { return s; }
IDistributedLock& lock_;
IStateStore& store_;
}; 내 리뷰 · C#
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.
내 리뷰 · C++
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.