[Kafka Study] 기본 개념

Created
Aug 5, 2023
Created by
Tags
Kafka
Property
 
 
자료 출처
 
 

Broker

notion image
 
  • 카프카 토픽을 서빙하는 노드.
  • 가용성을 유지하기 위해 브로커를 여러 개 둬서 클러스터로 관리한다.
  • 동일한 토픽의 여러 파티션을 여러 브로커에게 분산하여 수평적 스케일링이 가능해진다.
    • ex) 어떤 토픽에 발행하는 메세지 양이 많아져서 레이턴시 발생 → 파티션 추가 → 추가된 파티션을 담당할 새 브로커도 추가.
  • 클러스터 내 브로커 중 1개는 부트스트랩 서버 역할을 하고, 다른 브로커들도 잠재적으로 부트스트랩 서버 역할을 할 수 있다.
  • 각 브로커들은 서로에 대한 메타데이터를 가지고 있다.
 
 

Topic

  • 데이터 스트림 단위.
  • 기본적으로 producer/consumer 를 통해서만 데이터를 생성/읽기 할 수 있으며 토픽 내 데이터를 쿼리할 수 없다. (쿼리하려면 ksqlDB 필요.)
 
 

Partition

notion image
 
  • 토픽 내 데이터 저장 공간을 1개 이상으로 나누는 단위.
  • 파티션 내에 메세지의 수명은 기본적으로 1주일. (메세지 수명 설정은 변경 가능하다.)
  • 파티션 내에 메시지는 불변하다. (파티션에 메세지가 한 변 기록되고 나면 변경할 수 없다.)
  • 파티션 내 메세지들은 고유의 incremental 아이디를 갖는다. 이 아이디는 오프셋으로 쓰인다. → 메세지의 순서는 파티션 내에서만 보장한다.
 
 

Topic replication factor

 
  • 어떤 파티션을 담당하는 브로커가 다운되었을 때 다른 브로커에 해당 파티션이 복제되어 있으면 가용성을 유지할 수 있다.
  • 운영에서는 보통 replication factor 를 2~3 으로 둔다.
  • 아래 그림은 replication factor = 2, 브로커 갯수 = 3 일 때를 가정.
 
notion image
 
 

Leader for a partition

 
  • 파티션이 여러 브로커들에 복제되어 있다면 프로듀서로부터 메세지를 받는 리더 브로커가 존재한다.
  • 리더 브로커는 항상 1개만 존재한다.
  • ex)
    • A 토픽의 0번 파티션의 리더 브로커는 101 → 프로듀서가 0번 파티션에 메세지를 발행하면 메세지는 101 브로커에 들어간다.
    • A 토픽의 1번 파티션의 리더 브로커는 102 → 프로듀서가 1번 파티션에 메세지를 발행하면 메세지는 102 브로커에 들어간다.
 
notion image
 
  • 리더 브로커로부터 메세지를 정상적으로 복제 받는 다른 브로커들을 ISR (In-Sync Replica) 라고 불린다.
  • 복제를 정상적으로 받지 못하는 리플리카는 out-of-sync replica 으로, 리더에 장애가 발생할 경우 리더를 대체할 수 없다.
  • 프로듀서/컨수머는 기본적으로 리더 브로커에게만 메세지를 produce/consume 할 수 있는데, kafka 2.4 버전 이상부터는 컨수머가 ISR 으로부터 메세지를 읽을 수 있다. (called ‘Replica Fetching’)
    • 클라우드 환경에서 운영하려면 보통 고가용성을 유지하기 위해 multi-AZ 환경에 레플리카를 여러개 둔다.
    • 레플리카는 여러개 있어도 리더 브로커는 단 하나만 존재하는데, 리더 브로커와 컨수머 간의 네트워크가 멀다면 레이턴시가 발생할 수 밖에 없다.
    • → replica fetching 을 통해 ISR 을 컨수머와 같은 네트워크에 두고 컨수머가 ISR 에서 메세지를 읽게 한다면 레이턴시 개선에 효과를 볼 수 있다.
 
 

