본문 바로가기

Infra

카프카 기본 개념 정리

 

  • 이 글은 간단하게 카프카의 기본 개념을 용어위주로 정리한 글입니다. 편하게 읽어주시고 틀린 내용이 있다면 언제든 댓글 부탁드립니다.

1. 카프카 구성

1.1 카프카 브로커, 클러스터, 주키퍼

  • 카프카 브로커는 카프카 클라이언트와 데이터를 주고받기 위해 사용하는 주체이자, 데이터를 분산 저장하여 장애가 발생하더라도 안전하게 사용할 수 있도록 도와주는 애플리케이션이다.
  • 하나의 서버에는 한 개의 카프카 브로커 프로세스가 실행된다.
  • 3대 이상의 브로커 서버를 1개의 클러스터로 묶어서 운영한다.
  • 카프카 클러스터로 묶인 브로커들은 프로듀서가 보낸 데이터를 안전하게 분산 저장하고 복제하는 역할을 수행한다.
  • 주키퍼는 카프카의 메타데이터를 관리하는 데에 사용된다.

1.2 컨트롤러

  • 클러스터의 다수 브로커 중 한 대가 컨트롤러의 역할을 한다.
  • 다른 브로커들의 상태를 체크하고 브로커가 클러스터에서 빠지는 경우 해당 브로커에 존재하는 리더 파티션을 재 분배한다.

1.3 코디네이터

  • 클러스터의 다수 브로커 중 한 대는 코디네이터의 역할을 수행한다.
  • 코디네이터는 컨슈머 그룹의 상태를 체크하고 파티션을 컨슈머와 매칭되도록 분배하는 역할을 한다.
  • 파티션을 컨슈머로 재할당하는 과정을 ‘리밸런스’라고 부른다.

2. 데이터 관리

2.1 데이터 저장, 전송

  • 프로듀서로부터 데이터를 전달받으면 카프카 브로커는 프로듀서가 요청한 토픽의 파티션에 데이터를 저장하고 컨슈머가 데이터를 요청하면 파티션에 저장된 데이터를 전달한다.
  • 프로듀서로부터 전달된 데이터는 파일 시스템에 저장된다.
  • 토픽 이름과 파티션 번호의 조합으로 하위 디렉토리를 생성하여 데이터를 저장한다.
    • index는 메시지의 오프셋을 인덱싱한 정보를 담은 파일이다.

2.2 데이터 복제, 싱크

  • 데이터 복제(replication)는 카프카를 장애 허용 시스템(Fault Tolerent System)으로 동작하도록 하는 원동력이다.
  • 복제된 파티션은 리더와 팔로워로 구성된다.
  • 프로듀서 또는 컨슈머와 직접 통신하는 파티션을 리더, 나머지 복제 데이터를 가지고 있는 파티션을 팔로워라고 부른다.
  • 데이터를 복제를 통해 안전하게 사용할 수 있으므로 복제 개수를 2 이상으로 정하는 것이 좋다.

2.3 데이터 삭제, 압축

  • 데이터 삭제는 파일 단위로 이루어지는데 이 단위를 로그 세그먼트라고 부른다.
  • 특정 데이터를 선별해서 삭제할 수 없다.
  • 카프카 브로커에 log.segment.bytes 또는 log.segment.ms 옵션에 값이 설정되면 세그먼트 파일이 닫힌다.
  • 데이터를 삭제하지 않고 메시지 키를 기준으로 오래된 데이터를 압축하는 정책을 가져갈 수도 있다.

3. 토픽, 파티션, 레코드

3.1 토픽

  • 토픽은 카프카의 시작과 끝이다.
  • 토픽은 카프카에서 데이터를 구분하기 위해 사용하는 단위이다.
  • 토픽은 하나 이상의 파티션을 소유한다.

