[백엔드] Kafka 완전정복 - 데이터 순차처리 보장 및 안전한 관리를 위해 내부적인 구조와 동작원리를 깊게 파악하며(중요도 ★★★★★★★★★★)

Hyo Kyun Lee·어제
0

백엔드

목록 보기
26/26

1. 개요

대용량 트래픽이 발생할 수 있는 상황에서 순차처리와 안정성 등의 고가용성을 보장하기 위한 도구로 Kafka를 많이 사용한다.

일전에 Kafka에 대해 한번 정리한 적이 있긴한데, 이번에 다시 한번 살펴보면서 잘못 알고 있었던 내용들을 바로 잡고, 이에 따라 Kafka에 대해 확실하게 이해할 수 있는 계기가 되었다.

특히 우리가 흔히 알고있는 Kafka 도식도는 Kafka의 특징적인 부분을 강조하여 이해하기 쉽도록 나타낸 것이고, 실제 동작 방식은 데이터 로그와 디스크 저장 방식에 초점이 맞추어져 있다.

좀 더 깊게 살펴보려고 하면 헷갈릴 수 있는 부분이 많은데, Kafka를 정확히 이해하고 활용하기 위해선 확실하게 짚고 넘어갈 필요가 있겠다.

이에 대해 공부한 내용을 기록한다.

참고로 전체적으로, [어떤 문제 상황이 발생하였고, 이 문제 상황에 대한 대안으로 이러한 아이디어를 구상하였다]라는 흐름으로 정리하였기 때문에, 별도 내용 분류없이 모두 순차적으로 정리한다.

2. Kafka 정의

Kafka는 분산 이벤트 스트리밍 플랫폼으로, 말 그대로 분산환경에서 동시 혹은 대규모 트래픽이 발생하는 상황을 실시간 처리(스트리밍)를 해주는 장치이며, 나아가 순차처리 및 원자성 보장, 병렬처리 등 각 이벤트들의 안전한 처리를 보장해주는 강력한 도구이다.

대규모 트래픽을 안정적으로 처리할 수 있기에 고성능, 고가용성을 확보할 수 있다고 할 수 있고, 결국 여러 서비스 간 대규모 이벤트를 생산하고 이를 안전하게 통신/소비하는 것을 도와주는 도구이다.

3. Kafka를 이해하기 위한 아이디어

기존의 데이터 처리 방식과 비교하여, 왜 Kafka가 필요하며 현재의 Kafka(혹은 동작방식)을 이해하기 위해 어떠한 관점에서 바라보아야 하는지 생각해보자.

먼저 데이터를 생산하는 Producer, 생상한 데이터를 소비하는 Consumer가 있다고 해보면, 이 사이에서 데이터 전송을 하기 위한 방안을 구상해야 한다.

이를 위해 1차적으로, 단순하게, 서버의 요청/응답 체계를 그대로 활용하는 API통신 방법을 생각할 수 있겠다.

하지만 API를 통해 Producer/Consumer 간의 강한 결합이 생기고, 이로 인해 Consumer에서 장애가 발생할 경우 장애전파 및 데이터 유실이 생길 수 있는 여지가 있다.

이러한 문제를 해결하기 위해 중간에 Message Queue를 두는 방안을 고려할 수 있겠다. 데이터 전송을 중간의 Message Queue에 위임을 하는 방법인데, Prodcuer와 Consumer의 강한 결합이 느슨해지고 그만큼 장애 전파 및 데이터 유실의 위험을 감소할 수 있다.

Message Queue의 처리 방안에 따라 데이터 처리를 비동기로 할 수 있을텐데, 이는 이후 내용에서 자세히 다루도록 하겠다.

4. Kafka Cluster의 등장 - 여러대의 Message Queue가 필요할때

하지만 이 상황에서 Producer와 Consumer의 수가 많아져 처리해야 하는 데이터량이 증가한다면, 단일 Message Queue로는 한계가 있을 수 밖에 없다.

처리 데이터가 많아지고 그만큼 적절한 라우팅이 필요한 상황일때, 단일 Message Queue로는 힘든 상황에서 다수의 Message Queue를 배치하는 방안을 생각해볼 수 있겠다.

이처럼 다수의 Message Queue를 두고, 이 Queue를 관리하는 Broker를 두어 대규모 데이터를 처리할 수 있다. 특히 병렬처리가 가능하기에 복잡한 데이터 요구사항을 구현할 수 있겠다.