Offset

  • 파티션 내에서만 고유하다.
  • 메세지가 파티션에서 없어지더라도 해당 메세지의 오프셋은 재사용되지 않는다.
 
 

Producer

  • 토픽에 메세지를 발행하는 주체.
  • 어느 파티션에 메세지를 기록할지 프로듀서에서 결정한다.
  • 메세지에 키를 명시하지 않으면 (null 이면) round-robin 방식으로 파티션에 메세지가 로드밸러싱 된다.
  • 메세지에 키를 명시하면 키를 해싱한 결과에 해당하는 파티션으로 메세지가 기록된다.
    • → 동일한 키를 갖는 메세지는 동일한 파티션에 기록된다.
    • → 순서를 보장할 필요가 있는 메세지들은 키를 동일하게 한다.
    • ex) 어떤 주문의 생성/배차/배달완료 이벤트가 순서대로 발생하고, 컨수머에서도 해당 이벤트들을 순서대로 처리해야 한다면 프로듀서에서는 주문생성/배차/배달완료 메세지들의 키를 주문 아이디로 통일하면 된다.
 
 

Producer’s Acknowledgement

  • 프로듀서는 메세지가 토픽에 정상적으로 기록이 되었는지 브로커로부터 확인(ACK) 받을 수 있다.
  • Acknowledgement options
    • acks = 0
      • 브로커로부터 응답이 왔는지 확인하지 않고 다음 메세지를 바로 전송한다.
      • 리더 브로커가 정상적으로 메세지를 기록하지 못했거나, 셧다운 되어도 프로듀서는 모르기 때문에 메세지가 유실될 가능성이 있다.
      • 처리량이 높고 데이터 유실을 어느정도 용인하는 분야 (ex. 지표 수집) 에서 사용한다.
    • acks = 1
      • 리더 브로커로부터 ACK 를 받으면 다음 메세지를 전송한다.
      • 리더 브로커가 정상적으로 처리해도 ISR 에서 복제가 정상적으로 이뤄지지 않는다면 프로듀서는 알 수 없다.
      • 1.0 ~ 2.8 버전까지 디폴트 옵션.
    • acks = all
      • 리더 & ISR 브로커로부터 ACK 를 받으면 다음 메세지를 전송한다.
      • min.insync.replicas 만큼 리플리카에 메세지가 기록되어야 다음 메세지를 발행한다. (min.insync.replicas 만큼 리플리카에 메세지 기록이 처리될 때 까지 재시도한다.)
      • acks=0, acks=1 보다 리더 브로커로부터 응답을 받기까지 지연시간이 더 길다.
      • 3.0 버전 이상부터 디폴트 옵션.
 
💡
프로듀서의 메세지 기록에 신뢰성을 높이는 것과 메세지 기록 속도는 서로 trade-off 관계에 있다.
하지만 메세지를 기록하고 컨수머가 읽을 때 까지의 시간(end to end latency)은 프로듀서의 acks 설정과 무관하게 동일하다. (카프카는 일관성을 유지하기 위해 ISR 에 모두 메세지가 기록되고 나서야 컨수머의 메세지 읽기를 허용하기 때문이다.)
따라서 end to end latency 를 고려할 때는 acks 설정을 타협하지 않아도 된다.
 
 

Record

 
notion image
 
  • 카프카는 레코드라고 불리는 단위로 데이터를 처리한다.
  • 레코드는 message, compression type, header, partition, offset, timestamp 으로 구성되어 있다.
    • 레코드 내 메세지의 키/값은 직렬화에 의해 바이트 형태로 변환되어 있다.
  • 레코드는 압축 가능하다. (compression type 에 어떤 압축 알고리즘을 쓸 지 지정 가능하다.)
  • 레코드에 헤더를 추가할 수 있다.
  • 레코드는 파티션 넘버와 오프셋을 포함한다.
  • 레코드는 레코드가 생성된 타임스탬프를 포함한다.
 
 