3.1.1 토픽 정리 정책

  • 토픽의 데이터는 시간 또는 용량에 따라 삭제 규칙을 적용할 수 있다.
  • 데이터를 오랫동안 삭제하지 않으면 저장소 사용량이 지속적으로 늘어나게 된다.
  • 데이터를 더이상 사용하지 않을 경우 cleanup.policy 옵션을 사용하여 데이터를 삭제 또는 압축 하자.
    • delete policy
    • compact policy

3.2 파티션

  • 파티션에는 프로듀서가 보낸 데이터들이 들어가 저장된다.
    • 이 데이터를 레코드라 한다.
  • 파티션은 카프카의 병렬처리의 핵심이다.
    • 그룹으로 묶인 컨슈머들이 레코드를 병렬로 처리할 수 있도록 매칭된다.
    • 레코드를 병렬로 처리하는 가장 좋은 방법은 컨슈머의 개수를 늘려 스케일아웃하는 것이다.
    • 컨슈머 개수를 늘림과 동시에 파티션 개수도 늘리면 처리량이 증가하는 효과를 볼 수 있다.
  • 카프카에서는 레코드를 읽어도 삭제하지 않는다.
    • 이러한 특징으로 다양한 목적을 지닌 컨슈머 그룹들이 토픽의 데이터를 여러 번 가져갈 수 있다.

3.2.1 적정 파티션 개수

토픽 최초 생성 시 파티션의 개수를 정하는 데에 고려해야 할 점은 3가지가 있다.

  • 데이터 처리량
  • 메시지 키 사용 여부
  • 브로커, 컨슈머 영향도

3.2.2 데이터 처리량

파티션은 카프카의 병렬처리의 핵심이다. 파티션의 개수가 많아지면 많아질수록 1:1 매핑되는 컨슈머 개수가 늘어나기 때문이다. 파티션 개수를 정할 때는 해당 토픽에 필요한 데이터 처리량을 측정하여 정하는 것이 중요하다.

  • 데이터 처리 속도를 올리는 방법
    • 컨슈머의 처리량을 늘리는 것
      • 서버 사양을 올리는 스케일 업, GC 튜닝
    • 컨슈머를 추가해서 병렬 처리량을 늘리는 것
      • 파티션 개수를 늘리고 컨슈머를 추가 프로듀서 전송 데이터량 < 컨슈머 데이터 처리량 x 파티션 개수
  • 컨슈머 데이터 처리량을 구하는 방법은 상용에서 운영 중인 카프카에서 더미 데이터로 테스트를 해보면 된다.
  • 파티션 개수를 무조건 늘리는 것이 좋은 것은 아니다.
    • 파티션 개수를 늘리면 컨슈머, 브로커의 부담이 있을 수 있다.

3.2.3 메시지 키 사용 여부

  • 메시지 키 사용 여부는 데이터 처리 순서와 연관이 있다.
  • 파티션 개수가 달라지면 이미 매칭된 파티션과 메시지 키의 매칭이 깨지고 전혀 다른 파티션에 데이터가 할당된다.
    • 메시지 키를 사용하고 컨슈머에서 메시지 처리 순서가 보장되어야 한다면 최대한 파티셔의 변화가 발생하지 않는 방향으로 운영해야 한다.
    • 반대로 데이터 처리 순서를 지키지 않아도 된다면 파티션 개수를 처음부터 넉넉하게 잡지 않아도 된다.

3.2.4 ISR(In-Sync-Replicas)

  • ISR은 리더 파티션과 팔로워 파티션이 모두 싱크가 된 상태를 뜻한다.
  • 리더 파티션은 replica.lag.time.max.ms 값 만큼의 주기를 가지고 팔로워 파티션이 데이터를 복제하는지 확인한다.
  • 팔로워 파티션이 replica.lag.time.max.ms 값보다 더 긴 시간동안 데이터를 가져가지 않는다면 해당 팔로워 파티션에 문제가 생긴 것으로 판단하고 ISR그룹에서 제외한다.

