Apache Kafka ‐ 카프카 기본 개념 - thought-corner/Backend-PlayGround GitHub Wiki

브로커

  • 컨트롤러
    • 클러스터의 다수 브로커 중 한 대가 컨트롤러의 역할을 수행한다.
    • 컨트롤러는 다른 브로커들의 상태를 체크하고 브로커가 클러스터에서 빠지는 경우 해당 브로커에 존재하는 리더 파티션을 재분배한다.
    • 카프카는 지속적으로 데이터를 처리해야 하므로 브로커 상태가 비정상이라면 빠르게 클러스터에서 빼내는 것이 중요하다.
    • 만약 컨트롤러 역할을 수행하는 브로커에 장애가 발생하면 다른 브로커가 컨트롤러 역할을 한다.
  • 데이터 삭제
    • 카프카는 RabbitMQ와 달리 컨슈머가 데이터를 가져가도 토픽 내의 데이터를 삭제되지 않는다.
    • 또한, 컨슈머나 프로듀서가 데이터 삭제를 요청할 수도 없다. 오직 브로커만이 데이터를 삭제할 수 있다.
    • 데이터 삭제는 파일 단위로 이루어지는데 이 단위를 로그 세그먼트라고 한다.
    • 이 세그먼트는 다수의 데이터가 들어있기 때문에 일반적인 데이터베이스처럼 특정 데이터를 선별해서 삭제할 순 없다.
  • 컨슈머 오프셋 저장
    • 컨슈머 그룹은 특정 파티션으로부터 데이터를 가져가서 처리하고 이 파티션의 어느 레코드까지를 가져갔는지 확인하기 위해 오프셋을 커밋한다.
    • 커밋한 오프셋은 __consumer_offsets 토픽에 저장된다. 여기에 저장된 오프셋을 토대로 컨슈머 그룹은 다음 레코드를 가져가서 처리한다.
  • 그룹 코디네이터
    • 코디네이터는 컨슈머 그룹 상태를 체크하고 파티션을 컨슈머와 매칭되도록 분배하는 역할을 한다.
    • 컨슈머가 컨슈머 그룹에서 빠지면 매칭되지 않은 파티션을 정상 동작 중인 컨슈머로 할당하여 끊임없이 데이터가 처리되도록 도와준다.
    • 이렇게 파티션을 컨슈머로 재할당하는 과정을 리밸런스(Rebalance)라고 부른다.
  • 데이터 저장
    • 카프카를 실행할 때 config/server.propertieslog.dir 옵션에 정의한 디렉터리에 데이터를 저장한다.
    • 토픽 이름과 파티션 번호의 조합으로 하위 디렉터리를 생성해 데이터를 저장한다.
    • log에는 메시지와 메타데이터를 저장한다. index는 메시지 오프셋을 인덱싱한 정보를 담은 파일이다.
    • timeindex 파일에는 메시지에 포함된 timestamp 값을 기준으로 인덱싱한 정보가 담겨 있다.

로그와 세그먼트

  • log.segment.bytes : 바이트 단위의 최대 세그먼트 크기 지정. 기본 값은 1GB
  • log.roll.ms(hours) : 세그먼트가 신규 생성된 이후 다음 파일로 넘어가는 주기. 기본 값은 7일
  • 가장 마지막 세그먼트 파일을 액티브 세그먼트라고 부른다. 액티브 세그먼트는 브로커 삭제 대상에서 포함되지 않는다. 액티브 세그먼트가 아닌 세그먼트는 retention 옵션에 따라 삭제 대상으로 지정된다.

세그먼트와 삭제 주기(cleanup.policy=delete)

  • retention.ms(minutes, hours) : 세그먼트를 보유할 최대 기간. 기본 값은 7일.
  • retention.bytes : 파티션당 로그 적재 바이트 값. 기본 값은 -1.
  • log.retention.check.interval.ms : 세그먼트가 삭제 영역에 들어왔는지 확인하는 간격. 기본 값은 5분.
  • 카프카에서 데이터는 세그먼트 단위로 삭제가 발생하기 때문에 로그 단위로 개별 삭제는 불가능하다.

