TIL - 20260930

juni·2일 전

TIL

목록 보기
466/467

0930 운영 자동화/AI 워크플로우 심화 (24/N): Workflow Orchestration, Saga, Compensation과 부분 실패 복구


✅ 1. 자동화가 길어질수록 ‘한 함수’로 처리하면 문제가 생긴다

처음에는 자동화가 단순하다.

주문 상태 변경
→ 알림톡 발송

하지만 기능이 늘어나면:

주문 상태 변경

↓

Audit Log

↓

NotificationJob 생성

↓

알림톡 발송

↓

외부 Provider 응답

↓

Webhook 수신

↓

고객 상태 업데이트

↓

관리자 알림

↓

통계 반영

처럼 여러 단계가 연결된다.

AI 자동화도 비슷하다.

Git 변경 수집

↓

LLM 분석

↓

Markdown 생성

↓

파일 저장

↓

Notion 업로드

↓

Git Commit

↓

Push

이걸 하나의 함수에서 끝까지 처리하면 운영이 어려워진다.


✅ 2. 긴 작업에서는 ‘전체 성공 / 전체 실패’만으로 부족하다

예를 들어:

Step 1
Git 수집
SUCCESS

Step 2
LLM Report 생성
SUCCESS

Step 3
Markdown 저장
SUCCESS

Step 4
Notion Upload
FAILED

라고 하자.

이때 전체 Workflow를:

FAILED

라고만 기록하면 정보가 부족하다.

이미 앞의 세 단계는 정상 완료됐다.

따라서 중요한 것은:

어디까지 성공했는가?

어디서 실패했는가?

다시 시작할 때 어디부터 시작해야 하는가?

다.


✅ 3. Workflow란?

Workflow는 여러 작업을 하나의 업무 흐름으로 묶은 것이다.

예:

DailyReportWorkflow

안에:

COLLECT_GIT

GENERATE_REPORT

SAVE_MARKDOWN

UPLOAD_NOTION

이 존재한다.

각 Step은 독립적인 상태를 가진다.


✅ 4. Workflow와 Job의 차이

Job은 보통:

하나의 실행 단위

다.

예:

Notion에 페이지 업로드

Workflow는:

여러 Job/Step의 실행 순서와 상태를 관리

한다.

예:

Report 생성
→ 파일 저장
→ Notion 업로드

이다.


✅ 5. Orchestration이란?

Workflow의 다음 Step을 중앙에서 결정하는 방식이다.

예:

Workflow Orchestrator

↓

Step 1 실행

↓

성공 확인

↓

Step 2 실행

↓

성공 확인

↓

Step 3 실행

즉 Orchestrator가:

지금 어느 Step인가?

다음에는 무엇을 해야 하는가?

실패하면 어떻게 해야 하는가?

를 알고 있다.


✅ 6. Orchestration과 Choreography

분산 시스템에서는 흔히 두 방식을 구분한다.

Orchestration

중앙 Workflow가 순서를 관리한다.

OrderWorkflow
↓
Payment
↓
Notification
↓
Complete

Choreography

각 서비스가 Event를 보고 다음 Event를 만든다.

ORDER_CREATED

↓

Payment Service
PAYMENT_COMPLETED

↓

Notification Service
NOTIFICATION_SENT

중앙 지휘자가 없다.


✅ 7. 현재 프로젝트에는 Orchestration이 더 이해하기 쉽다

현재는 1인 개발 환경이고 서비스 규모도 상대적으로 단순하다.

Choreography를 과도하게 사용하면:

어떤 Event가 어떤 Event를 발생시키는지

전체 흐름을 어디서 봐야 하는지

실패 시 어느 서비스가 복구해야 하는지

가 복잡해질 수 있다.

따라서 중요한 업무 흐름은:

명시적인 Workflow Orchestrator

를 두는 것이 관리하기 쉽다.


✅ 8. 모든 Event를 없애라는 뜻은 아니다

예:

Order Status Changed

같은 Domain Event는 여전히 유용하다.

다만 중요한 장시간 업무를:

Event A
→ Event B
→ Event C
→ Event D

에만 맡기지 말고 Workflow의 현재 상태를 별도로 기록하는 것이다.


✅ 9. Workflow Instance

Workflow 정의와 실제 실행을 구분한다.

예:

Workflow Definition

DailyReportWorkflow

실제 실행:

Workflow Run

2026-09-30
platform

이다.

0929의 Schedule / ScheduleRun과 같은 사고방식이다.


✅ 10. Workflow Definition과 WorkflowRun

Workflow Definition
=
실행 절차

WorkflowRun
=
실제 한 번의 업무 실행

예:

DailyReportWorkflow

├─ 09/28 Run
├─ 09/29 Run
└─ 09/30 Run

✅ 11. WorkflowRun 상태

예:

PENDING

RUNNING

WAITING

SUCCESS

FAILED

COMPENSATING

COMPENSATED

MANUAL_REQUIRED

CANCELLED

정도로 둘 수 있다.


✅ 12. WAITING 상태가 중요하다

Workflow는 항상 CPU를 사용하며 실행되는 것이 아니다.

예:

알림톡 발송

↓

외부 Webhook 대기

처럼 외부 Event를 기다릴 수도 있다.

이때:

RUNNING

으로 계속 Worker를 점유할 필요가 없다.

WAITING

상태로 저장해두고 Webhook이 오면 다시 Resume한다.


✅ 13. 긴 Workflow가 Worker를 계속 잡고 있으면 안 된다

예:

상품 신청
↓
외부 본인인증 대기
↓
최대 30분

이런 Workflow에서 Worker 하나가 30분 동안 기다리는 것은 비효율적이다.

대신:

Workflow 상태 저장

↓

WAITING_EXTERNAL_EVENT

↓

Worker 종료

후 Event가 들어오면 재개한다.


✅ 14. Workflow Step

예:

interface WorkflowStep {
  id: string;
  workflowRunId: string;

  name: string;

  status:
    | 'PENDING'
    | 'RUNNING'
    | 'SUCCESS'
    | 'FAILED'
    | 'WAITING'
    | 'SKIPPED';

  attemptCount: number;
}

✅ 15. Step은 가능한 한 작고 의미 있게 나눈다

너무 크게:

PROCESS_ORDER

하나로 만들면 실패 위치를 알기 어렵다.

반대로:

READ_FIELD_1
READ_FIELD_2
CHECK_VALUE

처럼 너무 작게 나누면 오케스트레이션이 복잡해진다.

업무 의미 단위로 나누는 것이 좋다.


✅ 16. 좋은 Step 예시

VALIDATE_ORDER

UPDATE_ORDER_STATUS

CREATE_NOTIFICATION_JOB

WAIT_NOTIFICATION_RESULT

FINALIZE_ORDER

처럼 실제 업무 단계가 드러나야 한다.


✅ 17. Step은 상태를 바꾸는 경계가 된다

각 Step이 완료될 때:

Checkpoint

를 남긴다.

예:

Step 1 SUCCESS

↓

DB 저장

↓

Step 2 실행

Worker가 Step 2 중간에 죽어도 Step 1은 다시 실행하지 않아도 된다.


✅ 18. Checkpoint의 핵심

Checkpoint는:

이 시점까지는 완료됐다는 확정된 사실

이다.

AI Workflow에서 매우 중요하다.

예:

CODE_MODIFIED
SUCCESS

TEST
SUCCESS

REPORT
PENDING

이면 Report부터 재개할 수 있다.


✅ 19. Resume는 Workflow의 핵심 기능이다

긴 작업을 안정적으로 운영하려면:

처음부터 Retry

보다:

마지막 성공 Step 이후부터 Resume

가 훨씬 낫다.


✅ 20. 단 Resume 전에 상태를 재검증한다

예:

Step 2에서 파일 생성 완료

라고 DB에 기록되어 있어도 실제 파일이 삭제됐을 수 있다.

그래서 Resume 전:

Artifact 존재 여부

Version

External State

Commit SHA

등을 확인한다.


✅ 21. Workflow는 사실 하나의 State Machine이다

예:

PENDING
↓
VALIDATING
↓
PROCESSING
↓
WAITING_EXTERNAL
↓
FINALIZING
↓
SUCCESS

잘못된 상태 전이는 차단한다.


✅ 22. State Machine을 명시하는 이유

예:

FAILED
→ SUCCESS

를 아무 조건 없이 허용하면 문제가 생긴다.

정상 흐름은:

FAILED
→ RETRY_PENDING
→ RUNNING
→ SUCCESS

처럼 명확해야 한다.


✅ 23. Workflow에서 가장 까다로운 것은 ‘부분 성공’이다

예:

1. 주문 상태 변경
SUCCESS

2. 알림톡 발송
SUCCESS

3. 관리자 통계 업데이트
FAILED

이때:

전체를 Rollback

할 수 있을까?

알림톡은 이미 고객에게 발송됐다.

되돌릴 수 없다.


✅ 24. DB Transaction으로 해결할 수 없는 영역

DB 내부에서는:

BEGIN

Order Update

Audit Log

Outbox Insert

COMMIT

처럼 Transaction을 사용할 수 있다.

하지만 외부 API까지 포함하면:

DB Commit

↓

외부 알림톡 전송