브로커와 컨슈머의 영향도

  • 파티션 개수가 늘어나면 동시에 접근하는 파일 개수가 늘어나고 운영체제의 최대 파일 개수 제한 등에 영향을 받을 수 있다.

3.3 레코드

  • 타임스탬프, 메시지 키, 메시지 값, 오프셋으로 구성되어 있다.
  • 프로듀서가 생성한 레코드가 브로커로 전송되면 오프셋과 타임스탬프가 지정되어 저장된다.
  • 한 번 적재된 레코드는 수정할 수 없고 로그 리텐션 기간 또는 용량에 따라서만 삭제된다.
  • 메시지 키를 사용하면 메시지 키의 해시값을 토대로 파티션을 지정하게 된다.
    • 메시지 키를 선언하지 않으면 null로 설정된다.
    • 이 때는 기본 설정 파티셔너에 의해 라운드로빈으로 파티션에 분배된다.
  • 메시지 키와 메시지 값은 직렬화되어 브로커로 전송되기 때문에 컨슈머가 이용할 때는 직렬화한 형태와 동일한 형태로 역직렬화를 해야한다.
  • 레코드의 오프셋은 0 이상의 숫자로 이루어져 있다.
  • 오프셋을 사용하면 컨슈머 그룹으로 이루어진 카프카 컨슈머들이 파티션의 데이터를 어디까지 가져갔는지 명확히 지정할 수 있다.

4. 카프카 프로듀서

  • 프로듀서는 카프카에 데이터를 저장하는 첫 단계이다.

4.1 acks 옵션

  • 0, 1, all(-1)
  • 전송한 데이터가 카프카 클러스터에 얼마나 신뢰성 높게 저장할지 지정할 수 있다.
  • acks 옵션에 따라 성능이 달라질 수 있다.
  • acks=0
    • 데이터가 저장되었는지 확인하지 않는다.
    • 프로듀서에는 데이터의 전송이 실패했을 때 재시도를 할 수 있도록 retries 옵션을 설정할 수 있다.
    • acks=0 일 때에는 재시도를 하지 않기 때문에 retries 옵션값이 무의미하다.
    • 데이터 전송 속도는 acks=1 or acks=all 보다 훨씬 빠르다.
  • acks=1
    • 보낸 데이터가 리더 파티션에만 정상적으로 적재되었는지 확인한다.
    • 리더 파티션에 적재되었음을 보장하더라도 데이터는 유실될 수있다.
    • 리더 파티션이 있는 브로커에 장애가 발생하면 동기화되지 못한 일부 데이터가 유실될 수 있기 때문이다.
  • acks=all or acks=-1
    • 보낸 데이터가 리더 파티션과 팔로워 파티션에 모두 정상적으로 저장되었는지 확인한다.
    • 일부 브로커에 장애가 발생하더라도 프로듀서는 안전하게 데이터를 전송하고 저장할 수 있음을 보장할 수 있다.
    • 토픽 단위로 설정 가능한 min.insync.replicas 옵션값에 따라 데이터의 안정성이 달라진다.
    • min.insync.replicas=2 로 설정하면 ISR의 2개 이상의 파티션에 데이터가 정상 적재되었음을 확인한다는 뜻이다.
    • 실제 카프카를 운영하면서 브로커가 동시에 2개가 중단되는 일은 극히 드물기 때문에 리더 파티션과 팔로워 파티션 중 1개에 데이터가 적재 완료되었다면 데이터는 유실되지 않는다고 볼 수 있다.
    • min.insync.replicas 옵션을 설정할 때 주의할 점은 절대로 브로커 개수와 동일한 숫자로 설정하면 안 된다는 것이다.
      • 브로커가 1대라도 중단되면 데이터를 추가할 수 없다.

