TL;DR
- 메시지 처리가 병렬로 이뤄져야 해도 병렬 작업 단위를 반드시 브로커가 관리할 필요는 없으며, 높은 병렬성이 필요한 작업에서는 클라이언트 로컬 병렬 처리가 브로커 자원 부담을 줄이는 선택지임.
- Kafka 공유 그룹(Share Group)은 파티션 수를 병렬성의 단위로 삼지 않으면서 큐와 같은 의미 체계를 제공하지만, 주된 목적이 작업 병렬화인 것은 아님.
- 브로커가 관리하는 병렬성은 소비자 수와 TCP 연결, 프로토콜 상호작용 및 상태를 늘리며, 클라이언트가 관리하는 병렬성은 가상 스레드, 비동기 작업 또는 운영체제 스레드로 구현 가능함.
- 필요한 병렬 작업 단위 수는 초당 메시지 처리율과 평균 처리 시간(초)의 곱으로 계산하며, 초당 6만 건을 1초씩 처리하려면 6만 건의 동시 처리가 필요함.
- 클라이언트 쪽 병렬 처리는 구현 복잡도가 커지지만, 병렬 소비 라이브러리가 이를 도울 수 있으며, 공유 그룹에 클라이언트 로컬 병렬성을 더하는 새 라이브러리도 고려할 수 있음.
공유 그룹의 목적과 병렬성
- 이 글은 ‘Kafka 공유 그룹과 소비 병렬화’ 연재의 곁가지 주제임. 연재의 [1부](part 1)와 [2부](part 2)는 공유 그룹 설정과 동작이 병렬 소비에 미치는 영향을 다룸.
- 공유 그룹을 잘못 구성하면 작업 큐처럼 동작하게 될 수 있으며, 여러 조건이 겹치면 소수의 소비자가 메시지를 독점하고 나머지 소비자는 메시지를 받지 못한 채 지연이 계속 증가할 수 있음. 설정과 각 설정의 효과를 파악하고 기본값에만 의존하지 않는 것이 중요함.
- 공유 그룹이 병렬 소비를 위한 것인지 묻는다면 답은 아님. 병렬 처리만이 관심사라면 다른 선택지가 있음.
- Chuck Larrieu Casias는 LinkedIn 게시물에서 파티션 수를 크게 늘리지 않고 작업을 병렬화하는 단 하나의 해법으로 공유 그룹을 보아서는 안 된다고 지적함.
- 공유 그룹의 목적은 로그 위에 큐와 같은 의미 체계를 제공하는 것임. 일반 소비자 그룹과 달리 공유 그룹에서는 한 레코드를 받아들이고 다른 레코드는 재시도를 위해 거부할 수 있음.
- 소비자 그룹은 파티션마다 커밋된 오프셋 하나를 추적하지만, 공유 그룹은 각 레코드의 사용 가능 여부, 전달 여부와 전달 대상, 확인 여부, 다시 사용 가능해져야 하는지 등을 개별적으로 추적해야 함.
- 공유 그룹의 주된 목적이 작업 병렬화가 아니더라도 병렬 처리에 활용할 수 있음. 메시지가 서로 독립적이거나 순서가 느슨해도 괜찮다면 파티션 수를 병렬성의 단위로 삼지 않는 간단한 선택지가 될 수 있음.
- Chuck의 게시물에서 얻은 핵심은 병렬성을 어디선가 관리해야 한다는 점임. 병렬성의 단위는 브로커가 볼 수 있고 관리하는 방식일 수도, 클라이언트 내부에서 관리하는 방식일 수도 있으며, 브로커가 보고 관리하는 방식에는 한계가 있음.
병렬성의 단위는 어디에 있어야 하는가
- 생산 속도를 따라잡기 위해 메시지 1,000개를 동시에 처리해야 할 때, 그 1,000개의 병렬 작업 단위를 파티션, 소비자, 가상 스레드 또는 비동기 작업 가운데 무엇으로 나타낼지 결정해야 함.
- 병렬성의 단위가 소비자 자체라면 직렬 소비자를 늘려 병렬 처리 규모를 키워야 함. 소비자 그룹에서는 이에 맞춰 파티션 수도 늘려야 함.
- 각 병렬 작업 단위인 소비자는 프로토콜 상호작용과 상태, 하나 이상의 TCP 연결을 통해 브로커에 드러남.
- 병렬성의 일부를 클라이언트가 제공한다면 단위는 가상 스레드, 비동기 작업 또는 운영체제 스레드가 될 수 있음. 이런 단위는 브로커에 보이지 않으므로 소비자 수와 TCP 연결 수, 브로커에 드러나는 프로토콜 상호작용 및 상태를 줄일 수 있음.
- 브로커 쪽과 클라이언트 쪽 가운데 어디에서 병렬성의 단위를 관리할지 나누는 방식은 Kafka에만 한정되지 않고 모든 메시징 시스템에 존재함.
필요한 병렬 작업 단위 수 계산
- 전체 병렬성은 다음과 같이 계산 가능함.
- 전체 병렬성 = 초당 처리율 × 평균 처리 시간(초)
- 계산 예시는 다음과 같음.
- 초당 60,000건 × 1초 = 60,000
- 초당 60,000건 × 5초 = 300,000
- 초당 100건 × 20초 = 2,000
- 초당 10,000건 × 0.5초 = 5,000
- 초당 50건 × 5초 = 250
- 동시에 처리해야 하는 메시지 수를 알면 병렬성 전략을 정할 수 있음. 공식은 필요한 병렬성의 규모를 알려주며, 그 병렬성을 어디에 둘지는 별도로 판단해야 함.
- 공유 그룹 연재에서 다룬 초당 60,000건의 작업 부하를 기준으로 메시지 하나를 처리하는 데 1초가 걸린다면, 어느 순간이든 메시지 60,000건을 처리할 수 있어야 함.
- 각 병렬성 단위가 직렬 소비자라면 소비자 60,000개가 필요하며, 이에 따라 연결과 프로토콜 상태가 많이 늘어나고 소비자 그룹도 매우 커짐.
- 메시지 하나의 평균 처리 시간이 10초라면 소비자 600,000개가 필요하며 TCP 연결은 100만 개를 훨씬 넘게 됨.
- 작업 대부분이 입출력이고 CPU가 대기하는 시간이 많다면 클라이언트 하나가 더 많은 작업을 처리하도록 할 수 있음. 클라이언트 하나가 메시지 1,000개를 병렬로 처리한다면 초당 60,000건을 1초씩 처리하는 예시에서 소비자는 60개만 필요함.
- 그림 1은 왼쪽에 직렬 소비자 N개에 걸친 병렬 작업을, 오른쪽에 병렬 처리 역량을 갖춘 소비자 N개에 걸친 병렬 작업을 비교함.
결론
- 브로커가 관리해야 하는 대상으로 최종 병렬성 단위가 브로커에 드러나면, 병렬성이 높은 작업 부하에서 자원 비용이 매우 커질 수 있음. 이는 어떤 메시징 시스템을 쓰는지와 무관함.
- 가상 스레드나 운영체제 스레드를 관리하는 비용은 병렬성 단위마다 하나 이상의 TCP 연결과 메타데이터를 관리하는 비용보다 훨씬 낮음. 사용해 본 모든 메시징 시스템에서 이 점이 동일함.
- 클라이언트 쪽 병렬 처리의 비용은 클라이언트 복잡도 증가임. 직접 로직을 구현하고 싶지 않다면 이를 도울 라이브러리가 있으며, 관련 예시는 Chuck의 게시물에서 확인 가능함.
ParallelConsumer라이브러리는 더 이상 유지 관리되지 않지만, 향후 포크가 나올 가능성은 있음. 이 라이브러리는 소비자 그룹 위에 큐 의미 체계와 클라이언트 내부 병렬 처리를 더했음.- 공유 그룹이 등장한 지금, 공유 그룹에 클라이언트 쪽 병렬 처리를 더하는 새 라이브러리를 고려할 수 있음.
- 다음으로 공유 그룹과 소비자 그룹에서 브로커 관리형 병렬성과 클라이언트 관리형 병렬성을 비교하는 연재 3부를 이어서 작성할 예정임.
댓글 (0)
로그인하면 이 기사에 내 생각을 남길 수 있어요