19. 해설 (C#) — 비동기 영속화 큐가 밀릴 때 최신값 유실/순서 역전
난이도 최상 해설 보기 →
결함을 모두 찾고 원인·수정안·더 나은 설계를 제시하라. 마커
(A)(B) 는 주목 위치 힌트다.
결함 코드 · C#
// ============================================================================
// [코드리뷰 문제] C# - 비동기 영속화 큐가 밀릴 때 최신값 유실/순서 역전
// ----------------------------------------------------------------------------
// 시나리오 (영속화 / 동시성):
// - 게임 로직은 캐릭터 상태(골드/레벨 등)를 메모리에서 실시간으로 바꾸고, 변경이
// 생기면 MarkDirty(charId, state) 로 "저장해 달라" 고 영속화 큐에 알린다.
// - 같은 캐릭터의 연속 변경을 매번 DB 에 쓰면 비싸므로, 캐릭터별 "최신 스냅샷" 만
// 보관(coalesce)하고, 더티 캐릭터 ID 를 큐에 한 번만 넣는다.
// - 여러 "저장 워커" 스레드가 큐에서 ID 를 꺼내 latest 스냅샷을 읽어 DB 에 쓴다.
// - 부하가 높으면 DB 쓰기가 느려져 큐가 밀리고, 같은 캐릭터가 짧은 간격으로 여러 번
// 더티가 된다(거래·전투 보상 연타 등).
//
// 요구사항:
// - 한 캐릭터에 대해 "더 최근 상태가 더 오래된 상태로 덮여 저장" 되면 안 된다(순서 역전 금지).
// - 더티가 된 변경분은 반드시 언젠가 DB 에 반영돼야 한다(최신값 유실 금지).
// - 동시에 여러 워커가 돌아도 큐/맵 자료구조가 손상되거나 예외로 죽으면 안 된다.
//
// 과제:
// 이 코드의 잠재적 문제를 모두 찾고, 왜/어떻게 순서 역전·유실·손상이 나는지(인터리빙
// 포함) 설명하고, 수정안과 더 나은 설계를 제시하라.
// (먼저 직접 리뷰를 적은 뒤 answer.md 와 대조할 것)
// ============================================================================
using System;
using System.Collections.Generic;
public struct CharState
{
public long Gold;
public int Level;
}
public class PersistenceQueue
{
private readonly Dictionary<long, CharState> _latest = new(); // 캐릭터별 최신 스냅샷
private readonly HashSet<long> _inQueue = new(); // 현재 큐에 들어있는 ID
private readonly Queue<long> _dirty = new(); // 더티 ID 큐
// 게임 로직 스레드들이 호출(여러 스레드에서 동시 호출 가능)
public void MarkDirty(long charId, CharState s)
{
_latest[charId] = s; // 최신 스냅샷으로 덮어쓰기(coalesce)
if (_inQueue.Add(charId)) // 아직 큐에 없으면
_dirty.Enqueue(charId); // 큐에 한 번만 넣기
}
// 저장 워커 스레드들이 반복 호출
public void WorkerStep()
{
if (_dirty.Count == 0) return;
long id = _dirty.Dequeue();
_inQueue.Remove(id); // (A) 큐에서 꺼내며 더티 표시 해제
CharState snap = _latest[id]; // (B) 현재 최신 스냅샷을 읽어
WriteToDb(id, snap); // (C) DB 에 비동기로 기록(느릴 수 있음)
}
private void WriteToDb(long id, CharState s)
{
// 실제 DB 비동기 쓰기(네트워크 왕복, 수십~수백 ms 소요 가능)
}
} 결함 코드 · C++
// ============================================================================
// [코드리뷰 문제] C++ - 비동기 영속화 큐가 밀릴 때 최신값 유실/순서 역전
// ----------------------------------------------------------------------------
// 시나리오 (영속화 / 동시성):
// - 게임 로직은 캐릭터 상태(골드/레벨 등)를 메모리에서 실시간으로 바꾸고, 변경이
// 생기면 MarkDirty(charId, state) 로 "저장해 달라" 고 영속화 큐에 알린다.
// - 같은 캐릭터의 연속 변경을 매번 DB 에 쓰면 비싸므로, 캐릭터별 "최신 스냅샷" 만
// 보관(coalesce)하고, 더티 캐릭터 ID 를 큐에 한 번만 넣는다.
// - 여러 "저장 워커" 스레드가 큐에서 ID 를 꺼내 latest 스냅샷을 읽어 DB 에 쓴다.
// - 부하가 높으면 DB 쓰기가 느려져 큐가 밀리고, 같은 캐릭터가 짧은 간격으로 여러 번
// 더티가 된다(거래·전투 보상 연타 등).
//
// 요구사항:
// - 한 캐릭터에 대해 "더 최근 상태가 더 오래된 상태로 덮여 저장" 되면 안 된다(순서 역전 금지).
// - 더티가 된 변경분은 반드시 언젠가 DB 에 반영돼야 한다(최신값 유실 금지).
// - 동시에 여러 워커가 돌아도 큐/맵 자료구조가 손상되거나 UB 가 나면 안 된다.
//
// 과제:
// 이 코드의 잠재적 문제를 모두 찾고, 왜/어떻게 순서 역전·유실·손상이 나는지(인터리빙
// 포함) 설명하고, 수정안과 더 나은 설계를 제시하라.
// (먼저 직접 리뷰를 적은 뒤 answer.md 와 대조할 것)
// ============================================================================
#include <cstdint>
#include <unordered_map>
#include <unordered_set>
#include <queue>
struct CharState {
std::int64_t gold = 0;
int level = 0;
};
class PersistenceQueue {
public:
// 게임 로직 스레드들이 호출(여러 스레드에서 동시 호출 가능)
void MarkDirty(std::int64_t charId, const CharState& s) {
latest_[charId] = s; // 최신 스냅샷으로 덮어쓰기(coalesce)
if (inQueue_.insert(charId).second) // 아직 큐에 없으면
dirty_.push(charId); // 큐에 한 번만 넣기
}
// 저장 워커 스레드들이 반복 호출
void WorkerStep() {
if (dirty_.empty()) return;
std::int64_t id = dirty_.front();
dirty_.pop();
inQueue_.erase(id); // (A) 큐에서 꺼내며 더티 표시 해제
CharState snap = latest_[id]; // (B) 현재 최신 스냅샷을 읽어
WriteToDb(id, snap); // (C) DB 에 비동기로 기록(느릴 수 있음)
}
private:
void WriteToDb(std::int64_t /*id*/, const CharState& /*s*/) {
// 실제 DB 비동기 쓰기(네트워크 왕복, 수십~수백 ms 소요 가능)
}
std::unordered_map<std::int64_t, CharState> latest_; // 캐릭터별 최신 스냅샷
std::unordered_set<std::int64_t> inQueue_; // 현재 큐에 들어있는 ID
std::queue<std::int64_t> dirty_; // 더티 ID 큐
}; 내 리뷰 · C#
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.
내 리뷰 · C++
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.