을 하나의 DB Transaction으로 묶을 수 없다.


✅ 25. Distributed Transaction 문제

예:

우리 DB

외부 Kakao API

Notion

S3

GitHub

는 서로 다른 시스템이다.

이 모든 시스템에:

BEGIN TRANSACTION

을 걸 수 없다.

그래서 Saga 같은 사고방식이 필요하다.


✅ 26. Saga란?

긴 비즈니스 Transaction을 여러 Local Transaction으로 나누고,

중간에 실패하면 이미 완료된 작업을:

Compensation

으로 되돌리거나 보정하는 패턴이다.


✅ 27. 일반 Transaction과 Saga

일반 Transaction:

A
B
C

하나 실패

↓

전체 Rollback

Saga:

A SUCCESS

B SUCCESS

C FAILED

↓

Compensate B

↓

Compensate A

이다.


✅ 28. Compensation이란?

완벽한 Rollback이 아니라:

이미 일어난 업무 효과를
비즈니스적으로 반대되는 작업으로 보정

하는 것이다.


✅ 29. 예: 예약 시스템

좌석 예약

↓

결제

↓

티켓 발급

티켓 발급이 실패했다면:

결제 취소

좌석 예약 해제

가 Compensation이 될 수 있다.


✅ 30. 모든 Action에 Compensation이 가능한 것은 아니다

예:

고객에게 카카오톡 메시지 발송

은 이미 전송됐다.

이를:

발송 취소

할 수 없다.

이런 Action은 Irreversible Side Effect다.


✅ 31. Irreversible Action은 뒤쪽에 배치하는 것이 좋다

가능하다면 Workflow를:

검증

↓

DB 상태 변경

↓

필수 데이터 저장

↓

외부 메시지 발송

순으로 둔다.

실패 가능성이 높은 검증을 먼저 한다.

되돌릴 수 없는 Action은 가능한 한 뒤쪽에 둔다.


✅ 32. 하지만 무조건 뒤로 보낼 수 있는 것은 아니다

업무상 메시지 발송이 먼저 필요한 경우도 있다.

이때는 Rollback보다:

Forward Recovery

를 고려해야 한다.


✅ 33. Rollback과 Compensation은 다르다

Rollback:

실행 전 상태로 완전히 되돌림

Compensation:

업무적으로 최대한 원상태에 가깝게 보정

이다.

예:

고객에게 잘못된 안내 발송

↓

메시지 삭제 불가능

↓

정정 메시지 발송

은 Compensation이다.


✅ 34. Forward Recovery

이미 일어난 Side Effect를 되돌리지 않고:

앞으로 진행하면서 정상 상태를 만든다.

예:

주문 완료

알림톡 성공

통계 반영 실패

↓

통계 반영 Retry

이다.

이 상황에서는 주문과 알림톡을 되돌릴 필요가 없다.


✅ 35. 실제 운영에서는 Forward Recovery가 더 자연스러운 경우가 많다

특히:

메시지 발송

파일 업로드

외부 API

Webhook

AI 보고서

같은 작업은 완전 Rollback보다:

실패한 단계만 복구

하는 것이 낫다.


✅ 36. Compensation을 무조건 만들 필요는 없다

각 Step마다 먼저 판단한다.

Retry 가능한가?

Compensation 가능한가?

Forward Recovery가 더 나은가?

사람 판단이 필요한가?

✅ 37. Step Policy

예:

interface StepPolicy {
  retryable: boolean;
  compensatable: boolean;
  irreversible: boolean;
  requiresApproval?: boolean;
}

같은 Metadata를 둘 수 있다.


✅ 38. Step 예시

StepRetryCompensationIrreversible
DB 상태 변경가능가능X
S3 업로드가능파일 삭제 가능X
Notion 페이지 생성가능삭제/Archive 가능X
알림톡 발송제한적사실상 어려움O
Git Commit가능Revert 가능부분적
Production 배포가능Rollback 가능조건부

✅ 39. Compensation도 실패할 수 있다

예:

Step B 실패

↓

Compensation A 실행

↓

Compensation A도 실패

할 수 있다.

그래서 Compensation 자체도 하나의 Job으로 관리해야 한다.


✅ 40. Compensation 상태

예:

COMPENSATION_PENDING

COMPENSATING

COMPENSATED

COMPENSATION_FAILED

MANUAL_REQUIRED

를 둘 수 있다.


✅ 41. Compensation도 Idempotent해야 한다

예:

S3 파일 삭제

가 두 번 실행돼도 최종 상태가 같아야 한다.

이미 파일 없음
→ 성공 취급

처럼 처리할 수 있다.


✅ 42. Compensation 순서는 반대 방향

Workflow:

A
→ B
→ C

가 실행됐고 C에서 실패했다면 일반적으로:

Compensate B

↓

Compensate A

순서로 간다.


✅ 43. 예: 파일 기반 Workflow

1. Report 생성

2. S3 업로드

3. DB Record 생성

4. 외부 공유 링크 발송

3번 실패했다고 가정한다.

2번 S3 파일을 삭제할 수 있다면 Compensation을 수행할 수 있다.


✅ 44. 하지만 외부 공유 링크까지 이미 발송됐다면 다르다

발송한 메시지를 되돌릴 수 없다.

따라서:

보정 안내

새 링크 발송

관리자 개입

등이 필요하다.

이게 비즈니스 Compensation이다.


✅ 45. 현재 투게더몰에서 Saga가 필요한 영역

모든 기능에 Saga가 필요한 것은 아니다.

특히 다음처럼 여러 시스템이 연결되는 경우 가치가 있다.

주문 상태 변경
+
알림톡
+
외부 API

대량 Export
+
S3

AI Report
+
파일
+
Notion
+
Git

배포
+
Health
+
Rollback

✅ 46. 단순 CRUD에는 Saga를 쓰지 않는다

예:

관리자 메모 수정

같은 단일 DB 작업은 기존 Transaction이면 충분하다.

Saga를 붙이면 오히려 복잡해진다.


✅ 47. Saga를 도입할 기준

대략 다음 조건을 볼 수 있다.

여러 시스템에 Side Effect 발생

Workflow가 오래 실행됨

부분 실패 가능성 높음

Rollback이 단순 DB Transaction으로 불가능

실패 후 복구 과정이 중요

✅ 48. Order Workflow 예시

예:

OrderStatusChangeWorkflow

Step 1
Validate Transition

Step 2
Update Order

Step 3
Create Audit

Step 4
Create NotificationJob

Step 5
Wait Notification Result

Step 6
Complete

✅ 49. 여기서 Step 1~3은 DB Transaction으로 묶을 수 있다

굳이 각각 Workflow Step으로 쪼갤 필요는 없다.

예:

DB Transaction Step

- Order Update
- Audit
- Outbox

를 하나의 Local Transaction으로 처리한다.


✅ 50. Workflow 안에서도 Local Transaction을 사용한다

중요한 점이다.

Saga가 Transaction을 대체하는 것이 아니다.

구조는:

Workflow

↓

Local Transaction A

↓

External Side Effect

↓

Local Transaction B

처럼 된다.


✅ 51. Outbox와 Workflow를 같이 사용

예:

Order Transaction

- 상태 변경
- Audit
- Outbox Event

COMMIT

후:

Outbox Worker

↓

Workflow 다음 Step

으로 진행할 수 있다.


✅ 52. Workflow Orchestrator가 직접 모든 일을 할 필요는 없다

Orchestrator는:

다음 Step 결정
상태 기록
실행 요청

정도를 담당한다.

실제 작업은 기존 Service/Worker가 한다.


✅ 53. Orchestrator가 Business Logic까지 가지면 비대해진다

좋지 않은 구조:

WorkflowService
5000줄

주문 로직
알림톡 로직
DB 로직
Notion 로직
Git 로직

좋은 구조:

Workflow Orchestrator
↓
Use Case / Worker 호출

이다.


✅ 54. Workflow Step Executor

예:

interface WorkflowStepExecutor {
  execute(context: WorkflowContext): Promise<StepResult>;
}

각 Step을 독립 Executor로 구현할 수 있다.


✅ 55. Step Result

예:

type StepResult =
  | {
      type: 'SUCCESS';
      output?: unknown;
    }
  | {
      type: 'WAIT';
      waitFor: string;
    }
  | {
      type: 'RETRY';
      errorCode: string;
    }
  | {
      type: 'FAIL';
      errorCode: string;
    };

✅ 56. WAIT가 매우 중요하다

예:

외부 Webhook 기다리기

Human Approval 기다리기

다른 Job 완료 기다리기

같은 장시간 흐름에서 필요하다.


✅ 57. Human Approval도 Workflow Step이다

예:

AI 코드 수정

↓

Test

↓

Release Risk HIGH

↓

WAITING_APPROVAL

↓

승인

↓

Deploy

처럼 모델링할 수 있다.


✅ 58. Approval Timeout

영원히 기다리지 않도록:

expiresAt

를 둘 수 있다.

예:

24시간 내 승인 없음
→ CANCELLED

또는 MANUAL_REQUIRED로 보낸다.


✅ 59. 외부 Event를 기다리는 Step

예:

Notification Provider 발송

↓

WAITING_PROVIDER_RESULT

