본문 바로가기

Leafresh: 환경 챌린지 플랫폼

피드백 생성 요청의 비동기 아키텍처 전환 배경 및 구조 설계

1. 배경: 동기식 피드백 처리 구조의 한계

초기 시스템은 사용자가 주간 활동 피드백을 요청하면, 프론트엔드(FE)에서 백엔드(BE)로 HTTP POST 요청을 보내고, BE는 다시 인공지능(AI) 서버에 HTTP POST 요청을 전달하는 동기식 처리 구조를 가지고 있었다.

 

즉, 전체 플로우는 다음과 같았다:

[FE] → [BE] → [AI 서버]
                  ↓
            [메시지 큐 등록 → 처리]
                  ↓
            [AI → BE] 결과 POST

 

AI 서버는 받은 요청을 자체적으로 내부 메시지 큐(GCP Pub/Sub or Redis Queue)에 적재하고, 큐에서 하나씩 꺼내 순차적으로 주간 활동 피드백을 생성한 뒤, 해당 결과를 다시 BE로 다시 POST 요청으로 전송하는 구조였다.

 

이 구조는 일견 간단하고 명확했지만, 아래와 같은 심각한 문제점을 포함하고 있었다.

 

2. 문제점: AI 서버 장애에 따른 전체 요청 흐름 정지

  • AI 서버 장애 시 전체 흐름 단절
    • 서버 비용 절감을 위해 AI 서버가 오프라인 상태일 경우, 혹은 OOM(Out Of Memory)으로 인해 서버가 중단될 경우, BE → AI 로의 요청이 실패하면서 사용자의 피드백 생성 요청은 처리되지 않고 응답이 없는 상태로 무기한 대기하게 되었다.
  • 큐 진입조차 하지 못하는 요청
    • 메시지 큐가 AI 서버 내부에 존재했기 때문에, AI 서버가 살아 있어야만 큐에 요청을 넣을 수 있었다. 결과적으로 BE에서 전송한 피드백 요청은 AI 서버가 죽어 있으면 큐에 적재조차 되지 않아 요청 유실 가능성도 존재했다.
  • 응답 실패시 복구 불가능성
    • BE → AI → 큐 → AI 처리 → BE 응답이라는 구조는 중간에 장애가 발생했을 경우 재시도 로직이나 장애 감지, DLQ 처리 등을 구현하기가 까다로운 구조였다.

 

3. 전환: GCP Pub/Sub 기반 비동기 메시징 구조 도입

이러한 문제를 해결하기 위해 BE ↔ AI 간 통신을 GCP Pub/Sub 기반의 비동기 메시지 시스템으로 전환하였다. 새로운 구조에서는 다음과 같은 흐름을 갖는다:

 

[BE] ──▶ [AI]:
[leafresh-feedback-topic] ──▶ [leafresh-feedback-sub] (구독자: AI)

[AI] ──▶ [BE] :
[leafresh-feedback-result-topic]
                  └──▶ [leafresh-feedback-result-sub] (구독자: BE)
                            │
                            └──(DLQ)▶ [leafresh-feedback-result-dlq-topic] ─▶ [leafresh-feedback-result-dlq-sub] (구독자: BE))

 

 

GCP Pub/Sub을 통해 발행자(Publisher)구독자(Subscriber)를 완전히 분리함으로써, 시스템 간 결합도를 낮추고 유실 방지 및 확장성 확보가 가능해졌다.

 

 

  1. 피드백 요청 흐름
    • FE → BE: POST /api/members/feedback
    • BE에서는 요청을 AiFeedbackCreationRequestDto로 변환 후 Pub/Sub에 발행
  2. AI 서버 처리
    • AI 서버는 leafresh-feedback-topic을 구독하고, 비동기로 요청 수신
    • 피드백 생성 처리 후 결과를 leafresh-feedback-result-topic에 발행
  3. 결과 수신 및 저장
    • BE는 leafresh-feedback-result-sub를 통해 결과 메시지를 수신
    • 유효성 검증 → 이벤트 발행 → DB 저장 + Redis 캐싱
  4. 사용자 응답 (롱폴링)
    • FE는 주기적으로 GET /api/members/feedback으로 롱폴링 요청
    • Redis → DB 순으로 캐시 히트 여부 확인 후 응답

 

