티스토리 뷰

 

*본 포스팅은 인프런 [데브원영]님의 아파치 카프카 애플리케이션 프로그래밍 강의 섹션 1,2를 수강하고 이를 참고해 작성했습니다. 

 

[아파치 카프카 애플리케이션 프로그래밍] 개념부터 컨슈머, 프로듀서, 커넥트, 스트림즈까지! |

데브원영 DVWY | 실전 환경에서 사용하는 아파치 카프카 애플리케이션 프로그래밍 지식들을 모았습니다! 데이터 파이프라인을 구축하는데 핵심이 되는 아파치 카프카의 각종 기능들을 살펴보고

www.inflearn.com

 


섹션 1 : 아파치 카프카의 역사와 미래

#1 아파치 카프카의 탄생

링크드인(LinkedIn)에서 데이터 파이프라인의 파편화를 개선하기 위해 개발한 시스템이다. 카프카는 각각의 애플리케이션끼리 연결하여 데이터를 처리하는 것이 아닌 한 곳에 모아 처리할 수 있도록 중앙집중화했다. 카프카 파티션의 동작은 FIFO(Fisrt In First Out) 방식의 큐 자료구조로 동작한다. 

 

 

#2 카프카가 데이터 파이프라인으로 적합한 이유

[1] 높은 처리량

카프카는 프로듀서가 브로커로 데이터를 보낼 때, 컨슈머가 브로커로부터 데이터를 받을 때 모두 묶어서 전송한다. 이렇게 묶음 단위로 처리하는 배치 처리를 통해 네트워크 통신 횟수를 최소화하면서 비용을 줄일 수 있으므로 대용량의 실시간 로그 데이터 처리에 적합하다.

 

[2] 확장성

데이터 파이프라인에서 데이터를 모을 때 데이터가 얼마나 들어올지는 예측하기 어렵다. 데이터가 적을 때는 브로커를 최소한의 개수로 운영하다가 데이터가 많아지면 클러스트의 브로커 개수를 자연스럽게 스케일 아웃(Scale-out)한다. 반대로 데이터가 적어지면 브로커 개수를 줄여 스케일 인(Scale-in)한다.

- 카프카의 스케일 인아웃은 무중단 운영을 지원 -> 365일 24시간 데이터를 처리해야 하는 커머스나 은행 같은 비즈니스 모델에서도 안정적인 운영 가능

- 스케일 업: 컨슈머 자체의 처리량을 늘리는 것 

- 스케일 아웃: 컨슈머 수를 늘리는 것

 

[3] 영속성

- 영속성: 데이터를 생성한 프로그램이 종료되더라도 사라지지 않은 데이터의 특성

- 카프카와 다른 메시징 플랫폼의 차이점: 전송받은 데이터를 메모리에 저장하지 않고 파일 시스템에 저장

- 일반적으로 파일시스템은 느리지만 카프카는 OS 레벨에서 파일 시스템을 최대한 활용하기 위해 페이지 캐시(page cache) 영역을 메모리에 따로 생성하여 사용한다. -> 덕분에 파일 시스템에 저장하더라도 처리속도가 높다.

- 디스크 기반의 파일 시스템 덕분에 애플리케이션 장애 발생으로 급작스럽게 종료되더라도 프로세스를 재시작해 안전하게 다시 처리할 수 있다.

 

[4] 고가용성

- 카프카의 복제(replication) 기능을 통해 하나의 브로커에 장애가 발생하더라도 복제된 데이터가 나머지 브로커에 저장되어 있으므로 무중단으로 안전하고 지속적으로 데이터를 처리할 수 있다.

- 온프레미스나 퍼블릭 클라우드 환경에서도 데이터를 안전하게 복제할 수 있는 브로커 옵션들이 존재한다.

#3 빅데이터 아키텍처의 종류와 카프카의 미래

[1] 초기 빅데이터 플랫폼

- 엔드 투 엔드로 각 서비스 애플리케이션으로부터 데이터를 배치로 모았으나 배치로 모으는 구조는 유연하지 못했다.

- 실시간으로 생성되는 데이터들에 대한 인사이트를 빠르게 전달하지 못하는 단점이 있었다.

- 데이터 파편화로 인해 데이터 거버넌스(data governance: 데이터 표준 및 정책)를 지키기 어려웠다.

 

 

[2] 람다 아키텍처

- 3가지 레이어로 구성된다: 배치, 서빙, 스피드

