들어가기 앞서
이전 포스팅에서는 카프카가 탄생하게 된 배경에 대해 간략히 알아 보았습니다.
카프카(kafka)에 대해 알아보자
들어가기 앞서이번 포스팅에서는 카프카에 대해 알아볼 예정입니다. 이전까지 RabbitMQ에 대해 공부하였고, 앞으로 Kafka에 대해 공부하며 두 기술의 차이점에 대해 알아볼 예정입니다. Kafka에 대한
ez-dev-blog.tistory.com
이번 포스팅에서는 카프카의 기본적인 개념과 특징에 대해 알아보겠습니다. 이번 Kafka 공부에서는 아래 책을 활용하고 있으며, 3장 카프카 기본 개념 설명에 해당합니다.
https://www.yes24.com/product/goods/99122569
아파치 카프카 애플리케이션 프로그래밍 with 자바 - 예스24
아파치 카프카 애플리케이션 개발을 위한 「실전 가이드」아파치 카프카란 무엇일까? 카프카 애플리케이션은 어떻게 만들까? 데이터 파이프라인을 만들기 위해 어떤 카프카 라이브러리를 사용
www.yes24.com
브로커, 클러스터, 주키퍼
Kafka 브로커는 Kafka 클라이언트(프로듀서, 컨슈머)와 데이터를 주고받는 역할을 한다. 브로커는 데이터를 분산 저장하여 장애가 발생해도 안전하게 사용할 수 있도록 도와준다. 하나의 서버에 한 개 이상의 브로커를 실행할 수 있으며, 이론적으로는 브로커 한 대만으로도 운영이 가능하지만, 장애 대응과 안정성을 위해 일반적으로 3개 이상의 브로커를 사용하는 것이 권장된다.

Kafka 클러스터는 최소 3대 이상의 브로커로 운영하는 것이 안전하다.
- 1대 브로커: 장애 발생 시 복구 불가
- 2대 브로커: 장애 발생 시 운영 가능하나, 복제 지연에 따른 데이터 유실 가능성 존재
- 3대 이상 브로커: min.insync.replicas=2 설정 가능, 안정적인 데이터 복제 및 처리 가능
Kafka는 브로커 간 데이터 복제를 통해 일부 노드에 장애가 발생해도 지속적으로 메시지를 저장하고 전송할 수 있어, 무중단 운영이 가능한 고신뢰 데이터 파이프라인 플랫폼이다.
데이터 저장과 전송
Kafka에서 프로듀서(Producer)가 데이터를 전송하면, 브로커(Broker)는 해당 데이터를 프로듀서가 지정한 토픽(Topic)의 파티션(Partition)에 저장한다. 이후, 컨슈머(Consumer)가 데이터를 요청하면 브로커는 저장된 데이터를 컨슈머에게 전달한다.
Kafka는 일반적인 메시징 시스템과 다르게 데이터를 메모리나 데이터베이스가 아닌 파일 시스템에 저장한다. 별도의 캐시 메모리를 구현하지 않고, 대신 운영체제(OS)의 페이지 캐시(Page Cache)를 활용하여 I/O 성능을 최적화한다. 페이지 캐시는 운영체제에서 디스크 I/O 성능을 향상시키기 위해 사용하는 메모리 영역이다. 한 번 디스크에서 읽은 파일의 내용을 메모리에 저장해두고, 이후 동일한 파일에 접근할 경우 디스크 대신 메모리에서 읽어 들여 속도를 높인다. Kafka는 이 구조 덕분에 디스크 기반 저장 방식임에도 높은 성능을 유지할 수 있다.
데이터 복제와 싱크(Sync)
Kafka는 고가용성과 내결함성을 제공하기 위해 데이터 복제(Replication) 기능을 제공한다. 복제의 목적은 Kafka 클러스터 내 일부 브로커가 장애를 일으켜도 데이터를 유실하지 않고 지속적으로 사용할 수 있도록 하기 위함이다.
Kafka의 데이터 복제는 파티션 단위로 이루어진다. 토픽을 생성할 때 각 파티션의 복제 개수(replication factor)도 함께 설정하게 되며, 복제 개수는 최소 1에서 최대 브로커 수만큼 지정할 수 있다.
각 파티션은 다음과 같은 구조로 복제된다:
- 리더(Leader): 프로듀서와 컨슈머가 직접 통신하는 파티션
- 팔로워(Follower): 리더의 데이터를 복제하는 파티션