Webhook 수신 시:

workflowRunId

또는 Provider Message ID로 Workflow를 찾아 Resume한다.


✅ 60. Correlation ID가 여기서 중요해진다

Workflow 전체가 하나의:

correlationId

를 공유한다.

각 Step에는:

workflowRunId

stepRunId

jobId

correlationId

가 연결된다.


✅ 61. Workflow Timeline

운영 화면에서:

09:00
Workflow 시작

09:00
VALIDATE SUCCESS

09:01
UPDATE_ORDER SUCCESS

09:01
NOTIFICATION QUEUED

09:02
WAITING_PROVIDER

09:04
Webhook 수신

09:04
FINALIZE SUCCESS

09:04
Workflow SUCCESS

처럼 볼 수 있다.


✅ 62. Workflow 상태를 로그만으로 복원하지 않는다

중요한 상태는 DB에 저장한다.

로그는 분석용이고,

Workflow DB 상태는:

현재 어디까지 진행됐는가?

를 결정하는 Source of Truth다.


✅ 63. WorkflowRun 모델 예시

model WorkflowRun {
  id             String   @id @default(cuid())

  type           String
  status         String

  correlationId  String

  idempotencyKey String   @unique

  currentStep    String?

  input          Json?
  output         Json?

  startedAt      DateTime?
  completedAt    DateTime?

  createdAt      DateTime @default(now())
  updatedAt      DateTime @updatedAt

  steps          WorkflowStepRun[]
}

✅ 64. WorkflowStepRun

model WorkflowStepRun {
  id            String   @id @default(cuid())

  workflowRunId String

  name          String
  sequence      Int

  status        String

  attemptCount  Int      @default(0)

  input         Json?
  output        Json?

  startedAt     DateTime?
  completedAt   DateTime?

  errorCode     String?

  workflowRun   WorkflowRun @relation(
    fields: [workflowRunId],
    references: [id]
  )

  @@unique([workflowRunId, name])
}

✅ 65. Input/Output에 모든 데이터를 저장하지 않는다

예:

고객 개인정보 전체

Secret

큰 파일

을 Workflow DB에 그대로 넣으면 안 된다.

가능하면:

orderId

artifactId

filePath

providerMessageId

같은 참조만 저장한다.


✅ 66. Workflow Input Version

오래 실행되는 Workflow에서는 입력 구조가 바뀔 수 있다.

예:

{
  "version": 2,
  "orderId": "order_123"
}

처럼 Version을 둔다.


✅ 67. Workflow Definition Version

코드가 배포되면서 Workflow Step이 변경될 수 있다.

예:

v1

A → B → C

새 버전:

v2

A → B → X → C

기존에 실행 중인 Workflow는 어떻게 할까?


✅ 68. 실행 중 Workflow에 새 정의를 갑자기 적용하면 위험하다

예:

v1 Workflow
B까지 완료

했는데 새 코드가:

v2

로 배포됐다.

Resume했더니 갑자기 X Step이 생길 수 있다.


✅ 69. Workflow Version을 Run에 고정한다

예:

workflowType
DAILY_REPORT

workflowVersion
3

Run 생성 시 Version을 고정한다.

가능하면 기존 실행 중 Run은 해당 Version 규칙으로 끝낸다.


✅ 70. 현실적으로 오래된 Version 코드를 무한 유지할 필요는 없다

Workflow가 몇 분 안에 끝난다면 배포 시 Drain 후 교체할 수 있다.

하지만:

며칠 동안 외부 승인을 기다리는 Workflow

라면 Version 호환성을 더 신경 써야 한다.


✅ 71. Workflow Migration

장시간 Workflow에서 새 Version으로 옮겨야 한다면 별도 Migration이 필요할 수 있다.

예:

v1 Step B 완료

↓

v2 Step X부터 Resume

같은 명시적인 전환이다.

현재 규모에서는 복잡하게 만들 필요 없다.


✅ 72. Workflow Idempotency

같은 업무 요청이 두 번 들어올 수 있다.

예:

Order Completion Workflow
order_123

에:

order-complete:order_123

같은 Idempotency Key를 사용할 수 있다.


✅ 73. Workflow 자체 중복과 Step 중복을 둘 다 막아야 한다

Workflow Run
1개

라고 해도 Queue 중복으로 Step이 두 번 실행될 수 있다.

따라서 Step Side Effect 역시 Idempotent해야 한다.


✅ 74. Step Idempotency Key

예:

{workflowRunId}:{stepName}

을 사용할 수 있다.

예:

run_123:UPLOAD_NOTION

이다.


✅ 75. Step Attempt와 Idempotency는 다르다

Retry마다:

attempt 1
attempt 2
attempt 3

은 바뀌지만,

Logical Action은 동일하다.

따라서 Idempotency Key는 Attempt마다 바꾸지 않는다.


✅ 76. Step Retry

Retry 가능한 실패:

Timeout

503

Rate Limit

일시 DB 연결 오류

등은 해당 Step만 Retry한다.


✅ 77. Step Retry 한도

예:

UPLOAD_NOTION
maxAttempts = 3

초과:

Workflow FAILED

또는:

MANUAL_REQUIRED

로 보낸다.


✅ 78. 모든 Step의 Retry 횟수를 같게 하지 않는다

예:

Read-only External Query
5회

가능할 수 있지만:

대량 메시지 발송

은 더 보수적이어야 한다.


✅ 79. Retryable 여부를 Step Policy에 둔다

예:

const policy = {
  maxAttempts: 3,
  retryableErrors: [
    'TIMEOUT',
    'RATE_LIMIT',
  ],
};

✅ 80. Compensation Policy도 Step마다 다르게

예:

const policy = {
  compensation:
    'DELETE_UPLOADED_FILE',
};

또는:

NONE

일 수 있다.


✅ 81. Compensation을 자동으로 할지 승인받을지도 구분한다

예:

Temporary File 삭제
→ AUTO
고객 주문 취소
→ MANUAL_APPROVAL

처럼 위험도가 다르다.


✅ 82. Compensation이 실제 비즈니스에 더 큰 영향을 줄 수도 있다

예:

상품 예약 완료

↓

후속 통계 실패

했다고 예약을 취소해버리면 더 큰 문제가 된다.

따라서:

후속 통계 Retry

가 맞다.


✅ 83. Compensation은 마지막 수단이 아니다

정확히는 업무 의미에 따라 선택하는 전략이다.

다음 중 하나를 결정한다.

Retry

Resume

Compensate

Roll Forward

Manual Intervention

✅ 84. Recovery Decision Matrix

상황대응
일시적 API 오류Retry
이전 Step 완료Resume
예약 등 되돌릴 수 있음Compensation
메시지 발송 완료Roll Forward
금전/데이터 영향 큼Manual

✅ 85. Workflow Timeout

Workflow 자체에도 최대 시간이 필요할 수 있다.

예:

Daily Report
maxDuration = 30m

초과하면 이상 상태로 본다.


✅ 86. Step Timeout과 Workflow Timeout은 다르다

예:

LLM Step
10분 제한

전체 Workflow
30분 제한

처럼 둘 수 있다.


✅ 87. WAITING 시간은 Workflow Timeout 계산에서 별도로 볼 수 있다

예:

Human Approval
24시간 대기

를 일반 실행 시간과 똑같이 계산하면 안 될 수 있다.


✅ 88. Deadline

Workflow에:

deadlineAt

을 둘 수 있다.

예:

사전예약 고객 알림

오늘 18:00 이전 완료

처럼 업무 기한이 있는 경우다.


✅ 89. Deadline을 넘긴 Retry는 의미가 없을 수 있다

예:

오전 9시 안내

가 오후 10시에 Retry된다면 보내지 않는 편이 나을 수 있다.

이때:

EXPIRED

또는:

MANUAL_REQUIRED

상태로 보낸다.


✅ 90. Workflow Cancel

운영자가 진행 중 Workflow를 중단하고 싶을 수 있다.

하지만:

CANCEL 버튼

을 누른다고 이미 발생한 Side Effect가 사라지는 것은 아니다.


✅ 91. Cancel은 ‘앞으로 진행하지 않음’에 가깝다

예:

Step A SUCCESS

Step B SUCCESS

Step C PENDING

에서 Cancel하면:

C 실행 안 함

이다.

필요하다면 별도 Compensation을 실행한다.


✅ 92. Cancel Requested

즉시 강제 종료보다:

CANCEL_REQUESTED

를 기록하고 안전한 경계에서 중단하는 것이 낫다.


✅ 93. Worker Cooperative Cancellation

Worker가:

Step 시작 전

Batch 사이

외부 호출 전

Cancel 상태를 확인해 중단한다.


✅ 94. 강제 Process Kill은 마지막 수단

중간 Transaction이나 파일 작업이 깨질 수 있기 때문이다.


✅ 95. Workflow Concurrency

같은 대상에 여러 Workflow가 동시에 실행될 수 있다.

예:

order_123

Status Change Workflow A

Status Change Workflow B

충돌할 수 있다.


✅ 96. Aggregate 기준 Lock

예:

resourceKey
order:123

에 대해 동시에 하나의 Workflow만 허용할 수 있다.


✅ 97. 하지만 모든 Workflow를 전역 직렬화하지 않는다

