티스토리 뷰
도입
하.....
아파치 카프카는 분산 스트리밍 플랫폼이시다. 그게 무슨 뜻인고...
스트리밍 플랫폼이라 하면 흔히들 아래의 세 기능을 갖는 것을 생각한다지.
- 레코드 스트림을 구독하고 퍼블리싱 할 수 있는 것. 이건 메시지 큐나 엔터프라이즈 메시징 시스템과 유사하다 볼 수 있다.
- 장애 내결함성을 같는 레코드 스트림 저장 기능.
- 발생한 순서대로 레코드 스트림을 처리할 수 있는 기능.
크게 두 부류의 애플리케이션에 이용된다.
- 시스템간 혹은 애플리케이션간 데이터 수신이 보장되는 실시간 스트리밍 데이터 파이프라인 구축시
- 스트림 데이터를 변형하거나 데이터에 반응하는 실시간 스트리밍 애플리케이션 구축 시
- 카프카는 한 대 이상의 서버에서 클러스터로 구동된다.
- 카프카 클러스터는 레코드 스트림을 분류하여 저장하는데 이 분류는 토픽이라 불린다.
- 각각의 레코드는 키와 값, 시간표로 구성된다.
- Producer API, 이것은 애플리케이션이 한 개 이상의 카프카 토픽에게 레코드 스트림을 발행(publish)하는데 사용된다.
- Consumer API, 이것은 애플리케이션이 한 개 이상의 카프카 토픽을 구독(subscription)하고 그들에게 온 레코드 스트림을 처리할 때 사용된다.
- Streams API, 이것은 애플리케이션이 stream processor로서 동작하게 할 때 사용되는데, 하나 이상의 토픽으로부터의 입력을 소비(consumption)하고 하나 이상의 출력 토픽에 출력 스트림을 생성할 때 사용한다(동시에 데이터 쓰고 읽고 한다는 뜻 같다).
- Connector API, 이것은 기존하는 애플리케이션이나 데이터 시스템에 카프카 토픽을 연결하는 재사용 가능한 생산자(producer)와 소비자(consumer)를 구축하고 구동시키는 데 사용된다.
토픽이란, 분류 혹은 피드(feed) 이름이며 레코드가 발행되는 곳이다. 카프카의 토픽은 언제나 여러 구독자를 갖게 되며, 이는, 토픽이 자신에게 쓰여진 데이터를 구독하는 소비자를 0개, 1개 혹은 N개를 소유함을 뜻한다.
각각에 토픽에 대해, 카프카 클러스트는 로그의 분할 저장 방식(파티션)을 유지한다. 이는 아래와 같은 모양새다.
각각의 파티션은 정렬되어 있으며, 불변하는, 데이터가 끝에 추가되는 레코드열(구조화된 커밋 로그)이다. 파티션 안의 레코드는 각각 순차 식별자(sequential id number)를 할당받는데 이를 오프셋이라 칭한다. 오프셋은 파티션에서 레코드를 구분하는 유일한 값이다.
카프카 클러스터는 발행된 레코드들을 다른 소비자에 의해 이미 소비되었건 말건 상관없이 모두 계속 보유한다 - 물론 보유 기간을 설정할 수 있다. 예를 들어, 만일 보유 정책이 2일로 설정되었다면, 데이터가 발행되고 2일 동안은 소비가능상태가 된다. 그 이후가 되면 여유 공간을 확보하기 위해 폐기된다. 카프카의 성능은 데이터 사이즈에 무관하게 일정하기에 데이터를 장기간 저장하고 있다 해도 문제가 되지 않는다.
사실, 각 소비자별로 보유하고 있는 메타데이터는 오프셋 혹은 그 로그에서 해당 소비자의 위치가 유일하다. 이 오프셋은 소비자에 의해 제어된다: 일반적으로 소비자가 자신의 오프셋을 데이터를 읽어감에 따라 선형적으로 증가시킨다. 다만, 이렇기에 소비자가 오프셋을 제어하는 것이다보니 소비자는 실제로는 어떤 순서로든 레코드를 소비할 수 있다. 예를 들어 한 소비자가 과거의 데이터를 처음부터 다시 처리하기 위해서나 근처의 몇 메시지를 스킵하고 최신 레코드를 바로 받아 소비를 시작하고자 할때 기존의 오프셋을 리셋할 수 있다.
이러한 특징의 조합이 카프카 소비자가 매우 저렴하단 것(자유롭다)을 내포한다. 소비자들은 다른 소비자나 클러스터와의 충돌 없이 멋대로 왔다갔다 할 수 있기 때문이다. 예로, 당신은 우리의 명령줄 도구를 기존에 붙어 있던 소비자들이 무엇을 소비했는지 신경쓰지 않고 토픽의 가장 마지막을 추적(tail 명령 같은)할 수 있다.
이 로그 내의 파티션은 몇몇 목적에 공헌한다(도움이 된다). 첫째, 단일 서버에 적합한 크기를 뛰어 넘어 로그를 확장(scale)할 수 있다. 각 개별 파티션은 반드시 그 파티션을 호스팅하는 서버에 알맞아야(fit) 한다. 그러나 토픽은 많은 파티션을 가질 수 있기에 추가적인 양의 데이터를 더 다룰 수 있는 거다. 둘째로, 각각은 병행 수행되는 작업의 한 단위이다.
분산
로그의 파티션은 각각의 서버를 다루는 데 필요한 데이터를 갖고 카프카 클러스터 내의 여러 서버들에 걸쳐서 분배되고 파티션간 공유를 위한 요청을 보낸다. 각각의 파티션은 장애 내결함성을 갖추기 위해 설정된 서버의 수 만큼 복제된다.
각 파티션은 한 '리더'로서 동작하는 서버를 가지며, 0개 이상의 '팔로워'로서 동작하는 서버들을 갖는다. 리더는 그 파티션에 대한 모든 읽기와 쓰기 요청을 처리하고 동시에 팔로워는 수동적으로 리더의 데이터를 복제해간다. 만약 리더가 무너지면(fail), 팔로워 중 하나가 자동적으로 새로운 리더가 된다. 각 서버는 몇 일부 파티션에 대해선 리더로서 동작하고 그 외 나머지 파티션에 대해선 팔로워로서 동작한다. 이런 식으로 부하는 클러스터 안에서 균등하게 유지된다.
생산자
생산자는 그들이 선택한 토픽에 데이터를 발행한다. 생산자는 고른 토픽 내에 어떤 파티션에 레코드를 할당할지를 결정해야만 한다. 이것은 아주 단순하게는 부하를 분산시킬 요량으로 라운드 로빈 방식으로 수행되거나 구체적인 파티션 함수(레코드의 키를 기반으로 한다고 하자)에 따라 수행될 수 있다. 파티셔닝에 사용에 대한 자세한 건 조금 있다가 살펴보자.
소비자
소비자는 그들 스스로를 '소바자 그룹 명'으로 묶는다. 그리고 한 토픽으로 발행된 각각의 레코드들은 각 구독 소비자 그룹 내에 있는 한 소비자 인스턴스에게 전달된다. 소비자 인스턴스들은 각각 구분된 프로세스일 수도 있고 서로다른 구분된 머신에 있을 수도 있다.
만약 모든 소비자 인스턴스가 같은 소비자 그룹에 속한다면, 레코드는 그 소비자 인스턴스들에 거쳐 효과적으로 부하분산될 것이다.
만약 모든 소비자 인스턴스가 모두 다른 소비자 그룹에 속한다면, 레코드는 모든 소비자 프로세스에 전파(broadcast)될 것이다.
두 대의 서버로 클러스터링 된 카프카 클러스터와 4개의 파티션(P0~P1)이 있고 두 개의 소비자 그룹이 있다. 소비자 그룹 A는 2 대의 소비자 인스턴스를 가지며 그룹 B는 4대다.
헌데 알아낸 바에 따르면 토픽은 흔히 적은 수의 소비자 그룹과 대응된다..(?) 각각은 "(논리 구독자)logical subscriber"다. 각각의 그룹은 다수의 소비자 인스턴스로 구성되며 이는 확장성과 내결함성을 갖추기 위해서다. 이건 일반적인 발행-구독 구조 그 이상도 아니다. 흔한 싱글 프로세스 대신 소비자측에서 클러스터링을 하는 것일 뿐.
소비의 방식은 카프카에서 소비자 인스턴스를 거쳐 로그 내의 파티션을 나누는 것으로 구현된다. 이렇게 함으로써 각각의 인스턴스는 특정 시점의 파티션의 "공정 공유(fair share)에 대한 독점적 소비자가 된다. 그룹 내에서 멤버십(뜬금없이 멤버십??)을 유지하는 프로세스는 카프카 프로토콜에 의해 동적으로 조절된다. 만약 새 인스턴스들이 그룹에 추가된다면 다른 그룹 멤버로부터 일부 파티션을 인수받을 것이다. 인스턴스가 죽는다면, 파티션은 남은 인스턴스들에 재분배된다.
카프카는 파티션 내부에 레코드들의 전체 순서만을 제공하는데 한 토픽 내에서 서로 다른 파티션끼리는 아니다. 키(key)로 데이터를 분할(partition)해서 나누는 파티션에서 각 파티션에 한해서만 제공되는 정렬(Per-partition ordering)은 대부분의 애플리케이션에서 충분한 기능이다. 그러나 당신이 레코드들 간의 전체 순서(정렬)를 원한다면, 이것은 단 하나의 파티션만을 가진 토픽에서만 가능하다. 이는 그러나 단지 단 한 개의 소비자만이 소비자 그룹에 있음을 뜻한다.
보장
고수준의 카프카는 뒤따르는 내용을 보장한다.
- 특정 토픽 파티션으로 전송된 메시지들은 보내진 순서대로 들어온다. 이는, 만일 M1 레코드와 M2 레코드가 동일한 생산자에 의해 발행될 때, M1이 먼저 전송된다면, M1이 M2 보다 더 낮은 오프셋 값을 갖게 되며 로그의 앞단에 나타난다는 것을 뜻한다.
- 소비자 인스턴스는 로그에 저장된 순서대로 레코드를 읽게 된다.
- 복제 인자 N인 토픽에 대해, 최대 N-1 회까지 서버 결함을 로그에 커밋된 레코드의 어떠한 손실 없이도 버틸 수 있다.
더 자세한 보장에 대한 상세는 문서의 설계 섹션에서 상술되어 있다.
메시징 시스템으로서의 카프카
어떻게 카프카의 스트림의 개념이 전통적인 엔트프라이즈 메시지 시스템과 견주어질 수 있는가...
메시징은 전통적으로 두 개의 모델이 존재한다. '큐잉'과 '발행-구독'. 큐에선, 소비자 풀이 서버에서 데이터를 읽을 수 있고, 각각의 레코드는 그들 중 한 소비자에게 전달된다. 발행-구독에서는 레코드는 모든 소비자에게 전파(broadcast)된다. 이 두 모델 각각은 각자의 강점과 약점을 지닌다. 큐잉의 강점은 다수의 소비자 인스턴스들에 데이터 처리를 분산시킬 수 있다는 점이고 이는 처리능력의 확장(scale)이 용이하다. 그러나 뷰는 다수 동시 구독하는 것이 불가능하다(한번 한 소비자가 데이터를 읽어 처리하면 데이턴 사라져 다른 소비자가 볼 수 없다). 발행-구독에서는 다수의 프로세스들에게 데이터를 전파할 수 있지만 모든 메시지가 모든 구독자에게 전파되기 때문에 처리량의 확장 방법이 전무하다.
카프카의 소비자 그룹 개념은 이러한 두 개념을 일반화한다. 큐로서 소비자 그룹은 프로세스의 집합군에 걸쳐 처리 부하를 나눌 수 있다(멤버들이 소비자 그룹이면). 발행-구독으로선, 카프카에서 다수 소비자 그룹에게 메시지를 전파하는 기능을 제공한다.
이러한 카프카 모델의 강점은 모든 토픽이 이러한 두 속성을 갖는다는 것으로 결론내릴 수 있다. 처리능력을 확장(scale)할 수 있으며 또한 다수 동시 구독도 가능하다. 여기서 반드시 둘 중 하나를 선택해야 할 필요는 없다.
또한 카프카는 기타 여하 전통적인 메시징 시스템보다 순서 보장에 특화되어 있다.
전통적인 큐는 서버에 레코드를 순서에 맞춰 저장한다, 그리고 복수의 소비자가 큐로부터 소비를 하였을 경우, 서버는 메시지를 저장된 순서대로 전달해 준다. 그러나 비록 서버가 레코드들을 순서대로 꺼내준다고 해도, 레코드는 소비자들에게 비동기적으로 전달된다. 그렇기에 서로 다른 소비자에 대해선 순서가 벗어날 수 있다. 이것이 뜻하는 바는 병행 소비가 일어날 경우 레코드의 순서는 보장되지 않는다는 것이다. 메시징 시스템은 종종 "독점적 소비(exclusive consumer)"란 개념을 적용함으로써 이를 회피한다. 이 독점적 소비는 큐에서 단 한개의 소비 처리만이 진행되도록 하는 것으로 다만 처리에는 병행성을 잃는다.
카프카는 허나 더 좋은 방식을 취한다. 토픽 내에서 병행성의 개념을 가져가면서도(파티션), 카프카는 순서 보장과 부하 분산이 소비자 풀 처리기들에 골고루 일어나도록 한다. 이것은 토픽 내에 있는 파티션들에 소비자 그룹에 속한 소비자들을 할당함으로서 실현될 수 있다. 따라서 각각의 파티션은 정확히 대응된 그룹 내의 한 소비자에 의해서만 소비된다. 이렇게 함으로써 카프카는 한 소비자가 파티션을 읽는 유일한 존재이며 데이터를 순서를 보장하며 소비할 수 있도록 책임진다. 많은 파티션이 존재하므로 이 방식도 적절히 부하를 많은 소비자 인스턴스들에 분산시킬 수 있게 된다. 그러나 소비자 그룹 내에 파티션보다 더 많은 소비자 인스턴스가 있을 수는 없음을 명심하라.
저장 시스템으로서의 카프카
소비하는 측과 분리(구분)시켜 놓은 메시지를 발행하는 메시지 큐류(類)는 종종 활동중인(날아다니고 있는) 메시지에 대한 저장소 시스템처럼 동작한다. 카프카에서 다른 것은 카프칸 더 좋은 저장 시스템이란 것...(자찬?)
카프카에 쓰인 데이터는 디스크에 쓰이고 또 내결함성을 위해 복제된다. 카프카는 생산자(producer)가 확인 신호(acknowledgment)를 기다리도록 한다. 따라서 서버가 쓰기 작업에 실패한다 하더라도 데이터를 지키기 위해(유실 방지) 쓰기 작업은 복제까지 완전히 이뤄지기 전까진 완전히 수행된 것으로 간주되지 않는다.
카프카가 사용하는 디스크 구조는 확장이 용이하다. 카프카는 당신이 50KB나 50TB를 서버에 사용하든 말든 동일한 성능을 낸다.
진정으로 저장개념을 취하는 것과 클라이언트가 그들의 읽기 위치를 제어할 수 있다는 점을 토대로 생각하면, 카프가를 특수목적의 고속(고성능)이며, 낮은 지연을 갖는 로그 저장소, 복제소 전파소와 같은 것을 위하여 설계된 분산 파일 시스템으로 생각할 수 있게 된다.
카프카의 커밋 로그 혹은 복제기능 설계에 대해 살펴보려면 이 페이지를 참고하길.
스트림 처리를 위한 카프카
데이터 스트림을 읽고, 쓰고 저장하는 것만으론 충분치 않다. 목적은 실시간 스트림 처리까지이다.
카프카 스트림 프로세서는 입력 토픽으로부터 연속적인 데이터 스트림을 받는 것이면 무엇이든 될 수 있고, 입력에 대해 어떤 처리를 진행하고 출력 토픽으로 연속적인 데이터 스트림을 발행한다.
예를 들자면, 한 도소매 애플리케이션은 입력 스트림으로부터 판매와 운송 정보를 받아들이며, 받아들인 데이터를 기반으로 계산된 물가 변동이나 재주문 정보를 출력 스트림으로 내보낼 수 있다.
생산자 그리고 소비자 API를 직접 사용하면 이러한 기능을 간단히 구현할 수 있다. 하지만 더 복잡한 변환을 위해 카프카는 완전히 통합된 스트림 API를 제공한다. 이것을 사용하면 스트림을 조인한다거나 스트림의 집계를 계산한다거나 하는 사소하지 않은 일들도 처리할 수 있다.
이러한 재능은 이런 부류의 애플리케이션이 직면하는 어려운 문제를 해결하는데 도움을 줄거야. 가령 순서가 벗어난 데이터를 다룬다거나, 코드가 변화해서 입력을 다시 처리해야 한다거나 상태의존적인(?;stateful) 계산을 수행해야 한다거나 같은 일을.
이 스트림 API는 카프카가 제공하는 핵심 기반을 지탱해. 입력에 대해 생산자 소비자 API를 사용하며, stateful 저장소를 위해 카프카 기능을 사용하고 같은 그룹 메카니즘을 스트림 프로세서 인스턴스 사이에 내결함성을 확보하기 위해 적용한다.
조각을 합쳐보자
이러한 메시징, 저장소, 스트림 데이터 처리의 조합은 이상해 보일 수도 있다. 허나 카프카가 스트리밍 플랫폼으로서 동작하기에는 필수적이다.
HDFS와 같은 분산 파일 시스템은 배치 처리를 위해 정적 파일을 저장한다. 이러한 시스템은 확실히 과거부터 이력 데이터를 저장하고 처리한다(그래서 카프카가 안 이상한 거라고 말하고 싶은건가??).
전통적인 엔터프라이즈 메시지 시스템은 소비한 직후 받게 되는 다음(추후;?future) 메시지를 처리하는 식이다. 이러한 처리 방식을 내장한 애플리케이션은 메시지가 도착하는 대로 다음 데이터(future data)를 처리한다.
카프카는 이러한 두 기능을 합친 것으로 이 조합은 카프카가 스트리밍 애플리케이션을 위한 플랫폼으로서 동작하기 위한 것뿐만 아니라 스트리밍 데이터 파이프라인으로도 동작하기 위한 핵심이다.
저장 기능과 저지연 구독을 통합하면서 스트리밍 애플리케이션은 과거와 미래의 데이터를 같은 방식으로 다룰 수 있게 됐다. 즉, 이력으로 기록되고 저장된 데이터를 단일 애플리케이션이 처리할 수 있지만, 레코드의 마지막에 다다랐을 때, 처리를 미래 데이터가 도착하는 대로 처리하도록 유지(유보)할 수 있다. 이는 메시지-기반 애플리케이션뿐만 아니라 배치 처리를 포함하는 스트림 처리의 일반적인 개념이다.
마찬가지로 스트리밍 데이터 파이프라인을 위한 실시간 이벤트의 구독의 통합은 카프카에서 파이프라인이 매우 낮은 지연을 갖는 것을 가능하게 한다. 그러나 데이터 저장 능력은 확실하게 데이터의 전달이 반드시 보장되어야 하는 데이터를 위해 사용될 수도 있고, 운영를 위해서 일정 기간 동안 시스템이 내려거나 정기적으로만 데이터를 적재하는 오프라인 시스템의 통합을 위해서 사용될 수 있다. 이 스트림 처리 용이성은 데이터가 도착하는대로 변환하는 것을 가능케 한다.
보장에 대한 상세나 api, 그리고 기타 기능들에 대해 알고자 한다면 이젠 문서를 보라.