ONE QUESTION AT A TIME.

천천히 생각하고, 분명하게.

TODAY’S QUESTION939 / 1500
분산 시스템난이도 하5분 답변 목표 · 보강본

같은 Kafka partition의 메시지를 순서대로 받았지만 여러 워커가 처리하자 최종 상태가 뒤바뀝니다. 전달 순서와 처리 완료 순서를 어떻게 구분하고 필요한 순서를 보장하나요?

잠깐, 답변을 보기 전에
먼저 내 말로 설명해 볼까요?

답변과 점검 포인트 펼치기답변 접기
01

구두 답변

정의를 외우기보다, 원리와 조건을 연결해 설명해 보세요.

브로커가 보장하는 순서는 보통 특정 partition 또는 키가 배치된 흐름의 전달 순서입니다. 그것은 모든 topic의 전역 FIFO도 아니고, 소비자가 외부 시스템에 효과를 적용한 완료 순서도 아닙니다. 소비자가 1번을 받은 뒤 1번을 느린 워커에 넘기고 2번을 빠른 워커에 넘기면 브로커 전달은 순서였어도 완료와 DB(데이터베이스) 반영은 2번이 먼저 될 수 있습니다. 잔액을 먼저 더하고 나중에 빼는 이벤트라면 최종 값이 같아 보여도 중간에 허용되지 않은 잔액이 노출될 수 있습니다.

계정 잔액 변경처럼 같은 키의 순서가 필수라면 키를 같은 partition으로 보내고 소비자에서도 그 키의 적용을 직렬화하겠습니다. 첫 이벤트가 실패해 재시도되는 동안 둘째 이벤트를 먼저 적용해도 되는지 두 이벤트의 의미와 의존 관계를 확인해야 합니다. 전체 상태를 담은 version 8 이벤트는 version 7을 거절할 수 있지만, +10 증분 이벤트는 중간 하나를 버리면 최종 잔액이 틀립니다.

모든 메시지를 한 partition에 넣으면 순서는 단순해지지만 처리량과 장애 격리가 나빠집니다. 순서 보장이 필요한 키만 직렬화하고, 순서가 깨진 이벤트는 보류 큐·시간 상한·재동기화 정책으로 처리하겠습니다. 파티션 변경·재시도·느린 첫 메시지를 시험해 최종 상태뿐 아니라 금지된 중간 상태, 중복과 외부 커밋 순서까지 검증하겠습니다.

보장하는 순서의 범위를 먼저 정합니다

Kafka partition의 offset은 그 로그 안의 위치를 나타냅니다. 한 partition 안에는 비교 가능한 순서가 있지만 서로 다른 partition의 숫자만으로 발생 순서를 정할 수는 없습니다. 전체 이벤트를 하나로 줄 세우는 전역 순서와 계정별 변경만 정렬하는 키별 순서는 요구 비용이 다릅니다. 계정 A와 B의 독립 변경까지 서로 기다리게 할 필요가 없다면 키별로만 직렬화하고 다른 키는 병렬 처리하는 편이 효율적입니다. 단, 키를 고르는 단계에서 같은 계정이 정말 같은 partition으로 가는지 발행자의 계약이 필요합니다.

같은 키의 이벤트를 한 큐로 보내도 콜백이 외부 I/O를 시작한 뒤 다음 이벤트를 실행하면 완료 순서는 바뀔 수 있습니다. 처음부터 결과 반영까지를 한 단위로 보장하는 처리 직렬화가 필요한 이유입니다. 완료를 기다리는 동안 같은 키의 뒤 작업이 막히는 것은 선두 차단(head-of-line blocking)의 비용입니다. 느린 이벤트를 건너뛰어 다른 키는 진행시키더라도 같은 키의 후속 변경을 적용해도 되는지 따로 정해야 합니다. DLQ로 옮겼다는 사실은 그 이벤트의 의미를 생략해도 된다는 허가가 아닙니다.

스냅샷과 증분은 복구 방법이 다릅니다

