여러 출력 데이터셋의 변환 설계
같은 입력들에서 여러 출력을 만들 때에는 출력마다 변환을 나누거나 하나의 다중 출력 변환으로 묶을 수 있습니다. 여러 묶음의 다중 출력 변환을 사용하는 혼합 방식도 가능합니다. 아래 실행 비용 비교는 주로 Spark 기반 변환을 기준으로 하며 출력별 처리 방식이 다르면 단일 노드 변환도 함께 고려합니다.
출력마다 별도 변환
각 변환은 입력 X1, X2, ..., Xn 중 필요한 데이터를 받아 출력 하나를 만듭니다. 여러 출력 Y1, Y2, ..., Yn에는 각각 변환을 둡니다.
장점
출력별 로직이 분리되어 변경과 유지보수가 쉽습니다. 필요한 입력만 선언하면 불필요한 입력 의존 관계를 줄이고 보안 표지 전파 범위도 분리할 수 있습니다.
각 출력을 독립적으로 빌드하므로 다른 출력이 끝날 때까지 기다리지 않고 필요한 하나만 다시 실행할 수 있습니다. 출력별 실행 주기와 Spark profile도 다르게 정할 수 있습니다.
변환을 다른 저장소로 나누어 프로젝트별로 관리할 수 있고, 출력마다 다른 라이브러리와 실행 방식을 선택할 수도 있습니다. 예를 들어 일부 출력은 단일 노드 pandas, 나머지는 PySpark로 처리할 수 있습니다. 데이터, 코드 또는 플랫폼 문제에 따른 실패도 해당 변환에 한정됩니다. 변환 사이의 병렬 실행과 각 변환 내부의 병렬 처리를 함께 사용할 수 있습니다.
비용과 제약
공통 로직이 중복될 수 있습니다. 코드 중복은 공용 라이브러리로 정리하고 반복되는 비싼 Spark 연산은 중간 데이터셋으로 저장하여 재사용하는 방법을 고려합니다. 출력들이 서로 너무 의존하면 필요한 중간 데이터셋도 많아지므로 함께 묶는 설계를 검토합니다.
Spark 변환마다 별도 driver와 실행 환경이 필요하고 대개 executor도 필요합니다. 모든 출력을 동시에 실행하면 자원 사용량과 대기가 누적되며 Spark 초기화와 driver 유지 비용도 반복됩니다.
출력별 profile 조정은 유연하지만 관리 비용이 있습니다. 기본 profile만 사용하면 특히 큰 출력이 필요한 실행기를 충분히 받지 못해 느려지거나 제한 시간에 걸릴 수 있습니다.
다중 출력 변환
하나의 변환이 여러 입력을 받아 여러 출력을 만듭니다. 하나의 driver와 job을 사용하므로 변환 시작 비용이 반복되지 않으며 출력의 공통 로직을 한곳에 둘 수 있습니다.
중간 결과를 별도 데이터셋으로 쓰지 않고 실행 중 재사용하도록 구성할 수 있습니다. 다만 다중 출력이라는 선언만으로 모든 Spark 계산이 자동 캐시되는 것은 아닙니다. 여러 출력에서 같은 DataFrame을 다시 계산하지 않도록 필요한 공통 결과를 cache 또는 persist하는 등 실제 재사용 방식을 설계합니다.
실행 결합의 제약
각 출력을 독립적으로 빌드하거나 서로 다른 주기로 실행할 수 없습니다. 앞선 빌드의 모든 출력이 끝나야 다음 실행을 시작합니다. 출력별 로직이 너무 다르면 하나의 변환 안에서 유지보수가 어려워질 수 있습니다.
Spark profile은 변환 전체에 한 묶음으로 적용됩니다. 동적 할당으로 일부 차이를 완화할 수 있지만 자원 대기를 빠르게 채울 수 있습니다. 한 driver가 전체 작업을 조정하므로 driver로 데이터를 collect하거나 일반 Python 및 API 호출을 직렬 실행하는 구간이 병목이 될 수 있습니다.
Python UDF는 실행기에 배포되어 병렬 실행될 수 있으므로 모든 UDF가 driver에서 직렬 실행된다고 가정하면 안 됩니다. 다만 기본 Spark 함수보다 느릴 수 있고 직렬화 및 Python 실행 비용이 있으므로 작업별 병렬성과 비용을 확인합니다.
데이터 규모에 비해 executor가 너무 많으면 실제 연산보다 네트워크 입출력 비용이 커질 수 있습니다. 여러 입력의 크기 변동도 같은 빌드의 소요 시간 변동에 누적되므로 증분 입력량이 크게 달라지는 작업에서는 동적 할당이 더 적합할 수 있습니다.
실패와 비용 귀속
한 출력의 연산이 24시간을 초과하거나 오류가 발생하면 전체 출력의 빌드가 실패하는 영향을 받습니다. 어느 출력의 로직이 원인인지 진단하기도 더 어려울 수 있습니다.
빌드 비용은 출력 데이터셋들에 균등하게 나누어 귀속합니다. 출력이 10개라면 각각 1/10이 기록되므로 특정 출력의 실제 연산량을 비용 귀속만으로 구분하기 어렵습니다.
다른 프로젝트에 결과를 배치해야 할 때에는 다중 출력 결과 뒤에 별도 복사 단계를 두는 방식도 사용할 수 있습니다.
선택 기준
출력들이 비슷한 로직을 공유하고 연산을 병렬화할 수 있으며 실행 주기와 실패를 함께 묶어도 된다면 다중 출력이 적합합니다. 로직이나 실행 주기가 다르거나 실패를 분리해야 하면 별도 변환이 더 유연합니다.
Spark Details에서 작업 병렬성이 할당한 executor 수와 맞는지 확인합니다. 빌드가 너무 오래 걸리거나 profile이 지나치게 크면 변환을 나눌 수 있습니다. 충분한 연산량으로 executor를 활용하는 작업은 고정 시작 비용의 비중을 줄일 수 있지만, 작은 데이터를 많은 출력으로 만드는 작업은 네트워크 입출력이 더 클 수 있습니다. 이런 경우 여러 단일 출력 변환이나 로컬 병렬 처리하는 단일 노드 변환을 검토합니다.