LINE DEVELOPER DAY 2021에서 김남일 님이 발표하신 KSETL로 Kafka 스트림 ETL 시스템을 빠르게 구성하기 세션 내용을 옮긴 글입니다.
안녕하세요. Messaging Data Engineering 실의 김남일입니다. 이번 글에서는 'KSETL로 빠르게 Kafka Stream ETL 개발하기'라는 주제로 말씀드리겠습니다.
KSETL(Kafka Stream ETL) 개발 배경
KSETL은 Kafka 스트림 ETL(extract, transform, load)의 약자로 Kafka의 데이터를 추출하고 변환해서 적재하는 시스템이라는 의미입니다. 스트림은 한없이 연속으로 생성되는 데이터 형태로 사용자 로그나 센서에서 수집하는 데이터가 대표적인 스트림 데이터입니다.
데이터를 스트림으로 처리하는 이유
데이터를 스트림 형태로 처리하는 이유는 데이터 처리 지연을 줄이기 위해서입니다. 특정 사업 분야에서는 데이터 처리 지연을 줄이는 것이 가장 기본적인 요구 사항입니다. 예를 들어 음식 배달 사업에서는 배달 상태와 주문 상태가 항상 최신이어야 하겠죠. 금융 거래에서 사기 거래를 판별하는 작업도 제때 판별하지 못하면 쓸모가 없습니다. 이와 같이 지연 시간을 줄여야 할 때 데이터를 스트림 형태로 처리합니다. 또한 성능을 위해서 스트림 형태로 처리하는 경우도 있습니다. 예를 들어 사용자에게 콘텐츠를 추천하는 경우 사용자의 최신 정보를 기반으로 하면 추천 성능이 더 좋아지기도 합니다.
스트림이 아닌 기존의 배치 처리 방식으로는 처리 지연 시간을 충분히 줄일 수 없었습니다. 배치 처리는 처리량을 늘리기 위해서 데이터를 모아 주기적으로 처리하는 방식입니다. 보통 처리 주기를 일 단위, 시간 단위, 분 단위로 설정하는데 이를 초 단위로 줄이기는 어려웠습니다. 이에 지연 시간을 줄이기 위한 스트림 처리 시스템을 별도로 설계하고 개발했습니다.
늘어나는 스트림 처리 시스템
여러 곳에서 처리 지연 시간을 줄이려는 요구가 많아지면서 점점 더 많은 스트림 처리 시스템이 필요해졌습니다. 스트림 처리 시스템을 개발하고 운영할 때에는 공통적으로 수행하게 되는 많은 작업이 있습니다. 먼저 프로그램을 개발하고 디버깅해야 하며, 배포 가능한 형태로 빌드해야 하고, 빌드 결과물을 장비에 배포한 뒤, 운영하면서 모니터링해야 합니다. 많은 데이터 엔지니어가 스트림 처리 시스템을 만들면서 비슷한 일을 반복하고 있습니다.
KSETL 개발 목표
KSETL은 이와 같은 반복 작업을 줄여 스트림 처리 시스템을 쉽게 개발할 수 있도록 만든 시스템입니다. LINE에서는 데이터 처리에 Kafka를 광범위하게 사용하고 있는데, Kafka 토픽을 입력과 출력으로 사용하면서 데이터 엔지니어가 직접 스트림 처리 시스템을 쉽게 만들 수 있게 만드는 것이 목표였습니다. 이에 두 가지 측면에서 노력을 기울였습니다. 첫 번째로 데이터 엔지니어가 ETL 로직을 쉽게 표현할 수 있도록 만들었고, 두 번째로 데이터 엔지니어가 시스템을 쉽게 개발하고 운영할 수 있도록 만들었습니다.
ETL 로직을 쉽게 표현할 있도록 SQL 엔진, ksqlDB 도입
데이터 엔지니어는 보통 프로그래밍 전문 지식이 없는 경우가 많지만, 대부분 SQL은 아주 잘 알고 있습니다. 이에 스트림을 처리하기 위한 SQL 엔진을 도입하기로 결정하고, ksqlDB와 Flink SQL, Spark Structured Streaming, 이 세 가지 엔진을 검토했습니다. 각 SQL 엔진으로 프로토타입을 만들면서 어떤 엔진이 적합한지, 우리가 필요한 기능을 모두 포함하고 있고, 또한 사용하기 쉬운지 검토했습니다. 최종적으로 ksqlDB를 선정했습니다.
ksqlDB는 우리가 필요한 대부분의 기능을 제공하고 있었고, 사용자 정의 함수를 지원해서 필요한 경우 직접 기능을 만들어 확장할 수도 있었습니다. 스트림 처리만을 위한 시스템이어서 구조가 비교적 단순해, 필요하면 직접 소스 코드를 분석해 문제를 해결할 수 있다는 장점도 있었습니다. 또한 ksqlDB는 Kafka만 있으면 작동하는데 LINE에 훌륭한 Kafka 지원 팀이 있다는 점도 ksqlDB를 선정하는 데 한몫했습니다.
ksqlDB가 어떻게 작동하는지 아래 그림과 함께 간단히 살펴보겠습니다. 두 개의 스트림을 조인하는 경우, 각 파티션별로 처리하고 내부 상태 로그는 내부 토픽으로 저장합니다. 내부 토픽은 필요에 때라 ksqlDB가 동적으로 생성합니다. 여기서 동적으로 생성된 내부 토픽이 운영하면서 문제를 일으켰는데 나중에 자세히 설명하겠습니다.
만들고 운영하기 쉬운 ODA 아키텍처
두 번째 목표인 시스템을 쉽게 만들고 운영하기를 어떻게 달성했는지 설명하겠습니다. 로직을 쉽게 표현했는데 시스템을 만들고 운영하기 어려우면 안 되겠죠. 이에 KSETL에서는 필요할 때마다 동적으로 ksqlDB 클러스터를 만들어 낼 수 있도록 설계해서 이를 'On-Demand Applications', 줄여서 'ODA'라고 부르고 있습니다. ODA에서는 로깅과 모니터링 기능을 기본으로 제공해서 데이터 엔지니어는 클러스터를 생성하고 쿼리를 실행하기만 하면 됩니다. 아래는 KSETL ODA의 간략한 구조입니다.
커다란 쿠버네티스 클러스터 하나에 여러 개의 ksqlDB 클러스터가 작동하고 있으며, 이들은 사내 공통 Kafka를 사용합니다. LINE에는 공통 인프라가 잘 갖춰져 있어서 비교적 쉽게 개발할 수 있었습니다(작업하면서 쿠버네티스 운영 팀과 Kafka 운영 팀의 도움을 많이 받았습니다). 생성한 ksqlDB 클러스터에서 쿼리를 수행하면 자동으로 로그가 수집돼 검색할 수 있습니다. 성능과 상황을 확인할 수 있는 대시보드도 기본으로 제공합니다. 이와 같은 로깅과 모니터링 관련 기본 기능들은 이미 LINE 사내 시스템에서 많이 제공하기 때문에 시스템 개발에 필요한 노력을 많이 절약할 수 있었습니다.
기존 스트림 처리 시스템 개발과 KSETL을 이용한 개발의 차이점을 요약하자면 다음 표와 같습니다.
기존 시스템이 Java와 Scala와 같은 일반적인 개발 언어를 사용한다면 KSETL은 데이터 처리 전용 언어인 SQL을 사용합니다. 프로그램을 빌드할 때 기존에는 컴파일러를 사용했다면 KESTL은 대화형 셸(shell)을 사용합니다. 빌드 결과물을 배포할 때 기존 시스템은 CI/CD 툴을 사용한다면 KSETL은 온디맨드(on-demand) 클러스터에 배포하는 방식을 사용합니다. 또한 모니터링할 때 기존 시스템은 전용 툴을 사용했다면 KSETL은 이미 만들어 놓은 대시보드를 자체 제공합니다.
KSETL 적용 사례 - A/B 테스트 리포트 시스템
LINE에서 KSETL을 적용해 시스템을 구축한 사례를 공유하겠습니다. LINE에서 A/B 테스트를 진행할 때 사용하는 A/B 테스트 리포트 시스템은 LINE 서버가 생성하는 요청 로그와 LINE 클라이언트가 생성하는 이벤트 로그를 처리하는 시스템입니다. 초당 최대 5만 건의 로그를 처리해야 하며, 특정 요청에 대한 클라이언트의 반응을 찾아서 통계 처리할 때 스트림 조인과 시간 구간별 취합 기능이 필요합니다.
기존 A/B 테스트 리포트 시스템
기존에는 스트림을 처리하기 위해서 꽤나 복잡한 과정을 거쳤습니다. 서로 키가 같은 이벤트 로그와 요청 로그를 찾기 위해 한 스트림을 Redis에 저장하면서 다른 스트림을 읽어 Redis를 검색하는 방식을 사용했습니다. 이때 두 스트림의 시간 차이를 감안해서 딜레이를 처리하기 위해 조인 윈도도 구현했습니다. 이와 같이 기존 시스템에서는 별도로 Redis를 운영하면서 딜레이도 처리해야 하는 등 로직이 복잡했기 때문에 데이터 엔지니어가 직접 처리하지 못했습니다. 전문 개발자가 프로그램으로 구현했습니다.
KSETL 적용 후 A/B 테스트 리포트 시스템
이런 복잡한 로직을 KSETL에서는 간단한 SQL 쿼리 하나로 구현할 수 있습니다. 아래 쿼리는 event
스트림과 request_log
스트림을 동일한 rowkey
에 대해서 JOIN
하는 쿼리입니다. Redis를 사용해서 복잡하게 처리해야 했던 JOIN 윈도 구문도 간단한 구문으로 처리할 수 있습니다.
시간 구간별 데이터 취합 또한 아주 간단하게 표현할 수 있습니다. 취합하려는 구간을 윈도로 선언하기만 하면 됩니다. 아래 예시에서는 1분 단위로 취합하도록 했고, 최대 10분까지 지연된 데이터도 포함하도록 설정했습니다. 기존에는 이와 같은 시간 구간별 취합을 프로그래밍 언어로 복잡하게 개발해야 했지만 KSETL에서는 SQL을 이용해서 비교적 단순하게 처리할 수 있습니다.
KESTL을 적용하면서 A/B 테스트 리포트 시스템은 아주 단순한 구조가 되었습니다. Redis가 없어지면서 Redis를 별도로 설정하고 운영하는 데 필요한 노력이 자연스럽게 없어졌고, 대화형 셸에서 개발하면서 빠른 개발과 빠른 릴리스가 가능해졌습니다. 또한 성능 대시보드와 로그를 기본 제공하기 때문에 릴리스 후 성능 대시보드를 통해 빠르게 모니터링하고 튜닝할 수 있게 됐고, 문제가 발생했을 때는 로그를 확인해 빠르게 문제를 파악하고 대응할 수 있게 됐습니다.
KSETL의 한계와 향후 과제
실제로 적용해 보면서 ksqlDB와 LINE 전사 Kafka에 의존하는 KSETL의 한계를 알 수 있었습니다.
우선 ksqlDB가 활발하게 개발되고는 있지만 아직 몇 가지 기능이 부족했기 때문에 A/B 테스트 리포트 시스템을 구현하면서 직접 구현한 부분이 있습니다. 앞서 SQL 엔진을 검토했을 때 Flink SQL이 후보에 있었다고 말씀드렸습니다. 당시에는 ksqlDB에 밀려 탈락했지만, 그 뒤로 새로운 기능이 많이 추가됐기 때문에 다시 검토해 볼 만하다고 생각합니다.
또 다른 한계는 전사 Kafka 시스템 의존성입니다. 현재 LINE 전사 Kafka를 관리하는 팀에서 관리 안정성을 높이기 위한 일환으로 동적 토픽 생성을 금지하고 있습니다. 하지만 앞서 말씀드린 것처럼 ksqlDB는 내부 토픽을 동적으로 생성하는 구조입니다. 따라서 사내 Kafka 시스템을 직접 이용할 수가 없어서 별도로 개발 환경을 만들어 해결해야 했습니다. 개발 단계에서는 별도 Kafka를 이용해서 동적 토픽을 생성하고 이후 시스템에 배포할 때 토픽을 생성하는 요청을 만들어 문제를 해결했지만, 현재 이 부분 때문에 개발 속도가 느려지고 있어서 향후 개발 속도를 높이기 위해서 반드시 해결해야 한다고 생각합니다.
그 외에도 해결해야 할 일이 몇 가지 남아 있습니다. 먼저 프로젝트를 진행하다 보니 Hive 테이블을 임포트하는 기능이 필요해졌습니다. 즉 Hive 테이블을 임포트해서 Kafka 토픽으로 만드는 기능입니다(이 기능은 세션 발표 후 구현해서 현재 사용하고 있으며, LINE Data platform용 워크플로 엔진(참고)을 사용해 개선하는 방안도 고려하고 있습니다). 두 번째로 ksqlDB 클러스터에 쿼리를 실행시키는 방법을 개선할 필요가 있습니다. 현재 대화형 쉘에서 쿼리 스크립트를 실행하는 방식으로 배포하고 있는데요. 사람이 대화형 쉘에서 쿼리를 직접 입력하는 것은 좋지 않은 것 같습니다. 휴먼 에러가 발생할 가능성이 높아 이 부분을 자동화하면 좋을 것 같습니다.
마치며
이번 글에서는 LINE Kafka 처리 시스템, KSETL을 개발하게 된 배경과 목표를 설명하고 실제 적용 사례를 공유했습니다. 또한 적용하면서 알게 된 KSETL의 한계와 향후 과제도 말씀드렸습니다. 이상으로 글을 마치겠습니다. 아래에서 발표 영상도 확인하실 수 있습니다. 긴 글 읽어주셔서 감사합니다.