카프카(Apache Kafka)란?

Apache Kafka is an open-source distributed event streaming platform used by thousands of companies for high-performance data pipelines, streaming analytics, data integration, and mission-critical applications.

카프카는 대용량의 실시간 데이터 스트리밍 플랫폼으로서, 분산형 메시지 큐 시스템으로 동작한다. 카프카는 다음과 같은 특징을 가지고 있다.

  • 높은 처리량과 낮은 지연시간
    • 페이지 캐시
    • 배치 전송 처리 (프로듀서 및 컨슈머는 단 건이 아닌 여러 개로 처리 가능)
    • 압축 전송 (네트워크 통신 효율)
      • 배치와 같이 사용하면 효과가 더욱 커진다.
      • 높은 압축률이 필요한 경우, gzip or zstd 권장
      • 빠른 응답 속도가 필요한 경우, lz4 or snappy 권장
  • 높은 확장성
  • 고가용성
    • 리플리케이션 (replication)
  • 내구성
    • 프로듀서 acks 설정 (저장 확인)
    • 소비된 데이터는 바로 지우지 않음 (설정된 시간 또는 로그의 크기만큼 저장)
    • 여러 브로커에 분산 저장
  • 개발 편의성
    • 프로듀서와 컨슈머는 독립적으로 개발 가능
    • 카프카 커넥트, 스키마 레지스트리 등 여러 편의 기능 제공
  • 운영 및 관리 편의성

이와 같은 많은 장점을 가지고 카프카는 현재 수많은 기업에서 사용하고 있는 메시징 시스템으로 자리잡았다. 이 글에서는 카프카의 기본 개념 및 중요하게 기억해야할 점과 설정에 관한 글이다. 카프카를 빠르게 파악하고 설정할 때 읽기 위한 목적으로 작성하였다.

카프카 구성요소와 구조

카프카는 여러 용어를 사용하고 있어 초기에는 헷갈릴 수 있다. 아래 용어는 기본적으로 많이 사용하는 용어 위주로 설명하였다.

주키퍼 (ZooKeeper)

  • 카프카의 메타데이터(metadata) 관리 및 브로커의 정상상태 점검(health check)을 담당하는 애플리케이션

Kafka or Kafka cluster

  • 카프카에서 여러 대의 브로커를 구성한 클러스터

브로커 (Broker)

  • 카프카 애플리케이션이 설치된 서버 또는 노드

프로듀서 (Producer)

  • 카프카로 메시지를 보내는 역할을 하는 클라이언트

컨슈머 (Consumer)

  • 카프카에서 메시지를 꺼내가는 역할을 하는 클라이언트

토픽 (Topic)

  • 카프카는 메시지 피드들을 토픽으로 구분하고, 각 토픽의 이름은 카프카 내에서 고유하다.

파티션 (Partition)

  • 병렬 처리 및 고성능을 얻기 위해 하나의 토픽을 여러 개로 나눈 것을 말한다.

세그먼트 (Segment)

  • 프로듀서가 전송할 실제 메시지가 브로커의 로컬 디스크에 저장되는 파일

Message or Record

  • 프로듀서가 브로커로 전송하거나 컨슈머가 읽어가는 데이터 조각

위 구성요소를 그림으로 나타내면 아래와 같다.

image

리플리케이션 (Replication)

위 그림을 보면 브로커가 똑같은 모습으로 3개 존재한다. 이를 리플리케이션이라고 부르며, 같은 데이터를 복제해놓는 것을 말한다. 리플리케이션을 하는 이유는 고가용성을 위함이다. 브로커가 하나가 장애가 발생하여 종료되어도 나머지 브로커가 살아있으므로 운영을 유지하는데는 문제가 되지 않는다. 이는 데이터를 계속 안전하게 유지할 수 있다는 의미이다.

리플리케이션의 개수는 환경마다 추천하는 개수가 다르다.

  • 테스트나 개발 환경: 1
  • 운영 환경 (로그성 메시지로서 약간의 유실 허용): 2
  • 운영 환경 (유실 허용하지 않음): 3

참고로 브로커 개수가 3개인데, 리플리케이션 개수를 4개로 설정하면 같은 브로커 내에 2개가 저장되므로 비효율적이다.

파티션