5. Kafka의 정책 - Consumer가 데이터를 안정적으로 처리하기 위한 기본 전략

하지만 이 경우에도, Producer의 데이터를 Consumr에게 push하는 상황에서 Producer의 데이터 생산량이 Consumer의 처리량보다 크다면, 리소스 부족으로 인해 장애를 전파하고 데이터 유실이 발생할 수 있다.

즉, Consumer가 데이터를 안정적으로 처리할 수 없는 위험이 있기에 Consumer가 데이터를 더 안정적으로 처리하기 위한 기본 전략이 여기서 나타난다.

이처럼 Consumer가 데이터를 push받아 그대로 처리하는 것이 아니라, Consumer가 주도적으로 데이터를 요청하고 이를 pull 받아 처리하는 쌍방향 통신이 바로 그 전략이다.

이를 Pub/Sub 패턴이라고도 하며, Consumer 측은 본인이 관심을 가지는 데이터를 구독하여 데이터 처리 이벤트를 생산할때 주도적으로 이를 파악하고 처리한다.

6. 지금까지 살펴본 내용으로 Kafka 이해하기

지금까지 Kafka의 필요성과 기본전략에 대해 살펴보았는데, 이를 바탕으로 Kafka를 이해해보면 다음과 같다.

한마디로, Kafka는 고성능, 가용성, 안정성을 보장할 수 있는 시스템 구축을 도와준다.

기본적으로 Kafka는 데이터를 중개해주고 실행하기 위한 기본 단위로 Kafka Broker를 두고 있고, 이를 application 실행 단위로 볼 수 있다.

이 Broker를 중간에 두고 Producer의 데이터 생산과 Consumer 데이터 소비가 이루어지며, 이 둘은 서로 양방향 통신을 한다.

7. Kafka topic 이해하기 - Kafka Broker가 Consumer와 Producer의 소통을 구분하고 처리해야하는 데이터를 구분하는 방법

이때 Kafka Broker는 Producer와 Consumer를 중재하면서, 어떻게 각각 처리해야 하는 데이터를 구분해주고 서로 정상적인 소통이 이루어질 수 있도록 해주는 것일까?

그 방안으로 topic으로 데이터를 구분하는 방안이 있겠다.

Kafka는 Producer가 생산하는 데이터를 topic이라는 논리적 기준을 통해 분리하고, 이에 따라 Producer는 topic 단위로 데이터를 생산한다.

Consumer는 topic 단위로 데이터를 소비하여, 서로에게 맞는 소통과 데이터 처리가 이루어질 수 있도록 한다.

8. Kafka Cluster의 등장 - 대량의 트래픽을 한대의 Broker가 처리할 수 없기에 다수의 Kafka Broker를 배치한다.

이때 Kafka Broker가 중재해야 하는 데이터가 많아지면, 1대의 Broker가 모두 처리해야 할까?

Kafka는 처리해야하는 데이터를 늘리기 위해 다수의 Broker를 배치하기로 한다.

이처럼 여러대의 Kafka Broker를 위치하여, 여러 기준으로 구분한 데이터를 생산 및 소비할 수 있도록 하고, 동시에 처리할 수 있는 데이터의 물리적인 양을 늘릴 수 있으므로 이전보다는 훨씬 고가용성 관점에서 유리해졌다.

이때 여러대의 Kafka Broker를 위치하여 대규모 데이터를 처리할 수 있도록 하는 체계를 Kafka Cluster라 한다.

9. Partition의 등장 - topic의 분산

여기서, topic을 통해 여러 Broker에서 병렬처리를 해주도록 하여 처리량을 늘렸는데 이때 topic은 어떻게 여러 Broker에 분산할 수 있는 것인가?

각 topic은 Partition 단위로 물리적 분리가 이루어진다.

이 경우 각 topic은 3개의 Partition으로 분리된 상황이고, Producer는 각 Partition 단위로 데이터를 생산하여 Consumer 역시 Partition 단위로 데이터를 처리한다.

쉽게 말하면 Producer는 이벤트를 생산하여 각 Partition에 보낸다. Consumer는 각 Parition을 바라보면서 생산한 이벤트를 파악하고 처리한다.

(여기서 병렬처리와 순차처리를 구분할 수 있는데, 이후 내용에서 다룬다.)

10. 지금까지 살펴본 내용으로 Kafka Cluster 이해하기

