외부 서비스 장애 경험을 통해 본 동기 호출 구조의 한계

최근 시스템 설계를 공부하면서, “이 이론을 지금 프로젝트에 적용해보면 어떨까?”라는 질문에 시작하여 블로그 글을 작성하게되었습니다. 현재 진행중인 프로젝트는 외부 서비스와 통합하여 분석 파이프라인 서비스를 제공하고 있습니다. 이 실무 과정에서 외부 서비스와 관련되어 문제가 발생하였습니다.

자료 업로드 후, 외부 전처리 API를 호출했는데, 500 오류가 발생하였습니다. 내부 시스템은 이 오류에 대한 정확한 원인을 파악할 수 없어, 타임아웃으로 처리하였습니다.

이런 문제는 사용자에게 무한 로딩에 빠지게 하거나, 시스템이 실패하지 않았어도, 전체가 실패한 것으로 보여지게 됩니다.

또한, 현재 MSA 환경에서 특정 서비스가 지연/실패가 나면, 다른 서비스로 장애 전파될 가능성이 큽니다. 이 환경에서 타임아웃 조정만으로 근본적인 해결이 되지 않는다고 느꼈습니다.

외부 서비스의 오류를 근본적으로 고치기는 힘들고, 내부적으로 가용성을 높이는 구조로 설계하여 사용자에게 원활한 서비스를 제공하는 것을 목표하였습니다.

그래서 이번 글에서는 이 실무 사례로 동기 호출 기반 서비스를 상태 기반 이벤트 구조로 재해석한 과정을 정리하였습니다.

이 설정한 목표를 위해 우선 시스템 관점과 도메인 관점으로 2가지로 나누었습니다.

  • 시스템의 가용성을 높여 장애 대응 및 원환할 시스템을 제공하는 것
  • 높은 품질의 데이터로 분석/추론 결과로 나오는 것

현재 시스템 아키텍처는 API 게이트웨이를 통해 사용자 요청을 받고, 여러 서비스에 라우팅하고 있습니다.

API 게이트웨이가 부하를 분산하여 라우팅하는 것으로 트래픽을 처리해줄 수 있습니다. 하지만, 사용자 요청이 분산되어 들어와도 내부 동기 통신은 가용성이 저하되는 상황들이 생길 수 있습니다.

즉, 시스템 목적에 따라 동기식 통신이 장점이지만, 현재 MSA 환경에서는 오로지 동기식 통신만으로 외부 서비스와 통신하는 것은 장애가 일어날 가능성이 높습니다.


1. 가용성과 데이터 품질을 위한 설계 접근

동기 HTTP 통신에서 일반적인 웹 서버는 요청 1건당 스레드 1개를 할당합니다. 이 구조에서 외부 서비스 호출이 발생하면, 응답이 반환될 때까지 해당 스레드는 대기 상태로 점유됩니다.

문제는 외부 API가 지연되거나 실패하는 경우 응답 시간이 길어질수록 스레드 점유 시간이 증가하며, 동시 사용자 수가 많아질 경우 스레드 풀 고갈이 발생할 수 있습니다. 또한 트랜잭션이 열린 상태라면 DB 커넥션 역시 장시간 점유될 수 있어, 커넥션 풀 고갈로 이어질 가능성도 존재합니다.

이 경우, 단일 서비스의 지연이 시스템 전체의 성능 저하로 이어지며, 특정 외부 서비스의 장애가 연쇄적으로 다른 기능의 응답 지연을 유발하는 현상인 장애 전파로 이어질 수 있습니다.

특히, 업로드 → 전처리 → 분석 → 추론과 같이 순차적으로 실행되는 파이프라인에서 동기 호출을 사용한다면, 이전 단계의 완료되어야 다음 단계가 실행되는 시간적 결합이 발생하는데, 특정 단계의 지연/실패는 전체 흐름 중단으로 확산될 수 있습니다.