그러면 처리량이 크게 떨어진다.

같은 Order

끼리만 제한하고 다른 Order는 병렬 처리한다.


✅ 98. Optimistic Concurrency와 함께 사용할 수 있다

예:

Order.version = 12

를 읽고 Workflow가 상태를 변경한다.

업데이트할 때:

WHERE id = ?
AND version = 12

로 처리한다.

다른 Workflow가 먼저 변경했다면 Conflict가 발생한다.


✅ 99. Workflow Conflict 처리

Conflict가 나면:

무조건 Retry

하지 않는다.

다시 최신 상태를 읽고:

현재 Workflow가 아직 유효한가?

를 판단한다.


✅ 100. 예: 주문 상태 Conflict

Workflow A:

WAITING
→ COMPLETED

Workflow B:

WAITING
→ CANCELLED

둘이 동시에 실행되면 한쪽은 Conflict 처리해야 한다.

상태 머신 규칙이 필요하다.


✅ 101. Workflow Dependency

한 Workflow가 다른 Workflow 완료를 기다릴 수도 있다.

예:

DailyReportWorkflow

↓

DataAggregationWorkflow

이 완료돼야 진행 가능하다.


✅ 102. Dependency를 시간으로 해결하지 않는다

나쁜 구조:

03:00 Aggregation

03:30 Report

Aggregation이 40분 걸리면 Report가 먼저 실행될 수 있다.


✅ 103. 상태/Event 기반 Dependency

Aggregation SUCCESS

↓

REPORT_READY Event

↓

Report Workflow 시작

이 더 안전하다.


✅ 104. Parent/Child Workflow

예:

CampaignWorkflow

├─ CustomerBatchWorkflow 1
├─ CustomerBatchWorkflow 2
├─ CustomerBatchWorkflow 3

형태로 만들 수 있다.


✅ 105. Child Workflow 상태 집계

Parent는:

3개 Child 모두 SUCCESS

이면 SUCCESS.

일부 실패하면:

PARTIAL_SUCCESS

또는 FAILED로 판단한다.


✅ 106. Partial Success

대량 작업에서는 매우 중요하다.

예:

고객 10,000명

9,980 성공

20 실패

전체를 실패라고만 하면 운영상 불편하다.


✅ 107. Partial Success 정책

예:

95% 이상 성공
→ PARTIAL_SUCCESS

80% 미만
→ FAILED

처럼 업무별 기준을 둘 수 있다.

단 숫자는 업무 특성에 맞게 정해야 한다.


✅ 108. 실패 대상만 Retry

대량 알림에서:

20명 실패

했다고 10,000명을 다시 보내면 안 된다.

실패 Item만 별도로 Retry한다.


✅ 109. Item-level Idempotency

각 고객 메시지에는:

campaignId + recipientId

같은 Idempotency Key를 둘 수 있다.


✅ 110. Workflow Data Growth

Workflow가 오래 실행되며 Step Output을 모두 JSON으로 저장하면 DB가 커질 수 있다.

예:

LLM 전체 Prompt

파일 전체 내용

대량 고객 데이터

를 저장하지 않는다.


✅ 111. Artifact Store를 분리

큰 데이터는:

S3

File System

별도 Artifact Storage

에 저장하고 Workflow DB에는:

artifactId

만 넣는다.


✅ 112. Workflow Context

작은 Metadata만 유지한다.

예:

{
  "project": "platform",
  "businessDate": "2026-09-30",
  "reportArtifactId": "artifact_123"
}

✅ 113. Context Mutation을 조심한다

Step마다 하나의 거대한 JSON Context를 계속 덮어쓰면:

어떤 Step이 어떤 값을 만들었는지

알기 어려워진다.


✅ 114. Step Output을 명확하게

예:

COLLECT_GIT.output
commitRange

GENERATE_REPORT.output
artifactId

UPLOAD_NOTION.output
notionPageId

처럼 구분한다.


✅ 115. Workflow Event Log

상태 변경을 별도 Event로 남길 수 있다.

예:

workflow.started

step.started

step.completed

step.failed

workflow.waiting

workflow.resumed

workflow.completed

✅ 116. Event Log와 Current State

Current State:

지금 Workflow가 어디에 있는가?

Event Log:

어떻게 지금 상태까지 왔는가?

를 설명한다.

둘 다 있으면 운영성이 높아진다.


✅ 117. Event Sourcing까지 갈 필요는 없다

모든 상태를 Event만으로 재구축하는 복잡한 Event Sourcing을 도입할 필요는 없다.

현재는:

Current State Table
+
History/Event Log

면 충분하다.


✅ 118. Workflow Observability

중요 Metric:

workflow_started_total

workflow_success_total

workflow_failed_total

workflow_duration

step_duration

step_retry_total

workflow_waiting_count

compensation_total

등이다.


✅ 119. Workflow Success Rate

예:

DailyReportWorkflow

지난 30일
30 Run

SUCCESS
28

FAILED
1

PARTIAL
1

처럼 볼 수 있다.


✅ 120. Step별 Failure Rate

Workflow 전체보다 더 유용할 때가 많다.

예:

UPLOAD_NOTION

Failure Rate
12%

이면 해당 Step이 병목이라는 뜻이다.


✅ 121. Step Duration

예:

COLLECT_GIT
2초

LLM_GENERATE
47초

SAVE_FILE
100ms

NOTION_UPLOAD
800ms

어디에서 시간이 많이 걸리는지 확인할 수 있다.


✅ 122. Workflow Critical Path

전체 시간을 결정하는 가장 긴 경로를 생각할 수 있다.

현재처럼 대부분 순차 Workflow면 단순하다.

나중에 병렬 Step이 생기면 중요해진다.


✅ 123. 병렬 Step

예:

Prepare Report

├─ Git 분석
├─ Incident 분석
└─ Metric 분석

↓

Merge

↓

Final Report

처럼 일부 Step을 병렬화할 수 있다.


✅ 124. Join Step

여러 병렬 Step이 끝난 뒤:

모두 완료

를 기다리는 Step이다.

예:

GIT_ANALYSIS
SUCCESS

METRIC_ANALYSIS
SUCCESS

INCIDENT_ANALYSIS
SUCCESS

↓

MERGE_REPORT

✅ 125. 하나의 병렬 Step이 실패하면?

정책을 정해야 한다.

ALL_REQUIRED

BEST_EFFORT

MINIMUM_N

같은 방식을 생각할 수 있다.


✅ 126. BEST_EFFORT

예:

Git 분석 성공

Metric 분석 실패

Incident 분석 성공

이어도 Report를 생성하되:

Metric 데이터 unavailable

이라고 표시할 수 있다.


✅ 127. 필수 Step과 선택 Step

Step Metadata에:

required = true / false

를 둘 수 있다.

선택 Step 실패가 전체 Workflow 실패로 이어지지 않게 한다.


✅ 128. Graceful Degradation과 연결된다

0923~0925에서 배운 원칙과 같다.

부가 기능 실패 때문에 핵심 Workflow 전체가 중단되지 않도록 한다.


✅ 129. Workflow Rate Limit

Workflow 생성 자체가 폭주할 수도 있다.

예:

Webhook 10,000건
→ Workflow 10,000개

가 한 번에 실행되면 부하가 커진다.


✅ 130. Workflow Queue

Workflow Run을 생성하더라도 실제 Step 실행은 Queue를 통해 제한할 수 있다.

Workflow
10000개 생성

↓

Worker Concurrency
20

처럼 처리한다.


✅ 131. Workflow Priority

예:

Order
HIGH

Notification Recovery
HIGH

Daily Report
NORMAL

AI Analysis
LOW

이다.


✅ 132. 긴 Workflow가 짧은 업무를 막지 않도록 한다

Queue 분리 또는 Priority를 고려한다.

특히 AI Workflow가 CPU/RAM을 많이 쓰는 경우 중요하다.


✅ 133. Local LLM Workflow

현재 자동화에 적용하면:

DailyReportWorkflow

Step 1
COLLECT_GIT

Step 2
BUILD_CONTEXT

Step 3
RUN_LLM

Step 4
VALIDATE_OUTPUT

Step 5
SAVE_MARKDOWN

Step 6
UPLOAD_NOTION

정도로 나눌 수 있다.


✅ 134. VALIDATE_OUTPUT Step이 유용하다

LLM 결과를 바로 저장하지 말고:

Empty인가?

필수 Section이 있는가?

파일 크기가 이상하지 않은가?

Markdown 형식이 깨졌는가?

를 확인한다.


✅ 135. Validation 실패는 LLM Retry 후보가 될 수 있다

예:

INVALID_OUTPUT

이면 Prompt를 그대로 한 번 더 실행할 수 있다.

하지만 무한 Retry는 금지한다.


✅ 136. AI Step은 Non-Deterministic할 수 있다

같은 입력이어도 결과가 달라질 수 있다.

따라서 Workflow Idempotency가:

LLM을 두 번 실행하지 않게 하는 것

과:

같은 결과를 보장하는 것

은 다른 문제다.


✅ 137. AI Artifact를 Checkpoint로 사용

LLM 결과 생성에 성공했다면:

artifactId

를 저장한다.

Notion Upload 실패 때문에 LLM을 다시 호출할 필요가 없다.


