10. 메시지 큐 소비자의 오프셋 커밋 순서·멱등성·리밸런스 결함 (C#)
난이도 상 해설 보기 →
결함을 모두 찾고 원인·수정안·더 나은 설계를 제시하라. 마커
(A)(B) 는 주목 위치 힌트다.
결함 코드 · C#
// 시나리오: 게임 백엔드의 이벤트 파이프라인. 결제/보상 이벤트가 파티션된 메시지 큐
// (Kafka 류)로 흐르고, 소비자(consumer)가 파티션별로 오프셋을 따라가며 배치로 읽어
// 재화를 지급한다. 소비자는 컨슈머 그룹에 속해, 노드 증감/장애 시 파티션이
// 다른 소비자로 재할당(rebalance)될 수 있다.
//
// 요구사항:
// - Poll: 현재 오프셋부터 한 배치를 읽어 각 이벤트를 처리하고, 진행한 오프셋을 커밋한다.
// - GrantReward: 이벤트대로 플레이어에게 재화를 지급한다.
// - 크래시/재기동/재할당이 일어나도 "정확히 한 번 지급된 것과 동등"해야 한다.
//
// 과제: (A)(B) 지점을 검토하라. 크래시·재시도·재할당 타이밍에서 지급이 중복되거나
// 유실될 수 있다. 어떤 상황에서 그런지 밝히고 고쳐라.
using System.Collections.Generic;
public struct RewardEvent
{
public long Offset;
public long PlayerId;
public long Amount;
public string EventId; // 프로듀서가 붙인 고유 ID
}
public interface IMessageQueue
{
List<RewardEvent> Fetch(int partition, long offset, int maxCount);
void CommitOffset(int partition, long offset);
}
public sealed class RewardConsumer
{
private readonly IMessageQueue _mq;
private readonly int _partition;
private long _offset;
private readonly Dictionary<long, long> _wallet; // playerId -> balance
public RewardConsumer(IMessageQueue mq, int partition, long startOffset, Dictionary<long, long> wallet)
{ _mq = mq; _partition = partition; _offset = startOffset; _wallet = wallet; }
public void Poll()
{
var batch = _mq.Fetch(_partition, _offset, maxCount: 100);
foreach (var e in batch)
{
GrantReward(e); // (B)
}
if (batch.Count > 0)
{
_offset = batch[batch.Count - 1].Offset + 1;
_mq.CommitOffset(_partition, _offset); // (A)
}
}
private void GrantReward(RewardEvent e)
{
_wallet.TryGetValue(e.PlayerId, out long bal);
_wallet[e.PlayerId] = bal + e.Amount; // 잔액에 지급액을 더한다
}
} 결함 코드 · C++
// 시나리오: 게임 백엔드의 이벤트 파이프라인(C++ 소비자). 결제/보상 이벤트가 파티션된
// 메시지 큐(Kafka 류)로 흐르고, 소비자가 파티션별 오프셋을 따라 배치로 읽어 재화를
// 지급한다. 소비자는 컨슈머 그룹에 속해, 노드 증감/장애 시 파티션이 다른 소비자로
// 재할당(rebalance)될 수 있다.
//
// 요구사항:
// - poll: 현재 오프셋부터 한 배치를 읽어 각 이벤트를 처리하고, 진행 오프셋을 커밋한다.
// - grantReward: 이벤트대로 재화를 지급한다.
// - 크래시/재기동/재할당이 일어나도 "정확히 한 번 지급된 것과 동등"해야 한다.
//
// 과제: (A)(B) 지점을 검토하라. 크래시·재시도·재할당 타이밍에서 지급이 중복되거나
// 유실될 수 있다. 어떤 상황에서 그런지 밝히고 고쳐라.
#include <cstdint>
#include <string>
#include <vector>
#include <unordered_map>
struct RewardEvent {
int64_t offset;
int64_t playerId;
int64_t amount;
std::string eventId; // 프로듀서가 붙인 고유 ID
};
struct MessageQueue {
std::vector<RewardEvent> fetch(int partition, int64_t offset, int maxCount);
void commitOffset(int partition, int64_t offset);
};
class RewardConsumer {
public:
RewardConsumer(MessageQueue* mq, int partition, int64_t startOffset,
std::unordered_map<int64_t, int64_t>* wallet)
: mq_(mq), partition_(partition), offset_(startOffset), wallet_(wallet) {}
void poll() {
auto batch = mq_->fetch(partition_, offset_, 100);
for (const auto& e : batch) {
grantReward(e); // (B)
}
if (!batch.empty()) {
offset_ = batch.back().offset + 1;
mq_->commitOffset(partition_, offset_); // (A)
}
}
private:
void grantReward(const RewardEvent& e) {
(*wallet_)[e.playerId] += e.amount; // 잔액에 지급액을 더한다
}
MessageQueue* mq_;
int partition_;
int64_t offset_;
std::unordered_map<int64_t, int64_t>* wallet_;
}; 내 리뷰 · C#
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.
내 리뷰 · C++
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.