- 배치 레이어: 배치 데이터를 모아 특정 시간, 타이밍마다 일괄 처리한다 - 하둡이 위치하는 곳

- 서빙 레이어: 가공된 데이터를 사용자, 서비스 애플리케이션이 사용할 수 있도록 저장된 공간

- 스피드 레이어: 서비스에서 생성되는 원천 데이터를 실시간으로 분석하는 용도 - 카프카가 위치하는 곳

- 한계:  배치 처리용 레이어와 실시간 처리용 레이어를 분리하면서 각 데이터를 분석, 처리하는 데에 필요한 로직이 2개 필요하고, 배포나 디버깅도 2번 발생하므로 융합 처리 시 다소 유연하지 못한 파이프라인을 생성해야 함

 

 

[3] 카파 아키텍처

- 람다 아키텍처의 단점인 로직의 파편화, 디버깅, 배포 등의 운영 분리에 대한 이슈를 제거하기 위해 배치 레이어 제거

- 스피드 레이어 즉 카프카에서 실시간, 배치 등 모든 데이터 처리하도록 구성

- 스피드 레이어 즉 카프카에서 스트림 데이터를 배치로 어떻게 처리할까? 

스트림 데이터를 배치 데이터 스냅샷과 변환 기록 로그를 활용해서 배치 데이터를 기반으로 스트림 데이터를 운영할 수 있다. 

➡️ 로그는 배치 데이터를 스트림으로 표현하기에 적합하다. -> 타음 스탬프 기록 필수

 

 배치 데이터와 스트림 데이터의 차이

배치 데이터 스트림 데이터
- 한정된 데이터 처리
- 대규모 배치 데이터를 위한 분산 처리 수행
- 분, 시간, 일 단위 처리를 위한 지연 발생
- 복잡한 키, 조인 수행
- 무한 데이터 처리
- 지속적으로 들어오는 데이터를 위한 분산 처리 수행
- 분 단위 이하 지연 발생
- 단순한 키 조인 수행

 

카프카에서 스트림 데이터를 배치로 사용하는 방법 = Materialized View

- 특정 데이터를 뷰 형태로 가져와서 사용. 

- 로그에 남겨진 시간을 기준으로 데이터를 처리하면 스트림으로 적재된 데이터도 배치로 처리할 수 있게 된다.

 

[4] 스트리밍 데이터 레이크

- 2020년 카파 아키텍처에서 서빙 레이어를 제거한 아키텍처인 스트리밍 데이터 레이크(Streaming Data Lake)가 제안됨

- 스피드 레이어로 사용되는 카프카에 큰 용량의 데이터를 오랜 기간 저장하고 사용할 수 있다면 서빙 레이어는 제거돼도 된다. 

 

But 아직은 카프카를 스트리밍 데이터 레이크로 운영할 수 없음.

자주 접근하지 않는 데이터를 비싼 자원인 메모리나 디스크가 아닌 저렴한 오브젝트 스토리지와 같은 안전한 저장소에 옮겨 저장하고, 자주 사용하는 데이터는 브로커에서 사용하는 구분 작업이 필요한데 아직 개발되지 않았다.

 


섹션 2: 카프카 기본 개념 

#1 카프카 브로커와 클러스터

[1]  카프카 브로커

- 카프카 클라이언트와 데이터를 주고받기 위해 사용하는 주체

- 데이터를 분산 저장하여 장애가 발생하더라도 안전하게 사용할 수 있도록 도와주는 애플리케이션 

- 하나의 서버에 하나의 브로커 프로세스 실행

- 데이터를 안전하게 보관, 처리하기 위해 3대 이상의 브로커 서버를 1대의 클러스터로 묶어서 운영

 

[2] 주키퍼

-카프카 2.0 버전까지는 주키퍼가 필수 / 3.0부터는 필수는 아니지만 완벽히 대체할 수 없으므로 여전히 대부분 함께 사용한다.

주키퍼 앙상블 1에 클러스터 3 매칭 -> 만약 1:1 매칭한다면 리소스가 낭비된다.

브로커의 역할

1) 컨트롤러: 다른 브로커들의 상태를 체크하고, 브로커가 클러스터에서 빠지는 경우 해당 프로커의 리더 파티션을 재분배한다.

클러스터의 다수의 브로커 중 한 대가 컨트롤러의 역할을 한다. 만약 컨트롤러 브로커에 장애 발생 시 빠르게 빼내고 다른 브로커가 컨트롤러의 역할을 한다.

 