팔로워 파티션은 리더 파티션의 오프셋(offset)을 주기적으로 확인하고, 오프셋 차이가 발생할 경우 데이터를 리더로부터 받아 자신의 파티션에 저장한다. 이 동기화 과정을 복제(Sync)라고 부른다. 복제는 모든 복제본에 동일한 데이터를 저장하므로, 복제 개수만큼 디스크 사용량이 증가한다는 단점이 있지만, 데이터 안정성 측면에서는 매우 강력한 이점을 제공한다.
리더 장애 전환과 운영 전략
Kafka 클러스터에 브로커가 3대 존재한다고 가정했을 때, 리더 파티션을 담당하는 브로커가 장애로 다운되면, Kafka는 자동으로 팔로워 파티션 중 하나를 새로운 리더로 승격시킨다. 이를 통해 데이터 유실 없이, 컨슈머와 프로듀서가 지속적으로 데이터를 주고받을 수 있도록 한다.

운영 환경에서는 데이터의 중요도에 따라 복제 개수를 다르게 설정할 수 있다.
- 일반적인 데이터: 처리 속도가 중요하고 일부 데이터 유실이 허용된다면, 복제 개수를 1 또는 2로 설정하여 성능을 우선시할 수 있다.
- 금융정보 등 중요한 데이터: 복제 개수를 3으로 설정하면, 동시에 2대의 브로커가 장애를 일으켜도 데이터의 무결성과 가용성을 유지할 수 있다.
이처럼 Kafka는 파티션 단위의 복제 구조와 자동 장애 대응 메커니즘을 통해 높은 수준의 신뢰성과 확장성을 제공한다.
컨트롤러(Controller)
Kafka 클러스터에서 하나의 브로커는 컨트롤러 역할을 수행한다. 컨트롤러는 클러스터의 다른 브로커 상태를 지속적으로 모니터링하고, 특정 브로커가 클러스터에서 빠지면 해당 브로커가 담당하던 리더 파티션을 다른 브로커로 재분배한다. 컨트롤러는 Kafka의 안정적인 운영을 위해 반드시 필요하며, 컨트롤러가 장애를 겪으면 클러스터 내 다른 브로커가 자동으로 컨트롤러 역할을 이어받는다.
Kafka의 데이터 삭제 정책
Kafka는 일반적인 메시징 시스템과는 다르게, 컨슈머가 메시지를 가져가더라도 해당 데이터를 삭제하지 않는다. 메시지를 삭제할 수 있는 주체는 오직 Kafka 브로커뿐이며, 이 삭제는 로그 세그먼트(log segment) 단위로 수행된다.
세그먼트는 여러 레코드를 포함하는 파일이며, 데이터가 지속적으로 쌓이는 동안 파일 시스템 상에 열려 있는 상태로 유지된다. Kafka 브로커 설정에서 log.segment.bytes 또는 log.segment.ms 값을 기준으로 세그먼트 파일을 닫을 수 있다.
- log.segment.bytes: 세그먼트 파일의 최대 크기를 정의하며, 기본값은 1GB이다.
- log.segment.ms: 세그먼트 파일이 열린 이후 유지되는 최대 시간을 정의한다.
이 설정들 중 하나의 조건을 만족하면 해당 세그먼트 파일은 닫히게 된다. 세그먼트가 닫히면 이후 Kafka는 이 세그먼트 파일에 대해 삭제 정책을 적용할 수 있게 된다. 다만, 세그먼트는 레코드 단위가 아닌 파일 단위로 삭제되기 때문에, 일반적인 데이터베이스처럼 특정 레코드만 선별적으로 삭제할 수는 없다.
세그먼트 삭제(retention)는 설정된 조건에 따라 이루어진다. 예를 들어, log.retention.bytes 또는 log.retention.ms 옵션을 통해 저장 용량 또는 보존 시간을 기준으로 삭제 시점을 지정할 수 있다. 닫힌 세그먼트가 이러한 조건을 초과하게 되면 Kafka는 해당 세그먼트를 삭제한다. 이 삭제 작업은 브로커 설정 값인 log.retention.check.interval.ms 간격으로 주기적으로 실행되며, 이 옵션은 Kafka가 삭제 대상 세그먼트를 확인하는 주기를 결정한다.
한편 Kafka는 데이터를 삭제하는 대신, 메시지 키를 기준으로 오래된 데이터를 압축(compaction)하는 정책도 지원한다. 이 방식에서는 동일한 키를 가진 레코드 중 가장 최신 값만을 유지하고, 이전 값들은 삭제된다. 이는 데이터의 최신 상태만 필요할 때 유용하며, 일반적인 삭제 정책(retention)과는 다른 접근 방식이다.
컨슈머 오프셋 저장
Kafka에서 컨슈머는 자신이 어느 위치까지 데이터를 가져갔는지를 나타내는 오프셋(offset) 정보를 저장해야 한다. 이 정보는 Kafka 내부 토픽인 _consumer_offsets에 저장되며, 이를 기반으로 컨슈머는 다음 메시지 위치를 파악한다.
코디네이터(Coordinator)
Kafka 클러스터에서 특정 브로커는 코디네이터 역할을 수행한다. 코디네이터는 컨슈머 그룹의 상태를 관리하고, 각 파티션을 컨슈머에게 할당한다. 컨슈머가 그룹에서 나가거나 들어오면, Kafka는 리밸런스(Rebalance)를 통해 파티션을 재할당하여 데이터 처리가 중단되지 않도록 유지한다
토픽과 파티션
토픽(Topic)은 Kafka에서 데이터를 구분하기 위해 사용되는 단위이며, 하나의 토픽은 하나 이상의 파티션(Partition)을 소유한다. 프로듀서가 보낸 데이터는 특정 토픽의 파티션에 저장되며, 이 데이터를 레코드(Record)라고 부른다.