Message

 
notion image
  • 레코드의 일부로, 키/값으로 구성되어 있다.
  • 메세지의 키/값은 nullable 하다.
 
 

Message Serializer

 
notion image
 
  • 카프카는 프로듀서로부터 바이트 형태의 메세지 키/값만 받고 컨수머에게도 바이트 형태의 메세지 키/값만 제공한다.
  • 따라서 프로듀서는 카프카에게 메세지를 전달하기 전에 객체 타입의 메세지 키/값을 바이트 형태로 변환하는 직렬화가 필요하고, 컨수머는 바이트 형태의 메세지 키/값을 객체로 변환하는 역직렬화 과정이 필요하다.
  • 카프카 프로듀서는 기본적으로 String, Int, Float 같은 primitive 타입의 키/값을 직렬화 할 수 있으며 Avro, Protobuf 같은 프레임워크를 통해 구조화된 데이터를 직렬화 할 수 있다.
 
 

Partitioner

 
notion image
 
  • 파티셔너는 레코드를 어느 파티션에 기록할지 결정한다.
  • 직렬화 되어있는 메세지 키를 해싱하여 어느 파티션에 기록할지 결정하는데, 해싱 알고리즘은 기본적으로 murmur2 알고리즘을 사용한다.
 
 

Consumer

 
notion image
 
  • 토픽에서 메세지를 읽는 주체.
  • 카프카 클러스터에 데이터를 요청해서 응답을 받는 방식으로 데이터를 읽어들인다. (pull model)
  • 컨수머는 어느 파티션에서 레코드를 읽어들일지 정할 수 있다.
  • 컨수머가 파티션에서 읽어들인 메세지들은 메세지가 파티션에 기록된 순서대로 처리된다.
 
 

Message Deserializer

notion image
 
  • 직렬화에 의해 바이트 형태로 변환된 메세지를 객체 타입으로 변환한다.
  • 카프카 컨수머는 기본적으로 String, Int, Float 같은 primitive 타입의 키/값을 역직렬화 할 수 있으며 Avro, Protobuf 같은 프레임워크를 통해 구조화된 데이터를 역직렬화 할 수 있다.
  • 역직렬화를 하려면 직렬화 되기 전에 메세지의 키/값이 어떤 타입이었는지 컨수머에서 알고 있어야 하고, 프로듀서에서 생성하는 메세지 키/값의 타입과 일치해야 한다. → 프로듀서에서는 함부로 메세지 키/값의 타입을 변경하지 않아야 한다.
 
 

Consumer Group

notion image
 
  • 한 토픽 내에 파티션이 여러개 있을 경우, 컨수머도 여러개 둬서 파티션들의 메세지를 읽어들이도록 한다. 이 컨수머들의 묶음을 컨수머그룹이라고 한다.
  • 컨수머그룹 내에 2개 이상의 컨수머가 동일한 파티션의 메세지를 읽어들이면 메세지가 중복 처리될 것을 방지하기 위해 파티션은 컨수머 그룹 내 1개의 컨수머에게만 할당한다.
  • 한 토픽에 컨수머그룹이 여러개 있을 수 있다.
    • ex) order 토픽을 billing/logistics/data-analysis 서비스들이 구독 → 3개의 컨수머그룹 있을 수 있음
  • 파티션 갯수 > 컨수머 갯수 : 컨수머 일부가 2개 이상의 파티션을 담당하여 메세지를 읽어들인다.
  • 파티션 갯수 < 컨수머 갯수 : 파티션 수 만큼의 컨수머를 제외하고 나머지는 비활성화 되어 stand-by 한다.
 
 