4. 장애 복원력 강화: DLQ 및 재시도 정책

GCP Pub/Sub 기반 비동기 구조로 전환하면서, 다음과 같은 DLQ(Dead Letter Queue) 및 재시도(Retry) 정책을 적용하여 장애 복원성과 메시지 유실 방지력을 강화하였다.

 

  • DLQ(Dead Letter Queue)
    • 최대 5회까지 재시도 후 실패한 메시지는 leafresh-feedback-result-dlq-topic에 이동
    • DLQ 구독자(leafresh-feedback-result-dlq-sub)가 이를 수신 후 다음 처리:
      • FeedbackFailureLog 저장
      • Discord 등 운영 알림 전송 예정
      • 수동 재처리를 위한 API 제공 예정
  • 자동 재시도
    • GCP Pub/Sub의 기본 재전송 설정(최대 5회, 즉시 재시도)
    • 향후 처리량 증가 시 지수 백오프(Exponential Backoff) 적용 검토
  • 멱등성 보장
    • feedbackRequestId를 기준으로 결과 중복 저장 방지
    • 재전송되더라도 동일 ID는 한 번만 처리
  • 로깅 및 모니터링
    • 실패 시간, 요청자, 원인 등 로그로 기록
    • GCP Cloud Logging, Error Reporting 연동
    • DLQ 메시지 임계량 초과 시 운영 알림 전송
  • 데이터 보존 정책항목 기간
    구독 메시지 7일
    DLQ 메시지 7일
    DLQ 아카이빙 BigQuery 또는 GCS 아카이빙 예정

 

5. 비동기 구조의 장점

항목 개선 전 (동기 구조) 개선 후 (비동기 Pub/Sub)
장애 복원력 AI 서버 죽으면 전체 요청 실패 AI 서버 죽어도 메시지는 큐에 적재되어 추후 처리 가능
메시지 유실 방지 AI 서버가 살아야 큐 적재 가능 BE에서 메시지 발행 → 큐 보존
확장성 요청 수 증가 시 AI 서버 부하 급증 큐 기반 처리로 AI 서버 탄력적 확장 가능
재시도 및 백오프 직접 재요청 필요 BE 및 AI 양쪽에서 retry + backoff 설정
장애 감지 불명확 (FE는 무한대기) DLQ(Dead Letter Queue) 및 로그 기반 추적 가능
사용자 응답 UX 무한 대기 롱폴링 기반으로 UX degradation 최소화

 

 

6. 확장 고려 요소

  • 추가로 고려할 수 있는 항목:
    • 백프레셔(Backpressure) 대응 전략: AI 서버 처리 병목 시 큐 적재율 모니터링
    • 메시지 순서 보장: 피드백 요청 순서가 중요할 경우 orderingKey 고려
    • Burst 처리: 트래픽 급증 시 Pub/Sub와 AI 서버 사이에 버퍼 계층 도입 고려 (예: Cloud Tasks, Cloud Run Job)

 

7. 결론

GCP Pub/Sub 기반 비동기 구조 전환은 단순한 통신 방식의 변화가 아니라, 다음과 같은 핵심적인 효과를 가져왔다:

  • 서비스 복원력 확보: 장애에도 불구하고 요청 유실 없이 복구 가능
  • 운영 유연성 향상: DLQ 및 재시도 기반의 안정적인 에러 핸들링
  • 확장성 및 성능 개선: 트래픽 급증에도 대응 가능한 구조 확보

또한, GCP Pub/Sub을 기반으로 DLQ 처리, 캐싱 전략(Redis), 이벤트 기반 저장(Event Listener) 등 확장 가능한 이벤트 기반 백엔드 구조의 초석이 되었다.

 

 

8. 향후 개선 방향

  • WebSocket 또는 SSE 기반 실시간 알림 기능 도입
  • Firebase Cloud Messaging 기반 사용자 피드백 완료 알림
  • DLQ 처리량 및 실패율 기반의 운영 대시보드 시각화
  • BigQuery 기반 DLQ 메시지 통계 및 AI 처리 이력 관리