프로그래밍 언어/Java

실시간 번역 상태 처리를 위한 큐(Queue) 자료구조 아키텍처와 Redis 확장기

ioh'sDeveloper 2025. 7. 6. 22:33

💡 실시간 번역 상태 처리를 위한 큐 기반 아키텍처와 Redis 확장기

1. 도입 – 왜 이 기능이 필요했는가?

법령 번역 비교 서비스에서는 AI 기반 번역 요청 후, 번역 완료까지의 시간이 수초에서 수분 이상 소요되며, 프론트엔드는 SSE(Server-Sent Events)를 통해 사용자에게 실시간 상태를 반영해야 합니다.

그러나 다음과 같은 실무적 요구사항과 문제가 발생했습니다:

  • 다건 작업 트래킹: 여러 작업을 동시에 처리 중인 사용자가 각 작업별 상태를 실시간으로 받아야 함
  • 진행률 상태 역주행 이슈: 이전에 전송된 진행률보다 낮은 값이 수신되는 경우 사용자 혼란 야기
  • SSE 전송 누락/지연: 순차 처리 없이 push할 경우, 병렬 작업 간 race condition 발생
  • 수천 개의 법령 작업에 대한 상태 정합성 보장이 필요했으며, 단순한 Polling 방식으로는 한계가 명확

2. 구현 방식 – 큐 기반 이벤트 드리븐 구조

🔁 기본 구조: Concurrent Queue + 상태 동기화 워커

Queue<TranslationEvent> translationQueue = new ConcurrentLinkedQueue<>();
  • enqueue() 함수: 상태 변화 발생 시 TranslationEvent 객체 등록
  • startWorker(): 단일 스레드 워커에서 큐를 지속적으로 모니터링하여 SSE 전송 수행
  • trackedList: companyNo+employeeNo 기준으로 연결된 SSE emitter 리스트를 다건 관리

📦 TranslationEvent 구조

class TranslationEvent {
    String taskId;
    String companyNo;
    int progress;  // 0~100
    String status; // WAITING, PROCESSING, SUCCESS, FAIL
}

3. 문제 해결 전략

✅ 1. 상태 역주행 방지

if (currentProgress < lastSentProgress) {
    log.warn("Rollback detected. Ignore outdated progress.");
    return;
}
  • 단건/다건 모두 lastSentProgress Map으로 상태 기억
  • race condition 예방: 낮은 진행률 무시

✅ 2. 다건 SSE Emitter 트래킹

  • 사용자마다 여러 작업을 동시에 보게 되는 구조를 고려해, trackedTaskMap<userId, Set<taskId>> 구조 유지
  • 새로운 작업이 생성되면 subscribe() 시 tracked 리스트에 자동 추가
  • 모든 emitter는 상태 수신 시, 해당 taskId와 매핑된 emitter에만 push

✅ 3. 상태 전송 중복 방지 및 누락 대응

  • DeduplicationKey(taskId + progress + status) 기반 중복 제거
  • 작업 완료 후 SSE emitter 자동 해제하여 자원 누수 방지

4. 확장: Redis Pub/Sub로 구조 고도화

단일 인스턴스 기반 구조에서는 문제없이 동작하였지만, 서비스가 멀티 인스턴스(MSA 구조)로 확장됨에 따라 큐 + 워커 구조만으로는 한계가 발생했습니다. 따라서 Redis Pub/Sub 기반 브로드캐스팅 구조를 도입하였습니다.

🧱 Redis 구조

  • translation-status-channel 채널에 상태 변경 시 메시지 발행
  • 모든 WAS 인스턴스가 Subscriber로 등록되어 이벤트 수신 → 로컬 emitter로 전송
  • 메시지 페이로드는 JSON 직렬화된 TranslationEvent 객체
  • 트랜잭션 처리 후 commit 성공한 경우에만 publish
redisTemplate.convertAndSend("translation-status-channel", event);

🔧 장점

  • 무중단 배포 및 WAS scale-out 시에도 상태 푸시 일관성 유지
  • 트래픽 분산 환경에서도 상태 싱크 정합성 확보
  • 상태 전송 로직을 application decoupling 처리하여 유지보수성 향상

5. 실서비스에서 얻은 효과

항목 개선 전 개선 후

SSE 수신 누락 빈번 0건
진행률 역주행 있음 방지 로직으로 100% 해결
멀티 유저 동시 작업 처리 트래킹 누락 존재 trackedList + emitterMap 구조로 정합성 확보
MSA 대응성 불가능 Redis Pub/Sub 구조 도입으로 고가용성 확보

6. 결론 – 단순한 큐에서 시작한 구조적 진화

이번 구조는 단순한 ConcurrentLinkedQueue에서 시작해, 실시간성과 상태 정합성을 유지하는 이벤트 기반 아키텍처로 진화했습니다. 이후 Redis Pub/Sub로 확장하면서 멀티 인스턴스 환경에서도 안정적 실시간 상태 처리를 가능하게 했습니다.

이 과정은 단순히 큐를 사용하는 문제를 넘어, 실시간 시스템에서 발생할 수 있는 race condition, 중복 상태, 상태 일관성 이슈를 해결하기 위한 종합적인 접근이었습니다.