Tech
15만 개 발전소 데이터를 다루는 법
2025년 12월 3일
15만 개 발전소 데이터를 다루는 법
- 온프레미스 대규모 데이터 처리 아키텍처 구축기 -
저희 팀은 최근 전국 15만 개소 이상의 중소규모 재생에너지 발전소로부터 실시간으로 데이터를 수집하고, 이를 기반으로 발전량을 예측하고 모니터링하는 시스템을 온프레미스(On-premises) 환경에 성공적으로 구축했습니다. 클라우드 환경이 대세인 요즘, 고객사의 보안 정책과 데이터 소유권 문제로 인해 온프레미스 환경에 대규모 시스템을 설계하는 것은 저희에게도 큰 과제였습니다.
해결해야 할 과제들
프로젝트의 목표는 안정성과 확장성 두 가지로 압축되었습니다.
안정적인 데이터 수집
15만 개의 RTU(Remote Terminal Unit)는 LoRa, NB-IoT, HTTPS 등 서로 다른 통신망과 프로토콜을 사용합니다. 이를 단일 시스템으로 통합하여 데이터 유실 없이 수집해야 했습니다.
실시간 처리와 스케일 아웃
수집된 데이터는 즉시 처리되어야 하며, 향후 발전소 증가에 유연하게 대응할 수 있도록 모든 컴포넌트는 수평 확장(Scale-out)이 가능한 구조로 설계되어야 했습니다.
복합 분석 기능
단순 수집을 넘어, 외부 기상 데이터와 결합한 발전량 예측 및 실측치 비교를 통한 이상 감지 기능이 요구되었습니다.
보안 및 고가용성(HA)
외부 공격 방어는 물론, 일부 서버 장애 시에도 서비스 연속성을 보장하는 고가용성 아키텍처가 필수였습니다.
기술 스택 선정 - 왜 이 기술들을 선택했을까?
리액티브 스택을 선택한 이유
대규모 IoT 데이터 처리를 앞두고 가장 먼저 고민한 것은 동시성 처리 방식이었습니다. 전통적인 서블릿 기반의 Spring MVC도 충분히 검증된 기술이지만, 15만 개소에서 동시에 쏟아지는 연결을 처리하기에는 스레드 모델의 한계가 명확해 보였습니다.
그래서 저희는 Spring WebFlux를 선택했습니다. 비동기-논블로킹(Asynchronous Non-blocking) 방식으로 동작하는 WebFlux는 적은 수의 스레드로도 대량의 동시 연결을 처리할 수 있었습니다.

WebFlux를 선택한 결정적인 이유는 세 가지였습니다.
이벤트 루프 방식으로 수만 개의 연결을 동시에 처리할 수 있는 높은 동시성.
Reactor의 Flux/Mono를 통해 데이터 생산자와 소비자 간 속도 차이를 자동으로 조절하는 백프레셔(Backpressure) 지원.
스레드 기반 모델보다 더 적은 리소스로 동작하는 메모리 효율성이었습니다.
Kafka를 통한 이벤트 처리
Apache Kafka를 도입한 것은 이번 프로젝트에서 가장 중요한 아키텍처 결정 중 하나였습니다. 15만 개소에서 동시에 쏟아지는 데이터를 안정적으로 받아내기 위해 Kafka를 선택했습니다.
Kafka는 단순한 메시지 큐 이상의 역할을 담당했습니다. 순간적으로 폭증하는 트래픽을 버퍼링하고, 수집 시스템과 처리 시스템을 분리(Decoupling)함으로써 각 컴포넌트가 독립적으로 확장되고 장애에 대응할 수 있게 해줬습니다.
또한, 토픽 설계는 신중하게 진행했습니다. IoT Platform A, B, C, HTTPS 등 프로토콜별로 토픽을 분리해 각 채널의 특성에 맞게 독립적으로 관리할 수 있도록 했습니다. 각 토픽은 6개의 파티션으로 구성하여 병렬 처리 능력을 확보했고, 복제 계수를 3으로 설정하여 브로커 장애 시에도 데이터를 안전하게 보호했습니다. 메시지는 7일간 보관되도록 설정하여, 만약의 장애 상황에서도 충분한 복구 시간을 확보했습니다.

