선박 운항 데이터 수집 시스템
선박의 위치와 방향 정보, 화재 센서 데이터를 실시간으로 수신하고 처리하는 시스템
아키텍처
개요
- 군 보안 규정상 내부망을 거치지 않고 운항 데이터를 제공하는 외부 업체 서버와 직접 연결
- 외부 업체 서버가 시리얼로 내보내는 신호를 Ethernet Converter가 TCP 소켓으로 전달
- Netty 기반 비동기 TCP 서버에서 LineBasedFrameDecoder로 연속 바이트 스트림을 개별 센서 메시지로 디코딩
- 파싱한 센서 값은 WebSocket으로 프론트에 즉시 전달하거나 내부 로직에서 사용하고 수신 원본은 로그 테이블에 적재
문제
- 피크 시 초당 약 600건의 운항 데이터와 화재 센서 데이터를 기존 서비스 트래픽과 함께 처리
- HDD 기반 스토리지의 낮은 IOPS(초당 처리 가능한 입출력 횟수)로 Write 처리량 제약
- 초당 약 600건씩 로그 테이블에 단건 Insert하면서 서버 평균 I/O wait가 18%까지 상승
해결 전략
- 실시간 전달과 내부 로직은 그대로 두고 로그 저장 경로만 버퍼를 거치도록 분리
- 로그 데이터는 ConcurrentLinkedQueue에 쌓고, 600건이 모이거나 1초가 지나면 Batch Insert로 저장
기술 선택 이유
-
단건 Insert - 제외
- 커밋마다 Redo Log를 디스크에 동기화해야 하므로 초당 커밋 수가 HDD의 IOPS에 제한됨
-
Batch Insert - 채택
- 600건을 한 트랜잭션으로 묶어 Redo Log 동기화를 1회로 감소
-
BlockingQueue, synchronized List - 제외
- 저장 스레드가 락을 쥐는 동안 수신 스레드가 큐 앞에서 대기해 TCP 수신까지 지연
-
ConcurrentLinkedQueue - 채택
- 락 없이 CAS로 동작해 수신 스레드가 큐에 넣고 바로 반환
검증
- 선원 300명의 위치 측위 API 요청은 Locust로, 운항 데이터와 화재 센서 데이터는 TCP 시뮬레이터로 재현
- 운영 피크인 600 messages/s를 5분간 유지해 단건 Insert와 Batch Insert 비교
동일 부하 전후 결과
| 지표 | 단건 Insert | Batch Insert |
|---|---|---|
| TCP 입력 부하 | 600 messages/s | 600 messages/s |
| DB 저장 트랜잭션 | 약 600회/s | 약 1회/s |
| 서버 평균 I/O wait | 약 18% | 약 3% |
회고
- 피크 시 초당 약 600건은 센서 개수와 데이터 전송 주기를 고려해 가정한 값임. 평상시 유입량은 훨씬 적고 실제 분포는 측정하지 않음
- flush 크기 600건과 주기 1초는 유입량을 기준으로 처리에 무리가 없을 것이라 예상해 정한 값임. 크기와 주기를 달리한 비교 테스트는 진행하지 못함
- 다시 진행한다면 부하 기준을 세운 뒤 flush 크기와 주기를 바꿔가며 처리량, I/O wait, 데이터 대기 시간을 함께 측정해 운영 환경에 맞는 값을 찾아야 함