세그먼트와 삭제 주기(cleanup.policy=compact)

  • 토픽 압축 정책은 일반적으로 생각하는 zip과 같은 압축과는 다른 개념이다.
  • 여기서 압축이란 메시지 키 별로 해당 메시지 키의 레코드 중 오래된 데이터를 삭제하는 정책을 뜻한다.
  • 그렇기 때문에 삭제(delete) 정책과 다르게 일부 레코드만 삭제될 수 있다. 압축은 액티브 세그먼트를 제외한 데이터가 대상이 된다.
  • 테일 영역 : 압축 정책에 의해 압축이 완료된 레코드들. 클린 로그라고 부른다. 중복 메시지 키가 없다.
  • 헤드 영역 : 압축 정책이 되기 전 레코드들. 더티 로그라고 부른다. 중복된 메시지 키가 존재한다.

복제

  • 데이터 복제는 카프카를 장애 허용 시스템으로 동작하도록 하는 원동력이다.
  • 복제의 이유는 클러스터로 묶인 여러 브로커 중 일부에 장애가 발생하더라도 데이터가 유실되지 않고 안전하게 사용하기 위함이다.
  • 카프카의 데이터 복제는 파티션 단위로 이루어진다. 토픽을 생성할 때 파티션의 복제 개수도 같이 설정되는데 직접 옵션을 선택하지 않으면 브로커에 설정된 옵션 값을 따라간다.
  • 복제 개수의 최솟값은 1이고 최댓값은 브로커 개수만큼 설정해 사용할 수 있다.
  • 복제된 파티션은 리더와 팔로워로 구성된다. 복제의 기본 단위는 파티션이다.
  • 프로듀서 또는 컨슈머와 직접 통신하는 파티션을 리더, 나머지는 팔로워라고 부른다.
  • 팔로워들은 리더의 오프셋을 확인해 현재 자신이 가지고 있는 오프셋과 차이가 나는 경우 리더로부터 데이터를 가져와서 자신의 파티션에 저장하는데 이를 복제라고 한다.
  • 파티션 복제로 인해 나머지 브로커들 모두 파티션의 데이터가 복제되므로 복제 개수만큼의 저장 용량이 증가한다는 단점이 있다.
  • 그러나 이 복제를 통해 데이터를 안전하게 사용할 수 있다는 강력한 장점때문에 카프카를 운영할 때 최소 2 이상의 복제 개수를 정하는 것이 중요하다.

브로커에 장애가 발생한 경우

  • 브로커가 다운되면 해당 브로커에 있는 리더는 사용이 불가능하기 때문에 팔로워 중 하나가 리더의 지위를 넘겨받는다.
  • 이를 통해 데이터가 유실되지 않고 컨슈머나 프로듀서와 데이터를 주고받을 수 있도록 동작한다.
  • 운영 시에는 데이터 종류마다 다른 복제 개수를 설정하고 상황에 따라서는 토픽마다 복제 개수를 다르게 설정하도록 하여 운영하기도 한다.
  • 데이터가 일부 유실되어도 무관하고 데이터가 처리 속도가 중요하다면 1 혹은 2로 설정한다.
  • 허나 금융 정보와 같이 유실되면 안되는 경우 복제 개수를 3으로 설정하기도 한다.

ISR

  • ISR(In-Sync-Replicas)는 리더와 팔로워가 모두 싱크가 된 상태를 뜻한다.
  • 복제 개수가 2인 토픽을 가정하면 위와 같이 리더와 팔로워가 1개씩 존재하게 된다. 리더에 0 ~ 3번 오프셋이 존재한다고 가정할 때, 팔로워 파티션에 동기화가 완료되려면 0 ~ 3번까지의 오프셋이 존재해야 한다.
  • 동기화가 완료되었다는 의미는 리더의 모든 데이터가 팔로워에 복제가 되었다는 것을 말한다.
  • unclean.leader.election.enable = true : 유실을 감수한다. 복제가 안 된 팔로워 파티션을 리더로 승급한다.
  • unclean.leader.election.enable = false : 유실을 감수하지 않는다. 해당 브로커가 복구될 때까지 중단한다.

