Keyboard shortcuts

Press ← or → to navigate between chapters

Press ? to show this help

Press Esc to hide this help

동기화 실패, 지연과 데이터 이상 해결

동기화 문제는 연결 단계, 원천 조회 단계, 업로드 단계 또는 증분 상태에서 발생할 수 있습니다. 실행 로그와 실패 단계를 먼저 확인하고 해당 경로의 설정을 조정합니다. JDBC에서 실제 원천에 실행한 SQL은 _data-ingestion.log에서 확인할 수 있습니다.

인증서와 FTP 프로토콜

PKIX 또는 SSLHandshakeException 오류는 신뢰할 인증서, 서버가 보내는 체인 및 TLS 설정을 확인합니다. 연결 문제 해결에 인증서와 TLS 확인 절차가 있습니다.

FTP 계열에서 Response 421 received. Server closed connection이 발생하면 서버의 실제 SSL 방식과 포트가 소스 설정과 맞는지 확인합니다. FTP/FTPS 커넥터의 기본 제어 포트는 일반 FTP와 명시적 FTPS가 21, 암시적 FTPS가 990입니다. 명시적 방식이 기본이며 암시적 방식을 사용하는 서버에는 그에 맞는 SSL method를 선택합니다. FTP/FTPS 연결에 지원 방식과 데이터 포트 조건을 설명합니다.

외부 통신 프록시

FTP와 FTPS

FTP는 상태를 유지하는 프로토콜입니다. 프록시 부하 분산 때문에 연속 요청의 출발 IP가 달라지면 동기화가 간헐적으로 실패할 수 있습니다. 일부 미리보기나 실행이 성공했다는 사실만으로 이 문제가 없다고 판단하지 않습니다. 부하 분산 프록시 경로와 연결의 출발 IP 조건을 확인합니다.

HTTP 프록시는 FTP 및 FTPS의 Active 전송을 지원하지 않습니다. Active 모드는 서버가 클라이언트로 데이터 연결을 시작해야 하기 때문입니다. HTTP 프록시를 사용하는 소스는 Passive 모드로 구성합니다.

S3의 403 오류

프록시를 사용하는 S3 연결에서 다음 오류가 나타나면 리전, 네트워크 허용 범위와 인증을 나누어 확인합니다.

com.amazonaws.services.s3.model.AmazonS3Exception:Forbidden
(Service: Amazon S3; Status Code: 403; Error Code: 403 Forbidden;
Request ID: null; S3 Extended Request ID: null)
  • 버킷과 프록시의 리전이 다르면 필요한 Region 값이 비어 있지 않은지 확인합니다.
  • STS 엔드포인트가 허용 목록에 포함되어 있고 도달 가능한지 확인합니다.
  • 실제 S3 URL도 허용되어 있어야 합니다.
  • STS 인증 정보가 유효하고 대상 IAM 역할을 맡을 수 있어야 합니다.
  • 프록시 대신 허용된 VPC 경로로 접근해야 하는 연결이라면 해당 S3 호스트를 프록시 제외 목록에 넣습니다.

clientConfiguration.proxyConfiguration을 사용하는 소스의 예시는 다음과 같습니다. 호스트, 버킷과 리전은 가상 구성값이며 실제 배포의 승인된 경로에 맞춥니다.

clientConfiguration:
  proxyConfiguration:
    host: egress-proxy.example.com
    port: 8888
    protocol: http
    nonProxyHosts: "example-bucket.s3.us-east-1.amazonaws.com,s3.us-east-1.amazonaws.com,s3-us-east-1.amazonaws.com"

같은 리전의 여러 VPC 버킷 호스트를 대상으로 할 때에는 버킷 부분에 와일드카드를 사용할 수 있습니다.

nonProxyHosts: "*.s3.us-east-1.amazonaws.com,s3.us-east-1.amazonaws.com,s3-us-east-1.amazonaws.com"

Could not list objects in bucket 오류가 발생하면 region과 signing region을 함께 지정했는지도 확인합니다. 두 설정을 함께 둔 구성이 원인인 경우에는 서명 리전만 지정하여 연결합니다. 이 조치는 해당 오류와 설정 조합에 대한 것이며 모든 S3 연결에서 리전 설정을 제거하라는 뜻은 아닙니다.

증분 JDBC의 커서

증분 동기화에는 증가하는 열, 초기값과 SQL의 커서 바인딩 ?가 필요합니다. 바인딩은 하나만 사용할 수 있습니다. 예를 들어 가상 employees 테이블에서 id를 커서로 쓰는 쿼리는 다음과 같습니다.

SELECT *
FROM employees
WHERE id > ?

