28. 리샤딩 중 키→파티션 재매핑으로 인한 순서 보장 붕괴
난이도 최상 해설 보기 →
결함을 모두 찾고 원인·수정안·더 나은 설계를 제시하라. 마커
(A)(B) 는 주목 위치 힌트다.
결함 코드 · C#
// 시나리오: 키 기반 파티셔닝으로 "플레이어별 순서 보장" — C# 분산 처리
// - 플레이어 이벤트(장착/판매/강화 등)는 반드시 그 플레이어 단위로 발생 순서대로
// 처리되어야 한다(인과 순서). 예: "판매"가 "장착"보다 먼저 처리되면 안 됨.
// - 순서 보장을 위해 이벤트를 hash(playerId) % N 으로 N개의 파티션에 나누고,
// 각 파티션은 단일 소비자(single-threaded)가 FIFO 로 처리한다.
// 같은 플레이어의 이벤트는 항상 같은 파티션으로 가므로 순서가 지켜진다.
// - 부하가 늘면 파티션(소비자)을 추가해 스케일 아웃한다(리샤딩).
//
// 요구사항:
// 1) 같은 플레이어의 이벤트는 항상 발생 순서대로 처리되어야 한다.
// 2) 파티션을 추가(리샤딩)하는 동안에도 순서 보장이 깨지면 안 된다.
//
// 과제: 파티션을 추가한 직후, 일부 플레이어의 이벤트가 순서가 뒤바뀌거나 두
// 소비자에서 동시에 처리되는 일이 발생한다. (A),(B) 지점을 근거로 원인과
// 수정안을 설명하라.
using System;
using System.Collections.Generic;
public sealed class Event { public int PlayerId; public long Seq; public string Kind; }
public sealed class OrderedPartition
{
public readonly int Index;
public readonly Queue<Event> Queue = new Queue<Event>(); // 단일 소비자 FIFO
public OrderedPartition(int index) { Index = index; }
}
public sealed class ShardedProcessor
{
private readonly List<OrderedPartition> _partitions = new List<OrderedPartition>();
private int _count;
public ShardedProcessor(int n)
{
for (int i = 0; i < n; i++) _partitions.Add(new OrderedPartition(i));
_count = n;
}
public void Route(Event e)
{
int idx = (int)((uint)e.PlayerId % (uint)_count); // (A)
_partitions[idx].Queue.Enqueue(e);
}
// 스케일 아웃: 파티션(소비자) 추가
public void AddPartition()
{
var p = new OrderedPartition(_count);
_partitions.Add(p);
_count++; // (B)
}
} 결함 코드 · C++
// 시나리오: 키 기반 파티셔닝으로 "플레이어별 순서 보장" — C++ 분산 처리
// - 플레이어 이벤트는 그 플레이어 단위로 발생 순서대로 처리되어야 한다(인과 순서).
// - hash(playerId) % N 으로 N개 파티션에 나누고, 각 파티션은 단일 소비자가 FIFO 처리.
// - 부하가 늘면 파티션(소비자)을 추가한다(리샤딩).
//
// 요구사항:
// 1) 같은 플레이어의 이벤트는 항상 발생 순서대로 처리되어야 한다.
// 2) 리샤딩 중에도 순서 보장이 깨지면 안 된다.
//
// 과제: 파티션 추가 직후, 일부 플레이어 이벤트의 순서가 뒤바뀌거나 두 소비자에서
// 동시에 처리된다. (A),(B) 지점을 근거로 원인·수정안을 설명하라.
// (컴파일 가능해야 함)
#include <cstdio>
#include <cstdint>
#include <deque>
#include <string>
#include <vector>
struct Event { int player_id; int64_t seq; std::string kind; };
struct OrderedPartition {
int index;
std::deque<Event> queue; // 단일 소비자 FIFO
explicit OrderedPartition(int i) : index(i) {}
};
class ShardedProcessor {
public:
explicit ShardedProcessor(int n) {
for (int i = 0; i < n; ++i) partitions_.emplace_back(i);
count_ = n;
}
void Route(const Event& e) {
int idx = static_cast<int>(static_cast<uint32_t>(e.player_id) % static_cast<uint32_t>(count_)); // (A)
partitions_[idx].queue.push_back(e);
}
void AddPartition() {
partitions_.emplace_back(count_);
++count_; // (B)
}
int Count() const { return count_; }
private:
std::vector<OrderedPartition> partitions_;
int count_ = 0;
};
int main() {
ShardedProcessor sp(2);
sp.Route({42, 1, "equip"}); // 42 % 2 = 0 번 파티션
sp.AddPartition(); // count=3
sp.Route({42, 2, "sell"}); // 42 % 3 = 0 번? -> 42%3=0, 하지만 키에 따라 달라짐
std::printf("partitions=%d\n", sp.Count());
return 0;
} 내 리뷰 · C#
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.
내 리뷰 · C++
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.