아키텍처 설계 - 데이터는 어떻게 흐르는가?
기술 스택을 정했으니, 이제 이것들을 어떻게 조합할 것인지 고민할 차례였습니다. 저희는 데이터의 흐름을 따라 시스템을 크게 4개 영역으로 나누어 설계했습니다.
1. 데이터 수집 영역
전국 각지에 흩어진 발전소의 RTU 장비에서 보낸 데이터가 시스템으로 유입되는 진입점입니다. 방화벽과 L4 스위치를 거쳐 수집 서버로 전달된 데이터는, 여기서 프로토콜별로 정제되어 Kafka로 발행됩니다.
Data Collector
Spring WebFlux 기반의 비동기 서버로, 다양한 IoT 프로토콜을 표준화된 내부 포맷으로 변환합니다.
reactor-kafka를 사용하여 논블로킹 메시지 발행을 구현했으며, Reactor의 백프레셔 기능을 통해 Kafka 클러스터로 가는 부하를 조절합니다. 컨테이너 기반으로 설계되어 트래픽 증가 시 즉각적인 확장이 가능합니다.

2계층 로드밸런싱: L4 스위치와 Nginx, Docker
클라우드의 관리형 로드밸런서가 없는 온프레미스 환경에서 고가용성과 확장성을 확보하기 위해, 하드웨어 L4 스위치와 소프트웨어 로드밸런서를 계층화하여 유연한 트래픽 분산을 구현했습니다. 외부에서 들어오는 트래픽은 먼저 L4 스위치가 여러 대의 서버로 분산하고, 각 서버 내부에서는 Nginx가 Docker 컨테이너로 패키징된 수집 서버 인스턴스들에게 요청을 분배합니다.
하드웨어 스위치가 네트워크 레벨의 빠른 분산과 헬스체크를 담당하고, Nginx가 애플리케이션 레벨의 세밀한 라우팅을 담당하는 역할 분리가 핵심입니다.
Nginx는 Least Connections 방식으로 현재 활성 연결 수가 가장 적은 인스턴스에 요청을 전달하며, Passive Health Check를 통해 실패한 인스턴스를 자동으로 제외합니다.
Nginx 설정은 코드로 관리되어 버전 관리가 가능하고, 컨테이너 스케일 아웃 시 설정만 변경하면 됩니다. 이 패턴은 WEB/WAS 서버에도 동일하게 적용했습니다.
2. 이벤트 허브 영역
Kafka 토픽 설계는 단순해 보이지만, 실제로는 많은 고민이 필요한 부분입니다. 저희는 프로토콜별로 토픽을 분리하여 각 채널이 서로 영향을 주지 않도록 격리성을 확보하면서도, 파티셔닝을 통해 확장성도 함께 확보했습니다.

토픽 네이밍은 일관된 컨벤션을 따랐습니다. {namespace}.collector.{platform}.raw-data 형태로 명명하고, Event Contracts 모듈에서 모든 토픽 이름을 상수로 중앙 관리했습니다.
파티셔닝 전략도 중요했습니다. 각 토픽을 6개 파티션으로 나누어 컨슈머가 병렬로 처리할 수 있도록 했습니다. 파티션 리밸런싱 기능 덕분에 컨슈머가 추가되거나 제거될 때도 부하가 자동으로 재분배됩니다.
3. 데이터 중계 및 저장 영역
Kafka에 쌓인 데이터를 이제 안전하게 내부망의 데이터베이스로 옮겨야 합니다. 보안을 위해 DMZ에 중계 서버를 두고, 여기서 Kafka 메시지를 소비해 내부망 DB에 저장하는 구조로 설계했습니다.

