LINE 광고의 대용량 스트리밍 파이프라인
스트리밍 처리의 기술 포인트
LINE 광고 플랫폼은 하루에도 수십억 건 이상의 광고를 송출합니다. 그 속에서 데이터 파이프라인은 실시간으로 광고 결과 데이터를 수집하고, 재가공하며, 저장하고, 재전송하는 등의 역할을 수행합니다. 대표적으로 실시간으로 이벤트 적합성 여부를 판단한 뒤 역시 실시간으로 머신러닝 알고리즘에 활용하는 사례가 있겠습니다. 이런 역할을 수행하기 위해 데이터 파이프라인, 특히 실시간 스트리밍에서는 다음과 같은 기술적인 문제를 해결해야 합니다.
- 하루에 수십억 건 이상, 초당 수십만 건 이상의 이벤트를 실시간으로 처리할 수 있어야 한다.
- 플랫폼 성장에 맞춰 더 많은 양, 더 많은 건수의 데이터 처리 요청에 대응할 수 있어야 한다.
- 데이터 지연 시간을 최소화하며 처리해야 한다.
- 장애 발생 시 자동으로 복구돼야 한다.
- 다운 타임이 없어야 한다.
대용량 스트리밍 파이프라인의 특징
대용량 스트리밍 파이프라인을 구현하기 위해서는 필연적으로 분산 시스템을 사용해야 합니다. 한 대 또는 몇 대의 정해진 서버 의 스케일 업만으로는 많은 데이터를 지연 없이 처리할 수 없기 때문인데요. 대용량 스트리밍 파이프라인을 분산 시스템으로 구현할 때에는 다음과 같은 어려움이 있습니다.
- 분산 시스템을 사용하면 필연적으로 전체 데이터의 순서 보장이 어려워진다.
- 분산 시스템은 구성 요소별로 성능의 차이가 있습니다. 구성 요소는 논리적/물리적 요구 사항에 따라 애플리케이션마다 달라진다.
- 2번의 이유로 트래픽이 늘었을 때 필요한 리소스를 산정해서 대응하기가 어렵다.
- 2번의 이유로 병목이나 부하, 장애 지점 등이 다양해진다.
- 4번의 이유로 실시간 처리 지연이나 시스템 장애에 대응하기가 어렵다.
- 실시간 데이터 처리 과정은 한 번 지나가버리면 똑같은 상황을 재연하기가 어렵다.
- 실시간 처리 로직에 상태 데이터가 필요하다면 위 문제들을 해결하기가 더욱 어렵다.
이번 글에서는 위 2번에서 7번까지에 해당하는 문제들이 LINE 광고 스트리밍 파이프라인에서 어떤 식으로 나타났고, 이를 해결하기 위해 어떤 고민을 했는지 공유하겠습니다. 참고로 LINE 광고 스트리밍 파이프라인에는 위 1번에 해당하는 순서 보장과 관련된 요구 사항은 없었기에 이와 관련된 내용은 이 글에서 다루지 않습니다.
LINE 광고 스트리밍 파이프라인의 특징
이번 글에서 다룰 시스템의 특징을 간단히 살펴보겠습니다. 현재 LINE 광고 플랫폼에서는 크게 아래와 같은 구조로 실시간 데이터 파이프라인을 구성했습니다.
위 데이터 파이프라인의 작동 흐름을 간단히 설명하면 다음과 같습니다.
- SDK(LINE 앱, 웹 등)에서 발생한 이벤트를 이벤트 수신기(event receiver)를 통해 받아서 HTTP 관련 처리를 한 뒤 Apache Kafka로 최대한 빠르게 전송
- 전처리기(preprocessor)에서 실시간으로 데이터 변환 및 데이터 유효성 검사 로직 수행
- 후처리기(postprocessor)에서 데이터 조인 또는 집계해서 다른 스토리지에 저장
위 파이프라인 구조에서 이번 글에서 주로 다룰 시스템은 전처리기입니다. 전처리기는 Apache Heron(이하 Heron) 프레임워크로 구현했으며, 소스와 목적지(destination)로는 Apache Kafka(이하 Kafka)를 사용했습니다.
Kafka는 '생산자(producer)-소비자(consumer)' 패턴을 기반으로 하는 오픈소스 메시지 브로커입니다. LINE 광고 시스템에서는 사용자가 광고를 보거나 클릭하는 등 특정한 행위가 발생했을 때 이 행위 관련 데이터를 '발행-구독(pub-sub)' 구조로 전달하는 동시에 짧은 기간 동안 보관하기 위해 Kafka를 사용합니다.
전처리기는 실시간으로 이벤트 데이터를 파싱하고, 이벤트의 적합성 여부를 판단해서 값을 일부 변환한 뒤 이후 파이프라인으로 전송하는 역할을 담당하는 파이프라인입니다. 주요 특징은 아래와 같습니다.
- 데이터 파싱 과정에서 소스의 데이터 하나가 여러 개의 이벤트로 변환될 수 있다.
- 이벤트 유효성 판단으로는 중복 제거와 부정적 경로로 생성된 데이터 판별, 과금형 이벤트 판별 등이 있다.
- 유효성을 판단하기 위한 상태(state) 정보를 생성한다.
- 예를 들면 지난 한 시간 동안 동일한 이벤트 ID로 들어온 데이터는 중복 처리한다는 규칙으로 지난 한 시간 동안의 이벤트 ID를 저장하고 사용한다. 이 때문에 지금까지 처리한 데이터에 따른 상태 데이터가 생성되며, 이 상태 데이터를 저장하기 위해 Redis를 클러스터로 구성해서 사용한다.
위와 같은 역할과 특징을 고려해 전처리기를 Heron 프레임워크를 이용한 애플리케이션으로 구현했습니다. Heron은 논리적/물리적 처리 단위를 토폴로지 형태로 구성한다는 특징이 있습니다. 기능이나 역할 또는 물리적 구성 등을 고려해 시스템을 일정한 묶음으로 구성하는 것입니다.
그런데 애플리케이션을 Heron 토폴로지로 구현하면 다음과 같은 요소들이 시스템의 복잡성을 높입니다.
- Heron의 메시지 처리 시스템(Delivery Semantics, Stream Manager, 패키징 전략 등)의 복잡성
- 토폴로지를 구성하는 각 컴포넌트(Kafka Partition, Spout, Bolt 등)의 병렬성과 셔플(shuffle) 전략에 따른 상관 관계 등
- 위와 같은 요소로 구성된 시스템을 따라 흐르는 데이터가 대용량이라는 점
이 때문에 분산 시스템인 Heron으로 구성된 애플리케이션은 익히 알고 있는 애플리케이션 검증 방법인 단위 테스트나 목(mock) 데이터를 이용한 검증 등의 방법으로는 실제 처리량이나 성능, 데이터 오차율 등을 검증하기 어렵습니다.
LINE 광고 스트리밍 파이프라인이 직면한 과제와 문제점
다음으로 당면한 요구 사항을 하나씩 살펴보면서 앞서 설명한 배경을 바탕으로 LINE 광고 스트리밍 파이프라인이 직면한 문제를 짚어보겠습니다.
몇 배나 더 많은 데이터를 실시간으로 처리할 수 있는가?
LINE Corporation과 Yahoo Japan Corporation이 통합되면서 비즈니스 관점에서 광고에 더욱 많은 요청이 들어올 가능성이 있었습니다. 이에 대비해 시스템이 어디까지 감당할 수 있는지 미리 파악할 필요가 있었고, 대비책도 준비해야 했습니다. 이미 기존에도 하루에 수십억 건의 실시간 데이터를 처리하고 있었지만 향후 두 배, 세 배의 트래픽을 지연 없이 처리 가능한지, 가능하다면 인프라 증설 및 확장 비용이 얼마나 필요할지 추정할 수 있어야 했습니다.
가장 간단히 해결할 수 있는 방법은 현재 구성에서 받을 수 있는 최대 초당 처리량(TPS)을 계산한 뒤, 두 배로 인프라를 늘리려면 비용이 얼마나 드는지 계산하는 것인데요. 이 방법에는 두 가지 전제가 숨어 있습니다.
- 전제 1: 현재 구성이 성능/비용 측면에서 가장 효율적인 구성이다.
- 전제 2: 현재 구성 그대로 산술적으로 스케일아웃하면 수용 가능한 트래픽 또한 그에 따라 정비례로 늘어난다.
따라서 구체적으로 개발 계획을 세우기에 앞서 이 두 가지 전제가 맞는지 검토할 필요가 있었고, 이 검토는 실제 작동할 애플리케이션에 대한 실증이 뒷받침돼야 했습니다. 앞서 언급한 대용량 스트리밍 파이프라인의 특징과 LINE 광고 스트리밍 시스템의 특징 때문에 시스템의 복잡도가 높아서 이론적으로 계산하는 것에는 한계가 있기 때문입니다.
먼저 전제 1의 경우 그동안의 개발 히스토리를 살펴보니 정확하게 성능/비용의 효율을 측정한 기록이 없었습니다. 기존에는 운영하면서 트래픽이 증가해 병목이나 처리 지연이 발생하면 일부 지표 데이터만을 기준으로 개발자의 직관으로 스케일아웃하고 있었습니다. 또한 Heron 토폴로지 구성에서 각 컴포넌트별 리소스(코어, 메모리 등) 할당도 Heron 이전 Spring 기준으로 산정한 리소스를 그대로 이어받아온 상태였고, 분산 시스템으로 구성함에 따라 달라지는 부분에 대한 계산이 돼 있지 않았습니다.
다음으로 전제 2는 다음과 같은 경우에는 틀린 전제가 될 수 있었습니다.
- Heron 토폴로지 구성과 패키징 전략에 따라 각 컴포넌트 간 메시지 전달을 관리하는 StreamManager로 인한 병목 발생
- 상태 데이터를 Redis에 저장하는 과정에서 I/O 병목 발생
- 데이터양에 따라 JVM GC 성능의 차이 발생
따라서 시스템을 구성하기에 앞서 어느 지점에서 위와 같은 병목이나 성능 저하가 발생할 수 있는지 파악해야 트래픽이 몇 배 늘었을 때 어떤 부분에 어느 정도로 인프라나 설정을 변경해야 하는지 계산할 수 있었습니다.
위 내용을 종합해 보면 얼마나 많은 실시간 데이터를 지연 없이 처리할 수 있는지 정확한 값을 도출하기 위해서는 다음과 같은 성능/비용 검증 과정이 필요합니다.
- 실제 환경에서 유입되는 데이터와 똑같은 데이터로 처리할 것
- 토폴로지 구성이 논리적/물리적으로 효율적인지 검토할 것
- 토폴로지의 각 구성 요소별로 각 구성 요소를 구성할 수 있는 최소 단위의 리소스를 파악할 것
- 위와 같은 구성에서 각 구성 요소별 최대 성능(TPS)을 파악할 것
- 각 구성 요소와 인접한 구성 요소를 병렬로 구성할 때 토폴로지 전체의 처리량 산출할 것
- 각 구성 요소부터 전체 토폴로지에 이르기까지 구성 가능한 최대 병렬 구성에서의 최대 처리량 산출할 것
- 위 모든 과정을 처리할 때 Heron Stream Manager나 상태 저장, JVM GC 등으로 인해 병목이 발생하는지, 발생한다면 어느 지점에서 발생하는지 확인한 후 해결하기 위한 설정 또는 인프라 구성을 찾을 것
위 내용을 검증하기 위해서는 애플리케이션을 다양한 물리적 단위로 구성해서 실행하면서 실제 데이터를 흘려보내 프로덕션 환경에 응용할 수 있는 값을 도출해야 했습니다. 변인 통제를 잘 유지하면서 반복적으로 실험해야 했는데요. 이와 같은 검증 방식을 구현하기 위해 Tekton을 이용한 컨테이너 기반의 워크플로를 구성해서 활용했습니다(이와 관련해서는 2편에서 보다 자세히 살펴보겠습니다).
새로운 기술을 도입하기 전에 장애 시나리오 검증이 가능한가?
기존에 사용하고 있던 Apache Heron 대신 Apache Flink로 스트리밍 시스템을 변경하는 것을 검증할 필요가 생겼습니다. 다음과 같은 이유 때문이었습니다.
Apache 재단에서 기존에 사용하고 있던 Heron을 Apache incubating 단계에서 Apache Attic(유지 보수 불가, 스냅숏(snapshot)만 유지)으로 바꿨고, 이에 따라 장기적으로 Heron 프레임워크 사용에 기술적 제약이 생김
Flink는 분산 토폴로지에서 자체적으로 인 메모리(in-memory) 또는 로컬 디스크 기반의 상태를 구성할 수 있고, 이것이 Delivery Semantics와 함께 관리돼 성능과 비용, 관리 측면에서 이점이 큼
LINE 광고 플랫폼은 이름에서 볼 수 있듯 기업의 매출 및 수익과 직결되는 사업으로 시스템 장애에 민감할 수밖에 없습니다. 따라서 실시간 스트리밍 애플리케이션도 장애나 지연 발생하지 않도록 구성해야 하는데요. 앞서 언급한 대용량 분산 처리 시스템의 특성과 실제 데이터로 발생하는 상태 저장, 레거시 로직 때문에 TDD(test-driven development)와 같은 방법으로 모든 것을 코드로 관리할 수 없고 장애 상황을 재연하기 도 힘듭니다.
따라서 서비스에 도입하기 전에 실제 발생 가능한 장애 상황을 미리 실제 데이터와 함께 경험해 보고, 각 경우에 발생 가능한 다운타임과 지연 시간, 실제 데이터와의 오차율 등을 측정하면 실제 서비스에 도입하기에 적합한지 검증할 수 있습니다.
장애 상황에 따른 시스템 수준을 파악하는 데에는 카오스 테스트 방법을 사용했습니다. 카오스 테스트는 시스템의 내결함성(fault-tolerance)을 검증하기 위해 시스템에 의도적으로 장애를 발생시키는 테스트 기법입니다. 예를 들어 네트워크가 일시적으로 끊기는 상황이나 의도치 않게 일부 시스템이 다운되는 상황 등을 인위적으로 발생시켜 전체 시스템의 장애 복구 능력을 테스트해 봅니다. 이와 같은 방법으로 예상치 못한 장애가 발생했을 때 시스템의 복구 능력이 얼마나 되는지를 측정하며, 이를 통해 시스템의 안정성을 향상시킵니다.
통합 테스트를 위한 워크플로
데이터 스트리밍 애플리케이션의 성능을 측정할 때에는 통합 테스트(integration test) 환경을 구축한 뒤 직접 애플리케이션을 통해 데이터를 흘려보내서 테스트하는 것이 가장 효율적입니다. 외부 데이터베이스나 JAVA의 GC 같은 애플리케이션의 제어 아래에 있지 않는 요인에 영향을 받을 수 있기 때문입니다. 카오스 테스트 역시 실제로 구동 중인 애플리케이션에 장애를 의도적으로 발생시켜야 하기 때문에 통합 테스트 환경이 필수입니다.
따라서 통합 테스트 환경을 준비해야 했는데요. 통합 테스트를 진행하기에 앞서 해결해야 하는 여러 어려움이 있었습니다.
많은 준비 과정
전형적인 데이터 스트리밍 애플리케이션 의 구조는 흘려보내야 할 원본 데이터가 존재하는 '소스 DB(source database)', 소스 DB에서 데이터를 읽어와 가공하는 '데이터 스트리밍 애플리케이션', 애플리케이션이 가공한 데이터를 최종 저장한 '타깃 DB(target database)'로 구성돼 있습니다. 애플리케이션의 성능을 검증하기 위해서는 이 셋 모두 사전 준비가 필요하며, 당연히 실제 서비스에는 영향이 가지 않는 독립적인 환경으로 준비해야만 합니다.
데이터 스트리밍 애플리케이션의 성능 검증이 어려운 이유는 이런 구조에 기인합니다. 데이터 스트리밍 애플리케이션은 지속적으로 데이터를 수신하고, 처리하고, 송신하는 것을 주 목적으로 하는 애플리케이션입니다. 따라서 성능을 제대로 측정하기 위해서는 데이터가 지속적으로 흐르는 환경을 만들어야 합니다. 즉, 성능을 검증하려면 소스 DB를 준비해서 해당 DB에 데이터를 지속적으로 흘려보내야 하며, 최종적으로 애플리케이션이 송신할 데이터를 받아줄 타깃 DB 또한 준비해야 합니다.
성능 검증을 위한 반복
이와 같이 많은 것을 준비해야 하는 준비 과정만큼이나 걸림돌이 된 부분은 이를 반복해야 한다는 점입니다. 통합 테스트를 한 번 실행하기 위해서는 독립적인 통합 테스트 환경을 준비한 뒤, 테스트를 진행하고, 테스트 결과를 정리하는 과정을 거쳐야 하며, 이 과정을 성능 결과가 좋아질 때까지 몇 번이나 반복해야 합니다. 게다가 이 작업을 직접 손으로 반복해야 한다면 공수가 무척 많이 들 것입니다.
쿠버네티스 네이티브 워크플로가 필요한 이유
여러 가지 설정을 통해 반복 작업을 간편하게 실행하고 관리할 수 있게 해주는 쿠버네티스 네이티브 워크플로가 필요한 이유는 바로 위와 같은 어려움에 기인합니다. Tekton과 Litmus가 바로 이런 어려움을 해결해 줄 수 있는 쿠버네티스 네이티브 시스템인데요. 2편(Tekton)과 3편(Litmus)에서 하나씩 자세히 살펴보겠습니다. 많은 기대 부탁드립니다.