Keyboard shortcuts

Press ← or → to navigate between chapters

Press ? to show this help

Press Esc to hide this help

빌드와 검사 문제 해결

빌드 오류는 코드 변경, 입력 데이터 변화, 자원 부족과 권한 문제를 나누어 조사합니다. 오류 메시지와 실행 이력을 함께 보고 실패가 시작된 지점을 찾습니다.

일반 오류의 조사 순서

이전에 성공한 빌드가 있는지 확인하고 그 이후 생성 로직이 바뀌었는지 비교합니다. 변경을 되돌린 코드에서 성공한다면 새 로직으로 조사 범위를 좁힐 수 있습니다. Builds의 Datasets 패널에서 Logs를 열어 실행 이력을 확인합니다.

입력 데이터가 바뀌었는지도 확인합니다. 데이터 규모가 급증하면 연산 자원 문제나 실행 시간 악화가 나타날 수 있고, 열과 자료형이 바뀌면 특정 스키마에 의존한 코드가 실패할 수 있습니다. Builds의 Datasets 패널에서 Compare로 이전 트랜잭션과 차이를 봅니다. 상위 데이터셋 로직의 변경도 규모나 스키마를 바꿀 수 있습니다.

긴 오류 메시지 읽기

오류 추적의 아래쪽에서 원인을 찾을 수 있는 경우가 많습니다. 다음 문자열 주변을 확인합니다.

What went wrong:
Caused by:
Py4JJavaError
UserCodeError

ErrorInstanceID만 있고 구체적인 원인이 부족하면 Palantir 지원팀에 에스컬레이션합니다. 다만 사용자 로직 오류에도 이 ID가 나오므로 먼저 자신의 코드에서 원인을 확인합니다. 지원 요청에는 전체 오류 메시지, 이미 시도한 조사 내용과 오류가 난 정확한 날짜, 시각 및 시간대를 포함합니다.

Waiting for resources

플랫폼의 동시 빌드가 많으면 사용할 연산 자원을 기다릴 수 있습니다. 특히 Spark 작업이 자원을 점유하는 동안 다른 빌드는 완료된 작업이 자원을 반환할 때까지 대기할 수 있습니다.

사용량이 낮은 시각에 실행하도록 조정하고 예약 작업을 정각이나 자정 같은 집중 시간에 몰지 않는 방법을 고려합니다. 자원 대기와 실제 변환 실행 시간이 길어진 문제를 구분합니다.

실행 시간이 점점 길어지는 경우

최근 로직 변경, 입력 데이터 규모 증가와 같은 시각의 클러스터 부하를 확인합니다. 작은 코드 차이도 실행 계획과 시간이 달라지는 원인이 될 수 있습니다. 상위 데이터셋이 커졌다면 뒤쪽 단계에서도 처리량이 늘어납니다.

키가 편향된 조인

같은 키에 행이 많이 몰린 큰 왼쪽 테이블과 키별 행 수가 적은 작은 오른쪽 테이블을 조인할 때에는 salting으로 큰 키를 여러 작업으로 나눌 수 있습니다. 일부 작업만 매우 오래 걸리거나 shuffle 및 spill이 다른 작업보다 크면 skew를 의심할 수 있습니다.

Contour에서 각 테이블의 조인 키별 행 수를 집계한 뒤 키로 결합하여 양쪽 개수를 곱하면 키별 예상 조인 행 수를 비교할 수 있습니다. 특정 키의 값만 크다면 그 키에 작업이 집중됩니다.

EXPLODE의 역할

작은 쪽의 각 행을 salt 값마다 복제하면 큰 쪽의 임의 salt와 대응시킬 수 있습니다. 다음 예제는 a를 배열 원소별로 펼칩니다.

SELECT 'a' AS test, EXPLODE(ARRAY(1,2,3,4,5)) AS dummy2
testdummy2
a1
a2
a3
a4
a5

일반 조인과 salted join

다음 경로와 something은 예시입니다. 두 쿼리는 원래 열들을 left_record, right_record 구조체에 보존하여 양쪽의 같은 열 이름이 충돌하지 않도록 합니다.

SELECT
    STRUCT(l.*) AS left_record,
    STRUCT(r.*) AS right_record
FROM `/foo/bar/baz` AS l
JOIN `/foo2/bar2/baz2` AS r
    ON l.something = r.something

같은 조인 관계를 salt 8개로 나누는 예시는 다음과 같습니다. 원래 행은 salt를 추가하기 전에 구조체로 만들므로 보조 salt 열이 최종 출력에 섞이지 않습니다.

SELECT
    l.original_row AS left_record,
    r.original_row AS right_record
FROM (
    SELECT
        STRUCT(src.*) AS original_row,
        src.something AS join_key,
        FLOOR(RAND() * 8) AS salt
    FROM `/foo/bar/baz` AS src
) AS l
JOIN (
    SELECT
        STRUCT(src.*) AS original_row,
        src.something AS join_key,
        EXPLODE(ARRAY(0,1,2,3,4,5,6,7)) AS salt
    FROM `/foo2/bar2/baz2` AS src
) AS r
    ON l.join_key = r.join_key AND l.salt = r.salt

왼쪽 행은 0부터 7 중 하나의 salt를 받고 오른쪽의 같은 키는 모든 salt로 복제합니다. 따라서 원래 조인과 같은 행 조합을 만들면서 큰 키의 작업을 나눕니다. N개 salt를 쓰면 큰 묶음을 대략 1/N 규모로 분산하는 효과를 기대할 수 있습니다.

