Apache Spark에서 수백만 개의 활성 게임 디바이스 세션을 추적하고, 1초 미만의 대기 시간으로 실시간 하트비트를 생성하는 상태 저장(stateful) 스트리밍 파이프라인을 구축해 보세요.
작성자: Neha Prabhu , Murali Talluri
게임 업계에서는 1밀리초가 매우 중요합니다. 게임 내 개인화를 구현하고, 추천 엔진을 구동하며, 동적 콘텐츠 예약 결정을 내리려면 플랫폼이 전 세계 수백만 명의 플레이어 세션 데이터를 1초 미만의 대기 시간으로 처리할 수 있어야 합니다.
오늘날 이러한 초저대기 시간 요구사항을 충족하기 위해 더 이상 여러 엔진이 혼재된 복잡한 아키텍처를 사용할 필요가 없습니다. 이 블로그에서는 Apache Spark Real-Time Mode의 실제 구현 사례를 살펴봅니다. 복잡한 상태 저장(stateful) 로직을 위한 새로운 transformWithState 연산자를 활용하여 Spark가 어떻게 엔드투엔드 밀리초 단위 성능을 제공하는지 보여드립니다. 익숙한 Structured Streaming 에코시스템을 사용하여 팀이 개발 속도를 높이고 미션 크리티컬한 운영 애플리케이션을 구축하는 방법을 알아보세요.
게임 플랫폼의 경우, 어떤 기기가 얼마나 오랫동안 활성화되어 있는지 파악하는 것은 단순한 인프라의 문제가 아니라 비즈니스를 움직이는 핵심 요소입니다. 실시간 세션 데이터는 수백만 대의 콘솔과 PC에서 개인화된 게임 내 경험을 제공하고, 추천 엔진을 구동하며, 콘텐츠 예약 결정을 내리고, 기기 상태 신호를 제공합니다. 운영 팀은 이를 사용하여 자녀 보호 기능을 적용하고 비정상적인 세션 패턴을 감지합니다.
콘솔과 PC 모두에서 발생하는 세션 이벤트는 Kafka 토픽으로 유입됩니다. 각 이벤트에는 디바이스 ID와 세션 ID가 포함됩니다. 디바이스 ID는 콘솔 또는 PC를 식별하고, 세션 ID는 게임 세션을 식별합니다. 각 디바이스에서는 한 번에 하나의 세션만 활성화될 수 있습니다.
이 파이프라인은 네 가지 시나리오를 처리합니다:
마이크로 배치(micro-batch) 모드의 Spark Structured Streaming은 상태 저장 세션화(stateful sessionization)를 처리할 수 있지만, 입력 처리와 타이머 기반 출력 모두에서 1초 미만의 정밀도가 필요한 사용 사례에서는 마이크로 배치의 한계가 드러납니다. 과거에는 이러한 격차 때문에 개발 팀이 별도의 특화된 엔진을 추가로 관리하거나 맞춤형 솔루션을 구축해야 했습니다.
Apache Flink를 사용하는 경우: 상태 관리 및 타이머를 구현할 수 있지만, Flink를 도입한다는 것은 Databricks 플랫폼과 함께 별도의 클러스터, 상태 백엔드, 배포 모델, 모니터링 스택, 코드베이스 등 완전히 병렬적인 에코시스템을 도입해야 함을 의미합니다. 그 결과 인프라가 파편화되고 운영이 복잡해지며, 두 번째 스트리밍 엔진을 운영하고 인력을 배치하는 비용이 발생합니다.
자체 맞춤형 솔루션을 사용하는 경우: 일부 팀은 자체 세션화 서비스를 구축합니다. 예를 들어, 각 디바이스가 세션 상태, 타이머, 하트비트 전송을 관리하는 액터를 갖는 Akka 기반 액터 시스템이 있습니다. 이러한 방식은 Flink와 동일한 인프라 및 운영 오버헤드를 수반할 뿐만 아니라, 확장성이 없다는 추가적인 문제가 있습니다. 노드 전체에 수백만 개의 상태 저장 액터를 분산시키는 것은 직접 설계해야 합니다. 이러한 시스템은 처음에는 작동하지만, 시간이 지나면서 유지 관리 모드에 갇히게 됩니다. 실행하기에는 안정적이지만 쉽게 확장할 수는 없습니다.
오늘날 Real-Time Mode는 고객을 위해 이러한 격차를 해소하 여, 팀이 이미 사용하고 있는 것과 동일한 Spark API를 통해 단일 통합 엔진에서 1초 미만의 정밀도를 제공합니다.
transformWithState는 복잡한 상태 저장 처리를 유연하고 확장 가능하게 만드는 Spark Structured Streaming의 차세대 연산자입니다. 주요 기능으로는 객체 지향 상태 관리, 복합 데이터 유형, 타이머 기반 로직, 자동 TTL 지원, 스키마 진화 등이 있습니다. Real-Time Mode와 결합하여 입력 처리와 타이머 기반 출력 모두에서 1초 미만의 정밀도를 제공합니다.
게임 세션화 사용 사례는 다음 두 가지를 요구합니다:
transformWithState는 두 개의 전용 메서드가 있는 단일 StatefulProcessor 클래스에서 이 두 가지를 모두 제공합니다.
handleInputRows()는 들어오는 Kafka 이벤트에 반응하여, 이벤트가 도착할 때 세션 시작 및 세션 종료를 처리하고 세션화 상태를 유지합니다.
handleExpiredTimer()는 그 사이에 일어나는 모든 일을 처리합니다. 즉, 새로운 데이터의 도착 여부와 관계없이 작동하여 하트비트 및 시간 초과와 같은 능동형 출력을 생성합니다.

