728x90

안녕하세요, 카프카에 관련된 글을 적기 위해서 고민을 하던차에 미국의 대기업 중 하나인 월마트의 카프카 사용에 대한 좋은 글을 읽게 되어서 먼저 공유를 하고 점차적으로 글을 공유하려고합니다.

먼저 카프카에 대해서 어느정도 알고 있다면 큰 어려움 없이 읽을수 있을거같습니다.

 

요약

Walmart는 여러 클라우드(퍼블릭 및 프라이빗)에 걸쳐 25,000명 이상의 Kafka 소비자와 함께 Apache Kafka를 배포하고 있습니다. 데이터 이동, 이벤트 기반 마이크로서비스, 스트리밍 분석 등 비즈니스에 중요한 사용 사례를 지원합니다. 이러한 사용 사례는 99.99의 가용성을 요구하며 갑작스러운 트래픽 급증으로 인해 발생하는 백로그를 신속하게 처리해야 합니다. Walmart 규모에서는 여러 언어로 작성된 다양한 Kafka 소비자 애플리케이션을 보유하고 있습니다. 이러한 다양성과 안정성 요구 사항이 결합되어 소비자 애플리케이션은 고가용성 SLO를 보장하기 위해 모범 사례를 채택해야 합니다. 카프카 소비자 리밸런싱으로 인한 높은 소비자 지연은 카프카 소비자를 대규모로 운영할 때 가장 흔한 문제입니다. 이 글에서는 저렴한 비용과 탄력성으로 하루에 수조 개의 메시지를 안정적으로 처리하는 Apache Kafka 메시지의 안정적 처리 방법을 중점적으로 설명합니다.


소비자 리밸런싱


Kafka의 프로덕션 배포에서 자주 발생하는 문제는 소비자 리밸런싱과 관련이 있습니다. 카프카 리밸런싱은 각 소비자가 거의 동일한 수의 파티션을 처리하도록 하기 위해 카프카가 소비자 간에 파티션을 재분배하는 프로세스입니다. 이를 통해 데이터 처리가 소비자 간에 균등하게 분산되고 각 소비자가 최대한 효율적으로 데이터를 처리하도록 보장합니다. Kafka 애플리케이션은 컨테이너 또는 VM(가상 머신이라고도 함)에서 실행됩니다. 이 글에서는 현재 업계에서 널리 사용되고 있는 컨테이너에 초점을 맞춥니다. 컨테이너 이미지로 구축된 Kafka 소비자 애플리케이션은 Kubernetes를 기반으로 구축된 엔터프라이즈급 멀티클라우드 컨테이너 오케스트레이션 프레임워크인 WCNP(Walmart Cloud Native Platform)에서 실행됩니다. 따라서 다음과 같은 다양한 원인으로 인해 소비자 리밸런싱이 트리거될 수 있습니다:

소비자 그룹을 떠나는 소비자 파드: 이는 K8 배포 또는 롤링 재시작 또는 자동/수동 스케일-인으로 인해 발생할 수 있다.
소비자 그룹에 진입하는 소비자 파드: 이는 K8 배포 또는 롤링 재시작 또는 자동/수동 스케일아웃으로 인해 발생할 수 있다.
소비자가 실패한 것으로 판단하는 Kafka 브로커(예: 브로커가 세션.timeout.ms 내에서 소비자로부터 하트비트를 수신하지 못한 경우): JVM이 종료되거나 장기간 가비지 수집이 일시 중지되는 경우 트리거됩니다.
소비자가 멈췄다고 판단하는 경우(예: 소비자가 소비할 다음 레코드 배치를 폴링하는 데 max.poll.interval.ms보다 더 오래 걸리는 경우) Kafka 브로커가 트리거됩니다: 이전에 폴링된 레코드의 처리가 이 간격을 초과하면 트리거됩니다.

 