지금까지 살펴본 내용을 종합하여 Kafka Cluster를 이해해보면 다음과 같다.

Producer의 데이터 생산과 Consumer의 데이터 처리를 위해 여러대의 Kafka Broker가 모여있다.

이때 데이터 구분을 위해 각 Broker들은 여러 topic을 보유하고 있고, 내부적으로 세부적인 구분 및 순차/병럴처리를 위해 Producer/Consumer의 데이터 처리 단위인 Partition으로 물리적인 구분을 하게 되었다.

11. Broker/topic에서의 Partition 분산 - 병렬처리와 데이터 안전성

Partition은 각 topic 내에서 물리적으로 구분될 수 있지만, 각 Broker 별로 균등하게 분산, 구분될 수도 있다.

Partition의 분산 기준에 따라 Kafka가 데이터를 처리하는 기준도 달라지는데,

  • Broker 별로 Partition을 분산한 것은 데이터의 백업, 안정성을 위한 것이다.
  • topic 내에서 Partition을 분산한 것은 데이터의 병렬처리, 실시간 처리를 위한 것이다.

즉 Kafka에서 각 Partition은 동일 topic이라도 여러대의 Broker에 균등하게 분산되어, 데이터의 백업 관점에서 안정적인 데이터 보관을 가능하게 해준다.

더불어 각 Partition은 병렬처리를 통해 빠른 실시간 처리를 가능하게 해준다.

12. 순차보장이 필요할땐 Partition 1개로 1개의 Consumer가 데이터를 처리한다.

다만 위와 같은 분산 Partition 환경에서는 데이터 처리의 순차성을 보장해주지 않는다.

데이터 처리의 순차성을 보장하기 위해선 1개의 Partition에 대해 1개의 Consumer가 처리를 해주어야 한다.

이를 위해 순차처리가 필요한 데이터도 topic을 기준으로 1개의 partition에 순서를 부여받으면서 쌓이게 된다(보통 leader partition에 먼저 쌓이고 그 후에 follower에 적재됨).

즉 쉽게 말하면 순차보장이 필요하다면 1개의 Partition에, 순차보장이 필요없고 병렬처리를 해도 무방한 상황이라면 분산된 Partition에 데이터를 쌓는다.

13. 데이터 유실을 위한 Kafka의 전략 - leader/follower

만약 Kafka Broker 1대에 장애가 발생한다면, 그 브로커가 가지고 있는 모든 데이터가 유실되는 것인가?

위 그림처럼 Broker2에 장애가 발생하였다면, Broker2가 처리 중인 topic1, topic2 데이터는 모두 유실되어야 할까?

분산 시스템 환경에서 모든 Broker에 대해 장애가 발생할 확률은 적은데, 이를 이용하여 Kafka는 각 Broker 내부 Partition에 데이터를 분산저장하여 데이터가 유실되었을때의 상황을 대비한다.

위 도식도를 살펴보면,

  • Broker1에 topic1의 Partition1 leader 데이터가 들어간다.
  • Broker2에 topic1의 Partition1 follower 데이터가 들어간다.
  • Broker3에 topic1의 Partition1 follower 데이터가 들어간다.

즉 데이터 한뭉치를 한 곳에만 보관하는 것이 아니라, 여러 Broker Partition에 나누어 보관하여 한 Broker에 장애가 발생하더라도 다른 곳에서 이어서 사용할 수 있도록 분산 저장한다.

이때 leader Partition에 장애가 발생한다면, 다른 정상작동 중인 follower 중 하나를 leader로 재선출함으로써 데이터 생산 및 소비를 지속할 수 있도록 유지한다.\

14. acks - 데이터 복제 수준 정하기

위 과정대로라면 모든 데이터가 여러번 복제되어야하고, 이 복제과정을 온전히 완료할때까지 대기 과정을 수반한다는 의문이 들 수 있겠다.

Kafka의 데이터 복제 수준, 즉 leader/follower partition에 어느 정도 수준으로 데이터를 저장하고 이를 성공으로 간주할 것인지 그 기준을 acks 설정을 통해 정할 수 있다.