✅ 138. Prompt/Model Version을 Step에 기록

예:

RUN_LLM

model
shn-coder

promptVersion
v24

contextHash
abc123

를 기록한다.


✅ 139. AI Workflow Compensation

예:

Markdown 생성

Notion 페이지 생성

Git Commit

까지 했는데 Push가 실패했다고 하자.

무조건 이전 모든 결과를 지울 필요는 없다.


✅ 140. 이 경우 Forward Recovery가 더 낫다

Git Push Retry

하면 된다.

Markdown과 Notion을 되돌리는 것은 오히려 불필요하다.


✅ 141. 잘못된 Notion 페이지를 생성했다면?

페이지를 Archive할 수 있다면 Compensation이 가능하다.

Archive Notion Page

가 Compensation이다.


✅ 142. Git Commit Compensation

Commit을 만들었지만 아직 Push 전이라면:

commit reset

이 가능할 수 있다.

Push 후라면:

revert commit

이 더 안전하다.


✅ 143. Production 작업에서는 Compensation을 더 보수적으로

예:

DB 상태 변경

고객 알림

배포

등은 자동 Compensation이 실제 사고를 더 키울 수 있다.

Risk Level을 둔다.


✅ 144. Compensation Risk

예:

LOW
임시 파일 삭제

MEDIUM
Notion 페이지 Archive

HIGH
주문 취소

CRITICAL
금전 환불

이다.


✅ 145. HIGH 이상은 Human Approval

초기에는:

COMPENSATION_PENDING

↓

Approval

↓

실행

형태가 안전하다.


✅ 146. Compensation Audit

예:

Workflow
run_123

Failed Step
PAYMENT_CONFIRM

Compensation
RELEASE_RESERVATION

Result
SUCCESS

Actor
SYSTEM

처럼 기록한다.


✅ 147. Manual Intervention

Workflow가 자동으로 해결할 수 없는 경우:

MANUAL_REQUIRED

로 멈춘다.

관리자가 다음 선택을 한다.

Retry

Skip Step

Compensate

Mark Success

Cancel

✅ 148. ‘Mark Success’는 위험하다

운영자가 문제를 해결했는데 시스템에서 상태만 맞춰야 할 수 있다.

하지만 아무 확인 없이:

SUCCESS 처리

하면 데이터가 꼬일 수 있다.


✅ 149. Manual Override에는 이유를 필수 입력

예:

Reason:
Provider 관리자 페이지에서
실제 발송 완료 확인

처럼 기록한다.


✅ 150. Manual Action도 Audit Log에 남긴다

actorId

action

before

after

reason

workflowRunId

를 저장한다.


✅ 151. Workflow Inbox

관리자 페이지에:

자동화 운영
├─ 실행 중
├─ 실패
├─ 승인 대기
├─ 보상 대기
└─ 수동 처리 필요

같은 Inbox를 만들 수 있다.


✅ 152. Workflow Detail 화면

예:

DailyReportWorkflow

Run
run_20260930_01

Status
FAILED

Current Step
UPLOAD_NOTION

Timeline

COLLECT_GIT
SUCCESS

RUN_LLM
SUCCESS

SAVE_MARKDOWN
SUCCESS

UPLOAD_NOTION
FAILED

Attempts
3

그리고 조치 버튼:

Retry Step

Cancel Workflow

View Artifact

등이다.


✅ 153. Order Workflow Detail

예:

Order Status Change

Order
order_123

Status
WAITING

Current Step
WAIT_NOTIFICATION_RESULT

Provider Message ID
msg_9921

처럼 확인할 수 있다.


검색 기준:

workflowRunId

correlationId

orderId

jobId

status

type

기간

정도면 운영성이 높아진다.


✅ 155. Workflow Retention

모든 Workflow History를 영구 보관할 필요는 없다.

예:

운영 Workflow
90일

Audit 관련
더 길게

AI 내부 Report
필요한 기간

처럼 정책을 정한다.


✅ 156. 단 Audit 목적 데이터는 별도 정책을 적용한다

Workflow History 삭제와 Audit Log 삭제를 동일하게 처리하지 않는다.


✅ 157. Workflow Cleanup도 Scheduled Job이 된다

예:

매일 새벽

완료 후 90일 지난
Workflow Step 상세 데이터 정리

할 수 있다.

0929와 다시 연결된다.


✅ 158. Cleanup은 결과 Artifact까지 무조건 지우지 않는다

별도의 Retention Policy가 필요하다.


✅ 159. Workflow Definition을 코드로 관리하는 것이 초기에는 낫다

관리자 UI에서 자유롭게 Step을 조립하는 Workflow Builder까지 만들 필요는 없다.

예:

DailyReportWorkflowDefinition

을 코드로 작성한다.


✅ 160. Dynamic Workflow Builder가 과한 이유

조건 분기

Loop

Retry

Compensation

Version

Permission

등까지 일반화하면 자체 Airflow/Temporal 같은 시스템을 만드는 셈이다.

현재 규모에는 불필요하다.


✅ 161. Workflow Engine을 직접 만들다가 프로젝트가 커질 수 있다

목표는:

업무 자동화를 안정화

이지:

범용 Workflow Platform 개발

이 아니다.


✅ 162. 지금 필요한 최소 Orchestrator

기능은 대략 이 정도면 된다.

Run 생성

Step 순서 관리

상태 저장

Retry

Resume

WAIT

Manual Required

일부 Compensation

이면 충분하다.


✅ 163. 언제 전문 Workflow Engine을 고려할까?

예:

Workflow 수십~수백 개

며칠 이상 실행

복잡한 Timer

다수 서비스

수많은 Compensation

대규모 Parallel/Join

Workflow Version 관리 어려움

정도가 되면 Temporal 같은 전문 도구를 검토할 가치가 있다.


✅ 164. 하지만 지금 도입하면 운영할 시스템만 하나 더 늘어난다

현재는 기존:

NestJS

PostgreSQL

Queue

를 활용하는 편이 단순하다.


✅ 165. Workflow Orchestration과 기존 Use Case의 관계

기존 Use Case를 버리지 않는다.

예:

UpdateOrderStatusUseCase

SendNotificationUseCase

UploadReportUseCase

를 Workflow가 조합한다.


✅ 166. Workflow는 Application Layer 위에 가까운 역할

구조 예:

presentation

application
├─ use-cases
├─ workflows
└─ queries

domain

infrastructure

로 둘 수 있다.


✅ 167. 폴더 구조 예시

src/
└─ automation/
   ├─ workflows/
   │  ├─ workflow-runner.ts
   │  ├─ workflow-registry.ts
   │  ├─ daily-report/
   │  │  ├─ daily-report.workflow.ts
   │  │  └─ steps/
   │  └─ order-notification/
   │     ├─ order-notification.workflow.ts
   │     └─ steps/
   │
   ├─ jobs/
   ├─ reconciliation/
   └─ infrastructure/

✅ 168. Workflow Registry

예:

workflowRegistry.register(
  'DAILY_REPORT',
  DailyReportWorkflow,
);

Run의 type을 기준으로 정의를 불러온다.


✅ 169. Step Registry

예:

stepRegistry.register(
  'UPLOAD_NOTION',
  UploadNotionStep,
);

같이 구성할 수 있다.


✅ 170. Workflow Runner 의사 코드

async function runWorkflow(
  workflowRunId: string,
) {
  const run =
    await workflowRepository.findById(
      workflowRunId,
    );

  const definition =
    workflowRegistry.get(
      run.type,
      run.version,
    );

  const nextStep =
    definition.getNextStep(run);

  if (!nextStep) {
    await completeWorkflow(run.id);
    return;
  }

  await executeStep(
    run,
    nextStep,
  );
}

✅ 171. Step 실행

async function executeStep(
  run: WorkflowRun,
  step: StepDefinition,
) {
  const claim =
    await claimStepAtomically(
      run.id,
      step.name,
    );

  if (!claim) {
    return;
  }

  const result =
    await step.executor.execute({
      workflowRunId: run.id,
    });

  await applyStepResult(
    run,
    step,
    result,
  );
}

✅ 172. Step Claim도 Atomic하게

같은 Step을 Worker 두 개가 동시에 실행할 수 있다.

따라서:

PENDING
→ RUNNING

전환을 조건부 Update로 처리한다.


✅ 173. Step Worker Crash

Step을 Claim한 뒤 Worker가 죽으면:

RUNNING

에 남는다.

0917에서 배운:

Heartbeat

Lease

Stale Recovery

를 그대로 적용한다.


✅ 174. Workflow Reconciliation

주기적으로:

RUNNING인데 Step 없음

WAITING인데 Event 이미 존재

FAILED인데 Retry 가능한 상태

COMPENSATING인데 멈춤

등을 확인한다.


✅ 175. WAITING Event 유실 문제

예:

Workflow가 WAITING으로 저장되기 직전
Webhook이 먼저 도착

할 수 있다.

이 경우 Event를 잃어버리면 Workflow가 영원히 기다린다.


✅ 176. Event Inbox를 활용할 수 있다

외부 Event를 먼저 DB에 저장한다.

WebhookEvent

providerEventId

receivedAt

processed

