본 글은 워크플로우 시스템의 핵심을 이루는 두 가지 세계를 Nuke와 Terraform 사례를 통해 상세히 설명합니다.
1. 두 가지 세계의 정의
-
선언적 DAG (Declarative DAG): 워크플로우의 구조와 흐름을 정의하는 부분입니다. Nuke에서는 Tcl 스크립트, Terraform에서는 HCL이 이에 해당합니다. 이는 ‘무엇을 해야 하는가’를 기술합니다.
-
명령적 노드 코드 (Imperative Node Code): DAG 내 각 노드에서 실제로 작업을 수행하는 코드입니다. Nuke에서는 C++로 컴파일된 네이티브 객체, Terraform에서는 Go로 작성된 ‘프로바이더’가 이에 해당합니다. 이는 ‘어떻게 수행하는가’를 기술하며, 멱등성을 가져야 합니다.
2. 엄격한 분리의 중요성
이 두 세계는 DAG에 노출된 속성을 통해서만 상호작용하며, 명령적 코드가 선언적 DAG를 직접 제어할 수 없습니다. 엔진은 값을 한 방향으로만 노드에 전달하고, 결과는 수집됩니다. 이러한 분리는 다음과 같은 이점을 제공합니다:
-
명확한 책임 분리: 오케스트레이션(DAG)과 실행(노드)의 역할을 명확히 구분합니다.
-
멱등성 및 예측 가능성: 노드 코드가 멱등성을 유지하고, 엔진은 결과를 캐싱하거나 재시도할 수 있습니다.
-
견고성: 각 단계가 독립적으로 해결, 캐싱, 재시도 및 오케스트레이션될 수 있어 시스템의 안정성이 향상됩니다.
3. 통합 시도의 문제점: ‘3색 함수’
ActiveJob Continuation, Temporal, Vercel 워크플로우와 같이 두 세계를 융합하려는 시도는 다음과 같은 문제점을 야기합니다:
-
복잡한 실행 흐름: 동기(red), 비동기(blue)에 더해 워크플로우(green) 코드가 혼합되어 ‘3색 함수’를 생성합니다.
-
비결정성:
performance.now()와 같은 비결정적 함수가 워크플로우 코드 내에서 사용될 경우, 일시 중단 및 재개 시 의미 없는 결과를 초래할 수 있습니다. -
불확실한 보장: 체크포인트, 멱등성, 재시도 로직이 코드에 내재되어 추론하기 어렵고, 예상치 못한 버그를 유발합니다.
-
잘못된 친숙함: 단일 코드 블록으로 작성되어 익숙하게 느껴지지만, 실제로는 완전히 다른 실행 환경에서 작동하므로 오해를 불러일으킵니다.
4. YAML 사용에 대한 경고
DAG 정의를 YAML로 작성하려는 유혹이 있지만, YAML은 ‘최소 공통분모’로서 다양한 언어 사용자 간의 타협점일 뿐, 특정 언어 생태계에 최적화된 선택은 아닙니다. 사용자에게 친숙하고 강력한 언어로 DAG를 정의할 수 있도록 하는 것이 더 바람직합니다.