2) 데이터 삭제: 카프카는 컨슈머가 데이터 가져가도 토픽의 데이터는 삭제되지 않는다. 또한 데이터 삭제 요청도 불가. 하지만 오직 브로커만이 데이터를 삭제할 수 있다. 데이터 삭제 단위는 '로그 세그먼트(파티션)' 단위(특정 데이터 선별 삭제 불가)

 

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

 

4) 그룹 코디네이터: 컨슈머 그룹 상태를 체크하고 파티션을 컨슈머와 매칭되도록 분배하는 역할

예: 컨슈머 2, 파티션 2가 일대일 매칭 상황에서 컨슈머 하나에 문제 발생 시 문제 발생한 컨슈머 삭제하고 다른 컨슈머에 파티션 둘 다 할당해 지속적인 처리를 돕는다 = Rebalance

 

5) 데이터 저장: 카프카 실행 시 config/server.properties의 log.dir 옵션에 정의한 디렉토리에 데이터를 저장. 토픽 이름과 파티션 번호의 조합으로 하위 디렉토리를 생성해 데이터를 저장한다.

 

hello.kafka 토픽의 0번 파티션에 저장된 데이터

[3] 로그와 세그먼트

아래 사진에서 각 로그 파일 이름은 각 파일에서 가장 처음 만들어지는 오프셋 번호와 같다.

오프셋 번호: 레코드를 구분할 수 있는 고유 번호

첫 번째: 0 ~ 9

두 번째: 10 ~ 19

세 번째: 20 ~ 

- log.segment.bytes: 바이트 단위의 최대 세그먼트 크기 지정. 기본 값은 1GB

- log.roll.ms(hours): 세그먼트가 신규 생성된 이후 다음 파일로 넘어가는 시간 주기. 기본값은 7일로 최대 세그먼트 크기에 도달하지 않더라도 7일이 지나면 다음 세그먼트로 넘어가기 때문에 세그먼트마다 크기가 다를 수 있다. 

- Active segment: 가장 마지막 세그먼트 파일. 쓰기가 일어나고 있는 파일로 브로커의 삭제 대상에 포함되지 않는다. 

 

[4] 세그먼트 삭제 

액티브 세그먼트가 아닌 세그먼트는 retention 옵션에 따라 삭제 대상으로 지정된다.

 

삭제 옵션 두 가지: delete, compact

1) cleanup.policy = delete

- retention.ms(minutes, hours): 세그먼트를 보유할 최대 기간. 일반적으로 3일 기본값은 7일

- retention.bytes: 파티션당 로그 적재 바이트 값. 기본값은 -1.

- log.retention.check.interval.ms: 세그먼트가 삭제 영역에 들어왔는지 확인하는 간격. 기본값은 5분. 위의 두 기준을 5분마다 확인해서 넘었으면 삭제

- 세그먼트 단위로 삭제하기 때문에 로그 단위(레코드 단위) 개별 수정, 삭제는 불가능하다.

➡️ 데이터를 적재할 때(프로듀서) 혹은 사용할 때(컨슈머) 데이터를 검증하는 것이 좋다.

 

2) cleanup.policy = compact

토픽 압축 정책: 메시지 키 별로 해당 메시지 키의 레코드 중 오래된 데이터를 삭제하는 정책 

최신키만 남겨둔다. 오래된 것은 삭제

삭제되는 데이터 오프셋: 10, 12, 13 

남아있는 오프셋: 11, 14, 15, 16

- 헤드 영역(클린 로그): 압축 정책에 의해 압축이 완료된 레코드들. 중복된 메시지 키가 없다. 

- 테일 영역(더티 로그): 압축 정책 적용 전 레코드들. 중복된 메시지 키가 있다.

- min.cleanable.dirty.ratio: 데이터 압축 시작 시점. 테일 영역의 레코드 개수와 헤드 영역의 레코드 개수의 비율

예시: ratio 값이 0.5라면 테일 영역의 레코드 개수가 헤드 영역의 레코드 개수와 동일할 때 압축 실행.

- ratio 가 너무 크다면? 만약 0.9라면 테일 영역이 9, 헤드 영역이 1일 때 압축을 실행하는데 한 번 압축할 때 데이터가 많이 줄어드므로 압축 효과가 좋다. 하지만 0.9가 될 때까지 용량을 차지하므로 용량 효율은 좋지 않음.

