선박 운항 데이터 수집 엔진
DB I/O Wait 18%→3%Ethernet Converter(Serial→TCP) 환경에서 운항·안전 센서 데이터를 실시간 수신·처리하는 수집 엔진
JavaNettyBatch Insert
아키텍처
개요
- Ethernet Converter 기반 통신 환경에서 선박 운항 데이터(위치·방향)와 안전 데이터(화재)를 함께 수신·처리
- Netty 기반 비동기 TCP 수집 구조로 메시지 프레임 디코딩 처리
문제
- 기존 비즈니스 트래픽(선원 위치, 각종 센서, 서비스 기능) 처리 중 선박 위치·방향·화재 센서 데이터가 신규 유입
- 초당 약 300건 수준의 신규 유입 데이터가 메시지 수신 시점마다 건건이 단건 Insert로 DB에 적재되는 구조
- 운영 장비 예산과 기존 인프라 제약으로 SSD 대신 HDD 기반 스토리지 사용
- HDD 기반 스토리지의 낮은 IOPS(초당 처리 가능한 입출력 횟수)로 단건 Insert 반복 시 랜덤 Write 처리량 제약
- 기존 비즈니스 트래픽과 단건 Insert 로그 적재가 겹치며 DB I/O Wait 18%까지 상승
해결 전략
- 수집 로그를 ConcurrentLinkedQueue에 버퍼링하고 배치 작업이 주기적으로 DB에 반영
- 단건 Insert를 크기·주기 기반 Batch Insert로 전환해 HDD 랜덤 Write 부담 감소
- Flush 트리거 이원화: 누적 500건 도달 시 즉시 flush, 그 외에는 스케줄러 주기마다 flush
- 버퍼에 상한(피크 초당 300건 × 약 10초분 = 3,000건)을 둬, 과부하가 지속돼 DB가 적재를 못 따라가도 메모리가 무한히 증가하지 않도록 제한
- 데이터 유실 허용도에 따라 경로 분리: 운항(위치·방향) 데이터와 화재 센서의 평상시 값은 연속성 데이터로 일부 유실을 허용하고 버퍼를 경유, 실제 화재 이벤트는 버퍼를 거치지 않고 전용 테이블에 즉시 저장해 유실 방지
기술 선택 이유
-
Batch Insert
- HDD 환경에서 단건 Insert 반복 시 랜덤 Write 비용 증가
- 묶음 쓰기로 DB 왕복 횟수와 디스크 I/O 부담 감소
-
ConcurrentLinkedQueue
- 수집 스레드와 배치 저장 스레드 간 Lock 경합을 줄이는 메모리 큐
- 데이터 수신 흐름과 DB Write 흐름 사이에서 버퍼 역할 수행
- 500건·주기 flush로 정상 부하에서는 큐가 빠르게 비워져 적재량이 작게 유지
- 적재량이 상한(3,000건)에 도달하면 유실 허용 데이터의 초과 유입분을 드롭해, 정상 부하의 단기 버스트는 흡수하되 과부하 지속 시 메모리 증가를 차단
튜닝 기준
-
Flush 임계 (500건)
- 누적 500건 도달 시 즉시 Batch flush로 DB 왕복 횟수 감소
- 단일 배치가 과도하게 커지지 않는 선에서 묶음 크기 확보
-
Flush 주기 (스케줄러)
- 500건에 도달하지 못한 저유입 구간의 잔여 로그를 주기적으로 반영
- 데이터 반영 지연의 상한 역할
검증
테스트 설정
- Locust 기반 부하 테스트 수행
- 기존 비즈니스 트래픽(선원 300명 위치 갱신, 센서 100개, 서비스 기능 호출)과 신규 유입 트래픽(선박 위치·방향·화재 센서)을 함께 재현
- 3분 ramp-up 후 10분간 지속 부하 수행
- 부하 구간 동안
vmstat의wa(I/O Wait) 값을 측정해 단건 Insert와 Batch Insert를 전후 비교
측정 결과
- DB I/O Wait 18%에서 3%로 감소
- HDD 환경에서도 수집 지연 없이 안정적 Write 처리
배운 점
- Netty 기반 비동기 I/O는 적은 스레드로 다수 연결을 처리할 수 있고, 프레임 디코더로 가변 길이 메시지를 안정적으로 분리·파싱할 수 있음
- HDD의 낮은 IOPS 환경에서는 단건 Insert 반복보다 Batch Insert로 묶어 쓰는 것이 랜덤 Write 부담과 DB I/O Wait를 크게 줄이는 핵심임
- 동일 수집 파이프라인이라도 데이터의 유실 허용도에 따라 경로(버퍼 경유 vs 즉시 별도 저장)와 상한·드롭 정책을 다르게 설계해야 처리량과 정확성을 함께 확보할 수 있음