유민우 · Tech Notes


오케스트레이션 시스템 통합 타임라인 아키텍처 설계

🗓️ 9/23/2025 👤 Role: System Architect
FastAPIPostgreSQLDSLDAG

1. 문제 정의 및 제약 조건

AI 콘텐츠 오케스트레이션 시스템의 기능 확장에 따라 시스템 내 이벤트와 데이터 소스가 기하급수적으로 증가하고 있다. 이러한 데이터 분산은 필연적으로 파편화된 사용자 경험을 초래하며, 사용자가 전체 활동 흐름을 파악하기 위해 높은 인지적 부담을 지게 된다. 이 글에서는 시스템이 직면한 데이터 파편화 문제를 명확히 정의하고, 이를 해결하기 위한 통합 솔루션의 구체적 요구사항을 기술한다.

1.1 배경: 분산된 데이터 소스와 파편화된 사용자 경험

현재 시스템은 다양한 비동기 컴포넌트들이 독립적으로 데이터를 생성한다. 각 컴포넌트는 고유한 역할을 수행하며 중요한 이벤트를 발생시키지만, 이들은 개별적인 데이터 사일로(silo)에 머물러 있다. 주요 이벤트 소스는 다음과 같다.

  • PostPublication 라이프사이클: 게시물의 초안(Draft) 생성부터 예약(Schedule), 발행, 그리고 사후 모니터링에 이르는 전 과정에서 발생하는 이벤트
  • InsightSample 메트릭: 캠페인 성과 및 KPI 측정 결과 데이터
  • Sniffer 트렌드 데이터: 외부 RSS 소스에서 수집된 최신 트렌드 정보
  • CoWorker 상호작용: 트렌드 분석(Sniffer) 결과에 기반한 콘텐츠 초안 자동 생성, 사전 정의된 정책에 따른 댓글 응답, 이메일 회신을 통한 초안 생성(POST /internal/events/draft-from-email) 등 백그라운드에서 수행되는 모든 자동화된 액션 기록

이러한 데이터 소스들이 통합된 뷰를 제공하지 않을 경우, 사용자는 자신의 활동에 대한 완전한 그림을 그리기 위해 여러 화면을 끊임없이 오가야 한다. 이는 사용자의 인지 부하를 가중시키고 시스템의 전체적인 사용성을 저해하는 핵심적인 원인이 된다.

1.2 핵심 요구사항: 통합된 시간 축 관점의 필요성

이 문제를 해결하기 위한 핵심 요구사항은 사용자에게 모든 중요 이벤트를 하나의 시간 축 위에서 조망할 수 있는 단일 뷰, 즉 **타임라인 뷰(Timeline View)**를 제공하는 것이다. 이 뷰는 시스템 내에서 발생하는 다음과 같은 이질적인 이벤트들을 시간 순서에 따라 매끄럽게 통합하고 필터링할 수 있는 인터페이스를 제공해야 한다.

  • 초안(Draft) 및 발행(Publication) 이벤트
  • 캠페인 인사이트 및 KPI 결과(Insight, KPI)
  • Sniffer를 통해 수집된 트렌드 데이터(Trend Data)
  • 댓글 응답, 자동 초안 생성 등 CoWorker의 모든 시스템 액션

1.3 아키텍처 제약 조건

새로운 아키텍처는 반드시 기존 시스템의 제약 조건을 준수하며 설계되어야 한다. 주요 제약 조건은 다음과 같다.

비동기 이벤트 생성 Generator, CoWorker, Sniffer와 같은 백그라운드 워커들은 Celery를 통해 비동기적으로 동작하며, 각기 다른 시점에 독립적으로 이벤트를 생성한다.

독립적인 데이터 모델 PostPublication, Campaign, Schedule 등 각 데이터 소스는 고유한 엔티티와 라이프사이클을 가지며, 독립적으로 관리된다.

