5-1. 워크플로 관리
[기초 지식] 워크플로 관리: 데이터의 흐름을 일원 관리하기
- 워크플로 관리(workflow management): 기업 내의 정형적인 업무 프로세스(신청, 승인, 보고 등)와 같이 정해진 업무를 원할하게 진행하기 위한 구조
- 매일 매일의 태스크를 관리하는 구조가 정기적인 배치 처리 실행에도 유용하기 때문에 데이터 처리 현장에서도 자주 이용
- 일반적인 데이터 워크플로 관리:
- 태스크는 정해진 스케줄에 따라 자동으로 실행
- 무언가 비정상적 일이 발생한 경우에는 사람이 개입하여 문제 해결
워크플로 관리 도구
- 워크플로 관리 도구의 주요 역할:
- 정기적으로 태스크를 실행
- 비정상적인 상태를 감지하여 그것에 대한 해결
- 대표적인 오픈소스 워크플로 관리 도구: Airflow, Digdag, Oozie
워크플로 관리 도구와 태스크
- 태스크(Task): 실행되는 개별 데이터 처리 단위
기본 기능과 빅데이터에서 요구되는 기능
- 워크플로 관리 도구가 필요한 이유: 태스크 실행에 실패할 수 있기 때문
- 아래 기능이 필요 (모든 태스크를 일원 관리하기 쉽게 해야 함)
- 태스크를 정기적인 스케줄로 실행하고 결과 통지
- 태스크 간의 의존 관계 정하고 정해준 순서대로 빠짐없이 실행
- 태스크 실행 결과를 보관하고, 오류 발생 시에는 재실행할 수 있도록
선언 형과 스크립트 형
선언형(Declarative): XML, YAML 등의 서식으로 워크플로 기술
- ex. Oozie
- 미리 제공된 기능만 이용
- 누가 작성해도 동일한 워크플로가 되기 때문에 유지 보수성이 높아짐
스크립트형 (Scripting): 스크립트 언어(ex. python)로 워크플로를 정의
- ex. Airflow
- 유연함: 태스크 정의를 프로그래밍할 수 있음
- 데이터 수집 과정에서는 스크립트 형의 도구를 사용하여 유연하게 워크플로를 조립하고, 데이터가 모아지면 정형적인 처리만 하게 되므로 이후에는 선언형 도구를 사용할 수 있다
오류로부터의 복구 방법 먼저 생각하기
빅데이터를 취급하고 있으면 다양한 오류가 발생
- 미리 예기치 못한 오류가 발생할 가능성을 고려하여 오류 발생 시의 대처 방법을 결정해두는 것이 중요
복구와 플로우의 재실행
- 수작업에 의한 복구를 전제한 태스크 설계
- 오류로부터 자동 회복할 수 있다는 점은 고려하지 않는 것이 좋다 (오류의 종류는 수많기 때문에 전부 대응하는 것은 불가능)
- 플로우: 일련의 태스크
- 복구의 기초
- 각 플로우 실행시 날짜를 파라미터화 한다
- 동일 플로우에, 동일 파라미터를 건넬 시 동일한 태스크가 실행될 수 있다 => 플로우가 도중에 실패해도 나중에 동일 파라미터로 재실행 가능
워크플로의 버전 관리
- 복잡화된 데이터 파이프라인의 구축은 더 이상 시스템 개발고 ㅏ다른 분야가 아니기 대문에 소프트웨어 개발 분야에서 연마되어 온 버전 관리 기법을 도입하는 것을 추천 (Git)
태스크를 되도록 작게 유지하기
- 큰 태스크는 나누어 적당히 작은 복수의 태스크로 플로우를 구성하자
- 문제가 발생해도 도중에 재개할 수 있음
재시도
- 여러 차례 반복하는 오류는 자동화
- 오류의 원인을 그때마다 조사하여 대책 마련
백필
- 일정 기간의 플로우를 연속해서 실행하는 구조
- 대량의 태스크를 실행할 때에는 성능상의 주의가 필요
- 테스트 삼아 조금씩 백필 실행
멱등한 조작으로 태스크를 기술하기
- 멱등성: 동일한 태스크를 여러 번 실행해도 동일한 결과가 된다
- 재실행의 안전성을 확보하는 데에 가장 중요
- 태스크를 도중까지 실행하다 실패했을 때 도중 경과가 남아있으면, 태스크 재실행에 의해 데이터가 혼재 될 수 있음
- 각 태스크는 원칙적으로 '마지막까지 성공'하거나 '실패하면 아무것도 남지 않음' 이 둘 중 하나만 존재해야 함
- '도중가지 성공'이라는 어정쩡한 상황은 허가하지 않아야 함
원자성 조작
- 원자성 조작(atomic operation): 각 태스크가 시스템에 변경을 가하는 것을 한 번만 할 수 있도록 하는 것
- 워크플로에 포함된 태스크를 모두 원자성 조작으로 구현함으로써 재시도의 안전성을 높일 수 있음
- 원자성 조작이 문제를 야기하는 경우:
- 원자성 조작 직후에 문제가 발생하면, 워크플로 관리 도구에서 이를 오류로 여겨 재시도 하는 경우
-> 재시도로 인한 중복 발생
멱등한 조작 - 추가와 치환
- 멱등한 조작(idempotent operation): 동일한 태스크를 여러 번 실행해도 동일한 결과가 되도록 하는 것
- 안정성을 확보하는 가장 확실한 방법
- 위 예시에서 멱등한 조작이면 재실행 해도 중복 발생 안함
- 원칙적으로는 항상 데이터를 덮어써야 함
- 추가 (append)는 데이터가 중복되지만, 치환 (replace, overwrite)은 반복해도 결과가 변하지 않음
- 태스크에 부여된 파라티러를 잘 이용해 여러 번 실행해도 항상 치환이 되도록 설계
멱등한 추가
- 테이블 파티셔닝하여, 파티션 단위로 치환하도록 한다
- 태스크를 멱등으로 구성하는 것이 어렵다면, 그것을 포기하고 원자성을 지닌 추가로 운용
- 오류 발생 시, 수작업으로 복구
원자성을 지닌 추가
- 하나의 테이블에 몇 번이고 데이터를 써넣을 때가 있음
- 이 경우 추가를 반복하지 말고 중간 테이블을 만들어 처리한 후, 마지막에 목적 테이블에 한 번에 추가하는 것이 안전
워크플로 전체를 멱등으로 하기
- 데이터 파이프라인을 안정적으로 운용하기 위해서는 거기에 포함된 태스크나 플로우를 가능한 한 멱등으로 해야 함
- 데이터 마트를 구축하는 플로우에서도 되도록 추가는 삼가고 테이블마다 치환
- 멱등한 태스크는 실패시 안전하게 재실행할 수 있음
태스크 큐: 자원의 소비량 컨트롤하기
- 워크플로 관리 도구에서 요구되는 커다란 역할 중 하나가 부하 컨트롤
- 병렬화가 필요한 경우
- 너무 대량의 태스크를 동시 실행하면 서버에 과부하가 걸리므로 어느 정도 제한을 해야 함
- 잡 큐 (job queue) (= 태스크 큐(task queue)) 구조
- 모든 태스크는 일단 큐에 저장되고 일정 수의 워커 프로세스가 그것을 순서대로 꺼내면서 병렬화가 실현
병목 현상의 해소
- 워크플로 실행 시 자주 발생하는 문제 (서버의 내부적인 요인)
1. CPU 사용률 100% -> CPU 코어 수를 늘린다. 서버를 증설한다.
2. 메모리 부족 -> 메모리를 증설한다. 스왑 디스크를 추가한다. 태스크를 작게 분할한다.
3. 디스크 넘침 -> 각 태스크가 임시 파일을 삭제하고 있는지 확인한다. 디스크를 증설한다.
4. 디스크 I/O의 한계 -> SSD 등의 고속 디스크를 사용한다. 여러 디스크로 분산한다.
6. 네트워크 대역의 한계 -> 고속 네트워크를 사용한다. 데이터의 압축률을 높인다.
7. 통신 오류나 타임 아웃 -> 시스템 상의 한계일 가능성이 있다. 서버를 분리한다.
- 서버 외부 요인이 원인인 병목 현상은 문제를 제거할 수 없다.
태스크 수의 적정화 - 너무 크거나 너무 작지 않은 정도로 잘 분할하기
- 작은 태스크를 자주 실행하면 오버헤드만 커짐
- 태스크가 너무 클 경우에는 나누고, 너무 작을 경우에는 하나로 모음으로써 각 태스크가 적절한 크기가 될 수 있도록 조정
5-2. 배치 형의 데이터 플로우
MapReduce의 시대는 끝났다 - 데이터 플로우와 워크플로
- 데이터 플로우(data flow): 다단계의 데이터 처리를 그대로 분산 시스템 내부에서 실행 (워크플로와 구분해서 사용)
MapReduce의 구조
- 과거 빅데이터의 대표적인 기술이었지만 이제 새롭게 사용되는 일은 없어짐
- Google: MillWheel
- Hadoop: Tez
- MapReduce의 개념
1. Split: 분산 처리를 위해 데이터 파일을 일정 크로 나눔
2. Map: 분할된 데이터를 처리 (함수 적용 ex. count)
3. Reduce: 분산된 결과를 모아서 집계
- 하나의 사이클이 끝나지 않으면 다음 처리로 이동하지 못함
- 하나의 사이클에서 다음 사이클로 이동할 때까지의 대기 시간이 긴 단점
- 애드 혹 분석에서는 실현 어려움
MapReduce를 대신할 새로운 프레임워크 - DAG에 의한 내부 표현
- DAG(Directed Acyclic Graph) 데이터 구조로 새로운 프레임워크에서 사용
- 데이터 플로우에서 실행해야 할 일련의 태스크를 DAG에 의핸 데이터 구조로 표현
- 화살표는 태스크 실행 순서
- 의존 관계를 유지하며 실행 순서를 알맞게 정하면 모든 태스크를 빠짐없이 완료할 수 있음
- DAG는 시스템 내부적인 표현으로 이용자가 존재를 의식할 일 거의 없음: 데이터 플로우에 국한되지 않고, Hive on Tez나 Presto같은 쿼리 엔진에서도 DAG 구조 채택
Spark에서의 DAG
- Spark과 같은 데이터 플로우 프레임워크서는 프로그래밍 언어를 사용하여 DAG를 직접 조립
- 처리 내용이 복잡해지면 DAG도 그만큼 복잡해짐
- 지연 평가 (lazy evaluation): 프로그램의 각 행은 실제로는 DAG 데이터 구조를 조립할 뿐, 후에 실행 결과를 요구할 때 데이터 처리가 시작됨
=> 내부 스케줄러가 분산 시스템에 효과적인 실행 계획을 세울 수 있음
데이터 플로우와 워크플로를 조합하기
- 태스크를 정기적으로 실행하거나, 실패한 태스크를 기록하여 복구하는 것은 데이터 플로우에서 할 수 없음. 워크플로 관리 필요
- 데이터 플로우의 프로그램도 워크플로의 태스크로 고려할 수 있음
- 분산 시스템 외부와 데이터를 주고받을 경우도 워크플로 안에서 실행하는 것이 바람직
데이터를 읽어들이는 플로우
- 워크플로 조합
- Task 1: 데이터 소스에서 분산 스토리지로 벌크 전송
- Task 2: 데이터 플로우로 필요한 연산, 분산 스토리지에서 실행
데이터를 써서 내보내는 플로우
- 워크플로 조합
- Task 1: 데이터 플로우를 통해 CSV 같이 취급하기 쉬운 형식으로 변환
- Task 2: 외부로 전송
데이터 플로우와 SQL을 나누어 사용하기
- 데이터 웨어하우스 파이프라인 (SQL을 MPP 데이터베이스에서 실행)
- 데이터 플로우: DB에서 분산 스토리지로 벌크 전송 -> 데이터 웨어하우스로 로드
- SQL: 배치 처리 (시각화)
- 데이터 마트 파이프라인 (쿼리 엔진은 분산 시스템에서 실행)
- 데이터 플로우: DB에서 분산 스토리지로 벌크 전송
- SQL: 쿼리 엔진으로 배치 처리 (마트 제작)
대화식 플로우 - 애드혹 분석의 파이프라인
- 데이터 처리를 수작업으로 시행하므로 워크플로는 필요하지 않음
- 구조화되어 있지 않은 데이터를 애드 혹 분석시, 데이터 플로우가 매우 유용
- 구조화된 데이터는 쿼리 엔진을 사용
5-3. 스트리밍 형의 데이터 플로우
배치 처리와 스트림 처리로 경로 나누기
- 배치 처리 중심의 데이터 파이프라인의 결점은 데이터가 분석할 수 있게 될 때까지 시간이 걸림
- 실시간: 이벤트 발생에서 몇 초 후에는 결과를 알 수 있는 것
- 실시간성이 높은 데이터 처리 시스템 예
- 시스템 모니터링
- 로그 관리 시스템
- 복합 이벤트 처리 (Complex Event Processing, CEP)
- 스트림 처리 (streaming processing): 분산 스토리지를 거치지 않고 처리를 계속하는 것
- 스트림 처리는 실시간성이 우수하지만, 과거 데이터를 취급하는 데에는 부적합
배치 처리와 스트림 처리 통합하기
- 배치 처리: 먼저 데이터가 있고, 그것을 작게 나눠서 DAG에 흘려 넣음 (유한 데이터 bounded data)
- 스트림 처리: 끊임없이 데이터가 생성되며, 그것이 DAG 안에 흘러들어옴 (무한 데이터 unbounded data)
- DAG를 사용한 데이터 플로우에서는 배치 처리와 스트림 처리를 동일하게 프로그래밍하는 것이 가능
Spark 스트리밍의 DAG
- 데이터를 읽고 쓰는 초기화 부분(socketTextStream("localhost", 9999))에 차이가 있을 뿐, 데이터 처리 중심부 (Map -> Reduce)는 동일
- 배치 처리는 데이터 처리가 끝나면 종료되는데, 스트림 처리는 프로그램을 정지할 때까지 끝없이 실행
스트림 처리의 결과를 배치 처리로 치환하기
- 스트림 처리의 두 가지 문제
1. 잘못된 데이터 수정하기 어려움
2. 늦게 전송된 데이터 취급
- 배치 처리를 실행해 스트림 처리의 문제를 대처
- 스트림 처리 결과는 배치 처리 결과가 나올 때까지의 잠정 값으로 이용
람다 아키텍처: 배치 레이어, 서빙 레이어, 스피드 레이어
- 배치 레이어: 과거 데이터를 장기적인 스토리지에 축적하고, 여러 번이고 다시 집계
- 대규모 배치 처리를 실행할 수 있는 반면, 1회 처리에는 긴 시간 걸림
- 서빙 레이어: 응답이 빠른 데이터베이스 설치
- 배치 뷰: 서빙 레이어에서 얻어진 결과
- 정기적으로 업데이트되지만, 실시간 정보를 얻을 수 없음
- 스피드 레이어: 스트림 처리
- 실시간 뷰: 배치 뷰가 업데이트될 동안까지 이용되고 오래된 데이터는 순서대로 삭제
- 장점: 실시간 뷰의 결과는 나중에 배치 뷰로 치환
- 단점: 나쁜 개발 효율
- 스피드 레이어와 배치 레이어 모두 똑같은 처리를 구현하는 비효율
카파 아키텍처
- 카파 아키텍처: 람다 아키텍처를 단순화함
- 배치 레이어, 서빙 레이어 제거하고 스피드 레이어만 남김
- 메시지 브로커의 데이터 보관 기간을 충분히 길게 함
- 스트림 처리를 멱등으로 구현하여, 과거 데이터 보정(백필)을 가능하도록 함
- 단점: 부하가 높아짐
- 클라우드 서비스 보급에 의해 자원 확보가 어렵지 않게 되었으므로 '스트림 처리르 다시 하는 것이 간단하다'는 게 카파 아키텍처의 철학
아웃 오브 오더의 데이터 처리
- 아웃 오브 오더(out of order) 데이터: 늦게 도달하는 메시지, 프로세스 시간과 이벤트 시간의 차이
원래 데이터의 모습은 '이벤트 시간'으로 얻을 수 있다
- 이벤트 시간으로 집계한 것이 올바른 결과
이벤트 시간 윈도잉
: 이벤트 시간에 의해 윈도우를 나누는 것
- 과거 이벤트 상태를 보관하면서, 데이터가 도달할 때마다 해당하는 윈도우를 재집계할 필요 있음
- 일정 시간 늦게 온 데이터는 무시