각 acks 설정에 따라 다음과 같은 성공기준을 가진다.

  • acks = 0, Broker에 데이터 전달을 확인하지 않고 전송 즉시 바로 성공처리, 데이터 유실가능성이 가장 높다.
  • acks = 1, Broker의 leader Partition 데이터 저장이 성공할 경우 성공처리, follower 데이터 저장 시 장애가 발생할 경우 유실가능성이 있다.
  • acks = all, min.insync.replicas에서 설정한 기준에 따라 데이터 복제를 성공할때 성공처리, 가장 안전하지만 그만큼 처리 지연이 발생할 수 있다.

이때 min.insync.replicas는 데이터 복제를 동기화하기 위한 follower들을 지정하는 설정이고, 이때 중요하게 작용하는 설정값이 ISR이다.

ISR은 InSync Replicas, 동기화 복제와 비동기화 복제(동기 복제 성공 후 별도의 복제 과정)을 구분해주는 설정 값으로, acks=all일때 성공판단 기준을 정해주는 항목이다.

예를 들어, acks=all / min.insync.replicas=2 / replication factors=3 이라면

  • 데이터 복제는 3번(leader 1 + follower 2) 이루어진다.
  • 이 중 성공으로 판단하기 위한 기준은 데이터 복제가 2번 이루어 졌을 경우이고, 나머지 1번은 비동기로 진행한다.

15. Broker 장애 발생시 Kafka의 대처과정

Broker에 장애가 발생하였을때 Kafka가 대처하는 과정을 간단하게 정리해보자.

Broker2의 partition2 leader 데이터가 이상이 생기면, Broker1과 Broker3에 partition2 follower 중 하나가 다시 leader로 재선출되어 데이터 처리를 계속 유지할 수 있다.

16. Kafka에 대한 오해 바로잡기

이제 최종적으로 Kafka 데이터 처리과정을 살펴보기 전에, 몇가지 Kafka에 대해 오해하고 있는 부분들을 바로잡고자 한다.

(1) Kafka의 순서보장은 동일한 Partition 내에서 이루어지고, 이를 위해 Kafka에 데이터를 전송할때 동일한 key값으로 설정한다.
(2) 이때 순차처리를 위한 데이터가 offset에 저장되는 것이 아니라, 데이터가 실제 디스크에 offset과 함께 저장되어 Consumer 측에서 offset을 참고하기에 순차처리를 보장한다.
(3) 데이터 요청이 발생하였을때, Producer가 실제로 보내는 데이터 단위는 메시지이다.
(4) 이 메시지들이 실제로는 offset에 저장되는 것이 아니라, **offset 정보와 함께 파티션 별 파티션 로그 파일에 저장된다.
(5) 파티션 로그 파일은 실제 물리적인 디스크에 저장이 되고, Consumer는 파티션 로그 파일을 읽고 데이터를 처리한다.
(6) 디스크에 저장된 파티션 로그 파일은 실제 저장된 데이터 위치와 동일하며, Consumer가 파티션 로그 파일을 읽음과 동시에 실제 데이터를 읽고 처리하는 것과 같다.
(7) Consumer의 파티션 로그 파일에 저장된 데이터의 순차처리는, 로그 파일에서 데이터에 붙여진 offset이 있기에 가능하고 Consumer는 자신이 처리한 offset을 기억한다.

추가적으로, Kafka 데이터 처리 과정을 정리하기 전에 알아야 할 개념을 몇가지 정리해본다.

  • 메시지 - Producer가 보내는 데이터 단위(Json 한 건 및 문자열 한 줄), Producer가 보내는 데이터는 1요청 당 1개의 메시지에 담겨져 전송된다.
  • 파티션 로그 파일 & offset - kafka는 각 파티션을 로그 파일로 관리하는데, 정확히 말하면, 메시지를 발행할때마다 각 파티션 로그 끝에 메시지를 붙여서 순서대로 그 메시지를 읽고 메시지에 적혀진 데이터를 처리한다(메시지 저장 = 파티션 로그 파일 = 디스크 = 실제 데이터). 메시지를 생산할때마다 offset이 붙여져 순차처리를 보장할 수 있다(메시지 생산 시 순차처리가 필요하면 1개의 Partition에 저장, 필요하지 않다면 분산 저장).
  • 디스크 - 이러한 파티션 로그 파일이 저장되는 실제 물리적인 저장소, Kafka Broker를 실행하는 서버의 물리/가상 디스크. 온프레미스의 경우 HDD/SSD, Cloud의 경우 VM 인스턴스 및 할당받은 스토리지(EBS/GCP/Persistent Disk).

17. Kafka 데이터 처리 과정 정리(★★★★★★★★★★)