아키텍처, 코드 구현 및 프로덕션 고려 사항에 대한 자세한 내용은 이 관련 블로그를 참조하세요. 여기서는 StatefulProcessor 코드, 타이머 수명 주기, 상태 관리 패턴, StreamingQueryListener를 사용한 모니터링을 자세히 설명합니다. 다음 결과는 파이프라인의 처리량 및 대기 시간 특성을 보 여주며, 마이크로 배치 모드(MBM)와 Real-Time Mode(RTM) 간의 상당한 대기 시간 차이를 강조합니다.
실제 부하 환경에서 파이프라인을 검증하기 위해 다음과 같이 지속적인 처리량으로 테스트를 진행했습니다.
지표 (분당) | 값 |
입력 이벤트 (세션 시작 + 종료) | ~500K |
활성 세션 수 | ~4M |
전송된 하트비트 레코드 수 | ~8M |
입력 대비 출력 증폭 | ~16배 |
대부분의 출력은 들어오는 데이터에 의해 트리거되지 않으며, 일정에 따라 하트비트를 능동적으로 보내는 handleExpiredTimer()에 의해 전적으로 생성됩니다.
대기 시간은 Kafka 입력 토픽 타임스탬프부터 출력 토픽 타임스탬프까지 엔드투엔드로 측정됩니다. Real-Time Mode를 사용하면 파이프라인이 432ms의 p99 대기 시간을 달성하며, 이는 마이크로 배치 모드보다 20배 빠른 속도입니다.

게이밍 세션화와 같은 사용 사례에는 들어오는 이벤트를 처리하는 것을 넘어, 일정에 따라 선제적으로 하트비트를 전송하고 수백만 개의 동시 세 션을 추적하며 상태를 효율적으로 관리하는 파이프라인이 필요합니다. 이 패턴은 게임에만 국한되지 않습니다. IoT 하트비트, 세션 추적, 실시간 알림, 장비 모니터링 등 타이머 기반 출력이 필요한 모든 워크로드를 동일한 방식으로 구축할 수 있습니다.
transformWithState의 타이머를 사용하면 이것이 가능해집니다. 단일 StatefulProcessor 클래스가 반응형 입력 처리와 선제적 타이머 기반 출력을 포함한 전체 세션 수명 주기를 처리합니다. Real-Time Mode와 결합하면 입력 레코드가 처리되고 타이머가 1초 미만의 정밀도로 작동합니다. 다음 배치 간격이 아니라 바로 지금 실행됩니다. 이 모든 것이 두 번째 엔진 없이 Databricks 내에서 이루어집니다.
이미 마이크로 배치 모드로 Structured Streaming 파이프라인을 실행 중이고 더 낮은 대기 시간을 달성하기 위해 두 번째 엔진을 도입하려는 경우, Real-Time Mode를 먼저 사용해 보세요. 트리거 하나만 변경하면 전환할 수 있으며, 코드를 재작성하거나 플랫폼을 변경할 필요가 없습니다:
직접 시도해 보세요:
이제 Real-Time Mode를 정식 버전(GA)으로 사용할 수 있습니다.
(이 글은 AI의 도움을 받아 번역되었습니다. 원문이 궁금하시다면 여기를 클릭해 주세요)
블로그를 구독하고 최신 게시물을 이메일로 받아보세요.