Apache Kafka는 확장성이 뛰어난 고성능 이벤트 스트리밍 플랫폼입니다. Kafka의 잠재력을 최대한 활용하려면 애플리케이션 설계를 신중하게 고려해야 합니다. Kafka 애플리케이션을 작성하다 보면 성능이 떨어지거나 결국 확장성의 한계에 부딪히는 일이 너무 쉽게 발생합니다. 2015년부터 IBM은 IBM Cloud에서 실행되는 완전 관리형 Apache Kafka 서비스인 IBM Event Streams 서비스를 제공하고 있습니다. 그 이후로 이 서비스는 IBM 내 팀뿐만 아니라 많은 고객들이 작성한 Kafka 애플리케이션의 확장성 및 성능 문제를 해결하는 데 도움을 주었습니다.
이 아티클에서는 Apache Kafka의 몇 가지 일반적인 문제를 설명하고 애플리케이션에서 확장성 문제가 발생하지 않도록 하는 방법에 대한 몇 가지 권장 사항을 제공합니다.
특정 Kafka 작업은 클라이언트가 브로커에 데이터를 전송하고 응답을 기다리는 방식으로 작동합니다. 전체 왕복 시간은 10밀리초가 걸릴 수 있는데, 이는 빠른 것처럼 들리지만 최대 100번의 연산으로 제한됩니다. 따라서 이러한 종류의 작업은 가급적 피하는 것이 좋습니다. 다행히 Kafka 클라이언트는 이러한 왕복 시간을 기다리지 않아도 되는 방법을 제공합니다. 그 방법들을 제대로 활용하고 있는지 확인하기만 하면 됩니다.
처리량을 극대화하기 위한 팁
위 내용을 읽고 '이런, 이렇게 하면 애플리케이션이 더 복잡해지지 않을까?'라고 생각한다면 대답은 '그렇다'입니다. 사실 그럴 가능성이 높습니다. 처리량과 애플리케이션 복잡성 사이에는 상충 관계가 있습니다. 네트워크 왕복 시간이 특히 교묘한 함정인 이유는 이 한계에 도달하면 처리량을 더 개선하기 위해 애플리케이션을 광범위하게 수정해야 할 수도 있기 때문입니다.
Kafka의 유용한 기능 중 하나는 메시지를 처리하는 애플리케이션의 '활성 상태'를 모니터링하고, 실패한 애플리케이션의 연결을 끊는 것입니다. 이 기능은 브로커가 각 애플리케이션 클라이언트가 마지막으로 '폴(poll, 더 많은 메시지를 요청하는 Kafka 용어)'을 호출한 시간을 추적함으로써 작동합니다. 클라이언트가 충분히 자주 폴링하지 않으면, 연결된 브로커는 해당 클라이언트가 실패했다고 판단하고 연결을 끊습니다. 이 설계는 문제가 없는 다른 클라이언트가 개입하여 실패한 클라이언트의 작업을 이어받을 수 있도록 돕습니다.
안타깝게도 이 방식에서는 Kafka 브로커가 수신한 메시지를 처리하는 데 시간이 오래 걸리는 클라이언트와 실제로 실패한 클라이언트를 구분할 수 없습니다. 예를 들어, 다음과 같이 메시지를 반복 처리하는 애플리케이션을 생각해 보세요. 1) 폴을 호출하고 메시지 배치를 가져옵니다. 또는 2) 각 메시지를 배치에서 하나씩 처리하며, 메시지 하나당 처리 시간이 1초 걸립니다.
이 컨슈머가 10개의 메시지 배치를 수신하는 경우, 호출에서 폴까지 약 10초가 걸립니다. 기본적으로 Kafka는 클라이언트 연결을 끊기 전에 폴 사이에 최대 300초(5분)의 시간을 허용하므로 이 시나리오에서는 모든 것이 제대로 작동합니다. 그런데 애플리케이션이 처리하고 있는 토픽에 메시지 백로그가 쌓이기 시작하면, 정말 바쁜 날에는 어떻게 될까요? 애플리케이션은 각 폴 호출에서 10개의 메시지를 가져오는 대신, 500개의 메시지(기본적으로 폴 호출로 반환될 수 있는 최대 레코드 수)를 받게 됩니다. 이렇게 되면 Kafka가 애플리케이션 인스턴스가 실패했다고 판단하고 연결을 끊을 만큼 충분한 처리 시간이 소요됩니다. 이는 좋지 않은 상황입니다.
더 나빠질 수 있다는 사실을 알게 되면 놀라실 수도 있습니다. 일종의 피드백 루프가 발생할 수 있습니다. Kafka가 폴을 충분히 자주 호출하지 않는다는 이유로 클라이언트의 연결을 끊기 시작하면 메시지를 처리할 애플리케이션 인스턴스가 줄어듭니다. 토픽에 쌓이는 메시지 백로그가 증가하고, 더 많은 클라이언트가 대량의 메시지 배치를 받아 처리하는 데 시간이 너무 오래 걸릴 가능성이 높아집니다. 결국 애플리케이션의 모든 인스턴스가 재시작 루프에 빠지고 유용한 작업은 이루어지지 않게 됩니다.
이런 일이 일어나지 않도록 어떤 조치를 취할 수 있을까요?
이 아티클의 후반부에서 다시 컨슈머 실패라는 주제로 돌아와, 컨슈머 그룹 리밸런싱을 유발할 수 있는 방식과 그로 인한 파괴적인 영향에 대해 살펴보겠습니다.
내부적으로 Kafka 컨슈머가 메시지를 수신하는 데 사용하는 프로토콜은 Kafka 브로커에 'fetch' 요청을 보내는 방식으로 작동합니다. 이 요청의 일부로 클라이언트는 브로커가 반환할 메시지가 없을 경우 어떤 동작을 해야 하는지 지정할 수 있으며, 여기에는 브로커가 빈 응답을 보내기 전에 얼마나 기다려야 하는지도 포함됩니다. 기본적으로 Kafka 컨슈머는 브로커에게 최소 1바이트('fetch.min.bytes' 설정으로 제어)의 메시지 데이터가 준비될 때까지 최대 500밀리초('fetch.max.wait.ms' 컨슈머 설정으로 제어) 동안 기다리도록 지시합니다.
500밀리초를 기다리는 것은 그리 무리한 요구처럼 보이지 않지만, 애플리케이션에 대부분 유휴 상태인 컨슈머가 있고 인스턴스 수가 5,000개까지 확장된다면, 아무 작업도 하지 않는 요청이 초당 약 2,500건에 달할 수 있습니다. 이러한 각 요청은 브로커의 CPU 시간을 소모하며, 극단적인 경우 유용한 작업을 수행하려는 Kafka 클라이언트의 성능과 안정성에도 영향을 줄 수 있습니다.
일반적으로 Kafka의 확장 방식은 브로커를 더 추가한 다음 기존 브로커와 신규 브로커 모두에서 토픽 파티션의 균형을 균등하게 재조정하는 것입니다. 하지만 클라이언트가 Kafka에 불필요한 fetch 요청을 과도하게 보내는 경우, 이 방법은 도움이 되지 않을 수 있습니다. 각 클라이언트는 자신이 메시지를 처리하고 있는 토픽 파티션을 담당하는 모든 브로커에 fetch 요청을 전송합니다. 따라서 Kafka 클러스터를 확장하고 파티션을 재배포한 후에도 대부분의 클라이언트가 대부분의 브로커에 fetch 요청을 보내는 상황이 발생할 수 있습니다.
그렇다면 어떻게 해야 할까요?
다른 게시–구독 시스템(예: MQTT)을 사용해 본 배경에서 Kafka를 접하게 되면, Kafka 토픽이 매우 가볍고 거의 일시적이라고 예상할 수 있습니다. 하지만 실제로는 그렇지 않습니다. Kafka는 수천 개의 토픽을 다루는 데 더 적합하며, Kafka 토픽 또한 비교적 오래 지속될 것으로 예상됩니다. 단일 응답 메시지를 수신하기 위해 토픽을 만든 다음 바로 삭제하는 방식은 Kafka에서는 흔하지 않으며 Kafka의 강점을 제대로 발휘하지 못하는 방법입니다.
대신 오래 유지되는 토픽을 기반으로 설계하세요. 토픽은 애플리케이션이나 특정 활동의 수명과 함께할 수도 있습니다. 또한 토픽 수는 수백 개, 많아도 수천 개 이하로 제한하는 것이 좋습니다. 이를 위해서는 특정 토픽에 여러 메시지가 섞여 들어오는 방식에 대해 관점을 달리해야 할 수도 있습니다.
종종 "토픽에는 몇 개의 파티션이 있어야 할까요?"라는 질문이 제기됩니다. 전통적인 조언은 파티션 수를 넉넉히 잡는 것입니다. 토픽이 생성된 후 파티션을 추가해도, 토픽이 보유한 기존 데이터의 파티셔닝은 변경되지 않기 때문입니다(이로 인해 파티션 내 메시지 순서를 제공하기 위해 파티셔닝에 의존하는 컨슈머에게 영향을 줄 수 있음). 이는 좋은 조언이지만, 몇 가지 추가적인 고려 사항도 함께 살펴볼 필요가 있습니다.
메시지를 소비하는 대부분의 Kafka 애플리케이션은 Kafka의 컨슈머 그룹 기능을 활용하여 어떤 클라이언트가 어떤 토픽 파티션에서 소비하는지 조정합니다. 컨슈머 그룹 개념이 조금 흐릿하신 분들을 위해 핵심만 빠르게 짚어 보겠습니다.
Kafka가 발전함에 따라 점점 더 정교한 리밸런싱 알고리즘이 고안되었으며, 이는 지금도 계속 개선되고 있습니다. Kafka의 초기 버전에서는 컨슈머 그룹이 리밸런싱될 때, 그룹 내 모든 클라이언트가 소비를 중단하고 토픽 파티션이 새 멤버들에게 재배분된 후, 모든 클라이언트가 다시 소비를 시작했습니다. 이 접근 방식에는 두 가지 단점이 있습니다(이후 개선되었으니 걱정하지 마세요).
최근의 리밸런싱 알고리즘은 Kafka의 용어를 사용하자면 'stickiness(고착성)'과 'cooperation(협력성)'을 추가하여 크게 개선되었습니다.
이러한 최신 리밸런싱 알고리즘의 개선에도 불구하고, 애플리케이션이 자주 컨슈머 그룹 리밸런싱을 겪는 경우에는 여전히 전체 메시징 처리량에 영향이 발생하고, 클라이언트가 버퍼링된 메시지 데이터를 삭제하고 다시 가져오면서 네트워크 대역폭이 낭비될 수 있습니다. 다음은 이러한 상황에서 취할 수 있는 몇 가지 권장 조치입니다.
업계 뉴스레터
Think 뉴스레터를 통해 AI, 자동화, 데이터 등 가장 중요하고 흥미로운 업계 동향에 대한 최신 소식을 받아보세요. IBM 개인정보 보호정책을 참조하세요.
구독한 뉴스레터는 영어로 제공됩니다. 모든 뉴스레터에는 구독 취소 링크가 있습니다. 여기에서 구독을 관리하거나 취소할 수 있습니다. 자세한 정보는 IBM 개인정보 보호정책을 참조하세요.
IBM® Event Streams는 오픈소스 Apache Kafka를 기반으로 구축된 이벤트 스트리밍 소프트웨어입니다. IBM Cloud 상의 완전 관리형 서비스 또는 자체 호스팅으로 사용할 수 있습니다.
애플리케이션과 시스템을 연결하여 중요 데이터에 빠르고 안전하게 액세스할 수 있는 IBM 통합 솔루션을 활용해 비즈니스 잠재력을 실현하세요.
IBM Cloud 컨설팅 서비스를 통해 새로운 역량을 개발하고 비즈니스 민첩성을 향상하세요.