컨슈머 처리 로직
컨슈머 모듈은 Kafka 메시지를 소비하여 DB에 저장하는 역할을 합니다. 가장 먼저 집중한 부분은 배치 처리 최적화였습니다. 메시지를 개별적으로 처리하지 않고 배치(Batch) 단위로 묶어서 DB에 저장하도록 했는데, 최대 1,000개 메시지를 한 번에 가져오며 최소 1MB 데이터가 모이거나 3초가 경과하면 배치 처리를 수행합니다. 이 방식으로 DB Insert 성능을 크게 향상시킬 수 있었습니다.
처리량을 극대화하기 위해 컨슈머 그룹도 적극 활용했습니다. 6개의 파티션을 6개의 컨슈머가 병렬로 소비하며, 각 컨슈머는 독립적으로 메시지를 처리합니다.
안정성을 위한 재시도 및 에러 핸들링도 중요했습니다. 일시적인 오류 시에는 1초 간격으로 최대 3번 재시도하며, 배치 저장 실패 시에는 개별 저장으로 fallback하여 가능한 많은 데이터를 보존하도록 했습니다. 그래도 실패하는 데이터는 별도의 오류 테이블에 저장하여 추후 분석할 수 있도록 했습니다.
4. 발전량 예측 및 분석 영역
단순히 데이터를 쌓는 것만으로는 가치를 만들기 어렵습니다. 분석 예측 서버는 수집된 데이터를 분석하고 예측하는 역할을 합니다.

분석 예측 서버는 Dagster 기반 워크플로우 오케스트레이션을 사용합니다. Dagster를 활용해 데이터 파이프라인의 스케줄링과 실행을 관리하며, 데이터 수집, 전처리, 예측 실행을 하나의 워크플로우로 통합했습니다. 파이프라인 실행 이력과 의존성도 체계적으로 관리했습니다.
예측 정확도를 높이기 위해서는 외부 데이터 연동이 필수적이었습니다. Python 분석 파이프라인이 NOAA NWP(수치예보모델)를 통해 기상 예보 데이터를 수집하며, 태양광 발전에 영향을 미치는 기상 요소를 확보했습니다. 발전량 예측 모델은 과거 발전 실적 데이터와 기상 데이터를 결합하여 미래 발전량을 예측하고, 그 결과는 데이터베이스에 저장되어 분석 및 리포팅에 활용됩니다.
5. 웹 서비스 제공 영역
사용자들이 발전소 상태를 모니터링하고 시스템을 제어하는 웹 서비스 영역입니다.