첫 실행에는 설정한 초기값이, 이후에는 저장된 최대 수집값이 사용됩니다. 초기값과 엄격한 비교 연산의 경계는 JDBC 최적화에서 설명합니다.

행이 누락되거나 변경이 반영되지 않을 때

새 행의 증분 값이 현재 커서보다 작거나 엄격한 > 조건의 경계와 같으면 읽히지 않습니다. 예를 들어 마지막 커서가 ID 10인데 새 행을 ID 5로 추가하면 다음 증분 실행은 그 행을 가져오지 않습니다. 기존 행의 수정을 수집해야 한다면 생성 ID 대신 수정할 때 증가하는 열이 필요합니다.

이미 놓친 행은 SNAPSHOT 동기화로 전체 상태를 다시 읽거나, 누락 행을 지정하는 일회성 쿼리로 수집할 수 있습니다. 일회성 수집이 기존 커서 및 후속 증분 실행과 어떻게 연결되는지 확인하고 적용합니다.

중복 행이 생길 때

기존 행의 증분 열이 현재 커서보다 큰 값으로 바뀌면 그 행을 다시 수집합니다. APPEND 데이터셋에는 이전 버전도 남으므로 같은 업무 키의 행이 여러 개 보일 수 있습니다. 전체 최신 상태로 다시 구성하려면 SNAPSHOT을 사용하고, 변경 이력을 유지하려면 후속 변환에서 필요한 최신 행 또는 중복 제거 규칙을 적용합니다.

Timestamp 증분 상태는 밀리초 정밀도까지 직렬화하며 그보다 작은 단위는 아래 밀리초로 내림됩니다. 원천 값에 마이크로초나 나노초가 있으면 저장한 커서보다 큰 값으로 다시 비교되어 같은 행을 반복 수집할 수 있습니다. 원천 정밀도를 보존하는 LONG 또는 ISO-8601 형식의 STRING으로 증분 열을 변환하는 방법을 사용합니다. 변환 후 값도 필요한 증가 순서를 유지해야 합니다.

증분 실행의 NullPointerException

수집 결과에 증분 열이 NULL인 행이 들어오면 상태를 갱신하는 비교 과정에서 오류가 날 수 있습니다. 예를 들어 id > ? OR update_time > 1은 두 번째 조건만 만족하는 NULL ID 행도 허용합니다.

가상 employees 테이블의 id를 커서로 계속 쓰려면 전체 조건에 NULL 제외를 추가합니다.

SELECT *
FROM employees
WHERE (id > ? OR update_time > 1)
  AND id IS NOT NULL

NULL이 들어올 수 없는 다른 증분 열을 선택하는 방법도 있습니다. 증분 열 자체를 변경할 때는 새 동기화를 만드는 것이 권장됩니다.

간헐적 실패와 실행 지연

에이전트 메모리

에이전트 기반 실행이라면 설치 디렉터리의 var/data/processes 아래에서 가장 최근 bootstrapper~ 디렉터리를 찾고, 그 안의 var/log/magritte-agent-output.log를 확인합니다. JVM heap 부족과 운영체제 전체 메모리 부족을 구분합니다.

JVM heap이 작업량을 감당하지 못하면 에이전트 개요에서 agent heap size를 늘리는 방법을 검토합니다. 다만 호스트 전체 메모리가 부족하여 프로세스가 종료되는 상황에서는 heap 증가가 문제를 악화시킬 수 있습니다. Agent Manager, Bootstrapper, agent, Explorer의 메모리와 off-heap 사용량을 함께 계산하고 다른 프로세스를 위한 여유도 둡니다. heap을 늘릴 수 없으면 Maximum concurrent syncs를 낮춰 동시에 처리하는 작업량을 줄입니다.

Fetching 단계에서 멈출 때

Foundry나 에이전트를 사용하지 않는 원천 클라이언트에서도 같은 시스템에 연결하고 쿼리를 실행할 수 있는지 확인합니다. 원천에서도 느리거나 실패하면 원천의 부하 또는 장애를 먼저 다룹니다. 원천은 정상인데 Foundry 수집만 멈추면 실행 로그와 상황을 Palantir 지원에 전달합니다.

원천 부하를 줄이는 방법에는 동기화를 작은 단위로 나누기, 지원되는 증분 수집 사용, 원천이 덜 바쁜 시간대로 일정 변경이 있습니다.

JDBC의 Fetch Size는 데이터베이스 왕복 한 번에 읽는 행 수입니다. 소스의 고급 옵션에서 조정합니다.

변경효과
줄이기왕복당 행과 heap 사용량이 줄지만 호출 횟수가 늘어납니다.
늘리기왕복 호출 수가 줄지만 한 번에 보관할 행이 많아져 메모리를 더 사용합니다.