Workflow가 WAITING에 들어갈 때 이미 해당 Event가 있는지 확인한다.


✅ 177. Inbox Pattern

외부 Event는:

수신

↓

Inbox 저장

↓

처리

↓

processedAt 기록

한다.

중복 Event도 UNIQUE로 막는다.


✅ 178. Workflow와 Inbox 연결

예:

providerMessageId

로:

WAITING Workflow

를 찾는다.

Webhook이 먼저 왔든 나중에 왔든 최종적으로 연결할 수 있다.


✅ 179. Timeout Event

외부 Event가 영원히 안 올 수도 있다.

WAIT Step에는:

timeoutAt

을 둔다.

예:

30분 안에 Provider Result 없음

이면 Reconciliation을 수행한다.


✅ 180. Timer도 Workflow Event다

예:

WAIT 30분

↓

Timer Fired

↓

Reconcile

로 생각할 수 있다.

0929 Scheduler와 연결된다.


✅ 181. Human Approval도 Timer가 필요할 수 있다

예:

24시간 동안 승인 없음

↓

Workflow Cancel

같은 정책이다.


✅ 182. Saga Orchestration 예시

CreateApplication

Step 1
Create Order
SUCCESS

Step 2
Reserve Resource
SUCCESS

Step 3
External Submit
FAILED

Policy:

Compensate Reserve

↓

Order 상태
SUBMISSION_FAILED

처럼 처리한다.


✅ 183. 모든 Compensation이 완전한 이전 상태를 만들지는 않는다

예:

Order 생성됨

을 DB에서 삭제하는 것보다:

Order 상태
FAILED

로 보존하는 편이 Audit상 좋을 수 있다.


✅ 184. 상태 보존이 중요한 이유

실패한 Workflow 기록을 완전히 삭제하면:

고객이 실제 신청했는지

왜 실패했는지

확인하기 어렵다.

운영 데이터는 보통 삭제보다 상태 전환이 낫다.


✅ 185. Compensation 후 결과 상태

예:

COMPENSATED

라는 상태는:

원래 Workflow 목표는 달성하지 못했지만
발생한 Side Effect는 안전하게 정리됨

을 의미한다.


✅ 186. FAILED와 COMPENSATED는 다르다

FAILED

만 보면 아직 Side Effect가 남아 있을 수 있다.

COMPENSATED

라면 복구 조치까지 끝났다는 의미다.


✅ 187. Compensation Failed

COMPENSATION_FAILED

는 우선순위를 높게 봐야 한다.

자동 Workflow도 실패했고 복구도 실패했기 때문이다.

보통:

MANUAL_REQUIRED

로 넘긴다.


✅ 188. Incident와 연결

중요 Workflow에서:

COMPENSATION_FAILED

가 발생하면 자동으로 Incident Candidate를 생성할 수 있다.


✅ 189. Workflow Severity

Workflow Type별 중요도를 둔다.

예:

ORDER
CRITICAL

NOTIFICATION
HIGH

EXPORT
NORMAL

AI_REPORT
LOW

이다.


✅ 190. Alert도 중요도에 따라 다르게

예:

ORDER Workflow
1건 Compensation Failed
→ Critical
AI Report
1건 Failed
→ Info / P3

처럼 처리한다.


✅ 191. Workflow SLO

예:

OrderNotificationWorkflow

99%가
1분 이내 완료

또는:

UNKNOWN / WAITING stuck
0.1% 이하

같은 목표를 둘 수 있다.


✅ 192. Compensation Rate

Compensation이 자주 발생한다면 이상 신호다.

예:

전체 Workflow 1,000건

Compensation 120건

이면 설계나 외부 시스템에 문제가 있을 수 있다.


✅ 193. Manual Intervention Rate

자동화가 많지만:

30%가 Manual Required

라면 자동화 효과가 낮다.

중요 운영 Metric이 될 수 있다.


✅ 194. Resume Success Rate

예:

실패 Run 100건

Resume 성공
92건

같은 지표도 볼 수 있다.


✅ 195. Workflow 대시보드

예:

오늘

Running
8

Waiting
3

Failed
2

Manual Required
1

Compensating
0

정도로 운영 상태를 볼 수 있다.


✅ 196. Workflow 유형별

WorkflowSuccessFailedWaiting
OrderNotification98224
Export3210
DailyReport300
AI Analysis1421

✅ 197. Stuck Workflow 탐지

예:

RUNNING
2시간

현재 Step
UPLOAD_NOTION

Heartbeat
90분 전

이면 Stuck 상태다.


✅ 198. Stuck 기준은 Workflow마다 다르다

예:

Order
5분

AI 분석
30분

Approval Workflow
24시간

등이다.


✅ 199. Workflow Runbook

예:

Workflow가 멈췄을 때

1. Current Step 확인
2. Step Error 확인
3. 외부 상태 확인
4. Retryable 여부 확인
5. Artifact 확인
6. Resume / Compensate / Manual 결정

✅ 200. AI가 Workflow 운영을 도울 수 있다

AI에게:

실패 Run 요약

현재 상태 설명

관련 로그 정리

Retry 가능성 분석

Compensation 후보 설명

관련 Runbook 추천

을 맡길 수 있다.


✅ 201. AI에게 Compensation 자동 결정을 맡기는 것은 조심한다

특히:

주문 취소

환불

Production Rollback

대량 메시지

같은 업무는 Human Approval이 필요하다.


✅ 202. AI Workflow 자체의 Orchestrator

예:

AITaskWorkflow

Step 1
ANALYZE

Step 2
PLAN

Step 3
MODIFY

Step 4
TEST

Step 5
REVIEW

Step 6
REPORT

Step 7
OPTIONAL_COMMIT

으로 모델링할 수 있다.


✅ 203. AI Workflow 조건 분기

예:

TEST FAILED

이면 바로 Report로 가지 않고:

FIX
↓
TEST

Loop를 제한적으로 만들 수 있다.


✅ 204. Loop에는 반드시 상한이 필요하다

예:

maxFixAttempts = 3

없으면 AI가:

수정
→ 테스트 실패
→ 수정
→ 실패
→ ...

무한 루프에 빠질 수 있다.


✅ 205. AI Workflow Budget

상한 예:

maxSteps

maxAttempts

maxTokens

maxDuration

maxToolCalls

이다.


✅ 206. Budget 초과

예:

AI_BUDGET_EXCEEDED

로 중단하고 Manual Review로 보낸다.


✅ 207. AI Workflow Resume

Runner가 죽어도:

ANALYZE SUCCESS

MODIFY SUCCESS

TEST SUCCESS

REPORT RUNNING

이었다면 Report부터 복구한다.


✅ 208. AI Workflow 상태 검증

Resume 전에:

Git SHA

Working Tree

Artifact

Test Report

Prompt Version

Policy Version

을 확인한다.


✅ 209. AI Workflow와 Production Workflow는 분리한다

코드 분석용 AI Workflow가 실패했다고 Production 주문 처리까지 영향을 받으면 안 된다.

Queue/Worker Resource를 분리한다.


✅ 210. Workflow Orchestrator 장애도 고려

Orchestrator가 죽더라도 Workflow 상태는 DB에 남아 있어야 한다.

새 Worker가 다시 읽고 이어갈 수 있어야 한다.


✅ 211. In-memory 상태만 사용하면 안 되는 이유

예:

let currentStep = 3;

만 메모리에 저장했다가 Process가 죽으면 상태를 잃는다.

중요한 Workflow 상태는 Durable Storage에 저장한다.


✅ 212. Durable Workflow의 핵심

Process는 죽어도 된다.

Workflow 상태는 살아 있어야 한다.

라고 보면 된다.


✅ 213. 현재 프로젝트에서 PostgreSQL이면 충분하다

Workflow 규모가 크지 않다면 별도 전문 저장소보다:

PostgreSQL

에 Run/Step 상태를 저장하면 충분하다.


✅ 214. Queue는 실행 신호, DB는 상태

좋은 분리다.

DB
현재 Workflow State

Queue
이 Step을 실행하라는 신호

이다.

Queue를 유일한 Source of Truth로 삼지 않는다.


✅ 215. Queue Message가 유실돼도 복구 가능해야 한다

DB에는:

Step = PENDING

인데 Queue 메시지가 없다면 Reconciler가 다시 Queue에 등록한다.


✅ 216. Queue Message가 중복돼도 안전해야 한다

Atomic Claim + Step Idempotency로 하나만 실행한다.


✅ 217. Workflow Engine의 핵심은 결국 기존 원칙의 조합이다

새로운 마법 같은 기술이 아니다.

State Machine

Idempotency

Queue

Retry

Lease

Heartbeat

Reconciliation

Audit

Observability

를 하나의 긴 흐름에 적용한 것이다.


✅ 218. 구현 우선순위

현재 프로젝트라면:

1단계

WorkflowRun

WorkflowStepRun

명시적 상태

2단계

Step Retry

Resume

Atomic Claim

3단계

WAIT / External Event Resume

Timeout

Stale Recovery

4단계

Manual Required

Compensation

5단계

Parallel / Join

Parent/Child

Advanced Saga

순으로 가는 것이 현실적이다.


✅ 219. 처음 적용하기 좋은 Workflow

