28. 리샤딩 중 키→파티션 재매핑으로 인한 순서 보장 붕괴
난이도 최상해설 — 리샤딩 중 키→파티션 재매핑으로 인한 순서 보장 붕괴
난이도: 최상
요약
순서 보장의 전제는 "같은 playerId의 모든 이벤트가 항상 같은 단일 소비자로 간다"는 것이다. 그런데 라우팅이 playerId % _count (A)이고, AddPartition이 _count를 즉시 증가시킨다 (B). _count가 바뀌는 순간 거의 모든 키의 목적지 파티션이 바뀐다(모듈로 해싱은 N 변경 시 대량 재매핑). 이때 어떤 플레이어의 이전 이벤트들은 옛 파티션 큐에 아직 남아(in-flight) 옛 소비자가 처리 중인데, 그 플레이어의 새 이벤트는 새 파티션으로 들어가 새 소비자가 동시에 처리한다. 결과적으로 한 플레이어가 두 소비자에서 병렬 처리되어 인과 순서가 뒤바뀐다(예: 새 파티션의 "판매"가 옛 파티션의 "장착"보다 먼저 커밋).
문제점
- 분류태그: 리샤딩 중 파티션 키 순서 보장 붕괴(재매핑 + in-flight 미드레인, 배리어/에폭 부재)
- 증상: 리샤딩 직후 짧은 구간에 "판매가 장착보다 먼저 처리됨", 재화 이중 처리/유실, 상태 divergence. 리샤딩 안 하면 재현 안 됨(간헐).
- 재현조건: 파티션 수 변경 시점에, 재매핑된 플레이어의 이벤트가 옛 파티션에 남아 있고 새 이벤트가 새 파티션으로 유입.
- 근본원인: (1) 라우팅 함수(
% _count)가 원자적 스위치처럼 바뀌지만 in-flight 이벤트의 핸드오프/드레인이 없다. (2) 모듈로 해싱이라 재매핑 규모가 크다. (3) 키 소유권 이전에 배리어/에폭이 없어 옛·새 소유자가 겹친다.
수정안
핵심은 "키 소유권 이전 시, 새 소유자는 옛 소유자가 그 키를 다 비운 뒤에 시작한다"는 핸드오프 프로토콜과, 재매핑 규모를 줄이는 라우팅이다.
-
일관 해싱(consistent hashing)/rendezvous로 N 변경 시 재매핑되는 키를 최소화(영향 키만 이동).
-
키(또는 영향 파티션) 단위 드레인-후-컷오버:
리샤딩 시작:
- 이동 대상 키 집합 K 확정(일관 해싱으로 최소화).
- K 로 가는 신규 이벤트를 '보류 큐'에 버퍼링(아직 새 소비자에 안 넘김).
- 옛 소비자가 K 의 in-flight 이벤트를 모두 처리 완료할 때까지 대기(드레인).
- 드레인 완료 후: 라우팅을 새 매핑으로 전환하고, 보류 큐를 새 소비자에 순서대로 방류.
이렇게 하면 한 키가 두 소비자에서 동시에 처리되는 구간이 없어진다(소유권은 항상 배타적).
- 에폭/펜싱: 각 키에 소유 에폭을 부여하고, 소비자는 자신의 에폭이 최신일 때만 처리. 옛 소유자는 에폭이 밀리면 그 키 처리를 중단.
더 나은 설계
- 정적 파티션 + 가상 노드: 파티션 수를 크게 고정하고(예: 1024) 소비자에 파티션 그룹을 배정. 스케일 아웃은 파티션 재배정(키→파티션은 불변)으로 → 키 순서는 파티션이 지키고, 이동은 파티션 단위 드레인-핸드오프.
- 로그 기반(Kafka류) 파티셔닝: 파티션 수를 늘리면 같은 키가 다른 파티션으로 갈 수 있음을 인지하고, 리파티셔닝 시 오프셋 기준 드레인/정지-재개.
- 시퀀스/버전으로 순서 자기검증: 소비자가 키별 마지막 처리
Seq를 기억해, 더 낮은 Seq가 뒤늦게 오면 거부/재정렬(순서 위반을 데이터로 방어).
면접 포인트
- 모듈로 해싱 vs 일관 해싱의 재매핑 규모 차이와 리샤딩 안정성.
- "파티션 키 순서 보장"의 전제(키→소비자 단사·불변)가 리샤딩에서 어떻게 깨지는가.
- 무중단 리샤딩의 핵심: 배타적 소유권 이전(드레인-후-컷오버) + 신규 이벤트 버퍼링 + 에폭/펜싱.
해설 — 리샤딩 중 재매핑으로 인한 순서 보장 붕괴 (C++)
난이도: 최상
요약
C# 트윈과 동일한 분산 결함. 순서 보장은 "같은 player_id가 항상 같은 단일 소비자로 간다"에 의존하는데, 라우팅이 player_id % count_ (A)이고 AddPartition이 count_를 즉시 증가시킨다 (B). 파티션 수가 바뀌는 순간 모듈로 해싱은 대다수 키의 목적지를 바꾼다. 재매핑된 플레이어의 옛 이벤트는 옛 파티션 큐에 남아(in-flight) 옛 소비자가 처리 중인데, 새 이벤트는 새 파티션으로 들어가 새 소비자가 동시에 처리한다 → 한 키가 두 소비자에서 병렬·역순 처리. (데모의 42는 2→3 파티션에서 우연히 같은 인덱스지만, 일반적으로 키마다 목적지가 바뀐다.)
문제점
- 분류태그: 리샤딩 중 파티션 키 순서 붕괴(재매핑 + in-flight 미드레인, 배리어/에폭 부재)
- 증상: 리샤딩 직후 인과 역전("판매→장착" 순서 뒤집힘), 재화 이중/유실, 상태 divergence, 간헐 재현.
- 재현조건: 파티션 수 변경 시점에 재매핑 키의 옛 이벤트가 잔류 + 새 이벤트 유입.
- 근본원인: 라우팅 스위치가 in-flight 핸드오프 없이 즉시 전환, 모듈로로 재매핑 규모 큼, 키 소유권 배타성 미보장.
수정안
- 일관 해싱/rendezvous로 재매핑 키 최소화.
- 드레인-후-컷오버 + 신규 버퍼링으로 배타적 소유권 이전:
- 이동 키 집합 K 확정(일관 해싱).
- K 로 가는 신규 이벤트를 보류 큐에 버퍼링.
- 옛 소비자가 K in-flight 를 전부 처리(드레인)할 때까지 대기.
- 드레인 완료 후 라우팅 전환 + 보류 큐를 새 소비자에 순서대로 방류.
- 에폭/펜싱: 키별 소유 에폭. 소비자는 최신 에폭일 때만 처리, 옛 소유자는 에폭이 밀리면 중단. 소비자별 마지막 처리
seq를 기억해 역순 이벤트를 거부/재정렬(자기검증).
(이 문제는 프로토콜 설계가 핵심이라 코드 스니펫보다 상태 기계·핸드오프 절차가 정답. problem.cpp 는 g++ -std=c++17 -fsyntax-only 통과.)
더 나은 설계
- 정적 대규모 파티션 + 가상 노드: 키→파티션 불변, 스케일 아웃은 파티션 재배정(파티션 단위 드레인-핸드오프)로. 키 순서는 파티션이 항상 보장.
- 로그 기반(Kafka류): 파티션 증설 시 같은 키가 다른 파티션으로 갈 수 있음을 인지하고 정지-재개/오프셋 기준 드레인.
면접 포인트
- 모듈로 vs 일관 해싱의 재매핑 규모와 리샤딩 안정성.
- 파티션 키 순서 보장의 전제(키→소비자 단사·불변)와 리샤딩에서의 붕괴.
- 무중단 리샤딩 = 배타적 소유권 이전(드레인-후-컷오버) + 신규 버퍼링 + 에폭/펜싱.