웹 서비스는 전형적인 3-Tier 아키텍처로 구성했습니다. 가장 앞단의 WEB 계층에서는 정적 리소스 서빙과 SSL/TLS 터미네이션을 담당하며, L4 스위치를 통해 2대의 서버로 로드 밸런싱합니다. 들어온 요청은 WAS 서버로 프록시됩니다.
WAS 계층은 3대의 Application Server로 구성되어 고가용성을 확보했습니다. 여기서 동작하는 Business API Service는 Spring Boot 기반의 RESTful API 서버로, 모니터링 서비스의 핵심 비즈니스 로직을 처리합니다. 무중단 서비스는 필수 요구사항이었습니다. Oracle RAC Active-Active 클러스터로 DB를 이중화하고, 모든 계층에서 최소 2대 이상의 서버를 운영하며 L4 로드 밸런싱을 구성했습니다. Docker 기반 구성 덕분에 장애 발생 시에도 빠르게 복구할 수 있습니다.
배치 계층은 Spring Batch를 활용해 정기적인 통계 집계와 리포트 생성 같은 대용량 데이터 처리 작업을 수행합니다.
수집 서버 클러스터 성능 검증: 12,000 TPS 달성
아키텍처 설계가 실제 대규모 트래픽 환경에서 유효한지 검증하기 위해 강도 높은 부하 테스트를 수행했습니다. 단순 산술 계산으로 접근하면 15만 개의 장비가 60초 동안 균등하게 데이터를 전송한다고 가정했을 때, 필요한 처리량은 약 2,500 TPS입니다.
150,000 Requests / 60 Seconds = 2,500 TPS
하지만 실제 환경에서는 장비들이 완벽하게 분산되지 않습니다. 많은 설비들이 동시에 데이터를 전송하는 트래픽 스파이크가 빈번하게 발생하기 때문입니다. 클라이언트 측에 지연 로직이 없다면 서버는 순간적으로 10,000 TPS 이상의 폭주 트래픽을 감당해야 할 수도 있습니다. 저희는 이러한 트래픽 서지(Surge)를 고려하여, 평균 대비 약 4~5배의 예비 용량을 확보하는 것을 목표로 삼았습니다.
Grafana k6를 이용한 부하 테스트 결과 단일 노드 기준 초당 3,000건의 수집 요청(3,000 TPS), 전체 클러스터 기준 초당 12,000건(12,000 TPS)의 수집 요청을 지연 없이 안정적으로 처리하는 것을 확인했습니다. Spring WebFlux의 Non-blocking 구조와 Kafka를 통한 트래픽 버퍼링, 그리고 컨슈머의 Batch Insert 전략이 유기적으로 동작하여, 이론적 피크치를 상회하는 부하 상황에서도 데이터 유실 없이 안정적인 처리가 가능함을 증명했습니다.
회고 - 우리가 배운 것들
비동기-논블로킹의 이점
Spring WebFlux를 실전에 적용하면서 대규모 IoT 환경에서 리액티브 프로그래밍의 장점을 확인할 수 있었습니다. 적은 리소스로 높은 처리량을 달성하는 것은 물론, 백프레셔 제어를 통해 시스템 전체의 안정성을 확보할 수 있었습니다.
Kafka는 단순한 메시지 큐가 아니다
Kafka를 도입하면서 이벤트 스트리밍 플랫폼의 진가를 알게 되었습니다. 단순히 메시지를 전달하는 것을 넘어, 시스템 간 결합도를 낮추고 장애 격리를 가능하게 하며, 데이터를 일정 기간 보관해 재처리를 가능하게 하는 등 아키텍처 전반의 안정성을 높이는 중요한 컴포넌트였습니다.
확장성은 처음부터 고려해야 한다
수평 확장이 가능한 구조로 설계한 덕분에, 트래픽이 증가해도 서버를 추가하는 것만으로 대응할 수 있었습니다. 컨테이너 기반 아키텍처와 Kafka의 파티셔닝 메커니즘이 이를 가능하게 했습니다.
온프레미스에서도 유연한 인프라 구성이 가능하다
클라우드 없이도 L4 스위치와 Nginx를 계층화하여 충분히 유연한 로드밸런싱을 구현할 수 있었습니다. 중요한 것은 각 계층의 역할을 명확히 분리하고, 설정을 코드로 관리하는 것이었습니다.
마무리하며
온프레미스 환경에서 15만 개소의 실시간 데이터를 처리하는 시스템을 구축하는 여정은 쉽지 않았습니다. 클라우드 매니지드 서비스가 제공하는 편리함 대신, 하드웨어 선정부터 네트워크 망 분리, 각 서버의 역할 정의와 이중화 구성까지 모든 단계를 저희 손으로 직접 결정하고 구축해야 했습니다.
하지만 그만큼 배운 것도 많았습니다. 대규모 트래픽을 안정적으로 처리하기 위한 Spring WebFlux와 Kafka의 활용, 그리고 Nginx와 Docker를 활용한 소프트웨어 로드밸런싱을 통해 유연한 아키텍처를 구성해 본 것은 저희 팀의 큰 자산으로 남게 되었습니다.
이 글이 온프레미스 환경에서 대규모 트래픽 처리를 고민하는 엔지니어분들에게 작은 도움이 되기를 바랍니다.

——————————————————————————————————————————
식스티헤르츠와 함께 멋진 개발 여정을 직접 만들어갈 동료를 기다립니다! 😄
