실시간 센서 데이터 전송 시스템
선박에서 수집되는 센서 데이터를 외부 서버로 실시간 전송하는 데이터 연동 시스템
아키텍처
개요
- 선박 내부의 온습도와 화재, 도어 센서 데이터를 외부 서버로 실시간 전송하는 시스템
- API Server가 수신한 센서 데이터를 MQTT 메시지로 변환해 발행
전송 방식 선택
-
HTTP Polling - 제외
- 주기적 요청으로 실시간성이 떨어지고, 작은 센서 메시지마다 요청과 헤더 오버헤드 발생
-
Kafka - 제외
- 이벤트 영속성과 재처리보다 단순 실시간 전달이 우선인 요구사항
- Broker와 파티션, 컨슈머 그룹까지 운영해야 해 요구사항 대비 복잡도 큼
-
MQTT - 채택
- 작은 센서 상태 메시지를 반복 전송하기에 적합한 경량 Pub/Sub 방식
- MQTT Broker를 사이에 두어 API Server와 외부 서버의 직접 연결 제거
- 온습도와 화재, 도어 센서의 평상값은 일부 누락돼도 다음 값으로 상태가 갱신되므로 QoS 0 적용
- 실제 화재 이벤트는 유실을 막기 위해 QoS 1 적용, 중복 메시지는 수신 측에서 멱등 처리
문제
- WMS, API, ADMIN, SOCKET으로 나뉜 애플리케이션 네 개와 MySQL, Redis가 8코어 단일 서버를 공유하는 폐쇄망 환경
- MQTT 전송 메서드에
@Async만 붙이고 전용 executor를 지정하지 않아 사내에 이미 등록된 공통 스레드풀(pool-size="100",queue-capacity미설정)이 그대로 사용됨 - 400 req/s 부하가 이어지자 스레드가 최대 100개로 늘어나면서 CPU 사용률이 95%까지 상승
해결 전략
- MQTT 전송 전용 스레드풀을 분리하고 CorePoolSize 8, MaxPoolSize 16, Queue Capacity 200으로 제한
- 평상시 8개로 처리하고 순간적인 부하나 전송 지연이 겹칠 때만 16개까지 확장
- Queue는 운영 유입량의 약 0.5초분만 대기시켜 무제한 적체 방지
- Queue와 MaxPoolSize가 모두 차면 CallerRunsPolicy로 호출 스레드가 직접 처리해 추가 적체 억제
기술 선택 이유
-
공통 스레드풀 크기 축소 - 제외
- 크기를 줄여도 공통 풀을 쓰는 구조는 바뀌지 않아 용도에 맞는 스레드풀을 지정하는 편이 나음
-
MQTT 전송 전용 스레드풀 - 채택
- 공통 풀을 그대로 두고 MQTT 경로의 스레드 수만 제한
검증
- Locust로 400 req/s 수준의 MQTT 전송과 기존 서비스 트래픽인 선원 300명의 위치 측위를 함께 재현
- 같은 부하에서 공통 풀과 MQTT 전용 풀을 각각 5분간 실행해 비교
동일 부하 전후 결과
| 지표 | 기존 공통 풀 | MQTT 전용 풀 |
|---|---|---|
| 최대 activeCount | 100 | 8 |
| 5분 시점 queueSize | 900 | 20 |
| 8코어 서버 CPU 사용률 | 90~95% | 55~60% |
회고
- 운영 서버가 8코어 16스레드이고 기존 트래픽도 함께 처리하고 있어 스레드 수를 8개로 설정. 스레드 수별 성능 비교 없이 정한 값임
- 8코어에 스레드가 100개까지 늘면서 발생한 Context Switching이 CPU 사용률 상승의 주된 요인으로 보임. 다만 따로 측정하지 않아 원인으로 단정하기 어려움
- 다시 진행한다면 스레드 수에 따른 처리량, CPU 사용률, queueSize, Context Switching을 함께 측정해 필요한 최소 스레드 수를 정해야 함