Consumer Offset

 
  • 카프카 브로커는 어느 토픽의 어느 파티션의 메세지가 어디까지 읽혔는지 나타내는 오프셋을 __consumer_offsets 라는 토픽에 기록한다.
  • 컨수머그룹에서는 주기적으로 카프카 브로커에게 오프셋을 커밋한다. → 컨수머 일부가 셧다운되면 살아있는 컨수머가 가장 최근에 기록된 오프셋을 기준으로 다시 메세지를 읽어들인다.
  • 오프셋 커밋 방식은 3가지가 있다.
    • at least once (usually preferred)
      • 메세지를 읽어들이고 나면 오프셋 커밋.
      • 메세지를 읽어들이는 데 실패하면 오프셋은 아직 커밋하지 않았으므로 메세지가 재처리 될 수 있다. (적어도 1번 이상 메세지가 다시 읽힐 수 있다.) → 메세지 처리에 멱등성을 보장해야 한다.
    • at most once
      • 메세지를 읽어들이기 전에 오프셋 커밋.
      • 메세지 읽어들이는 데 실패하면 오프셋이 이미 커밋되어 있으므로 메세지는 재처리 되지 않는다. (최대한 1번 메세지가 읽힌다.)
    • exactly once
      • 컨수머에서 메세지를 읽어들이고 읽어들인 결과를 토픽에 기록하기까지 과정을 트랜잭션으로 묶는다. (kafka streams API 사용)
      • 해당 트랜잭션이 커밋되면 오프셋을 커밋하고 롤백되면 커밋하지 않는다.
 
 

Zookeeper

 
  • 브로커들을 관리한다. (브로커 목록을 가지고 있다.)
  • 리더 브로커에 장애가 발생하면 새로운 리더브로커를 선출한다.
  • 토픽 생성/삭제, 리더 브로커 장애와 같은 이벤트를 브로커들에게 알린다.
  • 주키퍼 클러스터는 홀수개의 노드로 구성하고 1개의 노드를 리더, 나머지 노드들은 팔로워로 구성한다.
  • 점차 없애고 있는 추세.
    • 브로커 갯수가 많아질수록 스케일링에 이슈 발생. (브로커들과 주키퍼 클러스터 간의 메타데이터 불일치 문제.)
    • 카프카 관리자가 카프카와 관련 없는 시스템 운영 방법을 배워야 하는 수고.
  • kafka 3.x 부터는 주키퍼 없이 운영 가능하고 Kafka Raft 를 사용할 수 있다.
  • kafka 4.x 부터는 주키퍼를 지원하지 않는다. (기존 Kafka 클라이언트도 주키퍼 의존성을 점차 없애고 있다.)
 
 

Post-Zoopkeeper concept (KRaft)

 
자세한 내용은 KIP-500 참고.
  • Raft 알고리즘을 사용하여 브로커들 스스로가 액티브 컨트롤러(리더)를 선출하고, 나머지는 팔로워 컨트롤러가 된다.
    • 팔로워 컨트롤러들은 액티브 컨트롤러가 셧다운 될 경우 대체되기 위한 standby 로도 활용된다.
  • 액티브 컨트롤러는 클러스터에서 발생하는 메타데이터를 수집하고, 팔로워 컨트롤러들은 주기적으로 액티브 컨트롤러에게 최신 메타데이터를 요청해서 각자의 디스크에 저장하고 캐싱도 한다. (요청할 때 오프셋 보냄)
    • 브로커가 죽었다 살아날 경우 외부 시스템에 메타데이터를 요청할 필요 없이 로컬에 있는 디스크를 읽으면 되므로 주키퍼에 의존하던 것에 비해 브로커 복구 시간이 짧아진다.
    • 액티브 컨트롤러가 오프셋을 받았는데 브로커가 가지고 있는 메타데이터가 너무 오래됐거나 캐싱된 메타데이터가 아예 없을 경우, 액티브 컨트롤러는 델타 분을 보내는 대신 풀 스냅샷을 전송한다.
  • 액티브 + 팔로워 컨트롤러들은 새로운 액티브 컨트롤러를 재선출 할 때 필요한 quorum 집합이 된다.
    • 어떤 팔로워 컨트롤러가 액티브 컨트롤러가 제 기능을 못한다 판단하여 리더로 승격하려고 할 때 정족수에 만족하는 컨트롤러들이 이에 동의를 해야 리더로 승격 가능.