사용자가 파이프라인을 구성하는 기능은 작업간의 의존성이 높으면, 시스템의 유연성, 재사용성이 저하됩니다. 그래서, 시간적 강한 결합도를 낮추어, 유연성, 재사용성을 해결하고 가용성을 높이는 비동기식 이벤트 기반 처리으로 실무 장애 사례를 설계 관점에서 재해석해보았습니다.

우선 시스템 목표를 달성하기 위한 조건을 살펴보겠습니다.

시스템 가용성 조건

  • 어떠한 외부 서비스가 실패/지연해도 전체 시스템은 장애 영향을 받지 않고, 실행되어야 한다.
  • 특정 작업이 실패해도 다른 작업들은 실행이 되어야 한다.
  • 다수 사용자에게는 작업의 실패/대기/재시도등과 같은 상태를 일관적으로 제공되어야 한다.

품질 데이터 확보 조건

  • 전처리 서비스의 표준화, 중복 제거, 노이즈 감소등 전처리 완료가 되어야 한다.
  • 전처리가 완료된 데이터로 분석과 추론을 진행한다.
  • 추론은 필수 입력은 전처리된 데이터이고, 옵션 입력은 분설 결과로 사용하여 결과를 낸다.

2. 설계 접근 방식

  • 실패 시, 예외로 터뜨려 전체를 중단시키는 방식이 아닌 변경 상태로 분류하여 시스템 흐름이 계속 이어지도록 한다.
  • 시간적 결합을 위해 이벤트를 발행하여 서비스 간 느슨한 결합으로 진행한다.
  • 특정 장애가 전체 장애 전파가 발생하지 않기 위해 상태 전이 기반 파이프라인으로 진행하도록 한다.
  • 각 특정 단계가 실행될 때마다, 상태 전이 기반 파이프라인이 실행하여 데이터 품질을 유지하도록 한다.
목표해결해야 할 상황설계 접근 방식
시스템 가용성외부 서비스 장애가 발생해도 전체 시스템은 멈추지 않는다.도메인과 운영을 분리하여 장애가 파이프라인 전체로 확산되지 않도록 설계
특정 작업이 실패해도, 다른 작업은 실행되어야 한다.서비스 간 직접 호출 대신 메시지 큐 기반 비동기 처리 구조 적용
사용자에게 작업 상태를 일관되게 보여줘야 한다.작업 상태로 전이하는 파이프라인 관리
데이터 품질전처리 완료된 데이터만 분석과 추론에 사용하여야 한다.전처리 성공/실패 상태를 이벤트로 발행하여 다음 단계 실행 여부를 결정

위의 표와 같이, 시스템 목표를 가용성과 데이터 품질로 설정하였고, 각 목표에 대한 상황과 설계 접근 방식을 정리하였습니다.

이를 통해 일부 외부 서비스가 실패하더라도 전체 시스템이 정상 작동하여, 사용자에게도 작업의 진행 상태를 일관되게 제공하도록 목표를 설정하였습니다. 또한 데이터 품질을 확보하기 위해 전처리 서비스의 요청/결과 등을 이벤트 상태로 관리하도록 하였습니다. 전처리가 실패하거나 완료되지 않은 경우에는 재처리할 수 있도록 하여 데이터 품질을 보완하였습니다. 이제 이러한 설계 목표를 기반으로 시스템의 도메인 관심사와 비관심사를 분리하여 살펴보겠습니다.


3. 도메인 로직과 파이프라인 제어의 분리

도메인은 특정 문제를 해결하기 위해 다루는 업무 영역으로, 그 안에서 통용되는 개념·규칙·역할이 모두 포함된 비즈니스 영역을 말합니다. 도메인 비관심사는 본질과는 거리가 있는 보조 기능·기술적 처리로, 주변·횡단 기능을 말합니다. 아래는 도메인 관심사와 비관심사로 우선 분리하였습니다.

도메인 관심사

  • 전처리 외부 서비스는 표준화/중복제거/노이즈 감소등 전처리를 수행한다
  • 네트워크 분석/추론은 전처리된 데이터를 기반으로 실행되어야 한다.
  • 추론은 필수 입력은 전처리 결과고, 옵션 입력으로 분석 결과를 조합해 결과를 생성한다.

