28. 일괄 지급의 all-or-nothing 트랜잭션 (한 건 중복이 전체를 롤백)
난이도 하 해설 보기 →
결함을 모두 찾고 원인·수정안·더 나은 설계를 제시하라. 마커
(A)(B) 는 주목 위치 힌트다.
결함 코드 · C#
// 시나리오: 이벤트 보상 일괄 지급 — C# 서버
// - 운영 이벤트로 여러 플레이어에게 같은 보상 아이템을 우편으로 일괄 지급한다.
// - 우편 테이블에는 (player_id, item_id, event_id) UNIQUE 제약이 있어, 같은 이벤트
// 보상을 같은 플레이어에게 두 번 넣으면 UniqueViolationException 이 발생한다.
// (지급 잡이 재시도되거나, 명단에 이미 받은 유저가 섞여 있을 수 있다.)
//
// 요구사항:
// 1) 명단의 각 플레이어는 서로 독립적으로 보상을 받아야 한다.
// 2) 일부 플레이어가 "이미 받음(중복)"이어도, 나머지 정상 플레이어의 지급은
// 완료되어야 한다.
//
// 과제: 명단에 이미 받은 유저가 한 명이라도 섞여 있으면, 정상 유저들도 보상을
// 받지 못하는 일이 발생한다. (A),(B) 지점을 근거로 원인과 수정안을 설명하라.
using System;
public sealed class UniqueViolationException : Exception { }
public interface IDb
{
void Begin();
void Commit();
void Rollback();
void Insert(int playerId, int itemId, int eventId); // 중복이면 UniqueViolationException
}
public sealed class RewardGranter
{
private readonly IDb _db;
public RewardGranter(IDb db) { _db = db; }
public void GrantToAll(int[] playerIds, int itemId, int eventId)
{
_db.Begin();
foreach (var pid in playerIds)
{
_db.Insert(pid, itemId, eventId); // (A)
}
_db.Commit(); // (B)
}
} 결함 코드 · C++
// 시나리오: 이벤트 보상 일괄 지급 — C++ 서버
// - 여러 플레이어에게 같은 보상 아이템을 일괄 지급한다.
// - (player_id, item_id, event_id) UNIQUE 제약이 있어, 이미 받은 유저에 대한
// Insert 는 예외(UniqueViolation)를 던진다(잡 재시도·중복 명단 가능).
//
// 요구사항:
// 1) 각 플레이어는 서로 독립적으로 보상을 받아야 한다.
// 2) 일부가 "이미 받음(중복)"이어도 나머지 정상 유저 지급은 완료되어야 한다.
//
// 과제: 명단에 이미 받은 유저가 섞이면 정상 유저도 미지급된다.
// (A),(B) 지점을 근거로 원인·수정안을 설명하라. (컴파일 가능해야 함)
#include <cstdio>
#include <stdexcept>
#include <vector>
struct UniqueViolation : std::runtime_error {
UniqueViolation() : std::runtime_error("unique violation") {}
};
struct IDb {
virtual void Begin() = 0;
virtual void Commit() = 0;
virtual void Rollback() = 0;
virtual void Insert(int playerId, int itemId, int eventId) = 0; // 중복 시 throw
virtual ~IDb() = default;
};
class RewardGranter {
public:
explicit RewardGranter(IDb& db) : db_(db) {}
void GrantToAll(const std::vector<int>& playerIds, int itemId, int eventId) {
db_.Begin();
for (int pid : playerIds) {
db_.Insert(pid, itemId, eventId); // (A)
}
db_.Commit(); // (B)
}
private:
IDb& db_;
};
// 데모용 더미 (컴파일 확인)
struct DummyDb : IDb {
void Begin() override {}
void Commit() override { std::printf("commit\n"); }
void Rollback() override { std::printf("rollback\n"); }
void Insert(int p, int, int) override { std::printf("insert %d\n", p); }
};
int main() {
DummyDb db; RewardGranter g(db);
g.GrantToAll({1, 2, 3}, 100, 7);
return 0;
} 내 리뷰 · C#
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.
내 리뷰 · C++
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.