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#
내 답안 · 자동 저장

작성 후 위 해설 보기에서 모범 해설과 대조하세요.