소비자 리밸런싱은 계획된 유지보수(예: 코드 릴리즈), 표준 운영 관행(예: 최소 포드/최대 포드 설정 수동 변경), 자동 자가 복구(예: 포드 충돌, 자동 확장) 모두에서 복원력을 달성하지만 지연 시간에는 부정적인 영향을 미칩니다. 오늘날 커머스의 거의 실시간에 가까운 특성을 고려할 때, 많은 Kafka 사용 사례에는 엄격한 전송 SLA가 적용됩니다. 이러한 애플리케이션은 프로덕션에서 빈번하고 예측할 수 없는 재조정으로 인해 지속적인 지연 경보로 인해 어려움을 겪었습니다.

현재로서는 카프카에서 리밸런싱을 피하도록 소비자를 구성하는 깔끔한 방법이 없습니다. 커뮤니티에서 정적 소비자 멤버십과 협업을 통한 점진적 리밸런싱을 제공하지만, 이러한 접근 방식에는 나름의 어려움이 따릅니다.

포이즌 필

HOL(헤드 오브 라인) 차단은 네트워킹 및 메시징 시스템에서 발생할 수 있는 성능 제한 현상입니다. Kafka 소비자가 성공적으로 처리될 수 없는 메시지를 접할 때 발생합니다. 메시지 처리 결과 Kafka 소비자 스레드에 잡히지 않은 예외가 발생하면 소비자는 브로커의 다음 폴링에서 동일한 메시지 배치를 다시 소비하게 되며, “포이즌 필” 메시지가 포함된 동일한 배치가 동일한 예외를 발생시킬 것으로 예상됩니다. 이 루프는 문제가 있는 메시지를 건너뛰거나 올바르게 처리하는 코드 수정이 Kafka 소비자 애플리케이션에 배포되거나 소비자 오프셋을 변경하여 문제가 있는 메시지를 건너뛸 때까지 무기한 계속됩니다. 이 포이즌 필 문제는 분할된 데이터 스트림의 순서 내 처리와 관련된 또 다른 문제입니다. 아파치 카프카는 포이즌 필 메시지를 자동으로 처리하지 않습니다.

비용

토픽의 파티션과 이를 읽는 소비자 스레드 사이에는 강력한 결합이 있습니다. 토픽의 최대 소비자 수는 해당 토픽의 파티션 수를 초과할 수 없습니다. 소비자가 토픽 흐름을 따라갈 수 없는 경우(즉, 지속적으로 낮은 소비자 지연을 유지하는 경우) 모든 파티션이 전용 소비자 스레드에 할당될 때까지만 소비자를 더 추가하는 것이 도움이 됩니다. 이 시점에서는 최대 소비자 수를 늘리기 위해 파티션 수를 늘려야 합니다. 좋은 아이디어처럼 들릴 수 있지만, 브로커 노드를 다음으로 큰 크기(4000개의 파티션/브로커)까지 수직 확장하기 전에 브로커에 추가할 수 있는 파티션의 수에 대한 일반적인 규칙이 있습니다.) 보시다시피, 소비자 지연이 증가하면 브로커 자체에 충분한 물리적 리소스(예: 메모리, CPU, 스토리지)가 있음에도 불구하고 파티션이 증가하고 잠재적으로 더 큰 규모의 브로커로 확장해야 하는 문제가 발생합니다. 파티션과 소비자 간의 이러한 강력한 결합은 카프카의 트래픽 증가에도 불구하고 낮은 지연 시간을 유지하고자 하는 많은 엔지니어들의 오랜 골칫거리였습니다.

Kafka 파티션 확장성

수천 개의 파이프라인이 있는 경우, 파티션 수를 늘리면 생산자, 소비자, 플랫폼 팀 간의 조정이 필요하고 가동 중단 시간이 짧아지기 때문에 운영상 부담이 됩니다. 갑작스러운 트래픽 급증과 대규모 백로그 소진은 모두 파티션과 소비자 포드의 증가를 필요로 합니다.

솔루션

위의 몇 가지 문제(예: 카프카 소비자 리밸런싱)를 해결하기 위해, 카프카 커뮤니티는 다음과 같은 카프카 개선 제안을 제안했습니다: KIP-932: Kafka용 대기열.

