본문으로 바로가기
728x90
반응형
SMALL

드론 관제 시스템을 개발하면서 가장 먼저 들었던 생각은 생각보다 "위치 판단"이 쉬운 문제가 아니라는 것이었다.

처음에는 단순히 드론 위치를 받아서 위험 여부를 판단하면 될 것이라고 생각했다.

현재 위치 수신
→ 위험 판단
→ 알림 발행
 

하지만 실제로 설계를 시작해보니 고려해야 할 요소가 예상보다 훨씬 많았다.

예를 들어 하나의 드론 위치 정보가 들어오더라도 동시에 다음과 같은 평가가 필요했다.

  • 다른 드론과 충돌 위험은 없는가
  • 비행금지구역(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 이탈
 

이후부터는 각 도메인이 독립적으로 판단하더라도 최종 결과는 일관성을 유지할 수 있었다.


마무리

이번 프로젝트를 진행하면서 느낀 점은 드론 안전 제어 시스템은 단순히 좌표를 계산하는 문제가 아니라는 것이다.

실제로는

  • 공간 판단
  • 위험도 평가
  • 회피 전략 생성
  • 이벤트 처리
  • 장애 격리
  • 우선순위 결정

등 다양한 문제를 함께 해결해야 했다.

특히 구현보다 어려웠던 것은 "어떤 책임을 어디에 둘 것인가"에 대한 설계였다.

결국 좋은 안전 시스템은 복잡한 알고리즘보다도 각 구성 요소가 자신의 역할만 수행하도록 만드는 구조에서 시작된다고 생각한다.

728x90
반응형
LIST