파티션의 역할과 병렬 처리
파티션은 Kafka의 병렬 처리의 핵심 요소다. 컨슈머들이 컨슈머 그룹으로 묶여 있을 때, Kafka는 각 파티션을 컨슈머에 할당하여 병렬로 레코드를 처리할 수 있도록 한다. 컨슈머 하나가 처리할 수 있는 메시지의 양에는 한계가 있기 때문에, 대량의 데이터를 효율적으로 처리하기 위해서는 컨슈머 수를 늘려 스케일 아웃하는 것이 좋다. 이때, 파티션 개수 역시 함께 늘리면 컨슈머들에게 더 많은 작업을 분산시킬 수 있어 처리량이 비례적으로 증가하는 효과를 볼 수 있다.
파티션 구조와 FIFO 특성
Kafka의 파티션은 FIFO(First In, First Out) 구조의 큐와 유사하다. 먼저 들어온 레코드는 먼저 처리된다. 그러나 일반적인 큐와는 달리 Kafka에서는 컨슈머가 레코드를 가져가도 해당 데이터가 삭제되지 않는다. 레코드는 컨슈머가 데이터를 가져가는 행위와는 독립적으로 보존된다. 이러한 구조 덕분에 Kafka는 하나의 토픽 데이터를 여러 컨슈머 그룹이 독립적으로 소비할 수 있다. 각각의 컨슈머 그룹은 자신만의 오프셋을 기준으로 데이터를 가져가기 때문에, 동일한 데이터를 여러 번 처리하거나, 특정 시점부터 다시 읽는 것도 가능하다.
토픽 네이밍 전략
Kafka에서 토픽 이름은 단순한 식별자를 넘어, 개발 환경, 데이터의 성격, 소유 팀 등을 파악할 수 있는 정보를 담는 것이 좋다. 토픽 네이밍이 명확하지 않으면 운영 시 혼란이 발생하고, Kafka는 토픽 이름 변경을 지원하지 않기 때문에 추후 유지보수 시 기술 부채로 이어질 수 있다.
좋은 토픽 네이밍 가이드라인
- 개발 환경을 포함: prd, dev, stage 등
- 서비스 또는 팀 소속을 명시: email-sender, marketing 등
- 애플리케이션 또는 메시지 타입을 명확히 구분
- 공통 Kafka 클러스터 사용 시 오너십 명시: 팀명, JIRA 번호 등 추가
- 복수 Kafka 클러스터 운영 시 클러스터명 포함
Kafka는 토픽 이름에 대소문자를 구분하므로 이름 규칙을 명확히 정하고 일관성 있게 사용하는 것이 중요하다. 만약 규칙 없이 자유롭게 이름을 짓는다면, 전체 Kafka 구조가 복잡해지고 운영 비용이 증가할 수 있다.
- <환경>.<팀명>.<애플리케이션명>.<메시지 타입>
→ prd.marketing.sms-platform.json - <환경>.<서비스명>.<JIRA번호>.<메시지 타입>
→ prd.email-sender.jira-1234.email-vo-custom - <Kafka 클러스터명>.<환경>.<서비스명>.<메시지 타입>
→ aws-kafka.prd.email-sender.json - <Kafka 클러스터명>.<환경>.<서비스명>.<메시지 타입>
→ aws-kafka.prd.payment.json
레코드(Record)
Kafka에서 레코드(record)는 메시지의 단위로, 프로듀서가 생성하여 브로커에 전송하고, 컨슈머가 소비하는 데이터이다. 레코드는 다음과 같은 요소들로 구성된다.