최종적으로, 지금부터 가장 중요한, Kafka에서 데이터 처리 과정을 살펴보겠다.

지금까지는 Kafka가 어떠한 문제상황을 해결하기 위해 어떤 전략으로 구현하였는지 이해하는 과정이었다면, 지금부터는 이러한 내부 동작원리를 바탕으로 어떻게 실제 데이터 처리가 이루어지는지 알아보는 과정이다.

1) Client 1, Client 2의 요청이 오고 Producer가 이를 메시지화(1요청 = 1메시지)하여 Kafka Broker에 전송한다.
2) 이 메시지는 Broker(Kafka 서버)의 파티션 로그 파일에 append 하여 저장, 메시지는 순차적으로 offset 번호가 함께 붙여 저장된다.
3) 파티션 로그 데이터는 및 실제 데이터는 Kafka 서버의 디스크에, 즉 Kafka Broker가 실행 중인 서버 디스크에 저장된다.
4) Consumer는 Broker에 polling하여 로그파일을 읽고, 로그 파일에 기록된 최초 offset = 0에 위치한 디스크 데이터(payload)를 읽고 처리한다(offset = 0에서 실제 데이터가 저장된 위치에 가서 읽는 것, 따라서 읽는 것은 곧 처리하는 과정과 동일).
5) Consumer는 다음 offset을 파악하여, 마찬가지 과정으로 다음 데이터 로그를 읽어 데이터를 처리한다.

여기서 몇가지 개념을 추가적으로 살펴보자.

  • 로그 파일을 Consumer가 읽는 것은 polling, 즉 주기적으로 노크하면서 이루어진다.
  • Producer는 자신이 생성한 메시지를 Broker의 leader Partition에 전송하여, Broker 메시지 로그에 메시지 정보를 기록한다.
  • Broker는 Consumer에게 메시지를 push하지 않고, 다만 Consumer가 메시지를 읽는 속도를 조절할 수 있게 하자는 것이 핵심이다.
  • Consumer가 Broker에게 메시지 있니? 노크하고(=polling), Consumer는 자신이 구독 중인 파티션에 offset = N 이후 메시지가 있으면 최대 X건, Yms까지 기다린다.
  • polling 요청 후, Broker가 해당 파티션 로그 파일에서 읽지 않은 메시지들을 읽게 되고(=데이터 처리), Consumer는 로그를 offset부터 읽고 commit 한다.

참고로 Kafka는 Pull패턴이라고도 하는데,

  • 소비속도제어(Back Pressure), Consumer가 스스로 속도를 제어하며 읽는다(pull모델, 안정적인 데이터 처리).
  • 배치처리 최적화, pull 호출 시 한번에 몇개 혹은 얼마만큼의 주기로 메시지를 읽을지 제어가능(배치 단위로 묵어 pull).

즉, Producer 생산에 직접 응답하지 않고 Broker에 메시지들이 누적기록되면(로그가 쌓이면), Consumer는 poll을 통해 그 로그를 일정 주기 및 요청 시점에 읽는다.

18. 추가개념 - Consumer Group

전역적으로 offset을 관리할 수는 없기에, 모든 Consumer가 Partition 별로 서로 다른 offset을 관리해야 할텐데 이를 위해 Consumer Group 개념이 등장한다.

1개의 topic에는 1개의 Consumer Group이 바라보는데, 순서보장이 필요할 경우나 병렬처리가 필요한(순서보장이 필요하지 않은) 경우에 따라 관리체계가 다르다.

  • 순서보장이 필요하다면 반드시 1 Consumer = 1 Partition, 즉 1 Consumer가 하나의 Partition 로그 파일을 처리해야 하며, 여러 Consumer가 같은 partition에 붙을 수는 없다.
  • 순서보장이 필요하지는 않다면 N개의 Consumer들이 적절하게 균등된 Partition들을 바라보면서 병렬로 처리한다. Partition수가 Consumer보다 크거나 같을때, 외부단편화가 발생하지 않고 효율적인 데이터 처리가 가능하겠다.

참고로 offset 정보는 기존 Zookeeper가 관리(여러대의 Zookeeper가 연결되어 Cluster를 이루었다), 이것의 의존성이 너무 심해지면서 최근의 Kafka 2.8v 이후에는 Kafka Broker가 직접 해당 메타데이터를 관리한다(KRaft 모드).

0개의 댓글