도메인 비관심사

  • 특정 단계가 지연/장애 나도 전체 파이프라인은 중단되지 않아야 한다.
  • 진행/재시도/재실행을 사용자에게 제공해야 한다.

높은 응집도는 서비스가 하나의 명확한 책임만 가지는 것입니다. 이를 기반으로 설계를 한다면, 도메인 관심사는 입력 데이터를 전처리 영역에서 정제하고, 성공/실패 상태 기반으로 이벤트를 설계합니다. 즉, 다른 외부 서비스도 마찬가지이며, 전체적인 외부 서비스는 요청/결과의 상태를 기반으로 이벤트로 설계합니다. 비관심사는 파이프라인 실행 후, 상태 전이 규칙과 재시도/실패 정책을 결정합니다.

그래서 책임 영역을 분리 구조로 설계하면 각 서비스는 맡은 책임 영역만을 수행합니다. 각 서비스가 자신이 맡은 규칙만 책임지고, 이로 인한 비관심사 영역은 응집도가 상승합니다.

기존 구조에서는 업로드 서비스가 전처리 제어, 오류 정책, 다음 단계 호출까지 함께 담당하고 있어 책임이 포함되었습니다. 각 단계가 자신의 도메인만 수행하도록 책임을 재배치하면, 전처리는 정제만, 분석은 연산만, 추론은 판단만 담당하여 분리할 수 있습니다. 이를 통해 각 서비스는 하나의 책임만을 가져 응집도가 높아집니다.


4. 상태 기준 이벤트 모델링

상태 기준은 DB의 상태 컬럼을 기준으로 이벤트를 세분화합니다. 그래서 각 단계는 이전 상태를 기반으로 실행 조건을 판단합니다.

동기 호출 기반 파이프라인에서 단계별 성공/실패가 나와 장애가 났을 경우, 단계별 흐름으로 사용자에게 전체 시스템 실패로 보여질수 있습니다. 가용성 목표 달성을 위해 완료 외 최소한 실패/타임아웃 이벤트도 있어야 장애 격리와 재처리 설계가 됩니다. 결국, 상태 기반 이벤트로 실행/장애격리/재처리를 진행하도록 하여, 시스템 가용성을 높입니다.

그래서 각 파이프라인 단계는 다음과 같은 상태를 갖는 이벤트를 발행하도록 정의했습니다.

  • 성공(SUCCESS): 다음 단계가 실행될 수 있는 조건이 충족됨
  • 실패(FAILED): 도메인 규칙상 다음 단계 진행 불가로 보정/재처리 필요
  • 타임아웃(TIMEOUT): 외부 의존성 지연으로 시스템 관점에서 재시도/격리 판단 필요

예를 들어 “전처리 단계”는 위 그림과 같이 성공은 PreprocessCompleted, 실패는 PreprocessFailed, 타임아웃은 PreprocessTimedOut 이벤트로 표현합니다.

이벤트의 큰 유형을 2가지로 분류하면, 상태 완료 이벤트(성공)와 예외 이벤트(실패/타임아웃)로 분류합니다. 분류된 이벤트는 상태 변경을 메시지로 설정합니다.

이렇게 분석 단계마다 성공/실패/타임아웃 상태로 이벤트를 모델링합니다. 상태 전이마다 이벤트가 등록되고, 수신자는 이 이벤트를 다음 처리로 실행할지 후처리로 사용할지 결정합니다. 그래서, 실패해도 시스템이 처리해야 할 상태로 인식하여 이벤트 모델링은 운영 정책에 따라 후속 처리를 분기할 수 있습니다.


5.메시지 큐를 통한 이벤트 기반 비동기 처리 구조

왜 메시지 큐가 필요한가?

동기 호출 기반 구조에서는 전처리 서비스가 지연되면 분석 서비스 역시 실행되지 못하고 대기하게 됩니다. 한 단계의 지연이 다음 단계의 실행에도 영향을 주게 되며, 결과적으로 파이프라인 전체의 처리 지연으로 이어질 수 있습니다. 이처럼 서비스 간 실행이 서로의 처리 시간에 의존하는 구조를 시간적 결합이라고 합니다.