현재 환경에서 첫 대상으로는 Local LLM Report 자동화가 좋다.

이유:

Production 고객 영향이 낮음

여러 Step 존재

Resume 효과 큼

Notion이라는 외부 시스템 존재

실패 테스트하기 쉬움

이다.


✅ 220. 그다음 적용 후보

Export Workflow

Notification Workflow

배포 Workflow

순으로 확장한다.

핵심 주문 흐름은 구조가 안정된 후 적용해도 된다.


✅ 221. Local LLM Workflow 상세 예시

Schedule Trigger

↓

WorkflowRun 생성

↓

COLLECT_GIT

↓

GENERATE_REPORT

↓

VALIDATE_REPORT

↓

SAVE_MARKDOWN

↓

UPLOAD_NOTION

↓

SUCCESS

✅ 222. 실패 예시

COLLECT_GIT
SUCCESS

GENERATE_REPORT
SUCCESS

VALIDATE_REPORT
SUCCESS

SAVE_MARKDOWN
SUCCESS

UPLOAD_NOTION
FAILED

Retry:

UPLOAD_NOTION

만 실행한다.


✅ 223. 다른 실패 예시

GENERATE_REPORT
INVALID_OUTPUT

이면:

attempt < 3

→ LLM Retry

3회 실패:

MANUAL_REQUIRED

로 전환한다.


✅ 224. Ollama 미실행

MODEL_UNAVAILABLE

이면:

Retryable

Backoff

최대 횟수

정책을 적용한다.

계속 실패하면 Workflow를 보류한다.


✅ 225. Notion API 장애

NOTION_TIMEOUT

은 Upload Step만 Retry한다.

생성된 Markdown은 Artifact로 유지한다.


✅ 226. Notion 페이지 생성 후 응답 유실

가장 까다로운 상황이다.

실제로는 페이지가 생성됐지만 우리 시스템은 Timeout이라고 볼 수 있다.


✅ 227. 여기서 Idempotency가 필요하다

예:

externalId
daily-report:platform:2026-09-30

같은 식별자를 Notion 데이터에 저장하거나 기존 페이지를 검색할 수 있어야 한다.


✅ 228. Reconcile before Retry

Timeout 후:

Notion 검색

↓

이미 페이지 있음
→ SUCCESS 복구

없음
→ Retry

하는 흐름이다.


✅ 229. 이것이 Workflow 수준의 UNKNOWN 복구다

UPLOAD_NOTION
UNKNOWN

상태를 둘 수 있다.

즉 실패와 결과 불명확을 구분한다.


✅ 230. Step 상태 확장

예:

PENDING

RUNNING

SUCCESS

FAILED

UNKNOWN

WAITING

SKIPPED

정도로 둘 수 있다.


✅ 231. UNKNOWN Step Reconciliation

각 Step별로 실제 상태 확인 방법이 있어야 한다.

예:

S3
→ Object 존재 확인

Notion
→ externalId 검색

Deploy
→ Running Release 확인

Notification
→ providerMessageId 조회

✅ 232. Reconciliation 지원이 어려운 Side Effect도 있다

그런 Side Effect는 Idempotency Key나 자체 처리 기록을 더 강하게 해야 한다.


✅ 233. Codex 구현 프롬프트

현재 NestJS + Prisma + 기존 Queue/Worker 구조를 기반으로
가벼운 Workflow Orchestration 구조를 추가해줘.

목표는 Temporal/Airflow 같은 범용 Workflow Engine을 만드는 것이 아니라,
여러 Step으로 이루어진 자동화를
부분 실패, Retry, Resume, External Event 대기 상황에서도
안전하게 실행할 수 있게 하는 것이다.

기존 Job, Queue, Audit, Reconciliation 구조가 있다면 반드시 재사용한다.

1. WorkflowRun 모델을 만든다.

필드 예:
- id
- type
- version
- status
- correlationId
- idempotencyKey
- currentStep
- input
- output
- startedAt
- completedAt
- createdAt
- updatedAt

2. WorkflowRun 상태는 다음을 고려한다.

- PENDING
- RUNNING
- WAITING
- SUCCESS
- FAILED
- COMPENSATING
- COMPENSATED
- MANUAL_REQUIRED
- CANCEL_REQUESTED
- CANCELLED

3. WorkflowStepRun 모델을 만든다.

필드:
- id
- workflowRunId
- name
- sequence
- status
- attemptCount
- input
- output
- errorCode
- startedAt
- completedAt
- heartbeatAt

4. Step 상태:

- PENDING
- RUNNING
- SUCCESS
- FAILED
- UNKNOWN
- WAITING
- SKIPPED

5. Workflow Definition과 WorkflowRun을 분리한다.

Workflow Definition은 코드로 관리한다.
현재 단계에서는 관리자 UI에서 자유롭게
Workflow를 조립하는 기능은 만들지 않는다.

6. WorkflowRun 생성 시
workflow version을 고정한다.

7. 동일 Logical Workflow가
중복 생성되지 않도록 idempotencyKey를 지원한다.

8. 각 Step 실행은 Queue / Worker를 통해 처리한다.

Workflow Orchestrator가 실제 Business Logic을
직접 구현하지 않도록 한다.

9. 각 Step은 기존 Use Case 또는 Service를 호출한다.

10. Step 실행 전에
PENDING → RUNNING 전환을
atomic하게 Claim한다.

동일 Step Queue Message가 중복되어도
Side Effect가 한 번만 실행되어야 한다.

11. Step Retry는 기존 WorkflowRun을 유지하고
Step attemptCount만 증가시킨다.

12. Retry 가능한 Error와
Non-Retryable Error를 구분한다.

13. Step 성공 시 Checkpoint를 DB에 기록하고
다음 Step을 Queue에 등록한다.

14. Worker가 종료되어도
Workflow 상태를 DB에서 복구할 수 있게 한다.

중요 상태를 Process Memory에만 저장하지 않는다.

15. RUNNING Step에는 heartbeat 또는
stale detection이 가능하도록 한다.

16. Stale Step은 무조건 처음부터 Retry하지 않고
기존 Reconciliation 정책과 연결한다.

17. UNKNOWN Step을 지원한다.

외부 Side Effect 결과가 불확실한 경우
FAILED로 확정하지 말고 Reconciliation 후
SUCCESS / RETRY / MANUAL_REQUIRED를 판단한다.

18. WAITING Step을 지원한다.

예:
- Webhook 대기
- Human Approval 대기
- 다른 Job 결과 대기

WAITING 상태에서는 Worker를 점유하지 않는다.

19. External Event 수신 시
correlation key 또는 provider ID를 이용해
대기 중 Workflow를 찾아 Resume할 수 있게 한다.

20. Webhook Event는 기존 Inbox / WebhookEvent 구조가 있다면 재사용한다.

Webhook이 Workflow가 WAITING으로 전환되기 전에
먼저 도착하는 Race Condition도 고려한다.

21. WAITING Step에는 timeoutAt을 지원할 수 있게 한다.

Timeout 발생 시
Reconciliation / Retry / Manual 정책을 적용한다.

22. Workflow Cancel을 지원한다.

즉시 Process Kill 방식이 아니라
CANCEL_REQUESTED 상태를 두고
안전한 Step 경계에서 중단한다.

23. Step Metadata 또는 Policy에 다음을 정의할 수 있게 한다.

- maxAttempts
- retryable
- compensatable
- irreversible
- required
- timeout

24. Compensation 구조를 추가한다.

자동 Compensation은
안전하고 되돌릴 수 있는 Action부터 지원한다.

예:
- 임시 파일 삭제
- Notion 페이지 Archive

주문 취소, 환불, Production Rollback 같은
고위험 Compensation은 자동 실행하지 않고
MANUAL_REQUIRED 또는 Approval 흐름으로 보낸다.

25. Compensation도 Idempotent하게 처리한다.

26. Compensation 실패 시
COMPENSATION_FAILED에 준하는 기록을 남기고
MANUAL_REQUIRED로 전환한다.

27. Workflow Step 전체 input/output에
개인정보나 Secret을 저장하지 않는다.

큰 데이터는 artifactId 같은 참조만 저장한다.

28. Workflow Event Log를 남긴다.

예:
- workflow.started
- step.started
- step.completed
- step.failed
- workflow.waiting
- workflow.resumed
- compensation.started
- workflow.completed

29. 모든 Workflow / Step 로그에
다음 Context를 연결한다.

- workflowRunId
- stepRunId
- correlationId
- jobId

30. Workflow Reconciliation을 구현할 수 있는 구조를 만든다.

탐지 대상:
- RUNNING인데 heartbeat 없음
- PENDING인데 Queue Job 없음
- WAITING인데 대응 Event 존재
- UNKNOWN Step
- COMPENSATING 상태에서 멈춘 Run

31. 다음 Metric을 기록할 수 있게 한다.

- workflow started
- workflow success
- workflow failed
- workflow duration
- step duration
- step retry
- waiting count
- compensation count
- manual required count

32. 우선 첫 적용 예시는
DailyReportWorkflow로 만든다.

Step:
- COLLECT_GIT
- GENERATE_REPORT
- VALIDATE_REPORT
- SAVE_MARKDOWN
- UPLOAD_NOTION

