도입
실무에서 작업은 거의 항상 서로 얽혀 있습니다.
어떤 일은 먼저 끝나야 다음 일을 시작할 수 있고, 어떤 일은 서로 독립적이라 동시에 진행할 수 있습니다.
이 관계를 단순한 체크리스트나 시간순 목록으로만 관리하면, 실제 병목이 어디에 있는지, 어떤 작업을 병렬화할 수 있는지, 무엇이 전체 일정을 결정하는지 파악하기 어렵습니다.
태스크 그래프는 이런 문제를 해결하기 위해 작업을 노드로, 의존 관계를 간선으로 표현하고, 그 구조를 바탕으로 실행 순서와 병렬성, 병목을 해석하는 모델입니다.
필요성
작은 작업 몇 개만 있는 경우에는 메모장 수준의 목록으로도 충분할 수 있습니다. 하지만 빌드 시스템, 데이터 파이프라인, 대규모 프로젝트 관리, 분산 처리처럼 작업 수가 많아지면 단순 목록은 거의 즉시 한계에 부딪힙니다.
이때 중요한 것은 “해야 할 일의 개수”가 아니라 “작업들 사이의 관계”입니다. 관계를 명확히 모델링해야 전체 일정이 왜 지연되는지, 어떤 작업을 동시에 실행할 수 있는지, 어떤 변경이 어디까지 전파되는지 해석할 수 있습니다.
그래서 태스크 그래프는 단지 시각화 도구가 아니라, 복잡한 실행 흐름을 제어하기 위한 기본 모델로 자주 사용됩니다.
- 선행 조건과 후속 작업을 명확하게 드러낼 수 있음
- 독립 작업을 분리해 병렬 실행 구간을 찾기 쉬움
- 사이클, 병목, 과도한 직렬화 같은 구조적 문제를 빨리 발견할 수 있음
- 전체 일정에 영향을 주는 임계 경로와 여유 구간을 분석하기 좋음
정의
태스크 그래프에서 노드(Node)는 하나의 작업을 의미합니다. 이 작업은 문서 작성, 컴파일, 테스트, 데이터 정제, 모델 학습, 배포, 승인 요청처럼 무엇이든 될 수 있습니다.
간선(Edge)은 작업 사이의 관계를 뜻합니다. 예를 들어 A → B 라는 간선이 있다면, B는 A가 끝난 뒤에야 시작할 수 있다는 의미로 해석하는 경우가 많습니다.
실행 시스템에서 태스크 그래프는 보통 DAG(Directed Acyclic Graph)로 다뤄집니다. 방향성이 있어야 선행 관계를 표현할 수 있고, 순환이 없어서야 실제 실행 순서를 계산할 수 있기 때문입니다.
"태스크 그래프는 작업의 목록이 아니라
작업이 서로를 어떻게 제약하는지 표현한 실행 모델에 가깝습니다."
핵심 원리
태스크 그래프를 이해할 때 가장 중요한 점은, 실행 순서를 사람이 줄줄이 적어 넣는 것이 아니라는 점입니다. 먼저 작업과 선행 조건을 정의하고, 그 결과로 어떤 작업이 지금 실행 가능한지 계산하는 방식에 가깝습니다.
그래프에서 선행 조건이 모두 해결된 태스크는 즉시 실행 가능 상태가 됩니다. 반대로 아직 끝나지 않은 선행 작업이 남아 있으면 해당 태스크는 대기 상태를 유지합니다.
그래서 태스크 그래프를 쓰면 “순서를 수동으로 관리하는 일”보다 “관계를 정확히 선언하는 일”이 더 중요해집니다. 관계가 정확하면 실행 순서와 병렬 구간은 시스템이 계산할 수 있습니다.
사이클이 생기면 어떤 작업도 끝낼 수 없는 모순 상태가 되기 때문에, 실행 가능한 태스크 그래프는 대부분 DAG 형태를 유지합니다.
graph = {
"collectRequirements": [],
"design": ["collectRequirements"],
"developBackend": ["design"],
"developFrontend": ["design"],
"integrationTest": ["developBackend", "developFrontend"],
"deploy": ["integrationTest"]
}
구성 요소
| 요소 | 역할 | 실무 해석 |
|---|---|---|
| Node | 개별 작업 단위 | 컴파일, 테스트, 문서 생성, 승인, 배포 같은 실행 단위 |
| Edge | 작업 간 선행 관계 | 어떤 결과가 나와야 다음 작업이 시작되는지 정의 |
| In-degree | 아직 해결되지 않은 선행 조건 수 | 0이 되면 즉시 실행 가능한 태스크가 됨 |
| Weight | 시간, 비용, 우선순위 같은 부가 정보 | 단순 순서가 아니라 전체 완료 시간과 자원 계획 분석에 사용 |
| Critical Path | 전체 완료 시간을 결정하는 가장 긴 경로 | 프로젝트 병목과 일정 지연의 핵심 원인 분석에 중요 |
특히 진입 차수와 임계 경로는 단순한 그래프 이론 용어가 아니라, 실제 운영에서는 “지금 무엇을 실행할 수 있는가”와 “무엇이 전체 완료 시간을 결정하는가”를 설명하는 핵심 지표입니다.
그래프 계산 흐름
| 단계 | 역할 | 실무 해석 |
|---|---|---|
| Modeling | 어떤 작업과 관계가 존재하는지 수집 | 작업 단위와 선행 조건을 선언하는 단계 |
| Validation | 사이클, 누락된 의존성, 잘못된 연결 확인 | 실행 불가능한 그래프를 사전에 차단하는 단계 |
| Scheduling | 현재 실행 가능한 태스크를 선택 | 진입 차수 0인 태스크를 큐에 올리는 단계 |
| Execution | 선택된 태스크를 실제로 수행 | 사람이 하든 시스템이 하든 결과물을 생성하는 단계 |
| Update | 완료 결과를 반영해 후속 태스크 상태 갱신 | 새롭게 실행 가능해진 작업을 다시 스케줄링 |
이 구조를 이해하면 같은 작업 집합이라도 자원 수, 우선순위, 재시도 정책에 따라 실제 실행 순서와 완료 시간이 달라질 수 있다는 점도 함께 이해할 수 있습니다.
Task Graph Handling
1) Collect tasks
2) Resolve dependencies
3) Detect cycles
4) Schedule ready tasks
5) Execute and update graph
기본 구조
task-graph/
├── nodes/
│ ├── task id
│ ├── input / output
│ ├── expected duration
│ └── retry policy
├── edges/
│ └── prerequisite -> dependent
├── metadata/
│ ├── priority
│ └── resource requirements
└── scheduler/
├── concurrency
└── execution policy
nodes: 작업의 정체성과 실행 단위를 정의edges: 어떤 작업이 어떤 작업에 의존하는지 표현input / output: 작업 간 데이터 또는 산출물 연결점을 설명metadata: 시간, 비용, 우선순위, 자원 제약 같은 운영 정보를 포함scheduler: 같은 그래프를 어떤 정책으로 실행할지 결정
초보자가 가장 많이 놓치는 부분은 태스크 그래프를 단지 “연결 그림”으로만 보는 것입니다. 실제로는 입력과 출력, 자원, 실패 처리까지 함께 정의되어야 재현 가능한 실행 모델이 됩니다.
기본 구현
from collections import defaultdict, deque
graph = {
"collectData": [],
"writeDraft": ["collectData"],
"makeImage": ["collectData"],
"review": ["writeDraft", "makeImage"],
"publish": ["review"],
}
dependents = defaultdict(list)
indegree = {task: len(prereqs) for task, prereqs in graph.items()}
for task, prereqs in graph.items():
for prereq in prereqs:
dependents[prereq].append(task)
queue = deque([task for task, deg in indegree.items() if deg == 0])
order = []
while queue:
task = queue.popleft()
order.append(task)
for nxt in dependents[task]:
indegree[nxt] -= 1
if indegree[nxt] == 0:
queue.append(nxt)
print(order)
패턴 1. 선형 워크플로
가장 단순한 형태의 태스크 그래프는 모든 작업이 앞 단계 결과를 기다리는 선형 구조입니다. 이런 형태는 설명과 운영이 단순하다는 장점이 있지만, 전체 시간이 각 작업 시간을 그대로 누적한 값에 가까워집니다.
즉, 병렬로 처리할 수 있는 구간이 거의 없기 때문에 처리량을 높이기 어렵고, 중간의 한 작업만 늦어져도 전체 일정이 그대로 밀리기 쉽습니다.
요구사항 분석
-> 설계
-> 구현
-> 테스트
-> 배포
패턴 2. 병렬 가능한 DAG
실무에서 태스크 그래프를 도입하는 가장 직접적인 이유는 병렬성 확보입니다. 작업 자체가 줄지 않더라도, 서로 독립적인 작업을 정확히 분리하면 기다리는 시간이 줄어들고 전체 완료 시간이 짧아집니다.
예를 들어 설계가 끝난 뒤에는 백엔드 구현, 프론트엔드 구현, 테스트 데이터 준비를 동시에 진행할 수 있습니다. 이런 구조는 단순한 목록보다 그래프로 봤을 때 훨씬 분명해집니다.
요구사항 분석 -> 설계
설계 -> 백엔드 구현
설계 -> 프론트엔드 구현
설계 -> 테스트 데이터 준비
백엔드 구현 -> 통합 테스트
프론트엔드 구현 -> 통합 테스트
테스트 데이터 준비 -> 통합 테스트
통합 테스트 -> 배포
패턴 3. 가중치와 임계 경로
| 요소 | 역할 | 실무 해석 |
|---|---|---|
| Duration | 태스크 소요 시간 | 어떤 경로가 전체 완료 시간을 지배하는지 판단하는 기준 |
| Cost | 작업 수행 비용 | 예산 제약이 있는 스케줄링에서 우선순위 결정에 영향 |
| Resource | 사람, 서버, GPU 같은 자원 요구량 | 이론상 병렬 가능해도 실제 동시 실행이 안 될 수 있음 |
| Critical Path | 가장 긴 누적 시간 경로 | 이 경로 위 작업이 늦어지면 전체 일정이 그대로 밀림 |
| Slack | 지연 허용 여유 시간 | 조정 가능한 작업과 조정 불가능한 작업을 구분하는 기준 |
즉, 태스크 그래프는 단지 실행 순서를 만드는 구조가 아니라, 일정 관리와 병목 분석, 자원 계획을 위한 분석 모델로도 매우 중요합니다.
한계와 주의점
태스크 그래프의 장점은 구조를 드러낸다는 점이지만, 그 구조가 잘못 모델링되면 문제도 함께 고정됩니다. 예를 들어 실제로는 독립적인 작업을 모두 직렬로 연결하면 그래프는 정상처럼 보여도 성능은 불필요하게 나빠집니다.
반대로 숨겨진 승인 절차, 수작업 검토, 외부 시스템 대기 같은 의존성을 그래프에 반영하지 않으면 계획은 깔끔해 보여도 실제 실행은 계속 어긋납니다.
또한 반복, 조건 분기, 동적 작업 생성 같은 흐름은 정적인 DAG만으로는 표현이 불편할 수 있습니다. 이 경우에는 재귀적 그래프 확장이나 런타임 스케줄링 규칙이 함께 필요합니다.
- 작업 단위를 너무 크게 잡으면 병목 원인이 흐려짐
- 반대로 너무 잘게 쪼개면 스케줄링 비용과 관리 복잡도가 커짐
- 실제 숨겨진 의존성을 그래프에 반영하지 않으면 계획과 실행이 어긋남
- 조건 분기와 반복 흐름은 단순 정적 DAG만으로 다루기 어려울 수 있음
자주 하는 실수
- 작업 이름만 적고 입력과 출력, 완료 조건을 정의하지 않음
- 단순 시간 순서를 그대로 의존 관계로 착각해 불필요하게 직렬화함
- 실제로는 병렬 가능한 작업을 하나의 큰 태스크로 뭉뚱그림
- 승인, 검토, 데이터 도착 같은 외부 의존성을 모델에서 누락함
- 사이클을 만들고도 왜 스케줄링이 멈추는지 원인을 찾지 못함
- 작업 시간이 바뀌었는데 임계 경로와 여유 시간을 다시 계산하지 않음
실무 루틴
- 먼저 각 작업의 산출물과 완료 조건을 정한다.
- 그다음 각 작업이 무엇을 기다려야 하는지 선행 조건을 정의한다.
- 불필요한 순차 연결이 없는지 보고 병렬 가능한 작업을 분리한다.
- 각 태스크에 시간, 비용, 우선순위, 자원 요구량을 붙인다.
- 사이클 여부와 위상 순서를 검증한다.
- 마지막으로 임계 경로와 병목 구간을 계산해 운영 계획에 반영한다.
디버깅
점검 체크리스트
- 시작 노드는 무엇인가
- 사이클이 존재하는가
- 위상 순서가 끝까지 생성되는가
- 병렬 가능한 작업이 불필요하게 직렬화되어 있지 않은가
- 현재 임계 경로는 무엇인가
- 숨겨진 외부 의존성이 있는가
요약
- ✅ 태스크 그래프는 작업을 노드, 의존 관계를 간선으로 표현하는 구조다.
- ✅ 실행 가능한 모델로는 대부분 DAG 형태가 사용된다.
- ✅ 진입 차수 0인 태스크가 현재 실행 가능한 작업이 된다.
- ✅ 태스크 그래프를 쓰면 병렬 구간과 병목 구간을 구조적으로 찾기 쉬워진다.
- ✅ 위상 정렬은 실행 가능한 순서를 계산하는 기본 도구다.
- ✅ 작업 시간과 비용을 함께 두면 임계 경로와 전체 완료 시간을 분석할 수 있다.
- ✅ 잘못된 작업 단위나 숨겨진 의존성은 그래프 전체를 왜곡한다.
- ✅ 좋은 태스크 그래프는 자동화와 일정 관리, 자원 계획을 동시에 개선한다.