티스토리 뷰

카프카와 확장성

카프카를 사용하여 환경을 구성하는 과정에서 확장성이 필요한 서비스를 운영하기 위한 고민이 있었습니다.
일면의 가용성이나 확장성에 대해서는 카프카는 아주 훌륭한 도구였으나 또 다른 측면에서 딱딱하게 구는 부분도 있었습니다.

 

리파티셔닝과 리밸런싱

카프카가 제공하는 기능의 몇 가지는 파티션이라는 구조를 기반하고 있습니다. 일단 카프카는 기본적으로는 메시지의 순서를 보장하지 않지만 메시지의 순서를 일부 보장하며 해당 순서를 기반으로 commit과 offset을 제공하는 기반은 파티션입니다. 따라서, 파티션에 의해 좌우되는 요소들이 많습니다. 예를 들면 파티션은 메시지의 동시처리의 상한을 만듭니다. 파티션 수 보다 많은 컨슈머의 수는 메시지 처리 속도 측면에서 의미가 없습니다. 더 많은 컨슈머를 활용하여 메시지 처리 속도를 늘리려면 파티션을 늘려야 합니다.

 

이처럼 시스템에 특성을 결정하는데 주요한 고려 요소인 파티션은 또한 시스템의 민감한 요소이기도 합니다. 카프카의 파티션 설정을 변경하는 것은 시스템에 일시적인 부담을 만드는 일입니다. 따라서, 기왕이면 적당하고 실효적인 예측을 통해서 파티션에 관한 설정을 대비 없이 변경하지 않도록 운영해야 합니다.

 

앞서 언급한 파티션 수를 조정하는 것을 포함하여 파티션에 변경사항을 조정하는 과정을 리파티셔닝이라고 합니다. 리파티셔닝은 시스템에 꽤 많은 부담을 만들기에 웬만하면 발생할 일을 만들지 않는 편이 좋습니다.

리파티셔닝만큼은 아니지만 역시 시스템에 부담을 주는 행위가 리밸런싱입니다. 리밸런싱이란 컨슈머 그룹 내의 컨슈머들 간에 파티션에 대한 소유권을 재조정하는 것을 말합니다. 리밸런싱이 발생하면 해당 컨슈머 그룹에 속한 컨슈머들은 잠시 읽기를 멈추고 리밸런싱에 대응합니다. 이 순간이 시스템에 부담으로 다가옵니다.

 

파티션 수의 확장성

카프카를 통하여 구성하는 시스템의 경우 되도록 리파티셔닝과 리밸런싱이 발생하지 않게 유지하는 것이 좋습니다. 리파티셔닝을 줄이는 방법은 다음과 같습니다.

 

  • 실효적인 예측을 통해서 충분한 파티션 수를 설정합니다.
  • 예측은 지연내성을 고려한 목표 TPS를 이루기 충분해야 합니다.

여기서 지연내성을 고려한 이유를 간략히 부연하자면, 메시지 서비스를 사용하고자 결정하였을 때에는 peek를 이루는 트래픽 유입을 대비하기 위해 평소에 불필요한 자원을 대기시키기보다 약간의 지연을 만들더라도 이내 곧 회복할 수 있는 시스템으로 설계했을 것이기 때문입니다. 이러한 시스템은 예상치 못할 최대 유입량을 목표 TPS로 설정할 필요는 없기에 이 점을 고려할 것이라 생각합니다.

 

 

다시 본론으로 돌아와서, 때문에 파티션의 수는 peek 를 이룰 때까지는 아니지만 그래도 역시 평소보다는 다소 부하가 있는 상황을 기준으로 설정해 두곤 합니다. 파티션 수를 늘릴 때의 부담이 크진 않아 평소 수준으로 준비하다가 부하 상황에 늘리는 것을 고려할 수도 있습니다. 이 부분은 아무래도 상황마다 다르긴 하겠지만 저의 상황에서는 다음과 같은 고려할 부분이 있었습니다.

 

  • 파티션 수를 줄이는 것은 매우 힘들다. 즉, 어떤 이유로 늘린 파티션을 상황이 지나간 후에 다시 줄이는 것이 어렵다.
  • 비즈니스 특성상, 특정 시점에만 유입이 몰리는 상황뿐 아니라 일정 기간 동안 평소 부하보다 높은 트래픽을 지속적으로 유지하기도 한다.

따라서 파티션 수는 확장성에 포인트로 사용하기 까다롭습니다.

 

확장성과의 교점

그렇다면 시스템의 자원 최적화를 위한 확장성은 어떻게 관리할까요? 또 하나의 포인트는 컨슈머입니다. 컨슈머의 수가 파티션의 수 보다 적어져도 컨슈머 그룹 코디네이터는 복수의 파티션을 컨슈머에 할당하는 방식으로 모든 파티션을 읽도록 조정합니다. 그러니까 파티션은 예측한 만큼 충분히 설정하고 컨슈머의 수를 적절히 줄여두면 평소에는 그 유입량을 충분히 감당할 수 있습니다.

 

하지만 이 방법도 리밸런싱을 피할 수 없습니다. 특히나 늘어난 트래픽을 대응하기 위해 컨슈머를 더 투입하는 순간 되려 리밸런싱을 위해 모든 컨슈머가 읽기를 멈출 것입니다.