33. GENERATE_REPORT 성공 후
UPLOAD_NOTION만 실패하면
LLM을 다시 실행하지 않고
UPLOAD_NOTION만 Resume해야 한다.

34. Notion 업로드 Timeout으로
결과가 불확실하다면 UNKNOWN 처리하고,
가능한 경우 기존 Page 존재 여부를 확인한 뒤
Retry 여부를 결정한다.

35. 테스트를 작성한다.

필수 Scenario:
- 정상 Workflow 완료
- Step 실패 Retry
- Retry Limit 초과
- Step 중복 Queue
- Atomic Claim
- Worker Crash 후 Stale Recovery
- 중간 Step 완료 후 Resume
- WAITING → External Event → Resume
- Event가 먼저 도착하는 Race Condition
- UNKNOWN → Reconciliation → SUCCESS
- Cancel Requested
- Safe Compensation
- Compensation 실패
- Idempotency 중복 Workflow 생성 방지

현재 코드베이스를 먼저 분석하고,
범용 Workflow Platform을 새로 만들지 말고
현재 프로젝트에 필요한 최소 구조로 구현해줘.

✅ 234. Local LLM Workflow Codex 프롬프트

현재 local-llm-work-report v2.0의 실행 흐름을 분석해서
Daily Report 생성을 Step 기반 Workflow로 개선해줘.

현재 동작과 CLI 사용 방식은 최대한 유지한다.

목표는 일부 단계 실패 시 전체 작업을 처음부터 재실행하지 않고,
마지막 성공 Step 이후부터 Resume할 수 있게 하는 것이다.

Workflow:

1. COLLECT_GIT
2. BUILD_CONTEXT
3. GENERATE_REPORT
4. VALIDATE_REPORT
5. SAVE_MARKDOWN
6. UPLOAD_NOTION

요구사항:

1. 날짜 + 프로젝트 기준으로 WorkflowRun을 식별한다.

예:
daily-report:platform:2026-09-30

2. 각 Run에 다음 정보를 저장한다.

- project
- businessDate
- fromCommit
- toCommit
- model
- promptVersion
- workflowVersion
- status

3. 각 Step 상태와 결과를 저장한다.

4. GENERATE_REPORT가 성공했다면
생성된 Report를 Artifact로 저장하고
artifact path/id를 기록한다.

5. SAVE_MARKDOWN 실패 또는
UPLOAD_NOTION 실패 때문에
LLM을 다시 호출하지 않는다.

6. VALIDATE_REPORT에서 다음을 확인한다.

- 결과가 비어 있지 않음
- 최소 길이
- 예상 Markdown 형식
- 필수 제목/섹션 존재

7. VALIDATE_REPORT 실패 시
GENERATE_REPORT를 제한된 횟수만 Retry한다.

8. Ollama unavailable은
MODEL_UNAVAILABLE로 분류한다.

9. Notion Timeout은
결과 불확실 상태로 볼 수 있게 한다.

가능하다면 날짜+프로젝트 External ID 또는 기존 notionPageId를 이용해
이미 업로드됐는지 확인하고
중복 페이지 생성을 방지한다.

10. Notion 업로드만 실패한 Run을
CLI에서 resume할 수 있게 한다.

예:
report resume <run-id>

11. 다음 CLI 조회도 고려한다.

report runs
report status <run-id>

12. Backfill과 Schedule 실행 역시
같은 Workflow Engine을 재사용할 수 있게 한다.

13. Workflow 상태 데이터 때문에
기존 Markdown 파일 경로나 Notion 구조가
불필요하게 바뀌지 않게 한다.

14. Git Repository에 예상하지 않은 수정,
reset, commit, push를 자동으로 수행하지 않는다.

15. 테스트:
- 정상 완료
- Ollama 실패
- Invalid LLM Output
- Markdown 저장 실패
- Notion 실패
- Notion 실패 후 Resume
- 동일 날짜 중복 실행
- Artifact 재사용

현재 구현을 먼저 확인하고,
필요 이상의 추상화는 만들지 말고
향후 다른 자동화에도 재사용 가능한 최소 구조로 설계해줘.

✅ 235. 실무 체크리스트

Workflow

  • Workflow와 Step이 분리되어 있는가?
  • 현재 Step을 확인할 수 있는가?
  • Workflow Version을 알고 있는가?
  • 상태가 DB에 저장되는가?
  • Process가 죽어도 복구 가능한가?

Step

  • 각 Step이 의미 있는 업무 단위인가?
  • Step별 Retry 정책이 있는가?
  • Atomic Claim이 있는가?
  • Step Side Effect가 Idempotent한가?
  • Timeout이 있는가?

Resume

  • 마지막 성공 Step을 알 수 있는가?
  • 성공한 Step을 불필요하게 다시 실행하지 않는가?
  • Resume 전에 Artifact/External State를 확인하는가?

Waiting

  • Webhook 대기를 표현할 수 있는가?
  • WAITING 상태에서 Worker를 점유하지 않는가?
  • Event가 먼저 들어오는 Race Condition을 처리하는가?
  • 대기 Timeout이 있는가?

Saga

  • DB Transaction으로 해결 가능한 부분은 Transaction을 쓰는가?
  • 외부 Side Effect와 Local Transaction을 구분하는가?
  • Compensation이 필요한 Step을 식별했는가?
  • 되돌릴 수 없는 Action을 알고 있는가?

Compensation

  • Compensation도 Idempotent한가?
  • Compensation 실패를 추적하는가?
  • 위험한 Compensation은 Approval을 요구하는가?
  • 무조건 Rollback하지 않고 Forward Recovery도 고려하는가?

Observability

  • WorkflowRun ID가 있는가?
  • StepRun ID가 있는가?
  • Correlation ID가 연결되는가?
  • Workflow Timeline을 볼 수 있는가?
  • Step별 Failure Rate를 확인할 수 있는가?

AI Workflow

  • Prompt Version을 기록하는가?
  • Model Version을 기록하는가?
  • LLM Output을 Artifact로 남기는가?
  • 후속 Step 실패 때문에 LLM을 다시 호출하지 않는가?
  • Step/Tool Loop에 최대 횟수가 있는가?
  • AI Production Action은 Approval을 거치는가?

📌 요약

0929에서는:

언제 실행할 것인가?

를 다뤘다.

0930에서는 그 실행이 여러 단계로 이어질 때:

어떻게 끝까지 안전하게 완료할 것인가?

를 다뤘다.

핵심은 모든 자동화를 하나의 거대한 함수로 만들지 않는 것이다.

Workflow

↓

Step

↓

Checkpoint

↓

다음 Step

형태로 나누면 부분 실패가 발생했을 때:

처음부터 다시 실행

할 필요가 없다.

대신:

마지막 성공 Step 이후부터 Resume

할 수 있다.

특히 외부 API가 섞이기 시작하면 일반 DB Transaction만으로 전체 업무를 Rollback할 수 없다.

그래서:

Local Transaction
+
Workflow
+
Saga / Compensation

의 조합을 사용한다.

Saga의 핵심은:

A SUCCESS
B SUCCESS
C FAILED

라고 해서 무조건 모든 것을 없애는 것이 아니다.

업무 특성에 따라:

Retry

Resume

Compensate

Roll Forward

Manual Intervention

중 적절한 복구 전략을 선택한다.

특히:

알림톡 발송

외부 메시지

외부 API

Production 변경

같은 Side Effect는 실제로 완전히 되돌릴 수 없는 경우가 많다.

그래서:

Rollback 가능한 것

Compensation 가능한 것

Irreversible한 것

을 구분해야 한다.

Workflow가 외부 응답이나 사람 승인을 기다리는 경우에는:

WAITING

상태로 저장하고 Worker를 해제한다.

Event 도착

↓

Workflow Resume

방식으로 동작한다.

중요한 Workflow 상태는 메모리가 아니라 DB에 저장한다.

즉:

Process는 죽어도 된다.

Workflow 상태는 살아 있어야 한다.

는 것이 Durable Workflow의 핵심이다.

현재 프로젝트에서는 별도의 거대한 Workflow Platform을 도입하기보다:

PostgreSQL

기존 Queue

WorkflowRun

WorkflowStepRun

Reconciliation

정도로 시작하는 것이 현실적이다.

특히 현재 Local LLM 보고서 자동화는 첫 적용 대상으로 좋다.

Git 수집
↓
LLM 생성
↓
검증
↓
Markdown 저장
↓
Notion 업로드

를 Step으로 나누면 Notion만 실패했을 때:

LLM 다시 실행

하지 않고:

Notion Upload만 Retry

할 수 있다.

지금까지의 전체 시리즈와 연결하면:

Time / Event
↓
Trigger
↓
Workflow Run
↓
Step
↓
Checkpoint
↓
Retry / Wait / Resume
↓
Compensate / Roll Forward
↓
Reconcile
↓
Complete
↓
Audit

가 된다.

결국 Workflow Orchestration의 핵심은

“자동화를 길게 연결하는 것”이 아니라, 중간에 어디서든 실패할 수 있다는 전제 아래 각 단계의 상태를 남기고, 이미 성공한 작업은 보존하면서 필요한 부분만 안전하게 다시 실행할 수 있게 만드는 것

이다.

0개의 댓글