단일 쓰기 창구(SSOT) 원칙 시스템의 모든 상태 변경은 Orchestrator API를 통해서만 이루어져야 한다. 이 원칙은 시스템의 핵심 설계 철학인 결정론적 실행(determinism)과 안정성(safety)을 보장하기 위함이며, 비결정론적 AI 작업은 콘텐츠 생성 단계에만 엄격히 국한된다. 내부 워커들은 작업 결과를 /internal/events/* 엔드포인트를 통해 보고하는 방식으로 이 원칙을 준수해야 한다.

이러한 요구사항과 제약 조건을 바탕으로, 문제 해결을 위한 몇 가지 아키텍처 접근 방식을 평가하였다.

2. 고려된 접근 방식

데이터 집계 아키텍처 패턴의 선택은 단순한 기술적 결정을 넘어, 시스템의 핵심 설계 원칙과 부합해야 하는 전략적 과제이다. 선정된 아키텍처는 클라이언트 측 인지 부하(client-side cognitive load)를 최소화하고, 백엔드 주도 오케스트레이션(backend-driven orchestration)이라는 시스템의 근본적인 방향성을 강화해야 한다. 이 글에서는 이러한 원칙에 입각하여 두 가지 주요 후보 아키텍처와 각각의 장단점을 심도 있게 분석한다.

2.1 후보 1: 클라이언트 측 집계

이 방식은 프론트엔드 애플리케이션이 데이터 집계의 책임을 지는 모델이다. 클라이언트는 타임라인을 구성하는 데 필요한 모든 데이터 소스에 대해 개별 API 엔드포인트(GET /timeline/post-publications, GET /timeline/trends 등)를 병렬로 호출한다. 이후, 브라우저에서 각기 다른 API로부터 수신한 데이터 스트림들을 병합하고, 중복을 제거하며, 시간순으로 정렬하는 모든 로직을 직접 수행해야 한다.

2.2 후보 2: 서버 측 합성

이 방식은 정교한 백엔드 오케스트레이션 계층을 활용하여 여러 데이터 조회 로직(Flow)을 서버 단에서 동적으로 연결(chaining)하는 모델이다. 백엔드는 필요한 모든 데이터를 내부적으로 조회하고 집계한 후, 완벽하게 정렬된 단일 데이터셋을 클라이언트에게 제공한다. 클라이언트는 오직 하나의 통합된 API 엔드포인트(GET /bff/timeline)만을 호출하면 된다. 이 아키텍처의 핵심 엔진은 Orchestrator Flow이다.

2.3 트레이드오프 분석

두 후보 아키텍처의 장단점을 비교 분석한 결과는 다음과 같다.

평가 기준후보 1: 클라이언트 집계후보 2: 서버 측 합성
클라이언트 복잡도높음: ‘씬 클라이언트(thin client)’ 원칙에 위배됨. 여러 데이터 스트림에 걸친 상태 동기화, 에러 처리, 병합 로직 등 복잡한 비즈니스 로직을 프론트엔드로 전가하여, 코드를 취약하고 유지보수하기 어렵게 만듦.낮음: 단일 API 응답을 받아 그대로 렌더링하면 되므로 클라이언트 로직이 매우 단순해짐.
백엔드 유연성 및 확장성낮음: 새로운 타임라인 소스가 추가될 때마다 클라이언트의 코드 수정 및 배포가 필수적임. 백엔드 API 시그니처 변경이 클라이언트에 직접적인 영향을 줌.높음: 데이터 소스 간 낮은 결합도(low coupling)를 촉진함. 새로운 타임라인 이벤트를 추가해야 할 경우, 독립적인 BFF Flow를 생성하기만 하면 되므로 클라이언트 애플리케이션에 전혀 영향을 주지 않음.
성능 및 네트워크 효율성비효율적: 다수의 네트워크 요청으로 인해 레이턴시가 증가하고, 모바일 환경에서 특히 불리함.효율적: 단 한 번의 네트워크 요청으로 모든 데이터를 가져오므로 빠르고 효율적임.
데이터 일관성낮음: 여러 API 호출 사이에 데이터가 변경될 수 있어 일관성이 깨질 위험이 존재함. (Race Condition)높음: 서버에서 트랜잭션과 유사하게 데이터를 한 번에 조회하여 일관된 스냅샷을 제공함.

분석 결과, 서버 측 합성 방식이 모든 평가 기준에서 명백한 우위를 보였다. 이 접근법은 다음 절에서 상세히 설명될 최종 아키텍처로 채택되었다.

3. 최종 아키텍처 결정 및 상세 설계

분석 결과를 바탕으로, 서버 측 합성(Server-Side Composition) 아키텍처를 최종적으로 채택하기로 결정하였다. 이 모델은 클라이언트의 복잡도를 혁신적으로 낮추고, 백엔드의 확장성을 극대화하며, 시스템의 핵심인 오케스트레이션 원칙과 완벽하게 부합한다. 이 글에서는 이 아키텍처의 구체적인 구현 방식을 4단계로 나누어, 여러 분산된 데이터 소스가 어떻게 하나의 DSL Flow로 연결되고 클라이언트에게 전달되는지 상세히 설명한다.

3.1 1단계: 데이터 소스의 모듈화

아키텍처의 가장 기초가 되는 단계는 각 데이터 소스를 독립적인 모듈로 분리하는 것이다. 게시물(Post Publications), 캠페인 KPI, 트렌드 데이터 등 타임라인을 구성하는 각각의 데이터 소스는 독립적으로 호출 가능한 BFF Flow로 정의된다. 예를 들어, GET /timeline/post-publications는 오직 게시물 관련 이벤트 데이터만을 조회하는 책임을 진다.

각 Flow는 자신의 데이터를 조회하고, 이를 시스템 전반에서 통용되는 표준 스키마인 TimelineEvent 형태로 변환하는 역할까지 담당한다. 이처럼 각 데이터 소스를 모듈화함으로써 개별 컴포넌트의 유지보수성을 높이고 시스템 전체의 결합도를 낮출 수 있다.

3.2 2단계: 오케스트레이터를 이용한 동적 흐름 체이닝

오케스트레이터는 이 아키텍처의 중앙 제어 시스템 역할을 한다. 사용자가 통합 타임라인을 요청하면, 오케스트레이터는 이 요청을 해석하여 필요한 BFF Flow들을 동적으로 연결하는 실행 계획(DAG, Directed Acyclic Graph)을 생성한다. 예를 들어, 모든 이벤트가 포함된 전체 타임라인을 요청하는 경우, 오케스트레이터는 트렌드 Flow, 캠페인 Flow, 게시물 Flow를 순차적으로 실행하는 계획을 수립한다.

3.3 3단계: 자동 합성을 위한 어댑터 메커니즘

이 단계는 분리된 Flow들을 마치 마법처럼 하나로 엮어주는 아키텍처의 핵심이다.

timeline_result_adapter의 도입

이 시스템을 위해 특별히 설계된 timeline_result_adapter는 Flow 실행 결과물을 합성하는 특수 목적의 어댑터이다.

레지스트리 패턴을 통한 자동 트리거

이 어댑터는 시스템의 중앙 레지스트리(registry)에 bff.timeline.*에서 bff.timeline.*으로의 전환 패턴에 대해 자동으로 실행되도록 등록된다. 즉, 이름이 bff.timeline.*으로 시작하는 Flow에서 또 다른 bff.timeline.* Flow로 제어가 넘어갈 때마다 이 어댑터가 자동으로 호출된다.

병합 로직 상세

Flow 체인이 실행되는 동안 어댑터는 다음과 같이 정밀하게 동작한다.

  1. dispatch 엔진이 체인의 첫 번째 Flow(예: bff.timeline.trends)를 실행한다.
  2. Flow가 완료되면, 어댑터는 그 결과물(TimelineEventCollectionOut)을 가로채 실행 컨텍스트 내부에 저장한다.
  3. dispatch 엔진은 이전 Flow의 결과를 입력으로 받지 않고, 독립적으로 두 번째 Flow(예: bff.timeline.campaigns)를 실행한다.
  4. 두 번째 Flow가 완료되면, 어댑터는 다시 그 결과물을 가로채어 첫 번째 단계에서 저장해 둔 이벤트 컬렉션과 새로 도착한 이벤트 컬렉션을 병합한다.
  5. 이 과정은 체인에 연결된 모든 Flow에 대해 반복된다.

최종 결과물 생성

체인의 마지막 Flow 실행이 끝나면, 어댑터는 그때까지 누적된 모든 TimelineEvent들을 타임스탬프(timestamp) 기준으로 최종 정렬하여 하나의 일관된 TimelineEventCollectionOut 페이로드를 생성한다.

3.4 4단계: 클라이언트로의 최종 데이터 전송

전체 데이터 흐름을 요약하면 다음과 같다.

  1. 클라이언트 애플리케이션(/timeline 페이지)은 통합된 단일 엔드포인트인 GET /bff/timeline으로 요청을 보낸다.
  2. Orchestrator는 요청을 받아 필요한 Flow들을 연결한 실행 계획(예: trends → campaigns → posts)을 구성한다.
  3. dispatch 엔진이 이 계획을 실행하며, 각 단계의 결과는 timeline_result_adapter에 의해 자동으로 병합되고 누적된다.
  4. 모든 Flow 실행이 완료되면, BFF는 완벽하게 정렬된 단일 JSON 배열(TimelineEvent 객체들의 리스트)을 클라이언트에게 반환한다.
  5. 프론트엔드는 이 데이터를 받아 복잡한 클라이언트 측 로직 없이 그대로 타임라인 뷰에 카드로 렌더링하기만 하면 된다.

결론적으로, 이 서버 주도 합성 아키텍처는 데이터 파편화라는 초기 문제를 직접적으로 해결하는 견고하고 분리된(decoupled) 솔루션을 제공한다. 이는 사용자의 높은 인지 부하를 단일하고 일관된 진실의 원천(single, coherent source of truth)으로 대체한다.

4. 기대 효과 및 향후 과제

서버 측 합성 아키텍처의 도입은 개발 효율성, 사용자 경험, 그리고 시스템 유지보수성 측면에서 상당한 긍정적 효과를 가져온다. 이 모델은 단순히 기술적인 문제를 해결하는 것을 넘어, 비즈니스의 빠른 변화에 민첩하게 대응할 수 있는 견고한 기반을 마련한다. 본 마지막 절에서는 아키텍처 도입의 정량적, 정성적 효과를 정리하고 향후 시스템 발전을 위한 개선 과제를 제시한다.

4.1 결과 지표: 아키텍처 도입 전후 비교

관점도입 전도입 후
사용자 경험여러 화면을 오가며 정보를 조합해야 하는 파편화된 경험. 높은 인지 부하 발생.핵심 UX 원칙인 Chat-first, Card-driven을 완벽하게 지원. 통합 타임라인은 채팅 인터페이스가 모든 백그라운드 활동을 반영하는 일관된 SystemMessage 카드 스트림으로 렌더링할 수 있는 핵심 데이터 계층을 제공하여, 직관적이고 매끄러운 경험을 완성함.
프론트엔드 개발다중 API 호출, 데이터 병합/정렬, 상태 동기화 등 복잡하고 오류 발생 가능성이 높은 로직을 클라이언트에서 직접 구현.단일 API 응답을 받아 상태 없이(stateless) 렌더링하는 단순한 패턴. 개발 생산성 향상 및 버그 감소.
백엔드 확장성새로운 데이터 소스를 타임라인에 추가하려면 클라이언트와 백엔드 양쪽의 상당한 코드 수정이 필요. 결합도가 높음.새로운 BFF Flow를 하나 정의하고 등록하기만 하면 어댑터가 자동으로 통합을 처리. 새로운 기능 추가가 매우 용이하고 시스템 결합도가 낮음.

4.2 보완점 및 향후 개선 과제

현재 아키텍처는 매우 강력하지만, 더 나은 시스템으로 발전하기 위해 다음과 같은 과제들을 고려할 수 있다.

실시간 업데이트 강화

현재 타임라인은 사용자가 요청하는 시점의 스냅샷을 제공한다. 향후 WebSocket/SSE 기술을 도입하여, 백그라운드 워커가 상태 보고를 위해 사용하는 바로 그 내부 이벤트(예: publish-done, brief-ready)를 구독하고 /internal/events/* 엔드포인트에서 발생하는 데이터를 실시간으로 스트리밍함으로써, 사용자에게 끊김 없는 피드백 루프를 제공할 수 있다.

고급 필터링 및 검색

현재는 시간 기반 필터링을 지원하지만, BFF 타임라인 엔드포인트에 이벤트 타입, 상태, 특정 키워드 등을 기준으로 데이터를 조회할 수 있는 고도화된 서버 측 필터링 및 검색 기능을 추가하여 사용자의 데이터 탐색 능력을 향상시킬 수 있다.

성능 최적화

타임라인에 수만 개 이상의 이벤트가 누적될 경우를 대비해야 한다. 현재의 limit 파라미터를 넘어서는 커서 기반(cursor-based) 페이지네이션을 도입하고, 자주 조회되는 기간의 타임라인 데이터를 Redis에 캐싱하는 전략을 통해 대규모 데이터에 대한 응답 성능을 최적화할 필요가 있다.