CoWorker: DAG 기반 비동기 자동화 시스템 설계
CoWorker: DAG 기반 비동기 자동화 시스템 설계 보고서
1. 문제의 시작
AI 기반 콘텐츠 오케스트레이션 시스템이 제대로 된 가치를 만들려면, 사용자가 늘 화면을 보고 있을 필요 없이 스스로 일을 처리할 수 있어야 했다. 하지만 현실은 달랐다.
이메일을 보내놓고 답장을 기다려야 하고, 정해진 시간에 게시물을 올려야 하고, 사용자 반응을 계속 지켜봐야 했다. 이런 복잡한 비동기 작업들을 안정적으로 처리하는 일이 생각보다 어려웠다. 간단한 스크립트로는 부족했고, 모든 걸 LLM에게 맡기자니 통제하기도 어렵고 비용도 만만치 않았다.
이 보고서는 이런 문제를 해결하기 위해 만든 안정적이고 확장 가능한 백그라운드 자동화 시스템, CoWorker의 아키텍처 설계 과정을 정리했다.
CoWorker가 해결해야 할 과제
CoWorker는 각 페르소나 계정별로 반복되는 이메일 보내기, 게시물 올리기 같은 아웃바운드 작업과 댓글 달기, 답장 받기 같은 인바운드 상호작용을 완전히 자동화하는 시스템이다. 구체적으로 다음 기능들을 갖췄다.
- 자동 초안 만들기: 외부 트렌드 데이터를 주기적으로 모아서 콘텐츠 초안을 자동으로 생성한다
- 예약 발행과 관리: 사용자가 지정한 시간에 콘텐츠를 정확히 올리고, 발행 전에 알림을 보내 취소할 기회도 준다
- 비동기 워크플로우 처리: 이메일을 보낸 후 상대방의 답장을 감지해서 중단됐던 작업을 다시 시작하고 다음 단계로 진행한다
- 상호작용 모니터링: 올라간 콘텐츠에 달리는 댓글을 계속 지켜보고, 미리 정한 정책에 따라 자동으로 답변한다
- 성과 모으기와 보고: 일일 KPI나 댓글 추이 같은 핵심 지표들을 자동으로 계산해서 사용자에게 보고한다
- 통합 가시성 제공: 타임라인 뷰를 통해 초안 만들기, 발행, KPI 계산, CoWorker의 모든 자동화 활동을 시간순으로 한눈에 볼 수 있게 했다
설계의 기본 방향
- 외부 API는 스케줄 행만 만들고 취소한다: 클라이언트는 스케줄 만들기와 취소만 담당하고, 실제 실행은 시스템 안에서 처리한다
- 모든 실행 로직은 내부 플로우의 DAG로 표현한다: 비즈니스 로직은 재사용 가능한 내부 플로우들의 조합으로 구성된다
- 레지스트리는 내부 플로우 조합만 정의한다: HTTP 의존성 없이 순수한 도메인 로직만 담아 재사용성을 높였다
- 액션 레이어는 사용자 맥락과 검증, 배치 계산에 집중한다: 사용자 권한, 입력 검증, 배치 스케줄링 같은 외부 인터페이스 관련 로직을 담당한다
- CoWorker는 스케줄과 DagExecutor만 알면 된다: 워커는 그저 스케줄을 가져와서 DAG를 실행하는 일에만 집중한다
2. 어떤 방식을 선택할까
최종 아키텍처를 정하기 전에, 문제를 해결할 수 있는 여러 방법을 비교해봤다. 각 방식의 장단점과 trade-off를 꼼꼼히 따져보는 과정은 시스템의 장기적인 안정성과 효율성을 담보하는 필수 단계였다.
첫 번째 방식: 간단한 Cron과 스크립트
주기적인 작업을 위해 Cron 같은 스케줄러를 쓰고, 각 작업은 독립적인 스크립트로 만드는 가장 전통적인 방법이다.
좋은 점:
- 빨리 만들 수 있다: 개념이 단순해서 초기 개발 속도가 아주 빠르다
- 복잡도가 낮다: 간단한 예약 작업(예: 매일 자정에 데이터 모으기)을 처리하기에 직관적이다
문제점:
- 복잡한 워크플로우를 못 한다: 작업 간 의존성, 조건부 실행, 상태 바뀜 같은 복잡한 비즈니스 로직을 표현하고 관리하기 어렵다
- 외부 이벤트에 반응 못 한다: 이메일 답장 같은 비동기적 외부 이벤트에 적극적으로 반응해서 워크플로우를 다시 시작하는 구조를 만들기 거의 불가능하다
- 확장성과 재사용성이 떨어진다: 로직이 각 스크립트에 흩어져 있어서 재사용하기 어렵고, 새 워크플로우를 추가할 때마다 중복 코드가 생기기 쉽다
두 번째 방식: LLM 중심의 완전 자율 에이전트
LLM에게 목표를 주면, LLM이 스스로 계획을 세우고 도구를 써서 작업을 끝내는 완전 자율 에이전트 모델이다.
좋은 점:
- 유연성이 높다: 복잡하고 추상적인 자연어 지시를 해석하고 유연하게 대처할 잠재력을 가졌다
문제점:
- 안정성에 심각한 문제가 있다: ‘LLM 사용 시점 제한’ 원칙에 정면으로 어긋난다. LLM의 비결정론적 성격 때문에 같은 입력에도 결과가 달라질 수 있어, 시스템 동작을 예측하고 제어하기가 극도로 어렵다
- 통제 불가능과 안전 위험: LLM이 잘못된 계획을 세우거나 작업을 잘못 실행했을 때 이를 통제하거나 되돌릴 명확한 방법이 없다
- 비용이 많이 들고 재현성이 없다: 모든 단계를 LLM에 의존하니 비용이 기하급수적으로 늘고, 디버깅과 테스트를 위한 재현성을 확보할 수 없다
세 번째 방식: 상태 저장과 DAG 기반 워크플로우 엔진
작업의 단계를 노드(Node)로, 작업 간 순서와 의존성을 엣지(Edge)로 정의하는 방향성 비순환 그래프(DAG, Directed Acyclic Graph)를 기반으로 워크플로우를 실행하는 방식이다. 모든 작업 상태는 데이터베이스에 명확히 저장된다.
좋은 점:
- 워크플로우 정의가 명확하다: 복잡한 의존성을 가진 작업들을 시각적이고 직관적인 DAG 구조로 명확하게 정의할 수 있다
- 상태 저장과 재개 기능: 각 작업의 상태(실행 전, 실행 중, 완료, 실패)를 저장하니, 외부 이벤트를 기다리며 워크플로우를 안전하게 멈추고 다시 시작할 수 있다
- 재사용성이 높다: 개별 비즈니스 로직을 재사용 가능한 ‘Flow’ 단위로 모듈화해서, 이를 조립하여 새 워크플로우를 쉽게 만들 수 있다
- 관측 가능성이 뛰어나다: 모든 실행 상태와 결과가 데이터베이스에 기록되니, 지금 어떤 작업이 어디까지 진행됐는지 명확하게 추적하고 디버깅할 수 있다
문제점:
- 초기 설계가 복잡하다: DAG 명세, 실행 엔진(Executor), 상태 관리 등 초기 시스템 설계와 구현에 더 많은 노력이 필요하다
- 학습 곡선이 있다: 개발팀이 DAG 기반 워크플로우 개념과 실행 엔진의 동작 방식을 배워야 한다
각 방식의 장단점을 종합적으로 따져본 결과, 초기 구현 복잡도라는 단점에도 불구하고 세 번째 방식인 상태 저장과 DAG 기반 워크플로우 엔진이 우리가 마주한 복잡한 비동기 자동화 과제를 가장 안정적이고 확장 가능하게 해결할 수 있는 최적의 방향이라고 결론 내렸다.
3. 최종 설계: DAG 기반 CoWorker 시스템
문제 정의와 여러 방식 비교를 통해, CoWorker 시스템은 상태 저장과 DAG 기반 워크플로우 엔진을 최종적으로 선택했다. 이 설계는 비즈니스 로직의 복잡성을 명확한 구조로 풀어내고, 비동기 이벤트를 안정적으로 처리하며, 코드 재사용성을 극대화해서 안정성, 확장성, 유연성이라는 핵심 가치를 모두 갖췄다.
핵심 컴포넌트와 역할
CoWorker 아키텍처는 다음과 같은 핵심 컴포넌트들로 이뤄지며, 각 컴포넌트가 명확한 역할을 하면서 유기적으로 연결된다.
Scheduler
- 모든 예약 작업의 단일 진입점인 schedules 테이블을 관리한다
- 주기적으로 실행할 항목을 골라서 워커에게 전달한다
- PostPublication, InsightSample 같은 모든 예약 작업을 한 곳에서 관리해서 일관성을 확보한다
DAG Executor
- schedules 레코드의 dag_spec을 해석해서 정해진 순서와 의존성에 따라 Flow를 실행하는 엔진이다
- Chat과 CoWorker가 같은 실행 엔진을 공유해서 로직 재사용성을 극대화했다
Resumable Flows
request_reschedule을 통해 작업을 잠시 멈추고, 외부 이벤트(_resume컨텍스트)를 통해 다시 시작되는 로직 단위다- 이메일 답장처럼 오랜 시간 기다려야 하는 비동기 상호작용을 안정적으로 구현했다
Sniffer Worker
- 외부 채널(이메일, 소셜 미디어)을 계속 확인해서 재개 이벤트를 찾는다
- 관련 스케줄의
_resume컨텍스트를 채워서 바로 실행되도록 한다 - 시스템 외부의 비동기적 사용자 입력을 워크플로우와 연결하는 핵심 다리 역할을 한다
Lease System
- 사용자(PersonaAccount) 단위로 CoWorker의 자동 실행 여부와 빈도를 조절하는 권한 관리 메커니즘이다
- 사용자가 시스템의 자동화 수준을 직접 통제할 수 있게 해서 안정성과 통제권을 제공했다
왜 이 설계를 선택했나
이 아키텍처를 선택한 이유는 다음과 같은 구체적인 장점들 때문이다.
1. 안정성과 예측 가능성
LLM의 역할은 창의적 콘텐츠 ‘생성’에만 명확히 한정시키고, 전체 워크플로우의 ‘오케스트레이션’은 결정론적인 DAG로 처리한다. 덕분에 시스템 동작이 예측 가능해지고, 이상한 동작이나 무한 루프 같은 잠재적 위험을 근본적으로 막아 안정성을 확보했다.
2. 모듈성과 재사용성
모든 비즈니스 로직은 재사용 가능한 Flow라는 단위로 분리해서 Registry에 중앙 관리된다. compose_trends_email, await_reply 같은 Flow들은 레고 블록처럼 기능해서, 이들을 DAG로 조립하면 새로운 워크플로우를 빠르고 안전하게 만들 수 있다. 이는 개발 생산성을 높이고 유지보수 비용을 줄이는 효과를 가져왔다.
3. 비동기 처리와 상태 관리
Schedule 엔티티가 작업의 상태(status), 중간 결과와 컨텍스트(context), 다음 실행 시간(due_at), 실행할 로직(dag_spec)까지 모든 것을 관리하는 명확한 진실의 원천(SoT, Source of Truth) 역할을 한다. 그래서 시스템에 문제가 생겨도 Schedule 레코드만으로 상태를 완벽히 복구할 수 있고, 복잡한 비동기 워크플로우의 현재 상황을 명확하게 추적할 수 있다.
4. 실제로 적용해보니
설계한 DAG 기반 아키텍처가 실제 비즈니스 시나리오에 적용됐을 때, 복잡한 자동화 요구사항을 얼마나 효율적이고 안정적으로 처리하는지 구체적인 사례를 통해 보여줄 수 있다. 대표적인 워크플로우인 ‘트렌드 수집과 이메일 답장 기반 초안 만들기’ 시나리오를 통해 그 효과를 자세히 살펴보자.
실제 워크플로우 예시: 이메일 상호작용 전 과정
이 시나리오는 CoWorker가 트렌드 기반 이메일을 사용자에게 보내고, 사용자의 답장을 받아 최종 콘텐츠 초안을 만드는 완전 자동화된 흐름이다.
1. 스케줄 만들기
사용자 요청이나 내부 트리거에 의해 mail.trends_with_reply 템플릿을 기반으로 Schedule 레코드가 생성된다. 이 레코드에는 실행할 작업의 청사진인 dag_spec이 들어 있다.
status: pending
2. 메일 보내기
CoWorker가 실행 시간이 된 스케줄을 가져간다. DagExecutor는 DAG의 첫 번째 노드인 compose_trends_email Flow를 실행해서 이메일을 보내고, 추적을 위한 고유 식별자인 pipeline_id를 context에 저장한다.
status: running
context.pipeline_id: [ID]
3. 답장 기다리기
다음 노드인 await_reply Flow가 실행된다. 이 Flow는 request_reschedule을 호출해서 스케줄을 장기 대기 상태로 바꾼다. due_at 필드가 먼 미래 시간으로 설정돼서 불필요한 실행을 막는다.
status: running
due_at: [future_timestamp]
4. 답장 감지하고 재개하기
Sniffer 워커가 주기적으로 이메일 서버를 확인하다가 사용자의 답장 메일을 찾는다. 메일 스레드에서 pipeline_id를 뽑아내서 원본 Schedule 레코드를 찾고, context._resume 필드에 답장 내용을 저장한 뒤 due_at을 지금 시간으로 바꿔서 바로 실행되도록 한다.
context._resume: [reply_content]
due_at: [current_time]
5. 초안 만들기
CoWorker가 바로 스케줄을 다시 가져가서 재개한다. DagExecutor는 _resume 컨텍스트에 담긴 답장 내용을 다음 노드인 ingest_draft_mail Flow의 입력으로 써서, 이를 바탕으로 최종 Draft 엔티티를 생성한다.
6. 완료
DAG의 모든 노드가 성공적으로 실행되고, 스케줄의 최종 상태가 done으로 바뀌면서 워크플로우가 성공적으로 끝난다.
status: done
주요 성과 비교
CoWorker 아키텍처를 도입한 결과, 기존의 수동적이고 파편화된 프로세스가 다음과 같이 완전히 바뀌었다.
| 구분 | 이전: 수동 프로세스 | 이후: CoWorker 도입 |
|---|---|---|
| 프로세스 | 모든 단계를 사람이 직접 처리 (트렌드 확인 → 메일 작성 → 발송 → 회신 확인 → 초안 작성) | 이메일 회신만 빼고 모든 과정이 100% 자동화된 End-to-End 워크플로우로 바뀜 |
| 확장성 | 처리량이 사람 수에 따라 늘어나서 확장성이 거의 없었음 | 새 채널(Slack, SMS 등)이나 워크플로우를 Flow와 DAG 추가만으로 쉽게 확장 가능 |
| 추적과 관리 | 각 단계가 따로 놀아서 전체 진행 상황 파악이 어렵고, 문제 생겼을 때 원인 찾기가 힘들었음 | Schedule 엔티티와 타임라인 뷰로 모든 작업 상태를 실시간으로 명확하게 추적하고 관리 가능 |
| 안정성 | 사람 실수(Human Error)가 생길 가능성이 높고, 비동기 대기 중 작업이 빠질 수 있었음 | 결정론적 DAG 실행으로 프로세스가 안정적이고, 상태 저장 기반으로 시스템 장애 시에도 안전하게 복구 가능 |
얻은 효과와 개선점
운영 효율성 극대화: 복잡한 비동기 워크플로우를 완전히 자동화해서 인적 자원을 창의적이고 전략적인 일에 집중할 수 있게 됐다.
시스템 안정성 확보: 결정론적 실행 엔진과 상태 저장 기반 복구 메커니즘으로 24/7 무인 운영이 가능한 수준의 안정성을 달성했다.
확장성과 유연성: 모듈화된 Flow 기반 설계로 새로운 비즈니스 요구사항에 빠르게 대응하고 지속적으로 기능을 확장할 수 있다.
결론
CoWorker의 DAG 기반 아키텍처는 복잡한 비동기 자동화 요구사항을 안정적이고 확장 가능하게 해결하는 최적의 솔루션임을 증명했다. LLM의 역할을 명확히 제한하고, 결정론적 워크플로우 엔진으로 시스템의 예측 가능성과 안정성을 확보하면서도, 모듈화된 설계로 높은 재사용성과 확장성을 달성했다.