4.2 멱등성(impotence) 프로듀서

  • 멱등성이란 여러 번 연산을 수행하더라도 동일한 결과를 나타내는 것이다.
  • 멱등성 프로듀서는 동일한 데이터를 여러 번 전송하더라도 카프카 클러스터에 단 한 번만 저장됨을 의미한다.
  • 기본 프로듀서의 동작 방식은 적어도 한 번 전달을 지원한다.
  • 프로듀서에서 enable.idempotence 옵션을 사용하여 정확히 한 번 전달(exactly once delivery)을 지원한다.
  • 멱등성 프로듀서는 데이터를 브로커로 전달할 때 프로듀서 PID(Producer unique ID)와 시퀀스 넘버(sequence number)를 함께 전달한다.
    • 브로커는 PID와 시퀀스 넘버를 확인하여 동일한 메시지에 적재 요청이 오더라도 단 한 번만 데이터를 적재한다.
  • 멱등성 프로듀서는 동일한 세션에서만 정확히 한 번 전달을 보장한다.
    • 동일한 세션이란 PID의 생명 주기를 뜻한다.
    • 프로듀서 애플리케이션을 재시작하면 PID가 달라진다.
  • 정확히 한 번 브로커에 데이터를 적재하기 위해 한 번 전송하는 것은 아니다. 상황에 따라 프로듀서가 여러번 전송하고 브로커가 중복 데이터를 적재하지 않는다.

4.3 트랜잭션(transaction) 프로듀서

  • 다수의 파티션에 데이터를 저장할 경우 모든 데이터에 대해 동일한 원자성(atomic)을 만족시키기 위해 사용된다.
  • 다수의 데이터를 동일 트랜잭션으로 묶음으로써 전체 데이터를 처리하거나 전체 데이터를 처리하지 않도록 하는 것을 의미한다.
  • 트랜잭션 프로듀서를 사용하려면
    1. enable.idempotence를 true로 설정하고
    2. transaction.id를 임의의 String값으로 정의한다.
    3. 그리고 컨슈머의 isolation.level을 read_committed로 설정하면 프로듀서와 컨슈머는 트랜잭션으로 처리 완료된 데이터만 쓰고 읽게 된다.
  • 트랜잭션은 파티션의 레코드로 구분한다.
    • 트랜잭션의 시작과 끝을 표현하기 위해 트랜잭션 레코드를 한 개 더 보낸다.
    • 트랜잭션 컨슈머는 커밋이 완료된 데이터가 파티션에 있을 경우에만 데이터를 가져간다.
    • 데이터만 존재하고 트랜잭션 레코드가 존재하지 않으면 아직 트랜잭션이 완료되지 않았다고 판단하고 데이터를 가져가지 않는다.

5. 카프카 컨슈머

  • 컨슈머는 카프카에 적재된 데이터를 처리한다.

컨슈머 오프셋 저장

  • 컨슈머 그룹은 어느 레코드까지 가져갔는지 확인하기 위해 오프셋을 커밋한다.
  • 커밋한 오프셋은 __consume_offsets 토픽에 저장한다.

5.1 멀티 스레드 컨슈머

  • 카프카는 처리량을 늘리기 위해 파티션과 컨슈머 개수를 늘려서 운영할 수 있다.
  • 토픽의 파티션은 1개 이상으로 이루어져 있으며 1개의 파티션은 1개 컨슈머가 할당되어 데이터를 처리할 수 있다. 파티션 개수가 n개라면 동일 컨슈머 그룹으로 묶인 컨슈머 스레드를 최대 n개 운영할 수 있다.
  • 하나의 컨슈머 스레드에서 예외적 상황(OutofMemoryException)이 발생할 경우 프로세스 자체가 종료될 수 있고 이는 다른 컨슈머 스레드에까지 영향을 미칠 수 있다.
  • 컨슈머를 멀티 스레드로 활용하는 방식은 크게 두 가지로 나뉜다.
    • 컨슈머 스레드는 1개만 실행하고 데이터 처리를 담당하는 워커스레드를 여러 개 실행하는 방법
    • 컨슈머 인스턴스에서 poll() 메서드를 호출하는 스레드를 여러 개 띄워서 사용하는 컨슈머 멀티 스레드 전략