조정의 시작값으로 500을 권장합니다. 모든 커넥터의 기본값이 500이라는 뜻은 아닙니다.

Upload 단계에서 지연되거나 실패할 때

JDBC 소스 고급 옵션의 Max file size는 Foundry에 올릴 출력 파일의 크기를 바이트 또는 행 수 기준으로 제한합니다. 작은 파일은 업로드 횟수와 네트워크 요청 부담을 늘릴 수 있지만 실패 시 다시 올릴 양이 작습니다. 큰 파일은 파일 수와 요청 부담을 줄일 수 있으나 긴 업로드의 실패 가능성과 재업로드 비용을 고려해야 합니다.

파일 크기를 기준으로 조정할 때 권장값은 120 MB입니다. 실제 네트워크 상태와 행 크기에 맞춰 사용합니다.

파일 원천의 탐색이 오래 걸릴 때

FTP, SFTP 또는 디렉터리 기반 동기화는 큰 파일시스템을 훑는 데 시간이 걸릴 수 있습니다. 별도 구성이 없으면 파일시스템을 두 번 탐색하여 전송할 파일이 쓰이거나 변경 중인지 확인합니다.

소스 설정에서 필요한 하위 폴더를 지정합니다. 하위 폴더가 없으면 소스 루트 전체를 탐색합니다. 정규식 필터는 소스 루트를 기준으로 한 상대 파일 경로에 적용됩니다.

대규모 파일 수집의 트랜잭션

파일 기반 동기화는 실행 전체를 하나의 트랜잭션으로 처리합니다. 도중에 실패하면 트랜잭션이 중단되고 그 실행의 파일은 하나도 커밋되지 않습니다. 예를 들어 백만 개 중 마지막 한 파일을 제외하고 업로드했더라도 마지막 전송에서 실패하면 데이터셋 뷰에는 그 실행의 파일이 반영되지 않습니다.

한 실행에서 처리하는 파일이 많을수록 일시적인 네트워크 또는 원천 장애의 영향을 크게 받습니다. 증분 APPEND와 파일 개수 제한을 사용하면 실행당 작은 배치로 나눌 수 있고, 실패한 배치만 다시 수집하면 됩니다. 파일 기반 동기화에 동기화 실패와 필터 조건을 설명합니다.

REQUEST_ENTITY_TOO_LARGE

개별 파일이 에이전트의 업로드 목적지 한도를 넘으면 REQUEST_ENTITY_TOO_LARGE가 발생할 수 있습니다. data-proxy 업로드 전략의 기본 한도는 100 GB입니다. 한도를 늘리기보다 원천 데이터를 더 작은 파일로 나누는 방식을 우선 검토합니다.

임시로 한도를 변경해야 한다면 Data Connection에서 에이전트의 Advanced > Agent를 열고, destinations 아래 해당 업로드 목적지의 기존 설정에 다음 uploadStrategy를 적용합니다. 값 161061273600바이트는 150 GiB입니다.

uploadStrategy:
  type: data-proxy
  maximumUploadedFileSizeBytes: 161061273600

이 설정은 대용량 파일 전송이 안정적으로 끝난다는 보장이 아니며, 느린 다운로드와 처리 및 업로드 문제를 없애지 않습니다.

갑자기 발생한 BadPaddingException

BadPaddingException은 에이전트가 보관한 소스 인증 정보의 암호화 키가 기대한 값과 다를 때 발생할 수 있습니다. Agent Manager를 수동 업그레이드하면서 이전 설치의 var/data를 새 위치로 옮기지 않은 경우가 대표적입니다.

해당 에이전트를 사용하는 각 소스의 인증 정보를 다시 입력하면 현재 키로 처리할 수 있습니다. 업그레이드 후에는 원래 데이터 디렉터리의 처리와 영향을 받은 소스 범위를 확인합니다.

Timestamp가 Long으로 들어온 경우

JDBC 수집의 하위 호환 처리 때문에 Timestamp 값이 Long 열로 보일 수 있습니다. 값이 밀리초 단위 시각이라면 PySpark 변환에서 초로 변환한 뒤 Timestamp로 캐스팅할 수 있습니다.

다음 함수는 mytimestamp라는 밀리초 Long 열을 가진 DataFrame을 받아 해당 열만 Timestamp로 바꾼 DataFrame을 반환합니다.

from pyspark.sql import DataFrame, functions as F


def restore_millisecond_timestamp(df: DataFrame) -> DataFrame:
    return df.withColumn(
        "mytimestamp",
        (F.col("mytimestamp") / 1000).cast("timestamp"),
    )

입력 Long 값의 단위가 밀리초인지 먼저 확인합니다. 모든 숫자 시각 열에 같은 나눗셈을 적용하는 변환은 아닙니다.