메시징 프록시 서비스(MPS)는 사용 가능한 다른 경로입니다. MPS는 HTTP를 통해 메시지를 소비자가 대기하는 REST 엔드포인트로 프록시하여 파티션의 제약으로부터 Kafka 소비를 분리합니다. MPS 접근 방식을 통해 Kafka 소비는 더 이상 리밸런싱으로 인한 문제를 겪지 않으며, 더 적은 수의 파티션으로 더 많은 처리량을 처리할 수 있습니다.

MPS 접근 방식의 또 다른 이점은 애플리케이션 팀이 더 이상 Kafka 소비자 클라이언트를 사용할 필요가 없다는 것입니다. 따라서 모든 Kafka 팀은 애플리케이션 팀을 쫓아다니며 Kafka 클라이언트 라이브러리를 업그레이드할 필요가 없습니다.

디자인

MPS Kafka 소비자는 두 개의 독립적인 스레드 그룹, 즉 Kafka message_reader 스레드(즉, 1개 스레드 그룹)와 message_processing_writer 스레드로 구성됩니다. 이러한 스레드 그룹은 표준 버퍼링 패턴(pendingQueue)으로 구분됩니다. 리더 스레드는 (폴링 중에) 바운드 버퍼에 쓰고, 쓰기 스레드는 이 버퍼에서 읽습니다.

또한 바운드 버퍼는 읽기 및 쓰기 스레드의 속도를 제어할 수 있습니다. pendingQueue가 최대 버퍼 크기에 도달하면 message_reader 스레드가 소비자를 일시 중지합니다.

이렇게 리더 스레드와 쓰기 스레드를 분리하면 리더 스레드가 매우 가벼워지고 max.poll.interval.ms

를 초과하여 재밸런싱 작업이 트리거되지 않습니다. 이제 작성자 스레드는 메시지를 처리하는 데 필요한 시간을 확보할 수 있습니다. 다음 다이어그램은 구성 요소와 디자인에 대한 그림 보기를 제공합니다.

 

 

위의 아키텍처는 다음과 같은 주요 구성 요소로 이루어져 있습니다:

 

리더 스레드

리더 스레드의 역할은 인바운드 토픽을 진행하면서 PendingQueue가 가득 차면 역압박을 가하는 것입니다.


순서 이터레이터



순서 이터레이터는 키가 지정된 메시지가 순서대로 처리되도록 보장합니다. 이 반복기는 pendingQueue에 있는 모든 메시지를 반복하고 동일한 키를 가진 메시지가 이미 전송 중인 경우 메시지를 남겨둡니다(즉, 일시적으로 건너뜁니다). 건너뛴 메시지는 동일한 키를 가진 이전 메시지가 처리되면 후속 폴링 호출에서 처리됩니다. MPS는 키당 1개의 메시지만 전송되도록 함으로써 키별 순서대로 전달되도록 보장합니다.

작성자 스레드

작성자 스레드는 병렬 처리를 통해 더 많은 처리량을 제공하는 풀의 일부입니다. 재시도가 모두 소진되거나 재시도할 수 없는 HTTP 응답 코드가 수신되는 경우 REST 엔드포인트에 데이터를 안정적으로 쓰고 메시지를 DLQ하는 것이 이 스레드가 하는 일입니다.

Deal Letter Queue(DLQ)


DLQ 토픽은 모든 Kafka 클러스터에서 생성할 수 있습니다. message_processing_writer 스레드는 처음에 기하급수적으로 백오프하면서 정해진 횟수만큼 메시지를 재시도합니다. 실패하면 메시지는 DLQ 토픽에 저장됩니다. 애플리케이션은 나중에 이러한 메시지를 처리하거나 삭제할 수 있습니다. 소비자 서비스에 중단(예: 시간 초과)이 발생하거나 소비자 서비스에 포이즌 필(예: 500 HTTP 응답)이 발생하는 경우 이 대기열에 메시지를 배치할 수 있습니다.

