데이터 백필

데이터 백필 (Backfill Data)

원시 데이터가 수정되어 Pinot에도 반영해야 할 때, 세그먼트 단위로 백필하는 방법을 설명하는 페이지예요.

출처: Backfill Data

본문

Pinot 배치 수집은 두 부분으로 나뉘어요: 정기 수집 잡(hourly/daily)과 백필(backfill). Pinot 오프라인 테이블에서 정기 배치 수집이 어떻게 동작하는지 보여 주는 예시는 다음과 같아요:

높은 수준의 설명

  1. 원시 데이터를 버킷으로 정리 (예: /var/pinot/airlineStats/rawdata/2014/01/01). 각 버킷은 보통 여러 파일을 포함해요 (예: /var/pinot/airlineStats/rawdata/2014/01/01/airlineStats_data_2014-01-01_0.avro)
  2. 특정 날짜 폴더(예: '/var/pinot/airlineStats/rawdata/2014/01/01')를 가리키는 Pinot 배치 수집 잡을 실행. 세그먼트 생성 잡이 각 avro 파일을 그 날짜의 Pinot 세그먼트로 변환하고 고유한 이름을 부여해요.
  3. 그 고유한 이름으로 세그먼트들을 Controller API를 통해 업로드하는 Pinot 세그먼트 푸시 잡을 실행.

{% hint style="info" %} 중요: 세그먼트 이름은 Pinot에서 그 세그먼트를 고유하게 식별하는 식별자예요. 컨트롤러가 같은 이름의 세그먼트에 대한 업로드 요청을 받으면 새 것으로 교체를 시도해요. {% endhint %}

이렇게 새로 업로드된 데이터는 이제 Pinot에서 쿼리할 수 있어요. 그런데 때로 사용자가 원시 데이터를 수정하게 되고, 그것이 Pinot에 반영되어야 해요. 이 과정을 'Backfill'이라 불러요.

Pinot에서 데이터를 백필하는 방법

Pinot은 세그먼트 수준에서만 데이터 수정을 지원해요. 즉 백필을 하려면 전체 세그먼트를 업데이트해야 해요. 높은 수준의 아이디어는 위에서 말한 2단계(세그먼트 생성)와 3단계(세그먼트 업로드)를 반복하는 거예요:

  • 백필 잡은 일일 잡과 같은 세분성으로 실행해야 해요. 예를 들어 2014/01/01 데이터를 백필해야 한다면 백필 잡에 그 입력 폴더를 지정해요 (예: '/var/pinot/airlineStats/rawdata/2014/01/01')
  • 백필 잡은 원래 잡과 같은 이름으로 (새 데이터로) 세그먼트를 생성해요.
  • 그 세그먼트들을 Pinot에 업로드할 때 컨트롤러가 이전 세그먼트를 하나씩 새 것으로 교체해요 (세그먼트 이름은 Pinot 내에서 기본 키 역할을 함).

엣지 케이스 예시

백필 잡은 백필 날짜에 같은 수(또는 그 이상)의 데이터 파일을 기대해요. 그래서 세그먼트 생성 잡은 원래 실행보다 같은 수(또는 그 이상)의 세그먼트를 만들게 돼요.

예를 들어 airlineStats 테이블이 2014/01/01에 2개의 세그먼트(airlineStats_2014-01-01_2014-01-01_0, airlineStats_2014-01-01_2014-01-01_1)를 갖고 있고 백필 입력 디렉터리에 입력 파일이 1개만 있다고 가정해요. 그러면 세그먼트 생성 잡은 세그먼트를 하나만 만들게 돼요: airlineStats_2014-01-01_2014-01-01_0. 세그먼트 푸시 잡 후에는 airlineStats_2014-01-01_2014-01-01_0만 교체되고, airlineStats_2014-01-01_2014-01-01_1의 오래된 데이터는 그대로 남아 있어요.

원시 데이터가 수정되어 원래 시간 버킷의 입력 파일 수가 첫 수집 실행보다 적어진다면, 백필은 실패해요.

LaunchBackfillIngestionJob으로 완전한 백필

위에서 설명한 케이스(백필 입력이 원래 수집보다 파일이 적은 경우)를 안전하게 처리하기 위해 Pinot은 전용 CLI 명령인 LaunchBackfillIngestionJob을 제공해요. 세그먼트 이름 충돌에 의존해 오래된 데이터를 교체하는 대신, Pinot의 세그먼트 라인리지(segment-lineage) 메커니즘을 사용해 날짜 범위의 기존 세그먼트를 새로 생성된 것으로 원자적으로 교체해요. 기본 라인리지 시맨틱은 일관된 데이터 푸시와 롤백 (Consistent Data Push and Rollback) 참고.