- ratio가 너무 작다면? 압축이 자주 일어나서 최신 데이터만 유지할 수 있고 브로커에게 부담이 된다.

 

[5] 브로커의 역할 - 복제(Replication)

- 복제는 카프카를 장애 허용 시스템(fault tolerant system)으로 동작하도록 하는 원동력

- 복제는 클러스터로 묶인 브로커 중 일부에 장애가 발생하더라도 데이터를 유실하지 않고 안전하게 사용하기 위함이다.

- 파티션 단위로 복제 발생

- 복제 개수(Replication factor): 리더 파티션을 포함한 복제 개수로 최솟값은 1(복제 없음), 최댓값은 브로커의 개수만큼 설정 가능

➡️ 복제 개수만큼의 저장 용량 증가하는 단점 but 복제는 가용성 확보에 필수적이므로 2 이상의 개수를 정하는 것이 중요

- 리더 파티션: 프로듀서/컨슈머와 직접 통신하는 파티션. 브로커에는 리더 파티션이 꼭 존재해야 한다. 

- 팔로워: 복제 데이터를 가지고 있는 파티션. 팔로워는 리더의 오프셋과 자신의 오프셋을 비교해 차이가 나는 경우 리더로부터 데이터를 가져와 저장한다.

 

리더 파티션을 가진 브로커에 장애가 발생한 경우

브로커가 다운되면 해당 브로커에 있는 리더 파티션은 사용할 수 없으므로 나머지 브로커에 있는 팔로워 파티션 중 하나가 리더 파티션 지위를 넘겨받는다 ➡️ 데이터가 유실되지 않고 프로듀서, 컨슈머와 지속적으로 주고받을 수 있음

 

데이터 종류마다 다른 복제 개수

데이터가 일부 유실 되어도 무관하고 데이터 처리 속도가 중요한 경우: 1 or 2

금융 정보와 같이 유실이 일어나면 안 되는 경우: 3

 

[6] ISR(In-Sync-Replicas)

리더 파티션의 오프셋 0~3이 팔로워 파티션에도 존재하는 모습 = ISR

ISR: 리더 파티션과 팔로워 파티션이 모두 싱크 즉 복제된 상태

 

- unclean.leader.election.enable

리더 파티션의 데이터를 모두 복제하지 못한 팔로워 파티션이 리더 파티션으로 선출되는 경우 ➡️ 데이터 유실 발생

이때, 유실이 발생하더라도 서비스 무중단으로 지속적으로 토픽을 사용하고 싶다면 해당 옵션을 사용해 설정할 수 있다.

unclean.leader.election.enable = true : 유실 감수. ISR 아닌 팔로워 파티션을 리더로 승급

unclean.leader.election.enable = false: 유실 감수 안 함. 해당 브로커가 복구될 때까지 중단

#2 토픽, 파티션, 레코드.

토픽: 카프카에서 데이터를 구분하기 위해 사용하는 단위. 한 개 이상의 파티션을 소유한다.

파티션: 큐(Queue)와 비슷한 구조로 Fisrt-In-First-Out으로 먼저 들어간 레코드를 컨슈머가 먼저 가져간다. 다만 가져가도 삭제되지 않음 ➡️ 토픽의 레코드는 다양한 목적을 가진 여러 컨슈머 그룹들이 여러 번 가져갈 수 있다.

레코드: 파티션에 저장되는 프로듀서가 보낸 데이터

 

토픽 생성 시 파티션이 배치되는 방법

파티션이 5개인 토픽을 생성할 경우 그림과 같이 0번 브로커부터 Round-Robin 방식으로 리더 파티션이 생성된다. 

카프카 클라이언트는 리더 파티션이 있는 브로커와 데이터를 주고받으므로 여러 브로커와 골고루 통신을 한다.

➡️ 데이터가 특정 서버(브로커)에 집중되는 hot spot 현상 방지 & 선형 확장을 통해 데이터 증가에 대응

 

특정 브로커에 리더 파티션이 쏠린 현상

- 리더 파티션이 집중되어 있는 0번 브로커에 CPU, RAM 과부하가 발생하므로 최대한 방지해야 한다.

- kafka-reassign-partition.sh 명령으로 파티션을 재분배할 수 있다.

 

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

- 파티션 한 개는 최대 한 개의 컨슈머를 가질 수 있다(여러 개의 컨슈머 매칭은 불가)

