파이프라인
처리를 여러 단계로 나눠 놓은 구조입니다. 앞 단계가 내놓은 것이 다음 단계의 입력이 됩니다. 단계는 저마다 자기 몫만 합니다. 나머지는 옆으로 넘깁니다.
쉽고 빠른 이해
처리를 여러 단계로 쪼개 놓고 앞 단계가 내놓은 것을 다음 단계가 그대로 받게 이어 붙인 구조입니다. 파일 이름을 나열하는 명령의 출력을 쪽 나누는 명령이 그대로 받아 인쇄용으로 넘기는 셸 한 줄이 그렇습니다.
이어 붙이는 자리를 규격으로 못 박아 두면 단계는 자기 앞뒤에 무엇이 붙었는지 몰라도 됩니다. 그것이 없으면 데이터를 다른 방식으로 주무를 필요가 생겨도 마디를 하나 더 돌려 끼울 데가 없습니다.
- 단계마다 무엇을 내놓을지 정해 둡니다.
- 앞 단계가 내놓은 것을 다음 단계의 입력에 이어 붙입니다.
- 단계 안에서 무슨 계산을 하는지는 각 단계가 알아서 합니다.
대가는 단계가 앞뒤로 묶인다는 것입니다. 앞이 안 내놓으면 뒤는 그냥 기다립니다. 자동화 쪽에서는 한 단계가 실패하면 그 뒤 단계를 대개 실행하지 않고 일찍 끝냅니다.
상세
급식실 배식대에서는 밥 푸는 사람이 식판에 밥을 담아 옆으로 밀어 놓습니다. 국 뜨는 사람이 그 식판을 받는 동안, 밥 푸는 사람은 벌써 다음 식판을 채우고 있습니다. 세 사람이 저마다 다른 식판을 붙들고 한꺼번에 손을 놀립니다.
파이프라인은 처리를 여러 단계로 나누고 앞 단계의 출력을 다음 단계의 입력으로 이어 붙인 구조입니다. 여기서 정해지는 것은 둘입니다. 단계가 무엇을 내놓느냐, 그리고 그 내놓은 것이 어떤 자리를 거쳐 다음 단계에 닿느냐입니다. 단계 안에서 무슨 계산이 벌어지는지는 파이프라인이 정하지 않습니다.
flowchart TD
입력 --> A[단계 1] --> B[단계 2] --> C[단계 3] --> 출력
이어 붙이는 자리가 규격이 되면 단계는 무엇에 이어 붙었는지 몰라도 됩니다. 프로세스 사이의 통로를 기술한 논문이 이 성질을 못 박았습니다. 프로세스는 파일 시스템 입출력에 쓰는 것과 같은 read·write 호출로 서로 통신합니다. 어느 쪽도 보통 파일이 아니라 통로가 끼어 있다는 것을 알 필요가 없습니다. 읽는 쪽은 쓰는 쪽이 쓸 때까지 기다립니다. 그 시점에 두 프로세스의 이미지 사이로 데이터가 건너갑니다.
같은 구조를 문법으로 못 박은 명세도 있습니다. POSIX(Portable Operating System Interface)
셸 명세는 파이프라인을 제어 연산자 | 로 구분된 하나 이상의 명령의 나열이라고 정의합니다.
마지막 명령을 뺀 각 명령에 대해, 셸은 그 명령의 표준출력을 다음 명령의 표준입력에 연결해야
합니다. 통로를 하나 만들어 쓰는 쪽 끝을 앞 명령의 표준출력으로, 읽는 쪽 끝을 뒤 명령의
표준입력으로 넘긴 것과 같게 동작합니다. 표준입력과 표준출력이 이렇게 파이프라인에 의해
정해지고 나서야, 명령에 붙은 리다이렉션 연산자가 그 위에 적용됩니다.
무엇이 단계 사이를 흐르느냐는 자리마다 다릅니다. 그 갈림은 아래 갈래에서 다룹니다.
배경
프로그램에 마디를 하나 더 돌려 끼울 방법이 없었습니다. 1964년에 쓰인 벨 연구소 내부 메모의 열째 쪽에 그 불편이 요약되어 있습니다. Doug McIlroy 는 가장 걱정되는 것을 한 줌으로 추려 첫 항목에 이렇게 적었습니다. 프로그램을 정원 호스처럼 잇는 방법이 있어야 한다는 것입니다. 데이터를 다른 방식으로 주무를 필요가 생기면 마디를 하나 더 돌려 끼울 수 있어야 합니다. 입출력도 그런 식이어야 한다고 덧붙였습니다.
그 제안이 실제로 들어간 자리는 프로세스 사이의 통로였습니다. 유닉스 시분할 시스템을 기술한
논문은 pipe() 호출이 파일 서술자를 돌려주면서 프로세스 사이 채널을 하나 만든다고 적습니다.
이 채널은 다른 열린 파일과 마찬가지로 fork 호출을 거쳐 부모에서 자식으로 넘어갑니다.
다만 같은 논문은 이것이 완전히 일반적인 수단은 아니라고 곧바로 못 박습니다. 통로는 관여하는
프로세스들의 공통 조상이 미리 만들어 줘야 하기 때문입니다.
이름은 두 자리에서 나왔습니다. 프로세스를 잇는 통로 자체가 파이프입니다. 세로 막대로 구분된 명령의 나열이 파이프라인입니다. 그 나열 안에서 표준입력을 받아 처리한 뒤 표준출력으로 넘기는 프로그램을 같은 논문이 필터라고 부릅니다. 문자 치환, 패턴에 맞는 줄 고르기, 입력 정렬 같은 것이 필터로 쓰이던 것들입니다.
flowchart TD
subgraph PL["파이프라인 · 세로 막대로 구분된 명령의 나열"]
F1["필터"] -->|파이프| F2["필터"] -->|파이프| F3["필터"]
end
갈래
파이프라인이라는 말은 여러 자리에서 각각 쓰입니다. 뜻은 같습니다. 갈리는 것은 단계 사이를 무엇이 흐르느냐, 그리고 단계를 무엇이 정하느냐입니다.
셸 파이프라인
흐르는 것은 바이트 스트림입니다. 단계는 사람이 한 줄에 적습니다. POSIX 셸 명세가 정한 꼴은
[!] command1 [ | command2 ...] 입니다. 세로 막대로 구분된 명령의 나열을 셸이 받아, 각 명령의
표준출력을 다음 명령의 표준입력으로 잇습니다. 유닉스 논문은 이때 셸이 명령들을 동시에
실행한다고 적습니다. 앞 명령이 다 끝나기를 기다렸다가 뒤 명령을 띄우는 것이 아닙니다.
flowchart TD
A[ls] -->|표준출력| B["pr -2"] -->|표준출력| C[opr]
block-beta columns 4 L0["시각"] H1["1"] H2["2"] H3["3"] R1["차례로 돌 때"] a["ls"] b["pr -2"] c["opr"] R2["파이프라인"] d["ls · pr -2 · opr"]:3
파이프라인이 백그라운드에 있지 않으면 셸은 나열의 마지막 명령이 끝나기를 기다립니다. 모든 명령이 끝나기를 기다릴 수도 있습니다.
CI/CD 파이프라인
단계 사이를 넘어가는 것은 데이터가 아니라 앞 단계가 성공했느냐는 판정입니다. 코드는 잡이
자기 안에서 다룹니다. 단계를 사람이 설정 파일에 적어 넣습니다.
CI/CD(Continuous Integration/Continuous Delivery, 지속적 통합·지속적 전달) 파이프라인의 한
예로 GitLab 은 .gitlab-ci.yml 파일에 YAML(YAML Ain't Markup Language) 키워드로
파이프라인을 구성한다고 적습니다. 파이프라인은 프로젝트 전체 동작을 정하는 전역 키워드, 명령을
실행하는 잡, 잡을 묶는 스테이지로 이루어집니다. 잡은 컴파일·테스트·배포 같은 일을 합니다.
잡들은 서로 독립으로 돕니다. 잡을 실행하는 것은 러너입니다.
flowchart TD
subgraph 스테이지A[앞 스테이지]
L[잡 · lint]
C[잡 · compile]
end
subgraph 스테이지B[뒤 스테이지]
T[잡 · test]
D[잡 · deploy]
end
스테이지A --> 스테이지B
스테이지는 순서대로 돕니다. 한 스테이지 안의 잡들은 병렬로 돕니다. 한 스테이지의 잡이 전부 성공하면 파이프라인이 다음 스테이지로 넘어갑니다. 어느 잡이라도 실패하면 다음 스테이지는 대개 실행되지 않고 파이프라인이 일찍 끝납니다.
데이터 처리 파이프라인
흐르는 것은 데이터 집합입니다. 단계를 정하는 것은 드라이버 프로그램입니다. Apache Beam 은
Pipeline 하나가 입력 데이터 읽기부터 데이터 변환, 출력 데이터 쓰기까지 데이터 처리 작업
전체를 감싼다고 적습니다. 그 Pipeline 을 만들어 읽기와 변환과 쓰기를 붙이는 것이 드라이버
프로그램의 몫입니다. 이 문서는 파이프라인을 유향 비순환 그래프(directed acyclic graph)로 생각하라고 적습니다. PTransform
노드가 PCollection 노드를 입력으로 받아 PCollection 노드를 내놓는 서브루틴입니다.
한 줄로 늘어선 나열이 아니라 그래프라는 점이 앞의 둘과 갈리는 자리입니다. PCollection 은
분산 데이터 집합을 나타냅니다. 그 데이터 집합은 파일처럼 고정된 소스에서 오면 유한입니다.
그렇지 않으면 무한입니다.
flowchart TD
C0["PCollection"] --> T1["PTransform"]
T1 --> C1["PCollection"]
C1 --> T2["PTransform"]
C1 --> T3["PTransform"]
T2 --> C2["PCollection"]
T3 --> C3["PCollection"]
그래픽스 파이프라인
흐르는 것은 정점과 픽셀입니다. 단계 목록을 API(Application Programming Interface, 응용 프로그램 인터페이스)가 미리 정해 둡니다. Direct3D 11 의 파이프라인은 입력 조립기, 버텍스 셰이더, 테셀레이션 단계 셋, 지오메트리 셰이더, 스트림 출력, 래스터라이저, 픽셀 셰이더, 출력 병합기로 이어집니다. 입력 조립기가 첫 단계입니다. 버텍스 셰이더는 입력 조립기에서 온 정점에 변환·스키닝·모핑·정점 단위 조명 같은 정점별 연산을 합니다. 래스터라이저는 도형으로 이루어진 벡터 정보를 픽셀로 이루어진 래스터 이미지로 바꿉니다. 출력 병합기가 최종 픽셀 색을 만듭니다. 그래서 래스터라이저 앞 구간에는 정점이 흐르고, 뒤 구간에는 픽셀이 흐릅니다.
flowchart TD
subgraph V["정점이 흐르는 구간"]
IA["입력 조립기"] --> VS["버텍스 셰이더"] --> TS["테셀레이션 단계 셋"] --> GS["지오메트리 셰이더"] --> SO["스트림 출력"]
end
subgraph P["픽셀이 흐르는 구간"]
PS["픽셀 셰이더"] --> OM["출력 병합기"]
end
SO --> RS["래스터라이저"] --> PS
앞의 셋과 달리 단계의 목록을 문서가 미리 못 박아 둡니다. Direct3D 11 파이프라인은 Direct3D 10 파이프라인과 같은 단계들을 지원합니다. 거기에 고급 기능을 위한 단계가 더 붙은 것입니다. 대신 단계마다 무엇을 할지는 고를 수 있습니다. 공통 셰이더 코어를 가진 단계들은 HLSL(High-Level Shader Language)로 프로그래밍합니다. 모든 단계는 API 로 설정합니다.
예시
셸 파이프라인 한 줄
ls | pr -2 | opr
ls 가 현재 디렉터리의 파일 이름을 나열합니다. 그 출력은 pr 로 넘어갑니다. pr 은 받은
입력에 날짜가 붙은 머리글을 달아 쪽을 나눕니다. 인자 -2 는 두 단 출력을 요청하는 것입니다.
pr 의 출력은 다시 opr 의 입력이 됩니다. opr 은 그 입력을 오프라인 인쇄용 파일로
스풀합니다.
종료 상태는 마지막 명령의 것을 씁니다. POSIX 셸 명세는 파이프라인이 예약어 ! 로 시작하지
않으면 파이프라인의 종료 상태가 나열의 마지막 명령의 종료 상태라고 규정합니다. ! 로 시작하면
마지막 명령 종료 상태의 논리 NOT 이 됩니다. 마지막 명령이 0 을 돌려주면 종료 상태는 1 입니다.
0 보다 큰 값을 돌려주면 종료 상태는 0 입니다.
Apache Beam 의 파이프라인 객체
Pipeline p = Pipeline.create(options);
with beam.Pipeline() as pipeline:
pass # build your pipeline here
모든 Beam 드라이버 프로그램은 Pipeline 을 만들어야 합니다. 이 객체 하나가 데이터 처리 작업
전체를 감싸므로, 읽기와 변환과 쓰기를 여기에 붙여 나가는 것이 파이프라인을 짓는 일이 됩니다.
관련 항목
셸 파이프라인을 이루는 구성 요소
파이프 · 파일 서술자 · 표준입력 · 표준출력 · 필터 · 리다이렉션 · 서브셸 · 종료 상태
파이프라인에 관여하는 역할·참여자
셸 · 러너 · 드라이버 프로그램
CI/CD 파이프라인을 이루는 구성 요소
잡 · 스테이지 · YAML · 배포
CI/CD 파이프라인이 속하는 상위 개념
지속적 통합 · 지속적 전달 · 지속적 배포 · 커밋에서 배포까지
파이프라인을 도메인마다 규정하는 표준·제품
POSIX · GitLab · Apache Beam · Direct3D 11
데이터 파이프라인을 이루는 구성 요소
PCollection · PTransform · 유향 비순환 그래프 · 노드 · 배치 처리 · 스트림 처리
그래픽스 파이프라인이 거치는 처리 단계
입력 조립기 · 버텍스 셰이더 · 테셀레이션 · 지오메트리 셰이더 · 스트림 출력 · 래스터라이저 · 픽셀 셰이더 · 출력 병합기
그래픽스 파이프라인의 셰이더 단계를 다루는 도구
다른 이름: pipeline