파티션은 토픽에 데이터를 저장하는 하나의 큐이다. 한 토픽의 여러 개의 파티션을 가질 수 있고, 이는 병렬 처리가 가능해져서 처리량을 높일 수 있다. 파티션의 특징은 다음과 같다.

  • 하나의 파티션은 데이터의 순서를 보장한다. (단, 여러 개의 파티션을 사용하면 순서를 보장할 수 없다.)
  • 여러 개의 파티션은 각각의 컨슈머에 연결될 수 있다.
    • 파티션 수와 컨슈머 수가 같으면 각각 1:1로 매핑될 수도 있고, 파티션 수가 더 많으면 하나의 컨슈머에 여러 개의 파티션이 매핑될 수도 있다.
  • 파티션은 데이터 위치를 나타내는 오프셋을 가지고 있다.

오프셋 (offset)

오프셋은 파티션 내에 데이터의 위치를 나타내는 고유한 번호(64비트 정수)이다. 오프셋은 0부터 시작하며, 데이터가 증가함에 따라 순차적으로 증가한다. 오프셋의 역할은 다음과 같다.

  • 컨슈머가 파티션의 데이터를 어디까지 읽었는지 나타내는 지표
  • 파티션의 데이터가 어디까지 보존되었는지 나타내는 지표

오프셋은 브로커 내에 영구적으로 저장되어 관리되는 데이터이다.

프로듀서

프로듀서는 ProducerRecord라는 데이터 객체를 만들어서, Serializer, Partitioner 순서의 과정을 거쳐서 브로커로 전송한다.

  • ProducerRecord
    • Topic (필수값): 어떤 토픽으로 보낼지
    • Value (필수값): 메시지 값
    • Partition: 특정 파티션으로 보낼지 지정
    • Key: 레코드들의 정렬을 위한 레코드 키
  • Serializer: 브로커로 전송하기 위한 데이터 형태로 만드는 작업
  • Partitioner: 어느 파티션으로 보낼 지 결정하는 과정
    • ProduceRecord의 값 중 Partition 이 지정된다면, Partitioner는 아무런 동작을 하지 않는다.
    • 위가 아니면, 기본적으로 RR 방식으로 동작한다.

프로듀서 주요 옵션

  • bootstrap.servers: 클라이언트가 카프카 클러스터에 처음 연결하기 위한 호스트와 포트 정보
  • client.dns.lookup: 하나의 호스트에 여러 IP를 매핑해 사용하는 일부 환경에서 클라이언트가 하나의 IP와 연결하지 못할 경우에 다른 IP로 시도하는 설정.
    • use_all_dns_ips가 기본값 (DNS에 할당된 호스트의 모든 IP를 쿼리하고 저장하고, 첫 번째 IP로 접근이 실패하면, 종료하지 않고 다음 IP로 접근을 시도한다.).
    • resolve_canonical_bootstrap_servers_only 옵션은 커버로스(Kerberos) 환경에서 FQDN을 얻기 위한 용도로 사용한다.
  • acks: 프로듀서가 카프카 토픽의 리더 측에 메시지를 전송한 후 요청을 완료하기를 결정하는 옵션.
    • 0은 빠른 전송을 의미하지만, 일부 메시지 손실 가능성이 있음.
    • 1은 리더가 메시지를 받았는지 확인하지만, 모든 팔로워를 전부 확인하지는 않는다. (대부분 기본값으로 1을 사용)
    • all(-1)은 팔로워가 메시지를 받았는지 여부를 확인한다. 다소 느릴 수 있지만, 하나의 팔로워가 있는 한 메시지는 손실되지 않는다.
  • buffer.memory: 프로듀서가 카프카 서버로 데이터를 보내기 위해 잠시 대기(배치 전송이나 딜레이 등)할 수 있는 전체 메모리 바이트
  • compression.type: 프로듀서가 메시지 전송 시 선택할 수 있는 압축 타입.
    • none, gzip, snappy, lz4, zstd 중 원하는 타입 선택 가능
  • enable.idempotence: 설정을 true로 하는 경우 중복 없는 전송이 가능하며, 이와 동시에 max.in.flight.requests.per.connection5이하, retries0이상, acksall로 설정해야 한다.
  • max.in.flight.requests.per.connection: 하나의 커넥션에서 프로듀서가 최대한 ACK 없이 전송할 수 있는 요청 수. 메시지의 순서가 중요하다면 1로 설정을 권장하지만, 성능은 다소 떨어진다.
  • retries: 일시적인 오류로 인해 전송에 실패한 데이터를 다시 보내는 횟수
  • batch.size: 프로듀서는 동일한 파티션으로 보내는 여러 데이터를 함께 배치로 보내려고 시도한다. 적절한 배치 크기 설정은 성능에 도움을 준다.
  • linger.ms: 배치 형태의 메시지를 보내기 전에 추가적인 메시지를 위해 기다리는 시간을 조정하고, 배치 크기에 도달하지 못한 상황에서 linger.ms 제한 시간에 도달했을 때 메시지를 전송한다.
  • transactional.id: ‘정확히 한 번 전송’을 위해 사용하는 옵션이며, 동일한 TransactionalId에 한해 정확히 한 번을 보장한다. 옵션을 사용하기 전 enable.idempotencetrue로 설정한다.