이 문제를 완화하기 위해 서비스 간 직접 호출 대신 이벤트 기반 비동기 처리 구조를 고려해볼 수 있습니다. 이때 서비스 간 이벤트 전달을 안정적으로 처리하기 위한 매개체로 메시지 큐를 사용할 수 있습니다.

메시지 큐를 사용하면 각 서비스는 다른 서비스를 직접 호출하는 대신 이벤트를 발행하거나 수신하는 방식으로 동작하게 됩니다. 이를 통해 서비스 간 직접적인 의존성을 줄이고, 느슨한 결합 구조를 만들 수 있습니다.

메시지 큐 도입 효과

  • 단계 간 응답 대기를 제거한다.
  • 특정 단계 실패가 다른 단계 실행을 막지 않는다.
  • 소비자는 자신의 처리 속도에 맞게 메시지를 받는다.

메시지 큐의 구성 요소는 생산자와 소비자이며, 일부 서비스는 이벤트를 메시지로 발행하는 생산자 역할을 가지고, 일부 서비스는 특정 이벤트를 수신받아 작업을 수행하는 소비자 역할로 구성됩니다.

예를 들어 전처리 서비스가 작업을 완료하면 해당 상태를 이벤트로 발행하고, 분석 서비스는 이 이벤트를 수신하여 다음 단계를 실행할 수 있습니다.

생산자가 작업의 상태를 메시지로 발행하면, 메시지는 브로커 내부의 메시지 큐에 저장합니다. 여기서 소비자는 필요 메시지를 수신하여 작업을 수행하도록 합니다. 메시지 큐에 점차 메시지에 쌓여 발신된 메시지의 상태가 실패하더라도, 재시도 처리 및 후속 처리가 가능한 비동기 방식으로 작업을 처리할 수 있습니다. 또한, 소비자들이 자신이 실행 가능한 상태일 때, 메시지 수신을 하면 큐가 메시지의 처리 속도와 처리량을 조절하여 메시지를 분산합니다. 이런 구조는 일반적으로 Pub/Sub 모델로 구성될 수 있습니다.

이와 같은 구조를 통해 서비스 간 직접 호출 대신 이벤트 기반 메시징을 사용하게 되고, 결과적으로 기존의 동기 호출 구조에서 발생할 수 있는 시간적 결합과 장애 전파 문제를 완화하는 설계 방향을 고려할 수 있습니다.


6. 결과

결과적으로, 동기 호출 기반 파이프라인 구조를 상태 기반 이벤트 구조로 전환하면, 서비스 간 직접적인 호출 의존성을 줄이고, 각 서비스가 자신의 도메인 책임에 집중할 수 있는 구조로 만들 수 있습니다.

또한 파이프라인의 실행 흐름을 상태 이벤트로 관리함으로써, 특정 단계에서 실패나 지연이 발생하더라도 전체 프로세스가 중단되지 않고 재시도나 후속 처리를 유연하게 설계할 수 있습니다.

이러한 방식은 시스템의 결합도를 낮추고 서비스의 응집도를 높이는 방향으로 아키텍처를 개선하는 하나의 접근 방법이 될 수 있습니다.

이러한 구조를 통해 서비스는 다음과 같은 방향으로 개선될 수 있습니다.

  • 서비스 간 직접 의존성을 줄이고, 도메인 단위로 책임을 분리한다.
  • 동기 기반 호출을 이벤트 기반 메시지 구조로 전환한다.
  • 파이프라인 상태를 이벤트로 관리하여 시스템 가용성을 높인다.

본 글에서는 실무에서 경험한 동기 호출 기반 파이프라인의 한계를 바탕으로, 상태 기반 이벤트 구조로 전환하는 설계 방향을 정리해보았습니다. 이러한 접근이 반드시 정답은 아니지만, 외부 서비스와의 연동이 많은 시스템에서 가용성과 유연성을 높이기 위한 하나의 설계 방법으로 참고할 수 있을 것입니다.