3. 우편 대량 발송 재시도 시 중복 지급 (멱등성 단위 오류)
난이도 상 해설 보기 →
결함을 모두 찾고 원인·수정안·더 나은 설계를 제시하라. 마커
(A)(B) 는 주목 위치 힌트다.
결함 코드 · C#
// ============================================================================
// [코드리뷰 문제] C# - 우편 대량 발송(운영자 보상) 재시도 시 중복 지급
// ----------------------------------------------------------------------------
// 시나리오 (영속화/DB · 운영 도구):
// - 운영자가 이벤트 보상을 수천 명에게 우편으로 일괄 발송한다(한 배치 = batchId).
// - 각 수신자에게: 보상(아이템/골드)을 우편함에 넣고 DB에 기록한다.
// - 발송 도중 일부 수신자에서 DB 타임아웃/일시 오류가 날 수 있고, 그러면 운영자(또는
// 자동 재시도)가 "같은 배치"를 다시 발송한다.
//
// 요구사항:
// - 한 배치의 각 수신자는 보상을 정확히 한 번만 받아야 한다(중복 지급 금지).
// - 중간에 실패해 재시도해도, 이미 받은 사람은 다시 받지 않아야 한다.
//
// 과제:
// 이 코드의 잠재적 문제를 모두 찾고, 1000명 중 600번째에서 예외가 나 배치를 재시도할 때
// 누가 보상을 몇 번 받는지 설명하고, 수정안과 더 나은 설계를 제시하라.
// (먼저 직접 리뷰를 적은 뒤 answer.md 와 대조할 것)
// ============================================================================
using System;
using System.Collections.Generic;
public class RewardSpec
{
public List<(int itemId, int count)> Items = new();
public int Gold;
}
public interface IMailDb
{
bool IsBatchSent(long batchId);
void InsertMail(long recipientId, long batchId, RewardSpec reward);
void MarkBatchSent(long batchId);
}
public interface IInventory
{
void Grant(long playerId, RewardSpec reward); // 아이템/골드 지급
}
public class MailSender
{
private readonly IMailDb _db;
private readonly IInventory _inv;
public MailSender(IMailDb db, IInventory inv) { _db = db; _inv = inv; }
// 운영자/스케줄러가 호출. 실패하면 같은 인자로 다시 호출될 수 있다.
public void SendBatch(long batchId, List<long> recipients, RewardSpec reward)
{
if (_db.IsBatchSent(batchId)) // (A) 배치 단위 발송 여부
return;
foreach (var r in recipients)
{
_inv.Grant(r, reward); // (B) 보상 지급
_db.InsertMail(r, batchId, reward); // (C) 우편 기록
}
_db.MarkBatchSent(batchId); // (D) 배치 완료 표시
}
} 결함 코드 · C++
// ============================================================================
// [코드리뷰 문제] C++ - 우편 대량 발송(운영자 보상) 재시도 시 중복 지급
// ----------------------------------------------------------------------------
// 시나리오 (영속화/DB · 운영 도구):
// - 운영자가 이벤트 보상을 수천 명에게 우편으로 일괄 발송한다(한 배치 = batchId).
// - 각 수신자에게: 보상(아이템/골드)을 우편함에 넣고 DB에 기록한다.
// - 발송 도중 일부 수신자에서 DB 타임아웃/일시 오류(예외)가 날 수 있고, 그러면 운영자
// 또는 프로세스 재기동 후 자동 재시도가 "같은 배치"를 다시 발송한다.
//
// 요구사항:
// - 한 배치의 각 수신자는 보상을 정확히 한 번만 받아야 한다(중복 지급 금지).
// - 중간 실패/프로세스 재기동 후 재시도해도 이미 받은 사람은 다시 받지 않아야 한다.
//
// 과제:
// 이 코드의 잠재적 문제를 모두 찾고, 1000명 중 600번째에서 예외가 나거나 프로세스가
// 재기동된 뒤 배치를 재시도할 때 누가 보상을 몇 번 받는지 설명하고, 수정안과 더 나은
// 설계를 제시하라. (먼저 직접 리뷰를 적은 뒤 answer.md 와 대조할 것)
// ============================================================================
#include <cstdint>
#include <vector>
#include <unordered_set>
struct RewardSpec {
std::vector<std::pair<int,int>> items; // (itemId, count)
int gold = 0;
};
struct IMailDb {
virtual void insertMail(long recipientId, long batchId, const RewardSpec& r) = 0;
virtual ~IMailDb() = default;
};
struct IInventory {
virtual void grant(long playerId, const RewardSpec& r) = 0; // 아이템/골드 지급
virtual ~IInventory() = default;
};
class MailSender {
public:
MailSender(IMailDb& db, IInventory& inv) : _db(db), _inv(inv) {}
// 운영자/스케줄러가 호출. 실패하면 같은 인자로 다시 호출될 수 있다.
void sendBatch(long batchId, const std::vector<long>& recipients, const RewardSpec& reward) {
if (_sentBatches.count(batchId)) // (A) 배치 단위 발송 여부(메모리)
return;
for (long r : recipients) {
_inv.grant(r, reward); // (B) 보상 지급
_db.insertMail(r, batchId, reward); // (C) 우편 기록
}
_sentBatches.insert(batchId); // (D) 배치 완료 표시
}
private:
IMailDb& _db;
IInventory& _inv;
std::unordered_set<long> _sentBatches; // 발송 완료된 배치 집합
}; 내 리뷰 · C#
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.
내 리뷰 · C++
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.