- 컨슈머는 여러 개의 파티션을 가질 수 있다.

- 컨슈머 처리량이 한정된 상황에서 많은 레코드를 병렬로 처리하는 가장 좋은 방법은 컨슈머의 개수를 늘리는 것(스케일 아웃)

 

예시: 컨슈머 처리량이 초당 1개 , 프로듀서가 전송하는 데이터는 초당 10개일 때 지연이 발생함 (컨슈머 렉)

➡️ 컨슈머와 파티션의 개수를 10개씩 늘려서 대응하기

 

이때, 파티션의 개수를 줄이는 것은 불가능하다.

만약 삭제가 가능하다면 여러 브로커에 저장된 데이터를 취합하고 정렬하는 복잡한 과정을 거쳐야 하므로 클러스터에 큰 영향을 준다.

그러므로 한번 늘리면 줄이는 것이 불가능하니 파티션을 늘리는 것은 신중히 정해야 한다.

#3 레코드 상세히 살펴보기

레코드: 타임스탬프, 헤더, 메시지 키, 메시지 값, 오프셋으로 구성된다.

 

[1] 타임스탬프

- 기본값은 ProducerRecord 생성 시간이 들어간다 / 브로커 적재 시간(LogAppendTime)으로 설정도 가능

 

[2] 오프셋

- 프로듀서가 생성한 레코드에는 존재하지 않고, 전송한 레코드가 브로커에 적재될 때 저장된다.

- 0부터 시작해서 1씩 증가

- 컨슈머는 오프셋을 기반으로 처리가 완료된 데이터 / 앞으로 처리할 데이터 구분한다.

- 각 메시지는 파티션별로 고유한 오프셋을 가지므로 컨슈머에서 중복 처리를 방지하기 위한 목적으로 사용한다.

 

[3] 헤더

- Key/Value 데이터를 추가할 수 있다.

- 레코드의 스키마 버전이나 포맷과 같이 데이터 프로세싱에 참고할 정보를 담는다.

 

[4] 메시지 키

- 처리하고자 하는 메시지 값의 분류를 위한 용도로 사용된다 = 파티셔닝

- 파티셔너(Partitioner)에 따라 토픽의 파티션 번호가 정해진다.

- 필수값이 아니며 지정하지 않으면 null로 설정된다 ➡️ 라운드 로빈으로 전달

- null이 아니라면 해쉬값에 의해 특정 파티션에 매핑되어 전달된다 ➡️ 순서 보장

 

[5] 메시지 값

- 실질적으로 데이터가 담기는 공간

- 다양한 타입으로 지정가능하며 필요에 따라 사용자 지정 포맷으로 직렬화/역직렬화 클래스를 만들어 사용할 수 있다.

- 컨슈머는 미리 역직렬화 포맷을 알고 있어야 한다.

#4 카프카 브로커와 클라이언트가 통신하는 방법

카프카 클라이언트는 통신하고자 하는 리더 파티션의 위치를 알기 위해 데이터를 주고받기 전에 메타데이터를 브로커로부터 전달받는다.

 

메타데이터 옵션

- metadata.max.age.ms: 메타데이터 강제로 리프래시 하는 간격 (기본 5분)

- metadata.max.idle.ms: 프로듀서가 유휴상태일 때 메타데이터를 캐시에 유지하는 기간 (기본 5분)

 

메타데이터에 이슈가 발생한 경우

만약 메타데이터가 현재의 파티션 상태에 맞게 리프래시 되지 않아서 잘못된 브로커로 데이터를 요청 시

➡️ LEADER_NOT_AVAILABLE 익셉션 발생: 클라이언트가 데이터를 요청한 브로커에 리더 파티션이 없는 경우

해결 방법: 메타데이터 리프래시 간격을 확인하고, 클라이언트가 정상적인 메타데이터를 가지고 있는지 확인해야 한다.

 

퀴즈

 

해설

[1] O / 삭제되지 않고 유지된다.

[2] X / 복제 단위는 토픽이 아닌 파티션 단위!

[3] X / Replication Factor = 1이면 리더 파티션만 존재한다. 즉 팔로워가 없는 경우도 있음

[4] X / 클라이언트는 리더 파티션과만 통신할 수 있다.

[5] X / 레코드에는 헤더도 포함된다. 

[6] 10, 14, 15, 16

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG
more
«   2024/09   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30
글 보관함