6. DB 커밋과 이벤트 발행의 이중 쓰기(dual write) 불일치
난이도 상 해설 보기 →
결함을 모두 찾고 원인·수정안·더 나은 설계를 제시하라. 마커
(A)(B) 는 주목 위치 힌트다.
결함 코드 · C#
// 시나리오: 보상 지급 서비스. 플레이어에게 아이템과 재화를 DB 트랜잭션으로
// 지급한 뒤, 다른 서비스(랭킹/도감/캐시 무효화/업적)에 "보상 지급됨" 이벤트를
// 메시지 버스로 통지한다. 다운스트림은 이 이벤트로 자기 상태를 갱신한다.
//
// 요구사항:
// - DB 에 반영된 보상은 반드시 이벤트로도 통지돼야 하고(유실 금지),
// DB 에 반영되지 않은 보상이 이벤트로 나가서도 안 된다(유령 이벤트 금지).
// - 서비스/프로세스가 임의 시점에 죽거나 메시지 버스가 일시 장애여도
// 최종적으로 DB 와 다운스트림이 일치해야 한다.
// - 같은 지급 요청이 재시도돼도 다운스트림이 두 번 반영하면 안 된다.
//
// 과제: DB 반영과 이벤트 발행을 어떻게 묶고 있는지 (A)(B) 지점을 보고,
// 장애/재시도 시 어떤 불일치가 생기는지 진단한 뒤 고쳐라.
using System.Data;
public sealed class RewardService
{
private readonly IDbConnection _db;
private readonly IMessageBus _bus;
public RewardService(IDbConnection db, IMessageBus bus) { _db = db; _bus = bus; }
public void GrantReward(long playerId, int itemId, long gold)
{
using (var tx = _db.BeginTransaction())
{
Exec(tx, "INSERT INTO inventory(player_id, item_id, qty) VALUES(@p,@i,1)", playerId, itemId);
Exec(tx, "UPDATE wallet SET gold = gold + @g WHERE player_id=@p", gold, playerId);
tx.Commit(); // (A) DB 커밋
}
// 다운스트림 통지
_bus.Publish(new RewardGranted // (B) 이벤트 발행
{
PlayerId = playerId,
ItemId = itemId,
Gold = gold
});
}
private void Exec(IDbTransaction tx, string sql, params object[] args) { /* 실행 */ }
}
public sealed class RewardGranted { public long PlayerId; public int ItemId; public long Gold; }
public interface IMessageBus { void Publish(object evt); } 결함 코드 · C++
// 시나리오: 보상 지급 서비스 (C++). 플레이어에게 아이템과 재화를 DB 트랜잭션으로
// 지급한 뒤, 다른 서비스(랭킹/도감/캐시 무효화/업적)에 "보상 지급됨" 이벤트를
// 메시지 버스로 통지한다. 다운스트림은 이 이벤트로 자기 상태를 갱신한다.
//
// 요구사항:
// - DB 에 반영된 보상은 반드시 이벤트로도 통지돼야 하고(유실 금지),
// DB 에 반영되지 않은 보상이 이벤트로 나가서도 안 된다(유령 이벤트 금지).
// - 프로세스가 임의 시점에 죽거나 메시지 버스가 일시 장애여도 최종적으로
// DB 와 다운스트림이 일치해야 한다.
// - 같은 지급 요청이 재시도돼도 다운스트림이 두 번 반영하면 안 된다.
//
// 과제: DB 반영과 이벤트 발행을 어떻게 묶고 있는지 (A)(B) 지점을 보고,
// 장애/재시도 시 어떤 불일치가 생기는지 진단한 뒤 고쳐라.
#include <cstdint>
#include <string>
struct DbTx { // RAII 트랜잭션(소멸 시 미커밋이면 롤백)
void exec(const std::string& sql);
void commit();
~DbTx();
};
struct Db { DbTx begin(); };
struct Event { int64_t playerId; int itemId; int64_t gold; };
struct MessageBus { void publish(const Event& e); };
class RewardService {
public:
RewardService(Db& db, MessageBus& bus) : db_(db), bus_(bus) {}
void grantReward(int64_t playerId, int itemId, int64_t gold) {
{
DbTx tx = db_.begin();
tx.exec("INSERT INTO inventory(player_id,item_id,qty) VALUES(...)");
tx.exec("UPDATE wallet SET gold = gold + ? WHERE player_id = ?");
tx.commit(); // (A) DB 커밋
}
Event e{ playerId, itemId, gold };
bus_.publish(e); // (B) 이벤트 발행
}
private:
Db& db_;
MessageBus& bus_;
}; 내 리뷰 · C#
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.
내 리뷰 · C++
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.