동작 방식

LaunchBackfillIngestionJob은 LaunchDataIngestionJob과 동일한 ingestionJobSpec.yaml을 재사용하고 4단계로 실행돼요:

단계 (Step) 설명 (Description)
1. 기존 세그먼트 조회 컨트롤러의 selectSegments API를 호출해 [startDate, endDate)와 겹치는 모든 OFFLINE 테이블 세그먼트를 나열. 이것들이 교체될 세그먼트들이에요.
2. 새 세그먼트 생성 입력 디렉터리에 대해 로컬 SegmentCreation 패스를 실행. 세그먼트 이름 생성기가 고유한 _<currentTimeMs> 접미사로 simple하게 강제되므로, 새로 생성된 세그먼트 이름은 기존 것과 절대 충돌하지 않아요 (그리고 라인리지 교체가 두 집합을 모호하지 않게 참조할 수 있어요).
3. 라인리지 엔트리 생성 segmentsFrom = <existing> 및 segmentsTo = <newly generated>로 startReplaceSegments 호출. 라인리지 엔트리가 확정될 때까지 이전 세그먼트가 계속 쿼리를 처리해요.
4. 푸시와 확정 SegmentTarPush로 새 세그먼트를 업로드하고, endReplaceSegments를 호출해 브로커를 새 세그먼트 집합으로 원자적으로 전환하고 이전 것을 은퇴시켜요. 푸시가 실패하면 라인리지 엔트리가 롤백되고 부분적으로 업로드된 세그먼트가 정리돼요.

이전 세그먼트 집합이 이름 충돌에서 추론되는 대신 3단계에서 명시적으로 기록되기 때문에, [엣지 케이스 예시](#edge case)에서 설명한 수축된 백필 입력이 오래된 세그먼트를 남기는 실패 모드가 발생할 수 없어요.

LaunchBackfillIngestionJob은 현재 OFFLINE 테이블만 지원해요.

명령 실행하기

bin/pinot-admin.sh LaunchBackfillIngestionJob \
    -jobSpecFile /path/to/ingestionJobSpec.yaml \
    -startDate 2014-01-01 \
    -endDate 2014-01-02
옵션 (Option) 필수 (Required) 설명 (Description)
-jobSpecFile 예 LaunchDataIngestionJob이 사용하는 것과 동일한 job-spec YAML
-startDate 예 백필 시작 날짜, 포함(inclusive). 형식 yyyy-MM-dd, UTC로 파싱
-endDate 예 백필 종료 날짜, 제외(exclusive). 형식 yyyy-MM-dd, UTC로 파싱
-partitionColumn 아니오 세그먼트가 BoundedColumnValue로 파티셔닝된 컬럼. -partitionColumnValue와 함께 사용해 교체할 세그먼트 범위를 한정
-partitionColumnValue 아니오 BoundedColumnValue 파티션이 이 값을 포함하는 세그먼트만 교체 대상

LaunchDataIngestionJob에서 상속된 모든 인증 옵션(-authToken, -user, -password, -authProvider, -authTokenUrl)이 지원돼요.

{% hint style="info" %} 날짜가 UTC 일 경계로 파싱되므로, 비-UTC 시간대의 호출자는 -startDate / -endDate를 만들 때 오프셋을 고려해야 합니다. 예를 들어 America/Los_Angeles 로컬의 "1월 1일" 백필은 2014-01-01T08:00Z에서 2014-01-02T08:00Z까지로, 이 명령이 사용하는 UTC 일 경계와 정렬되지 않습니다. {% endhint %}

파티션 컬럼으로 필터링

오프라인 세그먼트가 BoundedColumnValue로 파티셔닝된 테이블은 -partitionColumn과 -partitionColumnValue로 백필을 파티션 일부로 한정할 수 있어요:

bin/pinot-admin.sh LaunchBackfillIngestionJob \
    -jobSpecFile /path/to/ingestionJobSpec.yaml \
    -startDate 2014-01-01 \
    -endDate 2014-01-02 \
    -partitionColumn region \
    -partitionColumnValue us-east-1

이 명령은 컬럼의 기록된 파티션 함수를 사용해 us-east-1의 파티션 ID를 계산하고, 그 ID를 파티션 메타데이터가 포함하는 세그먼트만 유지해요. BoundedColumnValue 파티션 메타데이터가 없거나 하나 이상의 파티션을 덮는 세그먼트는 건너뛰어요.

더 알아보기 (Learn more)