조정값은 8, 16, 32 같은 2의 거듭제곱부터 비교할 수 있습니다. 가장 큰 키의 묶음이 다음으로 큰 키보다 X배라면 X 이상을 분산 계수의 출발점으로 삼고, 실행 중 executor별 행 수도 살펴봅니다.

양쪽 salt 집합은 정확히 일치해야 합니다. RAND()의 범위는 [0.0, 1.0)이므로 FLOOR(RAND() * N)은 0부터 N-1을 만듭니다. CEIL(RAND() * N)을 무조건 1부터 N까지라고 가정하면 RAND가 0일 때의 경계값을 놓칠 수 있습니다. 위 예제처럼 FLOOR와 같은 정수 목록을 사용하는 편이 분명합니다.

Salting은 오른쪽 데이터를 복제하는 비용이 있습니다. 반드시 더 빨라지는 방법은 아니며 편향 때문에 실패하던 작업의 성공 가능성을 높이는 수단입니다. 필요 없는 조인에 적용하면 성능이 나빠질 수 있습니다.

Shrinkwrap 오류

transforms-shrinkwrap.yml은 코드가 참조하는 경로, 그 경로의 데이터셋 RID와 데이터셋의 현재 경로 사이를 연결합니다. 데이터셋을 이동했을 때 다음 빌드가 올바른 리소스를 찾는 데 사용합니다. 데이터셋 삭제, 이름 변경, 이동이나 브랜치 간 변경 때문에 오류가 날 수 있습니다.

리포지토리 왼쪽 위의 설정 아이콘에서 Show hidden files and folders를 켜면 파일을 볼 수 있습니다.

오류에 표시한 데이터셋과 참조 위치를 확인합니다. 데이터셋이 휴지통으로 이동했다면 필요한 리소스를 복원합니다.

작업 브랜치를 유지하는 사이 운영 브랜치에 새 파일과 shrinkwrap 변경이 병합되었을 수도 있습니다. 자신이 추가한 참조와 들어오는 변경을 검토하고 필요한 항목을 남기도록 충돌을 해결합니다. 생성 파일을 제거하고 다음 검사에서 자동 재생성하는 방법도 있지만, 존재하지 않는 데이터셋이나 잘못된 경로 문제를 먼저 확인해야 합니다.

PERMISSION_DENIED

빌드를 시작하는 사용자는 필요한 리소스 권한을 가져야 합니다. 특히 출력 데이터셋을 변경하는 작업이므로 출력에 Editor 권한이 필요합니다. 오류가 가리키는 리소스의 접근 권한도 확인합니다.

Data Lineage에서 Permissions 필터를 켜고 Resource Permissions로 노드 색상을 표시하면 데이터셋 권한을 확인하는 데 도움이 됩니다.

Waiting for input dependencies to be computed

상위 데이터셋 실패 때문에 스케줄이 취소되면 필요한 입력이 계산되지 않은 상태로 남을 수 있습니다. 실패한 빌드 보고서에서 처음 실패한 데이터셋을 찾고 그 원인을 해결한 뒤 운영 스케줄의 조건에 맞게 재실행합니다. 뒤쪽의 대기 메시지만 따로 해결하려고 하지 않습니다.

CI에서 데이터셋 소유 저장소가 다르다고 할 때

여러 리포지토리가 같은 출력 데이터셋을 만들려고 하면 소유권이 충돌합니다. Dataset Preview의 Details > Job spec에서 sourceProvenance를 확인하면 현재 제어하는 저장소를 볼 수 있습니다.

현재 리포지토리로 소유권을 옮기기로 결정했다면 다음 순서로 진행합니다.

  1. 데이터셋의 Details > Job spec > Edit > Delete에서 기존 JobSpec을 제거합니다. 데이터셋 자체를 삭제하는 단계는 아닙니다.
  2. 현재 리포지토리에서 CI를 다시 실행하여 새 실행 정의를 등록합니다.
  3. 이전 리포지토리에서는 같은 출력 데이터셋을 생성하는 소스 파일을 제거하여 재충돌을 막습니다.

기존 리포지토리의 소유권을 유지할 경우에는 현재 리포지토리에서 충돌하는 출력 정의 소스 파일을 제거합니다. 어느 저장소가 출력을 만들지 먼저 결정해야 합니다.

CI 검사 timeout

간헐적인 실패인지 확인하고 동일 오류가 반복되는지 봅니다. 리포지토리를 업그레이드한 뒤에도 같은 실패가 발생하는지 확인할 수 있습니다.

의존성 캐시 문제가 의심되면 숨김 파일을 표시하고 ci.yml의 기존 Gradle 실행 명령에 ASCII 하이픈 두 개로 된 --refresh-dependencies 옵션을 추가하는 경로가 있습니다. 옵션을 YAML의 임의 위치에 독립된 줄로 넣는 것은 올바른 명령 구성이 아닙니다.

이 옵션은 의존성 메타데이터와 해석 결과를 다시 확인합니다. 변경이 없는 artifact까지 무조건 모두 다시 내려받는 옵션은 아닙니다. ci.yml은 저장소 업그레이드 때 덮어쓸 수 있으므로 이 변경도 업그레이드 후 확인해야 합니다. 사용자 정의 검사 자체를 추가할 때에는 ci.yml을 직접 확장하는 대신 지원되는 Gradle task 의존 관계를 사용합니다.