[SWM] 실시간 이상 탐지

반려동물 건강 관리 서비스에서 가장 중요한 기능은 "위험한 순간을 놓치지 않는 것"이다.

혈당이 급격히 떨어지거나(저혈당), 급상승하는(고혈당) 순간을 실시간으로 감지하지 못하면, 반려동물이 위험에 처할 수 있다. 하지만 단순히 "혈당이 높다/낮다"만 판단하는 것으로는 부족했다.

예를 들어:

  • 혈당 180 mg/dL → 위험한가?

    • 평소 100이었다면 → 위험

    • 평소 190이었다면 → 오히려 개선 중

즉, 절대적 기준(Threshold)과 상대적 변화(Trend)를 모두 고려해야 정확한 이상 탐지가 가능하다.

이 글에서는 실시간으로 혈당 이상을 감지하는 시스템을 어떻게 설계하고 구현했는지 정리했다.


🎯 요구사항 정의

탐지해야 할 이상 패턴

  1. 절대적 위험 (Threshold-Based)

    • 저혈당: 50 mg/dL 이하

    • 고혈당: 200 mg/dL 이상

    • 즉각적인 알림 필요

  2. 급격한 변화 (Trend-Based)

    • 급상승: 30분 내 30% 이상 증가

    • 급하강: 30분 내 30% 이상 감소

    • 스파이크 경고 필요

  3. 지속적 이상 (Duration-Based)

    • 혈당 180 이상이 2시간 지속

    • 혈당 60 이하가 30분 지속

    • 만성 문제 경고

시스템 요구사항

  • 실시간성: 데이터 수집 후 5초 이내 감지

  • 정확도: False Positive < 5%

  • 확장성: 동시 접속 사용자 1,000명 처리

  • 안정성: 99.9% 가용성


🏗️ 시스템 아키텍처


💡 핵심 로직: Threshold + Trend 알고리즘

1. Threshold-Based Detection (절대값 기반)

장점:

  • 구현 간단

  • 즉각적 감지 가능

  • 명확한 기준

단점:

  • 개체별 차이 고려 안 됨

  • False Positive 많음 (평소 혈당이 높은 경우)


2. Trend-Based Detection (변화율 기반)

장점:

  • 개체별 패턴 고려

  • 급격한 변화 감지 가능

단점:

  • 30분 데이터 필요 (초기 데이터 부족 시 작동 안 함)

  • 계산 복잡도 높음


3. Hybrid Approach (통합 접근)


🔍 고도화: 개인별 동적 임계값

문제점

모든 반려동물에게 동일한 임계값(예: 180 mg/dL)을 적용하면:

  • 평소 혈당이 높은 개체 → 항상 알림 (False Positive)

  • 평소 혈당이 낮은 개체 → 위험해도 알림 안 옴 (False Negative)

해결: 개체별 베이스라인 계산

효과:

  • False Positive 감소: 67% → 8%

  • 개체별 맞춤 알림


🚀 실시간 처리: Spark Streaming 구현

전체 코드


📡 Spring Boot: Redis 구독 및 WebSocket 전송


🎨 프론트엔드: 실시간 알림 표시


📊 성능 최적화

1. Spark Streaming 튜닝

python

2. Redis Pub/Sub 최적화

3. 부하 테스트 결과


🐛 트러블슈팅

문제 1: Spark Streaming 지연

증상: 5분 윈도우인데 실제로 8-10분 걸림

원인: Shuffle 파티션 수 부족 (기본 200개)

해결:

→ 처리 시간 8분 → 2분으로 단축


문제 2: Redis Pub/Sub 메시지 유실

증상: 알림이 간헐적으로 안 옴

원인: Subscriber가 처리 중일 때 새 메시지 유실

해결: Redis Streams로 변경

→ 메시지 유실률 5% → 0%


문제 3: False Positive 과다

증상: 알림이 너무 자주 옴 (하루 50건 이상)

원인: 센서 오작동으로 일시적 스파이크

해결: 연속 2회 이상 이상 시에만 알림

→ False Positive 67% → 8%


📈 개선 결과

Before (단순 Threshold)

  • False Positive: 67%

  • 평균 감지 시간: 15분

  • 놓친 이상: 23%

After (Threshold + Trend + Personalization)

  • False Positive: 8%

  • 평균 감지 시간: 1.2초

  • 놓친 이상: 2%

사용자 피드백:

  • "알림이 정확해서 믿을 수 있다" (92%)

  • "불필요한 알림이 줄었다" (88%)


🎓 배운 점

1. 단일 지표로는 부족하다

Threshold만으로는 False Positive가 너무 많았다. Trend와 개인화를 결합해야 정확도가 높아졌다.

2. 실시간 처리는 Trade-off

완벽한 정확도 vs 빠른 응답 시간 사이에서 균형을 찾아야 했다. 5분 윈도우가 최적이었다.

3. 도메인 지식이 핵심

수의사 인터뷰를 통해 "저혈당이 고혈당보다 더 위험하다"는 것을 알았고, 우선순위를 조정했다.

4. 모니터링이 필수

Spark Job 지연, Redis 메시지 유실 등은 Grafana 없이는 발견하기 어려웠다.


🚀 향후 개선 계획

1. ML 기반 이상 탐지

2. 다변량 분석

혈당뿐 아니라:

  • 체온

  • 심박수

  • 활동량

모두 고려한 종합 건강 이상 탐지

3. 예측 알림

"30분 후 저혈당 위험"처럼 미리 경고


💬 마무리

실시간 이상 탐지는 단순히 "높다/낮다" 판단이 아니라, 맥락을 이해하는 시스템을 만드는 것이었다.

Threshold + Trend + Personalization 조합으로:

  • 정확도 대폭 향상

  • 사용자 신뢰 확보

  • 실제 위험 순간 놓치지 않음

다음에는 ML을 도입해 더 정교한 패턴 분석을 시도할 계획이다.


관련 포스트:

  • Spark Streaming 실시간 처리 파이프라인 구축

  • Redis Pub/Sub vs Streams 비교

  • 시계열 데이터 이상 탐지 알고리즘

Last updated