[CS 논문 리뷰] Kafka: a Distributed Messaging System for Log Processing
1) 배경
- 크기가 있는 인터넷 회사라면, 어떤 회사든 많은 양의 '로그' 데이터를 생성합니다.
이 데이터는 일반적으로
(1) 로그인, 페이지뷰, 클릭, 라이크, 공유, 댓글, 검색 쿼리 등과 관련된 유저 활동
(2) 서비스 콜 스택, 콜 latency, 에러, 그리고 CPU, 메모리, 네트워크 혹은 디스크 활용과 관련 있는 운영 metric
을 포함합니다.
- 로그 데이터는 오랫동안 사용자 engagement, 시스템 활용 그리고 다른 메트릭을 트래킹하는 분석 도구로서
사용되었습니다. 그러나 최근의 인터넷 어플리케이션의 트렌드는 활동 데이터를 생산 데이터 파이프라인의 일부로
만들어서 사이트 기능에 직접 사용하는 것입니다.
- 이러한 용도로는
(1) 검색 관련성
(2) 활동 스트림에서 항목 인기나 동시 발생에 의해 추천되는 기능
(3) 광고 타겟팅 및 보고
(4) 스팸이나 불법 데이터 스크래핑과 같은 악용 행위로부터 보호하는 보안 애플리케이션
(5) 사용자 상태 업데이트나 "친구"나 "연결"이 볼 수 있는 뉴스피드 기능
이 포함됩니다.
- 로그 데이터의 이러한 생산적이고 실시간 사용은 데이터 시스템에 새로운 도전 과제를 만들어냅니다.
이 데이터의 양은 종종 "실제" 데이터보다 수십 배나 큽니다.
검색, 추천, 광고 등은 종종 사용자 클릭에 대한 세부적인 클릭들을 계산해야 하며,
이는 매일 수집되는 클릭 로그 기록뿐만 아니라 페이지에 있는 클릭되지 않은 수십 개의 항목에 대한
로그 기록도 생성합니다.
- 예를 들어, China Mobile은 매일 5~8TB의 전화 통화 기록을 수집하고,
Facebook은 거의 6TB의 다양한 사용자 활동 이벤트를 수집합니다.
이러한 종류의 데이터를 처리하기 위한 초기 시스템은 분석을 위해 프로덕션 서버에서
로그파일을 물리적으로 긁어내는 방식에 의존했습니다.
최근 몇 년 동안 Facebook의 Scribe, Yahoo의 Data Highway, Cloudera의 Flume 등
여러 전문화된 분산 로그 수집기가 구축되었습니다.
- 이 시스템들은 주로 로그 데이터를 데이터 웨어하우스 또는 Hadoop에 로드하여
오프라인으로 소비할 수 있도록 설계되었습니다.
LinkedIn에서는 전통적인 오프라인 분석 외에도 지연시간이 몇 초 이하인
실시간 애플리케이션을 지원해야 한다는 것을 알게되었습니다.
- 우리는 전통적인 로그 수집기와 메시징 시스템의 장점을 결합한 Kafka라는 새로운 메시징 시스템을 구축했습니다.
한편으로 Kafka는 분산 및 확장 가능하며 높은 처리량을 제공합니다.
다른 한편으로 Kafka는 메시징 시스템과 유사한 API를 제공하여 애플리케이션이 실시간으로 로그 이벤트를
소비할 수 있도록 합니다.
Kafka는 오픈 소스로 공개되었으며, LinkedIn에서 6개월 이상 프로덕션에서 성공적으로 사용되고 있습니다.
이로 인해 모든 유형의로그 데이터를 온라인 및 오프라인으로 소비할 수 있는 단일 소프트웨어를
활용할 수 있어 인프라가 크게 간소화되었습니다.
- 이 논문의 나머지 부분은 다음과 같이 구성되어 있습니다.
2장에서는 전통적인 메시징 시스템과 로그 수집기를 다시 살펴봅니다.
3장에서는 Kafka의 아키텍처와 주요 설계 원칙을 설명합니다.
4장에서는 LinkedIn에서의 Kafka 배포를 설명하고, 5장에서는 Kafka의 성능 결과를 제시합니다.
6장에서는 향후 작업과 결론을 논의합니다.
2) 관련 연구
- 전통적인 엔터프라이즈 메시징 시스템은 오랫동안 존재해왔으며,
비동기 데이터 흐름을 처리하는 이벤트 버스로 중요한 역할을 종종 수행합니다.
그러나 이러한 시스템들이 로그 처리에 적합하지 않은 몇 가지 이유가 있습니다.
- 첫째, 엔터프라이즈 시스템이 제공하는 기능과의 불일치가 있습니다.
이러한 시스템들은 종종 다양한 전송 보증 기능을 제공하는데 중점을 둡니다.
예를 들어, IBM Websphere MQ는애플리케이션이 여러 큐에 원자적으로 메시지를 삽입할 수 있는
트랜잭션 지원 기능을 갖추고 있습니다.
JMS 사양은 각 개별 메시지가 순서에 상관없이 소비 후에 확인될 수 있도록 합니다.
이러한 전송 보증은 로그 데이터를 수집하는데 과도한 경우가 많습니다.
예를 들어, 몇 개의 페이지뷰 이벤트가 가끔 손실되는 것은 큰 문제가 아닙니다.
이러한 불필요한 기능들은 API와 해당 시스템의 기본 구현을 복잡하게 만듭니다.
- 둘째, 많은시스템들이 처리량을 주된 설계 제약 조건으로 강하게 고려하지 않습니다.
예를 들어, JMS는 프로듀서가 여러 메시지를 단일 요청으로 명시적으로 배치할 수 있는 API를
제공하지 않습니다.
이는 각 메시지가 전체 TCP/IP 왕복을 요구하게 되어, 우리의 도메인에서 요구되는 처리량에 적합하지 않습니다.
- 셋째, 이러한 시스템들은 분산 지원이 약합니다.
메시지를 여러 기계에 분할 저장하는 쉬운 방법이 없습니다.
- 마지막으로, 많은 메시징 시스템들은 메시지의 즉각적인 소비를 가정하므로,
소비되지 않은 메시지의 큐가 항상 상당히 작습니다.
데이터 웨어하우징 애플리케이션과 같이 주기적으로 대량 로드를 수행하는
오프라인 소비자에게 메시지가 축적되는 경우, 성능이 크게 저하됩니다.
- 최근 몇 년 동안 여러 전문화된 로그 수집기가 개발되었습니다.
페이스북은 Scribe라는 시스템을 사용합니다.
각 프론트엔드 머신은 소켓을 통해 로그 데이터를 Scribe 머신 세트로 전송할 수 있습니다.
각 Scribe 머신은 로그 항목을 집계하고 주기적으로 HDFS 또는 NFS 장치에 덤프합니다.
야후의 데이터 하이웨이 프로젝트도 유사한 데이터 흐름을 가지고 있습니다.
일련의 머신들이 클라이언트로부터 이벤트를 집계하고 "분 단위" 파일로 롤아웃한 후 HDFS에 추가합니다.
- Flume은 Cloudera에서 개발한 비교적 새로운 로그 수집기입니다.
확장 가능한 "파이프"와 "싱크"를 지원하여 스트리밍 로그 데이터를 매우 유연하게 만듭니다.
또한, 보다 통합된 분산 지원 기능을 가지고 있습니다.
그러나 대부분의 이러한 시스템들은 오프라인으로 로그데이터를 소비하도록 설계되었으며,
종종 소비자에게 구현 세부사항을 불필요하게 노출합니다. (ex) "분 단위 파일")
추가적으로 대부분의 시스템은 브로커가 데이터를 소비자에게 전달하는 "푸시" 모델을 사용합니다.
- LinkedIn에서는 각 소비자가 자신이 감당할수 있는 최대속도로 메시지를 검색할 수 있고,
처리할 수 있는 속도보다 빠르게 메시지가 전달되는 것을 피할 수 있는 "풀" 모델이
우리 애플리케이션에 더 적합하다고 생각합니다.
풀 모델은 소비자를 되감는 것도 쉽게 만들어주며, 이 혜택에 대해서는 3.2절 끝에서 논의합니다
최근에 야후 리서치에서 새로운 분산 pub/sub 시스템은 HedWig를 개발했습니다
HedWig는 매우 확장 가능하고 가용성이 높으며, 강력한 내구성 보장을 제공합니다.
그러나 주로 데이터 스토어의 커밋 로그를 저장하는 데 사용됩니다.
3) Kafka 아키텍처와 설계 원칙
- 기존 시스템의 한계 때문에, 우리는 새로운 메시징 기반 로그 수집기인 Kafka를 개발했습니다.
Kafka의 기본 개념을 소개하겠습니다.
특정 유형의 메시지 스트림은 토픽으로 정의됩니다.
프로듀서는 토픽에 메시지를 게시할수 있습니다.
게시된 메시지는 브로커라고 불리는 일련의 서버에 저장됩니다.
소비자는 브로커로부터 하나 이상의 토픽을 구독하고, 브로커로부터 데이터를 끌어와서
구독한 메시지를 소비할 수 있습니다.
- 메시징은 개념적으로 간단하며, 우리는 Kafka API를 이 개념을 반영하여 동일하게 단순하게 만들기 위해
노력했습니다. 정확한 API를 보여주는 대신, API가 어떻게 사용되는지 보여주기 위해 몇 가지 샘플 코드를 제공합니다.
아래는 프로듀서의 샘플 코드입니다.
메시지는 단순히 바이트의 페이로드를 포함하도록 정의됩니다
사용자는 메시지를 인코딩하기 위해 선호하는 직렬화 방법을 선택할 수 있습니다.
효율성을 위해, 프로듀서는 단일 게시 요청으로 여러 메시지를 보낼 수 있습니다.
Sample producer code:
producer = new Producer(…);
message = new Message(“test message str”.getBytes());
set = new MessageSet(message);
producer.send(“topic1”, set)
.
- 토픽을 구독하려면, 소비자는 먼저 해당 토픽에 대한 하나 이상의 메시지 스트림을 생성해야 합니다.
그 토픽에 게시된 메시지는 이러한 하위 스트림에 고르게 분배됩니다.
Kafka가 메시지를 분배하는 방법에 대한 자세한 내용은 3.2절에 설명되어 있습니다.
각 메시지 스트림은 생성되는 메시지의 연속적인 스트림을 순회할 수 있는 반복자 인터페이스를 제공합니다.
소비자는 스트림의 모든 메시지를 반복하여 메시지의 페이로드를 처리합니다.
- 전통적인 반복자와 달리, 메시지 스트림 반복자는 절대 종료되지 않습니다.
현재 소비할 메시지가 더 이상 없으면, 반복자는 새로운 메시지가 토픽에 게시될 때까지 블록됩니다.
우리는 단일 복사본의 모든 메시지를 여러 소비자가 공동으로 소비하는 포인트 투 포인트 전달 모델과,
여러 소비자가 각각 토픽의 고유한 복사본을 수신하는 게시/구독 모델을 모두 지원합니다.
- Kafka의 전체 아키텍처는 그림 1에 나와 있습니다.
Kafka는 분산형 구조이기 때문에, Kafka 클러스터는 일반적으로 여러 브로커로 구성됩니다.
부하를 균형 있게 분산하기 위해, 하나의 토픽은 여러 파티션으로 나뉘고 각 브로커는 하나 이상의 파티션을 저장합니다.
- 여러 프로듀서와 소비자가 동시에 메시지를 게시하고 검색할 수 있습니다.
3.1절에서는 브로커의 단일 파티션 레이아웃과 파티션 액세스를 효율적으로 만들기 위해 선택한
몇 가지 설계 선택사항을 설명합니다.
3.2절에서는 분산 환경에서 프로듀서와 소비자가 여러 브로커와 상호 작용하는 방법을 설명합니다.
3.3절에서는 Kafka의 전달 보장에 대해 논의합니다.
3.1 단일 파티션에서의 효율성
- Kafka는 시스템을 효율적으로 만들기 위해 몇 가지 결정을 내렸습니다.
(1) 간단한 저장소
- Kafka는 매우 간단한 저장소 레이아웃을 가지고 있습니다.
토픽의 각 파티션은 논리적 로그에 해당합니다.
물리적으로, 로그는 대략 동일한 크기(예: 1GB)의 세그먼트 파일 집합으로 구현됩니다.
프로듀서가 파티션에 메시지를 게시할 때마다, 브로커는 단순히 메시지를 마지막 세그먼트 파일에 추가합니다.
더 나은 성능을 위해, 세그먼트 파일은 설정 가능한 수의 메시지가 게시된 후
또는 일정 시간이 경과한 후에만 디스크에 플러시됩니다.
메시지는 플러시된 후에만 소비자에게 노출됩니다.
- 일반적인 메시징 시스템과 달리, Kafka에 저장된 메시지에는 명시적인 메시지 ID가 없습니다.
대신 각 메시지는 로그에서 논리적 오프셋으로 주소 지정됩니다.
이는 메시지 ID를 실제 메시지 위치에 매핑하는 보조적인,
탐색 집약적인 랜덤 액세스 인덱스 구조를 유지하는 오버헤드를 피합니다.
우리의 메시지 ID는 증가하지만 연속적이지는 않습니다.
다음 메시지의 ID를 계산하려면 현재 메시지의 길이를 ID에 더해야 합니다.
앞으로는 메시지 ID와 오프셋을 상호 교환적으로 사용할 것입니다.
- 소비자는 항상 특정 파티션에서 메시지를 순차적으로 소비합니다.
소비자가 특정 메시지 오프셋을 확인하면 해당 파티션의 그 오프셋 이전의 모든 메시지를 수신했음을 의미합니다.
내부적으로 소비자는 브로커에게 데이터를 소비할 애플리케이션을 위해 버퍼를 준비하도록
비동기적 풀 요청을 보냅니다.
각 풀 요청에는 소비가 시작되는 메시지의 오프셋과 가져올 허용 가능한 바이트 수가 포함됩니다.
각 브로커는 오프셋의 정렬된 목록을 메모리에 유지하며,
여기에는 각 세그먼트 파일의 첫 번째 메시지의 오프셋이 포함됩니다.
브로커는 요청된 메시지가 있는 세그먼트 파일을 오프셋 목록을 검색하여 찾아내고,
데이터를 소비자에게 다시 보냅니다.
- 소비자가 메시지를 수신한 후, 다음 소비할 메시지의 오프셋을 계산하여 다음 풀 요청에 사용합니다.
Kafka 로그와 메모리 내 인덱스의 레이아웃은 그림 2에 나타나 있습니다.
각 상자는 메시지의 오프셋을 보여줍니다.
(2) 효율적인 전송
- 우리는 Kafka에서 데이터를 주고 받는 데 매우 신중합니다.
이전에 언급한 바와 같이, 프로듀서는 단일 전송 요청으로 여러 메시지를 제출할 수 있습니다.
소비자 API는 한 번에 하나의 메시지를 반복 처리하지만, 내부적으로 소비자의 각 풀 요청은
수백 킬로바이트의 크기까지 여러 메시지를 가져옵니다.
- 또한, 우리는 Kafka 레이어에서 메시지를 명시적으로 캐싱하지 않는 비전통적인 선택을 했습니다.
대신, 기본 파일 시스템 페이지 캐시를 사용합니다.
이렇게 함으로써 이중 버퍼링을 피할 수 있으며, 메시지는 페이지 캐시에만 캐시됩니다.
이는 브로커 프로세스가 재시작될 때에도 따뜻한 캐시를 유지할 수 있다는 추가적인 이점이 있습니다.
Kafka는 프로세스 내에서 메시지를 전혀 캐싱하지 않기 때문에
메모리의 가비지 컬렉션 오버헤드가 거의 없어서 VM 기반 언어로 효율적으로 구현할 수 있습니다.
- 마지막으로 ,프로듀서와 소비자가 모두 순차적으로 세그먼트 파일에 액세스하기 때문에,
소비자가 종종 프로듀서보다 약간 뒤쳐지는 경우가 많아 일반적인 운영 체제 캐싱 휴리스틱
(특히 쓰기-스루 캐싱 및 읽기-선행)이 매우 효과적입니다.