Comsumer Service

소비자 서비스는 애플리케이션이 메시지를 처리하기 위한 상태 비저장 REST 서비스입니다. 이 서비스에는 원래 Kafka 소비자 애플리케이션에서 사용할 수 있었던 처리의 일부였던 비즈니스 로직이 포함되어 있습니다. 이 새로운 모델을 사용하면 Kafka 소비(MPS)를 메시지 처리(소비자 서비스)에서 분리할 수 있습니다. 아래에서 모든 소비자 서비스에서 구현해야 하는 REST API 사양을 확인할 수 있습니다:

Kafka 오프셋 커밋 스레드

Kafka 오프셋 커밋은 별도의 스레드(즉, offset_commit 스레드)로 구현됩니다. 이 스레드는 일정한 간격(예: 1분)으로 깨어나서 쓰기 스레드가 성공적으로 처리한 가장 최근의 연속된 오프셋을 커밋합니다.

위 그림에서, offset_commit 스레드는 파티션 0과 1에 대해 각각 오프셋 124와 150을 커밋합니다.

 

 

 

구현 세부 정보
MPS는 카프카 커넥트에서 싱크 커넥터로 구현되었습니다. Kafka Connect 프레임워크는 다음과 같은 여러 가지 이유로 적합합니다:

멀티테넌시: 여러 개의 커넥터를 단일 Kafka Connect 클러스터에 배포할 수 있습니다.
DLQ 처리: Kafka Connect는 이미 DLQ 처리를 위한 기본 프레임워크를 제공합니다.
커밋 흐름: Kafka Connect는 커밋을 위한 편리한 방법을 제공합니다.
기본 제공 NFR(비기능 요구 사항): Kafka Connect는 많은 비기능적 기능(예: 확장성, 안정성)을 제공합니다.

 

 

결론

MPS는 리더 스레드가 할당된 시간인 max.poll.interval.ms 5분 이내에 폴링 목록의 모든 메시지를 pendingQueue에 넣도록 보장하기 때문에 다운스트림 시스템의 속도 저하로 인한 리밸런싱을 제거했습니다. 우리가 보는 유일한 리밸런싱은 Kubernetes POD 재시작과 극히 드물게 발생하는 Kafka 클러스터와 MPS 간의 네트워크 속도 저하로 인한 것입니다. 그러나 소규모 소비자 그룹에서는 이러한 주기의 지속 시간이 무시할 수 있을 정도로 짧으며 처리 SLA(서비스 수준 계약)를 초과하지 않습니다. MPS 서비스와 Kafka 클러스터는 동일한 클라우드와 지역에서 호스팅되어야 두 서비스 간의 네트워크 관련 문제를 줄일 수 있습니다.

애플리케이션이 포이즌 필을 감지하고 반환 코드 600 및 700을 통해 MPS에 알리는 포이즌 필의 협력적 처리는 계획대로 작동합니다.

이 솔루션의 비용 이점은 두 가지 영역에서 실현됩니다. 첫째, 상태 비저장 소비자 서비스는 Kubernetes 환경에서 빠르게 확장할 수 있으며 명절이나 캠페인 이벤트를 위해 미리 확장할 필요가 없습니다. 둘째, Kafka 클러스터 크기는 더 이상 파티션 크기에 의존하지 않으며, 파티션당 약 5~10MB로 처리량에 맞게 실제로 확장할 수 있습니다.

매년 연말연시에 Kafka 클러스터를 확장하면서 발생하는 사이트 관련 문제와 Kafka 파이프라인의 운영 요청을 재조정하는 데 있어 큰 개선이 있었습니다.

상태 비저장 소비자 서비스는 메시지 버스트를 처리하기 위해 Kubernetes 환경에서 쉽게 자동 확장되기 때문에 트래픽이 갑자기 급증해도 더 이상 Kafka 파티션을 확장할 필요가 없습니다.

728x90

'글또' 카테고리의 다른 글

메세지큐 - 카프카에 대해서  (5) 2024.10.27
복사했습니다!