그러면 어떻게 해야할까요?

 

메시지 서비스를 사용하는 것이 확장성과 결이 다른 것은 아닙니다. 카프카를 비롯한 메시지 도구들은 충분히 확장성을 포함하고 있습니다. 그리고 그 정도면 충분히 확장성을 누릴 수 있는 시스템도 많을 것입니다. 단지 제가 다뤄야 하는 시스템은 좀 더 재빠른 확장성이 필요했습니다. 리밸런싱을 일으키지 않고 확장성을 얻을 수 없을까요?

 

컨슈머와 서비스

카프카를 사용한 시스템은 일단 미뤄두고 우리는 보통 어떻게 애플리케이션에 확장성을 확보할까요? 보통은 동일한 instance를 원하는 만큼 투입할 수 있는 stateless 서비스와 이들 사이에 부하를 조율하는 계층을 사용합니다. 컨슈머도 대부분 stateless 한 서비스로 구현하기에 이와 비슷한 방안을 고려할 수 있으나 컨슈머 수의 조정은 리밸런싱을 유발하는 이슈가 있습니다.

 

그래서 컨슈머의 관심사를 좀 더 살펴보았습니다. 컨슈머는 다음 두 가지 기능을 수행합니다.

 

  • 메시지를 읽어오는 기능
  • 메시지를 처리하는 기능

이 중 실상 확장성을 확보하고 싶은 부분은 메세지를 처리하는 기능입니다. 메세지 처리를 위해 투입하는 자원을 적정하게 조정하고 싶기 때문이죠. 그런데 그 부분의 확장성과 함께 메세지를 읽는 기능이 같은 생명주기로 확장하니 문제입니다. 그래서 이 둘을 분리하기로 하였습니다.

 

저는 위 두 기능을 서로 다른 컴포넌트에 각기 역할을 맡겼습니다.

 

  • consumer: 메세지를 읽어 service에 처리를 위탁한다.
  • service: consumer에 호출에 대응하여 메시지를 처리한다.

consumer는 결정한 파티션의 수와 맞춰 변경하지 않을 최적의 세팅으로 구성합니다. 그리고 consumer 는 메세지 처리를 위해 service 를 호출합니다. service 는 확장성과 부하를 조율하는 계층을 앞에 두고 운영합니다. consumer 에서의 호출은 적절히 service 에 분배할 것이며 메세지의 유입량에 따라 필요한 만큼 service 에 자원을 투입할 수 있습니다.

 

consumer는 파티션에 대한 offset 관리와 commit 을 보고해야 하므로 service 와의 호출은 동기적으로 진행합니다. 좀 더 효율적인 방식을 고려할 수도 있지만 사실 consumer 는 메시지를 읽어 서비스에 전달하는 것이 전부이고 그 부하도 이미 파티션 수만큼으로 상한이 생기기 때문에 동기적으로 제공하더라도 큰 오버헤드가 발생하지는 않습니다.

 

스프링을 사용하는 경우는 ConcurrentKafkaListener 를 사용하면 하나의 consumer 서비스 인스턴스에 필요한 수만큼 consumer listener를 만들 수 있어서 consumer 서비스는 소수의 instance 만으로도 운영이 가능합니다.

 

정리

이 방식의 장점은 당연히 자원을 효율적으로 조절하면서도 리파티셔닝이나 리밸런싱이 발생하지 않는다는 것입니다. 그리고 여전히 파티션이 지켜주는 순서를 유지한다는 점도 있습니다.

 

카프카에는 Parallel Consumer 라는 도구도 있습니다. 이 역시 파티션에 대한 조정을 줄이고 메시지를 처리하는 자원을 효율적으로 쓰기 위한 방법 중 하나입니다. 하지만 이 방식으로도 부족한 자원을 늘리거나 유휴 자원을 줄이는 과정에서의 리밸런싱을 막을 수 없고, 또 제대로 성능적 이점을 활용하려면 메시지의 순서를 지키는 것도 포기해야 합니다.

 

그에 비하면 아주 단순히 consumer와 service에 역할을 나눈 것 만으로 충분히 자원을 효율적으로 사용하며 순서도 유지할 수 있습니다.

하지만 이 방식에서 이 시스템이 목표할 수 있는 트래픽의 상한은 파티션 수에 의존적입니다. 우리가 예상한 트래픽까지는 자원을 효율적으로 조율할 수 있으나 파티션 수가 한정한 한계 트래픽보다 높은 트래픽에서 메시지 처리는 지연이 생겨납니다. 물론 이것이 안 좋은 것만은 아닙니다. 지연은 견딜 수 있는 범위 내에서 다시 회복성을 가질 수 있을 것이기에 미처 대처하지 못한 일부 서비스가 밀려드는 부하에 죽는 상황을 막아주기도 합니다. 단지 어느 한계 지점 이상으로 서비스에 자원을 더 투입해도 효과가 나타나지 않는다는 점은 염두해야 합니다.

또한 당연히 네트워크 구간이 한 구간 더 늘어남으로써 불확실성과 네트워크 비용이 늘어납니다. 따라서 이 패턴은 트레이드오프를 잘 판단하여 사용을 고려해야 합니다.

 

긴 글 읽어주셔서 감사합니다.

댓글