드론 관제 시스템을 개발하면서 가장 먼저 들었던 생각은 생각보다 "위치 판단"이 쉬운 문제가 아니라는 것이었다.
처음에는 단순히 드론 위치를 받아서 위험 여부를 판단하면 될 것이라고 생각했다.
현재 위치 수신
→ 위험 판단
→ 알림 발행
하지만 실제로 설계를 시작해보니 고려해야 할 요소가 예상보다 훨씬 많았다.
예를 들어 하나의 드론 위치 정보가 들어오더라도 동시에 다음과 같은 평가가 필요했다.
- 다른 드론과 충돌 위험은 없는가
- 비행금지구역(Geofence)을 침범했는가
- 허용 비행구역(Area)을 이탈했는가
- 지정 경로(Route)를 벗어났는가
더 큰 문제는 이 모든 판단 결과가 서로 다른 회피 전략을 요구한다는 점이었다.
충돌 위험은 즉시 회피가 필요하지만, 경로 이탈은 상대적으로 우선순위가 낮을 수 있다.
단순히 모든 위험 상황에 대해 명령을 생성하면 오히려 서로 충돌하는 회피 명령이 발생할 수 있었다.
첫 번째 고민: 모든 판단을 하나의 서비스에서 처리할 것인가
프로젝트 초기에 가장 단순하게 생각한 구조는 다음과 같았다.
public void evaluateSafety(TrackPosition position) {
evaluateCollision(position);
evaluateGeofence(position);
evaluateArea(position);
evaluateRoute(position);
publishAlert();
registerCommand();
}
작동은 가능하다.
하지만 기능이 늘어날수록 문제가 발생하기 시작했다.
충돌 로직을 수정하려고 열어본 클래스 안에
- Geofence 로직
- Route 로직
- JMS 발행 로직
까지 모두 섞여 있었다.
결국 특정 도메인의 변경이 전체 시스템에 영향을 주기 시작했다.
도메인별로 책임을 분리하기
이 문제를 해결하기 위해 각 영역을 독립적인 도메인으로 분리했다.
collision
geofence
area
route
그리고 모든 도메인에 동일한 패턴을 적용했다.
Service
Evaluator
ResultHandler
Evaluator는 오직 판단만 수행한다.
RouteCheckResult result =
routeEvaluator.evaluate(position, route);
이 단계에서는
- 알림 발행
- 회피 명령 저장
- 이벤트 처리
를 하지 않는다.
오직 "위험한가?"만 판단한다.
판단과 부수효과를 분리하기
프로젝트를 진행하면서 가장 중요하다고 느꼈던 부분은 판단 로직과 부수효과를 분리하는 것이었다.
예를 들어 Route 평가 결과가 DANGER라고 가정하자.
Evaluator의 역할은 여기까지다.
RouteCheckResult result =
RouteCheckResult.builder()
.routeDeviationLevel(DANGER)
.build();
그 이후 알림 발행이나 회피 명령 생성은 ResultHandler에게 위임한다.
public void handle(
RouteCheckResult result,
TrackPosition position,
RouteModel route
) {
routeAlertPublisher.publishIfNeeded(result);
routeAvoidanceCommandService.register(
position,
route,
result
);
}
덕분에 위험 판단 로직을 테스트할 때는 MQ나 저장소를 신경 쓸 필요가 없어졌다.
MQ 장애가 안전 판단을 막아서는 안 된다
실시간 안전 시스템을 설계하면서 또 하나 중요하게 생각한 부분은 장애 격리였다.
만약 MQ 발행 실패 때문에 안전 판단 자체가 실패한다면 심각한 문제가 될 수 있다.
그래서 알림 발행은 별도의 이벤트 구조로 분리했다.
Evaluator
↓
ResultHandler
↓
ApplicationEventPublisher
↓
@Async EventHandler
↓
MQ Publish
이 구조를 적용한 이후에는 MQ 장애가 발생하더라도 안전 판단 자체는 정상적으로 수행된다.
실시간 시스템에서는 "기능 수행"과 "외부 연동"을 분리하는 것이 생각보다 중요하다는 것을 다시 한 번 느꼈다.
가장 어려웠던 부분은 우선순위였다
충돌 위험, Geofence 침범, 경로 이탈이 동시에 발생하면 어떤 명령을 수행해야 할까?
처음에는 각 도메인이 독립적으로 회피 명령을 생성하도록 구현했다.
하지만 실제 테스트를 해보니 서로 다른 명령이 동시에 생성되는 문제가 발생했다.
충돌 회피
→ 상승
Geofence 회피
→ 하강
결국 최종 단계에서 하나의 명령만 선택할 수 있는 Safety Layer를 추가했다.
Collision
Geofence
Area
Route
↓
Safety Layer
↓
Final Command
그리고 우선순위를 명확하게 정의했다.
1. 충돌 위험
2. Geofence 침범
3. Area 이탈
4. Route 이탈
이후부터는 각 도메인이 독립적으로 판단하더라도 최종 결과는 일관성을 유지할 수 있었다.
마무리
이번 프로젝트를 진행하면서 느낀 점은 드론 안전 제어 시스템은 단순히 좌표를 계산하는 문제가 아니라는 것이다.
실제로는
- 공간 판단
- 위험도 평가
- 회피 전략 생성
- 이벤트 처리
- 장애 격리
- 우선순위 결정
등 다양한 문제를 함께 해결해야 했다.
특히 구현보다 어려웠던 것은 "어떤 책임을 어디에 둘 것인가"에 대한 설계였다.
결국 좋은 안전 시스템은 복잡한 알고리즘보다도 각 구성 요소가 자신의 역할만 수행하도록 만드는 구조에서 시작된다고 생각한다.
'나의 주니어 개발 일기 > 트러블슈팅' 카테고리의 다른 글
| PolyCARP를 활용한 드론 Geofence/Area 공간 판단 구현기 - 좌표계, 경계선 그리고 수많은 삽질들 (0) | 2026.06.04 |
|---|---|
| [Troubleshoot] Next.js TTS 모바일 발음 이슈 및 Gemini API 서버 사이드 캐싱 해결기 (0) | 2026.02.03 |
| RabbitMQ 채널이 과도하게 생성되는 이슈 – Publisher만 사용했는데 Channel 51개 발생 (2) | 2025.07.22 |
| Spring Boot 쓰레드 풀 설정, 정말 안전할까? – 실무에서 놓치기 쉬운 포인트 (2) | 2025.05.30 |
| Spring Integration Udp 통신중 버퍼 크기로 인한 트러블 슈팅 (1) | 2024.07.05 |