파트 2 | Apache DolphinScheduler의 핵심 추상화 모델
Source: Dev.to
이 글은 시리즈 Deep Dive into Apache DolphinScheduler: From Scheduling Principles to DataOps in Practice의 Part 2입니다.
소스 코드와 스케줄링 모델 관점에서 DolphinScheduler의 핵심 추상화를 분석하며, Workflow, TaskDefinition, 그리고 인스턴스‑레벨 객체 간의 책임 경계를 중점적으로 살펴봅니다.
DAG 일러스트레이션을 사용해 스케줄러가 의존성 평가를 통해 복잡한 오케스트레이션을 어떻게 구동하는지 설명합니다.
Previous article:
A Scheduler Is More Than Just a “Timer”
왜 이렇게 많은 객체가 있을까?
DolphinScheduler를 어느 정도 사용해 본 뒤, 많은 사용자들이 묻습니다:
시스템에 워크플로 정의, 워크플로 인스턴스, 태스크 정의, 태스크 인스턴스와 같이 동시에 많은 객체가 왜 존재하나요?
이것이 과도하게 설계된 것이 아닌가요?
소스 코드를 살펴보고 스케줄러가 실제로 어떻게 동작하는지 보면, 답은 정반대입니다:
이러한 추상화는 복잡성을 관리하기 위해 의도적으로 분리된 것입니다.
워크플로우: 절대 “실행되지” 않는 DAG 청사진
DolphinScheduler 설계에서 Workflow(소스 코드에서는 ProcessDefinition에 매핑)는 처음부터 순수하게 정적 구조로 정의됩니다.
이는 제한된 정보만을 설명합니다:
- 프로세스에 어떤 작업이 존재하는지,
- 작업들 간의 의존 관계,
- 조건 분기나 서브‑워크플로우가 있는지 여부.
이 요소들이 합쳐져 DAG를 형성하지만, 이 DAG는 스스로 절대 실행되지 않습니다.
코드 관점에서 Workflow는 스케줄링 엔티티라기보다 구조화된 설정 객체에 가깝습니다. 데이터베이스에는 실행 결과가 기록되지 않으며(성공/실패 상태, 시작 시간, 종료 시간 없음) 특정 실행 중에 무슨 일이 일어나는지에 대한 인식도 없습니다.
Design principle: Structure and execution must be completely separated; otherwise, state will pollute the definition.
실제로 DolphinScheduler에서 DAG가 해결하는 문제
DAG는 단일하고 명확한 책임을 가집니다:
특정 시점에 작업을 스케줄링할 수 있는지 여부를 판단한다.
작업이 어떻게 실행되는지, 실행 결과의 비즈니스 의미에 대해서는 관심을 두지 않습니다. 오직 의존성이 충족되었는지 여부만을 평가합니다.
📌 여기에는 전형적인 다중 부모 의존 구조를 보여주는 DAG 일러스트를 사용할 수 있습니다.
노드가 스케줄링될 수 있는지는 실행 순서가 아니라 모든 상위 의존성이 완료되었는지에 달려 있습니다. 이것이 DolphinScheduler가 DAG를 동적으로 평가하는 핵심 런타임 로직입니다.

소스 코드에서는 프로세스 인스턴스가 시작될 때 DAG가 메모리 구조로 파싱되고, 이 구조가 이후 모든 스케줄링 결정을 주도합니다.
TaskInstance의 상태가 변하면 스케줄러는 단순히 “앞으로 진행”하지 않습니다. 대신 DAG를 재평가하여 이제 잠금이 해제된 노드가 어떤 것인지 판단합니다. 이 때문에 DolphinScheduler는 자연스럽게 다음을 지원합니다:
- 병렬 실행,
- 조건 분기,
- 실패 차단.
이러한 기능들은 하드코딩된 특징이 아니라, DAG 추론의 자연스러운 결과입니다.
TaskDefinition: 작업의 실행 템플릿
워크플로우가 프로세스의 설계도라면 TaskDefinition은 단일 작업에 대한 템플릿입니다.
소스 코드에서 TaskDefinition은 작업을 어떻게 실행해야 하는지에 관한 모든 정보를 저장합니다. 여기에는 다음이 포함됩니다:
- 작업 유형 (Shell, SQL, Spark, Flink 등)
- 파라미터 및 스크립트 내용
- 실패 전략, 타임아웃 설정, 리소스 설정
핵심 포인트: TaskDefinition은 완전히 무상태(stateless)입니다.
TaskDefinition 안에서 “실행 성공”과 같은 필드를 절대 보게 되지 않을 것입니다. 실행 결과는 정의에 속하지 않기 때문입니다.
public class TaskDefinition {
private Long id;
private String name;
private TaskType taskType;
private String taskParams;
private int timeout;
private int failRetryTimes;
// Note: no execution state here
}
TaskDefinition의 유일한 책임은 어떻게 실행할지를 설명하는 것이며, 어떻게 실행되었는지를 기록하는 것이 아닙니다.
프로세스 정의 vs. 프로세스 인스턴스: 실제 경계
DolphinScheduler를 이해하려면 정의와 인스턴스를 명확히 구분해야 합니다.
워크플로우가 트리거될 때, 시스템은 워크플로우와 해당 TaskDefinition을 기반으로 전체 런타임 객체 집합을 생성합니다:
ProcessInstanceTaskInstance
ProcessInstance
**“이 특정 워크플로우 실행”**을 나타냅니다.
public class ProcessInstance {
private Long id;
private Long processDefinitionId;
private ExecutionStatus state;
private Date startTime;
private Date endTime;
}
TaskInstance
**“이 특정 작업 실행”**을 나타냅니다.
UI에서 보는 모든 상태—실행 중, 실패, 재시도, 로그—는 정의 수준이 아니라 인스턴스 수준에서 완전히 존재합니다.
public class TaskInstance {
private Long id;
private Long taskDefinitionId;
private ExecutionStatus state;
private int retryTimes;
private Date startTime;
}
정의는 재사용 가능하고, 인스턴스는 일시적입니다.
이 구분은 스케줄링 시스템의 장기적인 안정성에 필수적입니다.
이러한 추상화가 복잡한 오케스트레이션을 가능하게 하는 방법
작업 수가 증가하고 워크플로가 중첩되며 실패가 일상이 되면, 명확한 추상화 경계가 없는 시스템은 빠르게 제어력을 상실합니다.
DolphinScheduler의 모델 분리는 여러 중요한 기능을 가능하게 합니다:
- 동일한 워크플로를 여러 인스턴스로 동시에 실행해도 서로 간섭하지 않습니다.
TaskInstance에 대한 재시도는 정의를 오염시키지 않고 오직 해당 인스턴스에만 영향을 미칩니다.- DAG 평가와 작업 실행이 완전히 분리됩니다.
- 스케줄링 로직은 비즈니스 로직이 아니라 상태 전이(state transitions)를 중심으로 동작합니다.
이 관점에서 DolphinScheduler는 “작업을 관리”하는 것이 아니라 상태와 의존성의 변화를 관리하는 것입니다.
요약
DolphinScheduler를 “보다 강력한 Cron”으로 간주하면, 이러한 모델들은 지나치게 복잡해 보일 수 있습니다.
하지만 시스템 및 소스 코드 관점에서 보면, 이들은 매우 엄격하고 깊이 있게 설계된 구조를 이루고 있습니다.
다음 글에서는 이 모델을 이어가면서 다음을 살펴볼 것입니다:
스케줄러가 상태 전환을 중심으로 어떻게 동작하는지 — 그리고 실패가 증폭되지 않고 흡수되는 방식.