토픽과 파티션

  • 토픽은 카프카에서 데이터를 구분하기 위해 사용하는 단위이다. 토픽은 1개 이상의 파티션을 소유하고 있다.
  • 파티션에는 프로듀서가 보낸 데이터들이 저장되는데 이 데이터를 레코드라고 부른다.
  • 파티션은 FIFO 구조로 먼저 들어간 레코드는 컨슈머가 먼저 가져간다. 여기서 다른 점은 메시지를 컨슈머가 가져간다고 하더라도 큐에서 메시지가 삭제되지 않는다는 것이다.
  • 파티션의 레코드는 컨슈머가 가져가는 것과 별개로 관리된다. 이런 특징 때문에 토픽의 레코드는 다양한 목적을 가진 여러 컨슈머 그룹들이 토픽의 데이터를 여러 번 가져갈 수 있다.
  • 위와 같이 파티션이 5개인 토픽을 생성했을 경우 round-robin 방식으로 리더들이 생성된다.
  • 카프카 클라이언트는 리더가 있는 브로커와 통신해 데이터를 주고 받고 여러 브로커에 골고루 네트워크 통신을 하게 된다.
  • 이를 통해 데이터가 특정 서버와 통신이 집중되는 현상을 방지하고 선형 확장을 통해 데이터가 많아지더라도 자연스럽게 대응할 수 있다.

특정 브로커에 파티션이 몰리는 경우

  • 특정 브로커에 파티션이 몰리는 경우 카프카 클라이언트 애플리케이션(프로듀서, 컨슈머)이 특정 브로커와만 통신을 하기 때문에 문제가 발생할 수 있다.
  • 특정 브로커에 파티션이 몰리는 경우에는 kafka-reassign-partitions.sh 명령으로 파티션을 재분배할 수 있다.

파티션 개수와 컨슈머 개수 처리량

  • 파티션은 카프카 병렬 처리의 핵심으로 그룹으로 묶인 컨슈머들이 레코드를 병렬로 처리할 수 있도록 매칭된다.
  • 컨슈머 처리량이 한정된 상황에서 레코드를 병렬로 처리하려면 컨슈머 개수를 늘려 스케일 아웃하는 것이다.
  • 컨슈머 개수를 늘림과 동시에 파티션 개수도 늘리면 처리량이 증가한다.
  • 허나 그렇다고 해서 컨슈머 개수와 파티션 개수를 무한히 늘리면 좋은 것은 절대 아니다.
  • 카프카에서 파티션 개수를 늘릴 순 있으나 줄이는 것은 불가능하다.
  • 그러므로 파티션을 늘리는 작업을 할 때는 신중히 해야 한다. 한 번 늘리면 줄이는 것이 불가능하기 때문에 토픽을 삭제하고 재생성하는 방법 외에는 없다.
  • 카프카에서 파티션 데이터를 세그먼트로 저장하고 있으며 만에 하나 지원을 한다고 하더라도 여러 브로커에 저장된 데이터를 취합하고 정렬해야하는 복잡한 과정을 거쳐야 하기 때문에 클러스터에 영향이 간다.
  • 파티션 하나는 컨슈머 하나만 담당한다.
  • 그룹마다 offset을 독립적으로 관리하기 때문에 같은 파티션을 동시에 읽을 수 있다.
  • Kafka에서 병렬 처리의 한계는 파티션 수가 결정한다. 그렇기 때문에 컨슈머 쓰레드를 늘려도 파티션 수를 초과하면 의미가 없다.

