2. 장애 후 재기동 시 배치 작업의 중복 처리/이중 실행 방지
난이도 중 해설 보기 →
결함을 모두 찾고 원인·수정안·더 나은 설계를 제시하라. 마커
(A)(B) 는 주목 위치 힌트다.
결함 코드 · C#
// ============================================================================
// [코드리뷰 문제] C# - 장애 후 재기동 시 배치 작업의 중복 처리/이중 실행 방지
// ----------------------------------------------------------------------------
// 시나리오 (서버-서버/분산 · 운영/배포):
// - "보상 지급 배치"가 있다. 운영 보상/시즌 정산/우편 일괄 지급처럼 대량의 작업을
// 주기적으로 처리한다. 각 작업(RewardJob)은 한 플레이어에게 골드를 지급한다.
// - 이 배치는 (1) 스케줄러 틱마다 호출되고, (2) 서버가 죽었다가 재기동되면 미처리분을
// 이어서 처리하기 위해 다시 호출되며, (3) 무중단 운영을 위해 여러 워커 인스턴스가
// 동시에 돌 수 있다(같은 작업 목록을 공유).
//
// 요구사항:
// - 각 RewardJob 은 정확히 "한 번만" 지급되어야 한다(at-most-once 효과).
// - 어느 워커가 처리하든, 중간에 죽었다 재기동하든, 같은 작업이 두 번 지급되면 안 된다.
// - 여러 워커가 같은 목록을 동시에 돌아도 한 작업을 두 워커가 같이 집으면 안 된다.
//
// 과제:
// 이 배치 러너의 잠재적 문제를 모두 찾고, (1) 동시 다중 워커, (2) 지급 직후 재기동 시
// 어떻게 이중 지급이 발생하는지 설명하고, 수정안과 더 나은 설계를 제시하라.
// (먼저 직접 리뷰를 적은 뒤 answer.md 와 대조할 것)
// ============================================================================
using System.Collections.Generic;
public class RewardJob
{
public long JobId;
public long PlayerId;
public int Gold;
public bool Processed;
}
public class Wallet
{
private readonly Dictionary<long, long> _gold = new();
public void AddGold(long playerId, int amount)
{
_gold.TryGetValue(playerId, out var g);
_gold[playerId] = g + amount;
}
}
public class RewardBatchRunner
{
private readonly List<RewardJob> _jobs; // 공유 작업 목록(DB 테이블 대용)
private readonly Wallet _wallet;
public RewardBatchRunner(List<RewardJob> jobs, Wallet wallet)
{
_jobs = jobs;
_wallet = wallet;
}
// 스케줄러 틱/재기동/여러 워커에서 호출될 수 있다
public void RunPending()
{
foreach (var job in _jobs) // (A)
{
if (job.Processed) continue; // (B)
_wallet.AddGold(job.PlayerId, job.Gold); // (C)
job.Processed = true; // (D)
}
}
} 결함 코드 · C++
// ============================================================================
// [코드리뷰 문제] C++ - 장애 후 재기동 시 배치 작업의 중복 처리/이중 실행 방지
// ----------------------------------------------------------------------------
// 시나리오 (서버-서버/분산 · 운영/배포):
// - "보상 지급 배치"가 있다. 각 작업(RewardJob)은 한 플레이어에게 골드를 지급한다.
// - 이 배치는 (1) 스케줄러 틱마다, (2) 서버 재기동 시 미처리분 이어서, (3) 여러 워커
// 스레드/인스턴스에서 동시에 RunPending 을 호출한다(같은 작업 목록 공유).
//
// 요구사항:
// - 각 RewardJob 은 정확히 "한 번만" 지급되어야 한다.
// - 어느 워커가 처리하든, 중간에 죽었다 재기동하든, 두 번 지급되면 안 된다.
// - 여러 워커가 같은 목록을 동시에 돌아도 한 작업을 두 워커가 같이 집으면 안 된다.
//
// 과제:
// 이 배치 러너의 잠재적 문제를 모두 찾고(동시 다중 스레드 접근의 안전성 포함),
// 지급 직후 재기동/동시 처리 시 무엇이 벌어지는지 설명하고, 수정안과 더 나은 설계를
// 제시하라. (먼저 직접 리뷰를 적은 뒤 answer.md 와 대조할 것)
// ============================================================================
#include <vector>
#include <unordered_map>
#include <cstdint>
struct RewardJob
{
int64_t jobId;
int64_t playerId;
int gold;
bool processed;
};
class Wallet
{
std::unordered_map<int64_t, int64_t> gold_;
public:
void AddGold(int64_t playerId, int amount)
{
gold_[playerId] += amount; // (C)
}
};
class RewardBatchRunner
{
std::vector<RewardJob>& jobs_; // 공유 작업 목록(DB 테이블 대용)
Wallet& wallet_;
public:
RewardBatchRunner(std::vector<RewardJob>& jobs, Wallet& wallet)
: jobs_(jobs), wallet_(wallet) {}
// 여러 워커 스레드/재기동에서 호출될 수 있다
void RunPending()
{
for (auto& job : jobs_) // (A)
{
if (job.processed) continue; // (B)
wallet_.AddGold(job.playerId, job.gold);
job.processed = true; // (D)
}
}
}; 내 리뷰 · C#
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.
내 리뷰 · C++
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.