타임스탬프 (timestamp)
레코드가 생성된 시점을 나타내는 유닉스 타임값이다. 기본적으로 프로듀서에서 생성한 시점의 타임스탬프가 저장되지만, 토픽 설정에 따라 브로커에 저장된 시점의 타임스탬프로 지정될 수도 있다. 컨슈머는 이 타임스탬프를 기반으로 메시지가 언제 생성되었는지를 확인할 수 있다. 다만, 프로듀서가 타임스탬프를 임의로 지정할 수 있다는 점에 유의해야 한다.
메시지 키 (key)
메시지 키는 다음 두 가지 목적으로 사용된다.
- 메시지를 순서대로 처리해야 할 때
- 메시지의 논리적 구분값을 부여할 때
Kafka는 메시지 키의 해시값을 기준으로 파티션을 지정한다. 즉, 같은 메시지 키를 가진 레코드는 항상 동일한 파티션에 저장된다. 이를 통해 특정 키에 대해 순서를 보장할 수 있다. 하지만 파티션 수가 변경되면 메시지 키와 파티션 간의 매핑이 달라질 수 있다. 그리고 메시지 키를 지정하지 않는 경우, 프로듀서는 메시지 키를 null로 설정하게 되며, 이 경우 Kafka는 기본 파티셔너 전략에 따라 파티션을 자동 분배한다.
메시지 값 (value)
메시지 값은 실제로 처리하고자 하는 비즈니스 데이터가 들어 있는 영역이다. Kafka에서는 메시지 키와 메시지 값이 전송되기 전 직렬화(Serialization)되어 브로커로 전송되고, 컨슈머 측에서는 역직렬화(Deserialization)를 통해 데이터를 읽어야 한다. 이때, 프로듀서와 컨슈머는 반드시 동일한 직렬화/역직렬화 방식을 사용해야 한다. 예를 들어, 프로듀서가 StringSerializer로 직렬화한 데이터를 컨슈머가 IntegerDeserializer로 읽으려고 하면 데이터 해석이 불가능하다.
오프셋 (offset)
오프셋은 각 파티션 내에서 레코드의 고유한 위치를 나타내는 숫자 값이다. 오프셋은 0 이상의 값으로 자동 할당되며, 수동으로 수정할 수 없다. Kafka는 이전 레코드의 오프셋에 +1을 더한 값을 새로운 레코드의 오프셋으로 할당한다. 컨슈머 그룹은 이 오프셋 값을 기반으로, 자신이 파티션의 어느 위치까지 데이터를 소비했는지를 추적하며, 이를 통해 메시지 중복 처리 없이 정확히 한 번씩 처리할 수 있다.
헤더 (header)
Kafka 레코드는 key-value 형식의 헤더를 가질 수 있으며, 이는 레코드의 부가 정보 또는 메타데이터를 저장하는 용도로 사용된다. 컨슈머는 이 헤더 값을 참조하여 데이터에 대해 추가적인 처리를 할 수 있다. 예를 들어, 특정 헤더 값을 기준으로 메시지를 필터링하거나, 메시지 유형에 따른 분기 처리를 수행할 수 있다.
레코드의 불변성
Kafka에 저장된 레코드는 수정이 불가능하다. 브로커에 한 번 저장된 레코드는 오직 로그 보존 정책(log retention) 또는 세그먼트 용량 제한에 의해 삭제될 수 있다. 이러한 특성은 Kafka가 로그 기반 데이터 저장소로서의 신뢰성을 유지하는 핵심 중 하나다.
'Kafka, RabbitMQ' 카테고리의 다른 글
| 카프카(kafka)에 대해 알아보자 - (4) 카프카 딥다이브 (토픽, 파티션) (0) | 2025.05.27 |
|---|---|
| 카프카(kafka)에 대해 알아보자 - (2) 카프카의 기본 개념 (프로듀서, 컨슈머) (0) | 2025.05.27 |
| 카프카(kafka)에 대해 알아보자 (0) | 2025.05.23 |
| RabbitMQ에 대해 알아보자 - (12) Producer 트랜잭션 전략 (0) | 2025.05.20 |
| RabbitMQ에 대해 알아보자 - (11) 트랜잭션 전략 / 신뢰성 있는 메시지 (0) | 2025.05.08 |