레코드

  • 레코드는 타임스탬프, 헤더, 메시지 키, 값, 오프셋으로 구성되어 있다.
  • 프로듀서가 생성한 레코드가 브로커로 전송되면 오프셋과 타임스탬프가 지정되어 저장된다.
  • 브로커에 한 번 적재된 레코드는 수정할 수 없고 로그 리텐션 기간 또는 용량에 따라서만 삭제된다.
  • timestamp
    • 스트림 프로세싱에서 활용하기 위한 시간을 저장하는 용도로 사용된다.
    • Unix timestamp가 포함되며 프로듀서에서 따로 설정하지 않으면 기본값으로 ProducerRecord 생성 시간이 들어간다.
    • 또는 브로커 적재 시간으로 설정할 수도 있다.
    • 해당 옵션은 토픽 단위로 설정 가능하며 message.timestamp.type을 사용한다.
  • offset
    • 레코드의 오프셋은 프로듀서가 생성한 레코드에는 존재하지 않는다.
    • 프로듀서가 전송한 레코드가 브로커에 적재될 때 오프셋이 지정된다.
    • 오프셋은 0부터 시작하고 1씩 증가한다.
    • 컨슈머는 오프셋을 기반으로 처리가 완료된 데이터와 앞으로 처리해야할 데이터를 구분한다.
    • 각 메시지는 파티션별로 고유한 오프셋을 가지므로 컨슈머에서 중복 처리를 방지하기 위한 용도로 사용한다.
  • header
    • key/value 형식의 데이터를 추가할 수 있으며 레코드 스키마 버전이나 포맷과 같이 데이터 프로세싱에 참고할만한 정보를 담아서 사용할 수도 있다.
  • key
    • 메시지 키는 처리하고자 하는 메시지 값을 분류하기 위한 용도로 사용되며 이를 파티셔닝이라고 부른다.
    • 파티셔닝에 사용되는 메시지 키는 파티셔너에 따라 토픽의 파티션 번호가 정해진다.
    • 메시지 키는 필수 값이 아니며 지정하지 않으면 null로 설정된다.
    • 메시지 키가 null인 레코드는 특정 토픽의 파티션에 라운드 로빈으로 전달된다.
    • null이 아닌 메시지 키는 해시 값에 의해서 특정 파티션에 매핑되어 전달된다.
  • value
    • 실질적으로 처리할 데이터가 담기는 공간이다.
    • 메시지 값 포맷은 제네릭으로 사용자에 의해 지정된다.
    • 다양한 형태로 지정 가능하며 필요에 따라 사용자 지정 포맷으로 직렬화/역직렬화 클래스를 만들어 사용할 수도 있다.
    • 브로커에 저장된 레코드의 메시지 값은 어떤 포맷으로 직렬화되어 저장되었는지 알 수 없기 때문에 컨슈머는 미리 역직렬화 포맷을 알고 있어야 한다.

토픽 이름 제약 조건 및 의미 있는 토픽 이름 작명 방법

클라이언트 메타데이터와 브로커의 통신

  • 카프카 클라이언트는 통신하고자 하는 리더 파티션의 위치를 알기 위해 데이터를 주고 받기 전에 메타데이터를 브로커로부터 전달받는다.
  • 메타 데이터는 다음과 같은 옵션을 통해 리프레시 된다.
  • metadata.max.age.ms : 메타 데이터를 강제로 리프레시하는 간격. 기본 값은 5분.
  • metadata.max.idle.ms : 프로듀서가 유휴상태일 경우 메타 데이터를 캐시에 유지하는 기간

클라이언트 메타데이터에서 이슈가 발생한 경우

  • 카프카 클라이언트는 리더 파티션과 통신을 해야 한다.
  • 메타데이터가 현재 파티션 상태에 맞게 리프레시되지 않은 상태에서 잘못된 브로커로 데이터를 요청하면 LEADER_NOT_AVAILABLE 예외가 발생한다.
  • 이 에러는 클라이언트(프로듀서 혹은 컨슈머)가 데이터를 요청한 브로커에 리더 파티션이 없는 경우 나타나며 대부분의 경우 메타데이터 리프레시 이슈로 발생한다.
  • 이 에러가 자주 발생한다면 메타데이터 리프레시 간격을 확인하고 클라이언트가 정상적인 메타 데이터를 가지는지 확인해야 한다.