5.1.1 카프카 컨슈머 멀티 워커 스레드 전략

  • 브로커로부터 전달받은 레코드들을 병렬로 처리한다면 1개의 컨슈머 스레드로 받은 데이터들을 더욱 향상된 속도로 처리할 수 있다.
  • 멀티 스레드를 사용하면 각기 다른 레코드들의 데이터 처리를 동시에 실행할 수 있기 때문에 처리 시간을 현저히 줄일 수 있다.
  • 멀티 스레드를 생성하는 ExecutorService 자바 라이브러리를 사용하면 레코드를 병렬처리하는 스레드를 효율적으로 생성하고 관리할 수 있다.
  • ExecutorService는 다양한 스레드 풀을 제공하는데 완료되면 스레드를 종료하도록 CachedThreadPool을 사용할 수 있다.
  • 주의 사항
    • 데이터 처리가 끝나지 않았음에도 불구하고 커밋을 하기 때문에 리밸런싱, 컨슈머 장애 시에 데이터 유실이 발생할 수 있다.
    • 레코드 처리 역전 현상 발생 가능(레코드 순서 보장 x)

5.1.2 카프카 컨슈머 멀티 스레드 전략

  • 토픽의 파티션 개수만큼 컨슈머 스레드 개수를 늘려서 운영하는 것이다.
  • 각 스레드에 각 파티션이 할당되며, 파티션의 레코드들을 병렬처리할 수 있다.
  • 토픽의 파티션 개수만큼만 컨슈머 스레드를 운영한다.
  • 컨슈머 스레드가 파티션 개수보다 많아지면 할당할 파티션 개수가 더는 없으므로 파티션에 할당되지 못한 컨슈머 스레드는 데이터 처리를 하지 않게 된다.

5.2 컨슈머 랙

  • 컨슈머 랙은 토픽의 최신 오프셋과 컨슈머 오프셋 간의 차이다.
  • 컨슈머가 정상 동작하는지 여부를 확인할 수 있기 때문에 필수적으로 모니터링해야 하는 지표이다.
  • 컨슈머 랙을 확인하는 방법
    • 카프카 명령어를 사용하여 컨슈머 랙 조회
      • kafka-consumer-groups.sh 명령어를 사용하면 컨슈머 랙을 포함한 특정 컨슈머 그룹의 상태를 확인할 수 있다.
      • 카프카 명령어를 통해 컨슈머 랙을 확인하는 방법은 일회성에 그치고 지표를 지속적으로 기록하고 모니터링하기에는 부족하다.
    • 컨슈머 애플리케이션에서 metrics() 메서드를 사용하는 방법
      • 컨슈머 인스턴스가 제공하는 컨슈머 랙 관련 모니터링 지표는 records-lag-max, records-lag, records-lag-avg 로 총 3가지이다.
      • metrics() 메서드로 컨슈머 랙을 확인하는 방법은 3가지 문제가 있다.
        • 컨슈머가 정상 작동할 때만 확인 가능
        • 모든 컨슈머 애플리케이션에 컨슈머 랙 모니터링 코드를 중복해서 작성해야 한다.
        • 컨슈머 랙을 모니터링하는 코드를 추가할 수 없는 카프카 서드 파티 애플리케이션은 모니터링이 불가능하다.
    • 외부 모니터링 툴을 사용하는 방법
      • 데이터독, 컨플루언트 컨트롤 센터와 같은 카프카 클러스터 종합 모니터링 툴을 사용하면 카프카 운영에 필요한 다양한 지표를 모니터링할 수 있다.
      • 컨슈머 랙 모니터링만을 위한 툴로 오픈소스 버로우가 있다.

Reference

'Infra' 카테고리의 다른 글

스프링 카프카 with Template Source Code  (0) 2022.09.20