전체 계정 상태를 담은 version 8을 받았고 현재 version이 6이라면, version 7을 적용하지 않아도 최신 상태를 만들 수 있는 계약이 있을 수 있습니다. 하지만 중간에 발행해야 할 알림 같은 효과가 있다면 상태만 덮어쓰는 것으로 충분하지 않습니다. 반면 ‘잔액 10 증가’처럼 이전 상태에 누적하는 증분 이벤트(delta event)는 누락된 이벤트를 버리면 합계가 달라집니다. 다음 예상 버전과 실제 버전을 비교해 빠진 구간을 찾는 것을 갭 탐지(gap detection)라고 합니다. 갭이 있으면 보류·재조회·원본 재생 중 복구 방법을 택하겠습니다.

보류 큐에도 최대 개수와 시간을 둬야 합니다. 절대 오지 않을 version 7을 기다리며 version 8 이후를 메모리에 영원히 보관해서는 안 됩니다. 원본 저장소에서 현재 스냅샷과 기준 버전을 다시 얻고 그 다음 이벤트부터 이어갈 수 있는지 확인하겠습니다. 이벤트 ID의 중복 검사와 상태 버전 갱신은 실제 상태 변경과 같은 원자적 경계에서 이뤄져야 합니다. 버전만 올리고 변경이 실패하면 복구가 필요한 이벤트를 이미 처리했다고 오판할 수 있습니다.

완료 위치와 읽은 위치를 나눕니다

소비자가 offset 10을 읽고 처리 중인데 11의 빠른 작업이 먼저 끝났다고 하겠습니다. 이때 12부터 재개하겠다고 커밋하면 재시작 시 10을 건너뛸 수 있습니다. 병렬 처리에서는 뒤쪽 완료만 보고 커밋하지 않고, 그 앞의 필요한 레코드가 모두 끝난 연속 완료 지점을 추적해야 합니다. 읽기 위치, 완료된 개별 작업, 재시작 위치를 별도의 상태로 관리하는 것입니다. 중단된 트랜잭션이나 압축 등으로 숫자에 빈 구간이 있을 수 있으므로 단순히 모든 정수 offset이 등장해야 한다고 검사하지는 않습니다.

검증에서는 첫 이벤트만 느리게 하고 뒤 이벤트는 빨리 끝내 보겠습니다. 그 중간에 소비자를 재시작하거나 리밸런싱해 재처리와 누락을 확인합니다. 최종 합계가 우연히 같더라도 음수 잔액처럼 금지된 중간 상태가 노출될 수 있으므로 상태 전이 기록도 확인해야 합니다. 어떤 연산이 순서를 바꿔도 같은 결과를 만드는 교환 가능성(commutativity)을 갖는지는 별도 증명이 필요하며, 덧셈이라는 이유만으로 모든 제약과 외부 효과까지 순서와 무관하다고 보지 않겠습니다.

02

득점 포인트

이해가 드러나는 지점을 확인해 보세요.

  • 전달·워커 완료·외부 적용 순서의 범위를 분리한다.
  • 전체 상태와 증분 이벤트의 역전 영향을 대비한다.
  • 필요한 키만 직렬화하고 병렬성 비용을 명시한다.
03

감점 포인트

놓치기 쉬운 조건과 오해를 짚어 보세요.

  • 브로커 FIFO가 워커 완료 FIFO까지 보장한다고 말한다.
  • version이 높으면 어떤 증분 이벤트도 버려도 된다고 말한다.
  • 전체를 한 partition으로 만들면 처리량·장애 비용이 없다고 말한다.
04

더 파고들 거리

하나 더 질문하며 이해의 범위를 넓혀 보세요.

득점·감점 포인트는 학습용 자기 점검 기준이며, 특정 회사의 실제 채점표가 아닙니다.

원리부터 공부하고 싶다면

답변을 보충하는 개념·예제·슈도코드를 읽어 보세요.

← 전체 질문 보기