← 문제로

28. 리샤딩 중 키→파티션 재매핑으로 인한 순서 보장 붕괴

난이도 최상
내 리뷰 · C#
해설 · C#

해설 — 리샤딩 중 키→파티션 재매핑으로 인한 순서 보장 붕괴

난이도: 최상

요약

순서 보장의 전제는 "같은 playerId의 모든 이벤트가 항상 같은 단일 소비자로 간다"는 것이다. 그런데 라우팅이 playerId % _count (A)이고, AddPartition이 _count를 즉시 증가시킨다 (B). _count가 바뀌는 순간 거의 모든 키의 목적지 파티션이 바뀐다(모듈로 해싱은 N 변경 시 대량 재매핑). 이때 어떤 플레이어의 이전 이벤트들은 옛 파티션 큐에 아직 남아(in-flight) 옛 소비자가 처리 중인데, 그 플레이어의 새 이벤트는 새 파티션으로 들어가 새 소비자가 동시에 처리한다. 결과적으로 한 플레이어가 두 소비자에서 병렬 처리되어 인과 순서가 뒤바뀐다(예: 새 파티션의 "판매"가 옛 파티션의 "장착"보다 먼저 커밋).

문제점

  • 분류태그: 리샤딩 중 파티션 키 순서 보장 붕괴(재매핑 + in-flight 미드레인, 배리어/에폭 부재)
  • 증상: 리샤딩 직후 짧은 구간에 "판매가 장착보다 먼저 처리됨", 재화 이중 처리/유실, 상태 divergence. 리샤딩 안 하면 재현 안 됨(간헐).
  • 재현조건: 파티션 수 변경 시점에, 재매핑된 플레이어의 이벤트가 옛 파티션에 남아 있고 새 이벤트가 새 파티션으로 유입.
  • 근본원인: (1) 라우팅 함수(% _count)가 원자적 스위치처럼 바뀌지만 in-flight 이벤트의 핸드오프/드레인이 없다. (2) 모듈로 해싱이라 재매핑 규모가 크다. (3) 키 소유권 이전에 배리어/에폭이 없어 옛·새 소유자가 겹친다.

수정안

핵심은 "키 소유권 이전 시, 새 소유자는 옛 소유자가 그 키를 다 비운 뒤에 시작한다"는 핸드오프 프로토콜과, 재매핑 규모를 줄이는 라우팅이다.

  1. 일관 해싱(consistent hashing)/rendezvous로 N 변경 시 재매핑되는 키를 최소화(영향 키만 이동).

  2. 키(또는 영향 파티션) 단위 드레인-후-컷오버:

리샤딩 시작:
  - 이동 대상 키 집합 K 확정(일관 해싱으로 최소화).
  - K 로 가는 신규 이벤트를 '보류 큐'에 버퍼링(아직 새 소비자에 안 넘김).
  - 옛 소비자가 K 의 in-flight 이벤트를 모두 처리 완료할 때까지 대기(드레인).
  - 드레인 완료 후: 라우팅을 새 매핑으로 전환하고, 보류 큐를 새 소비자에 순서대로 방류.

이렇게 하면 한 키가 두 소비자에서 동시에 처리되는 구간이 없어진다(소유권은 항상 배타적).

  1. 에폭/펜싱: 각 키에 소유 에폭을 부여하고, 소비자는 자신의 에폭이 최신일 때만 처리. 옛 소유자는 에폭이 밀리면 그 키 처리를 중단.

더 나은 설계

  • 정적 파티션 + 가상 노드: 파티션 수를 크게 고정하고(예: 1024) 소비자에 파티션 그룹을 배정. 스케일 아웃은 파티션 재배정(키→파티션은 불변)으로 → 키 순서는 파티션이 지키고, 이동은 파티션 단위 드레인-핸드오프.
  • 로그 기반(Kafka류) 파티셔닝: 파티션 수를 늘리면 같은 키가 다른 파티션으로 갈 수 있음을 인지하고, 리파티셔닝 시 오프셋 기준 드레인/정지-재개.
  • 시퀀스/버전으로 순서 자기검증: 소비자가 키별 마지막 처리 Seq를 기억해, 더 낮은 Seq가 뒤늦게 오면 거부/재정렬(순서 위반을 데이터로 방어).

면접 포인트

  • 모듈로 해싱 vs 일관 해싱의 재매핑 규모 차이와 리샤딩 안정성.
  • "파티션 키 순서 보장"의 전제(키→소비자 단사·불변)가 리샤딩에서 어떻게 깨지는가.
  • 무중단 리샤딩의 핵심: 배타적 소유권 이전(드레인-후-컷오버) + 신규 이벤트 버퍼링 + 에폭/펜싱.