전송 방식

  • 중복없는 전송
    • 적어도 한 번 전송(at-least-once)
    • 최대 한 번 전송(at-most-once)
    • 정확히 한 번 전송(exactly-once)
  • 정확히 한 번 전송

컨슈머

consume 작업은 컨슈머 그룹으로 동작을 한다.

  • 컨슈머 그룹은 하나 이상의 컨슈머를 가지는 그룹이다.
  • 파티션 수와 컨슈머 수는 일대일로 매핑되는 것이 이상적이다.

컨슈머 주요 옵션

  • bootstrap.servers: 프로듀서와 동일하게 브로커의 정보를 입력
  • fetch.min.bytes: 한 번에 가져올 수 있는 최소 데이터 크기
    • 만약 지정한 크기보다 작은 경우, 요청에 응답하지 않고 데이터가 누적될 때까지 기다린다.
  • group.id: 컨슈머가 속한 컨슈머 그룹을 식별하는 식별자. 동일한 그룹 내의 컨슈머 정보는 모두 공유된다.
  • heartbeat.interval.ms: 하트비트가 있다는 것은 컨슈머의 상태가 active임을 의미한다.
    • session.timeout.ms와 밀접한 관계가 있으며, session.timeout.ms보다 낮은 값으로 설정해야 한다. 일반적으로 session.timeout.ms의 1/3로 설정한다.
  • max.partition.fetch.bytes: 파티션당 가져올 수 있는 최대 크기
  • session.timeout.ms: 이 시간을 이용해, 컨슈머가 종료된 것인지를 판단한다. 컨슈머는 주기적으로 하트비트를 보내야 하고, 만약 이 시간 전까지 하트비트를 보내지 않았다면 해당 컨슈머는 종료된 것으로 간주하고 컨슈머 그룹에서 제외하고 리밸런싱을 시작한다.
  • enable.auto.commit: 백그라운드로 주기적으로 오프셋을 커밋한다.
  • auto.offset.reset: 카프카에서 초기 오프셋이 없거나 현재 오프셋이 더 이상 존재하지 않는 경우에 다음 옵션으로 reset한다.
    • earliest: 가장 초기의 오프셋값으로 설정
    • latest: 가장 마지막의 오프셋값으로 설정
    • none: 이전 오프셋값을 찾지 못하면 에러
  • fetch.max.bytes: 한 번의 가져오기 요청으로 가져올 수 있는 최대 크기
  • group.instance.id: 컨슈머의 고유한 식별자. 만약 설정한다면 static 멤버로 간주되어, 불필요한 리밸런싱을 하지 않는다.
  • isolation.level: 트랜잭션 컨슈머에서 사용되는 옵션
    • read_uncommitted는 기본값으로 모든 메시지를 읽음
    • read_committed는 트랜잭션이 완료된 메시지만 읽음
  • max.poll.records: 한 번의 poll() 요청으로 가져오는 최대 메시지 수
  • partition.assignment.strategy: 파티션 할당 전략. 기본값은 range
  • fetch.max.wait.ms: fetch.min.bytes에 의해 설정된 데이터보다 적은 경우 요청에 대한 응답을 기다리는 최대 시간.

참고자료