Iceberg 브랜치의 스키마 격리
Foundry Iceberg 카탈로그는 브랜치마다 현재 스키마, 기본 파티션 명세와 기본 정렬 순서를 따로 추적합니다. Foundry 빌드에서 브랜치에 적용한 변경은 main 또는 다른 브랜치에 바로 적용되지 않습니다. 이 동작은 transforms-tables 0.1211.0 이상에서 사용할 수 있습니다.
Foundry의 master와 Iceberg의 main은 같은 기본 브랜치로 취급합니다. 외부 클라이언트는 별도 설정이 없으면 표준 Iceberg 브랜치 동작을 사용하므로 아래 헤더 조건을 확인해야 합니다.
표준 Iceberg와의 범위 차이
표준 Iceberg에서는 현재 스키마가 테이블 수준으로 공유됩니다. 어느 브랜치에서 시작한 변경이든 테이블의 현재 스키마를 바꾸면 main 소비자에게도 영향을 줍니다.
Foundry는 현재 스키마와 기본 파티션 및 정렬 선택을 브랜치 속성으로 보관합니다. 예를 들어 foundry.branch.feature-branch.schema-id 같은 메타데이터 속성을 사용합니다. 사용자가 이 예약 속성을 직접 수정하는 방식은 아닙니다.
| 메타데이터 | Foundry 확장 브랜치 | 표준 Iceberg 브랜치 |
|---|---|---|
| 스키마 이력 | 테이블 전체 | 테이블 전체 |
| 테이블 속성 | 테이블 전체 | 테이블 전체 |
| 현재 스냅샷 | 브랜치별 | 브랜치별 |
| 현재 스키마 | 브랜치별 | 테이블 전체 |
| 기본 파티션 명세 | 브랜치별 | 테이블 전체 |
| 기본 정렬 순서 | 브랜치별 | 테이블 전체 |
새 스키마가 공유 이력에 추가되더라도 main의 현재 스키마로 자동 선택되지는 않습니다. Foundry 작업은 브랜치 문맥을 카탈로그 요청에 자동 적용하므로 일반적인 브랜치 파이프라인에 별도 헤더 코드를 넣지 않습니다.
새 테이블을 개발 브랜치에서 만들기
다음은 feature/customer-scores에서 가상 고객 점수 테이블을 처음 만드는 예입니다. 출력 경로는 실제 프로젝트 위치로 변경합니다.
import pyarrow as pa
from transforms.api import transform
from transforms.tables import IcebergOutput, TableOutput
@transform.using(customer_scores=TableOutput("/Examples/iceberg/customer-scores"))
def create_customer_scores(customer_scores: IcebergOutput):
data = pa.table({
"customer_id": pa.array([1, 2, 3], type=pa.int64()),
"score": pa.array([0.91, 0.74, 0.88], type=pa.float64()),
})
customer_scores.write_table(data)
| 상태 | Foundry 확장 | 표준 Iceberg |
|---|---|---|
| 개발 브랜치 스키마 | 새 스키마 | 새 스키마 |
| main 스키마 | 빈 스키마 | 새 스키마 |
| 개발 브랜치 데이터 | 있음 | 있음 |
| main 데이터 | 없음 | 없음 |
Foundry에서는 리소스가 main에도 존재하지만 초기 스키마와 데이터는 개발 브랜치에만 있습니다.
기존 테이블의 스키마를 별도 브랜치에서 바꾸기
이번에는 main에서 이미 운영 중인 테이블이 있다고 가정합니다. feature/scoring-v2에서 비증분 table replace로 고객 ID를 문자열로 바꾸고 score_v2를 추가합니다.
import pyarrow as pa
from transforms.api import transform
from transforms.tables import IcebergOutput, TableOutput
@transform.using(customer_scores=TableOutput("/Examples/iceberg/customer-scores"))
def replace_customer_scores(customer_scores: IcebergOutput):
data = pa.table({
"customer_id": pa.array(["c1", "c2", "c3"], type=pa.string()),
"score": pa.array([0.91, 0.74, 0.88], type=pa.float64()),
"score_v2": pa.array([0.95, 0.80, 0.92], type=pa.float64()),
})
customer_scores.write_table(data)
| 상태 | Foundry 확장 | 표준 Iceberg |
|---|---|---|
| 개발 브랜치 스키마 | 새 스키마 | 새 스키마를 사용하려면 전역 스키마 변경 필요 |
| main 스키마 | 기존 스키마 유지 | 전역 변경의 영향 또는 검증 실패 |
| 개발 브랜치 데이터 | 새 데이터 | 쓰기 조건 충족 시 새 데이터 |
| main 데이터 | 변경 없음 | 변경 없음 |
이 예제는 비증분 교체를 사용합니다. 임의의 자료형 변경이 모든 증분 스키마 진화 작업에서 자동 지원된다는 뜻은 아닙니다.
외부 클라이언트의 X-PLTR-Branch
외부 클라이언트가 X-PLTR-Branch 헤더를 설정하면 Foundry의 브랜치 격리를 사용합니다.
- 브랜치를 명시하지 않은 요청은 헤더의 브랜치를 사용합니다.
- 요청에서
main을 명시해도 헤더의 브랜치를 사용합니다. - main이 아닌 다른 브랜치를 요청에 명시하면 그 명시값이 헤더보다 우선합니다.
예를 들어 헤더가 featurebranch1이면 SELECT * FROM table과 SELECT * FROM table.branch_main은 featurebranch1을 읽습니다. SELECT * FROM table.branch_featurebranch2는 featurebranch2를 읽습니다.
헤더를 설정하지 않으면 다음 표준 동작이 적용됩니다.
| 요청 | 스키마 기준 |
|---|---|
| 특정 스냅샷의 시간 여행 읽기 | 그 스냅샷에 연결된 스키마 |
| 브랜치 읽기 | 테이블 수준 main 스키마 |
| 쓰기 | main 스키마로 검증하며 맞지 않으면 실패 |
| 스키마 변경 | main의 테이블 수준 스키마에 적용되며 브랜치별로 격리되지 않음 |
PyIceberg
실제 카탈로그 URI, 액세스 토큰과 브랜치 이름을 인수로 받는 예제입니다.
from pyiceberg.catalog import load_rest
def load_foundry_branch_catalog(catalog_uri: str, access_token: str, branch_name: str):
return load_rest("foundry", {
"uri": catalog_uri,
"token": access_token,
"header.X-PLTR-Branch": branch_name,
})
Spark
카탈로그 연결, 인증과 Iceberg 런타임 라이브러리가 이미 구성된 SparkSession Builder에 브랜치 속성을 추가합니다. 예제의 카탈로그 이름은 foundry입니다.
def create_branched_spark_session(configured_builder, branch_name: str):
return (
configured_builder
.config("spark.sql.catalog.foundry.header.X-PLTR-Branch", branch_name)
.getOrCreate()
)
하나의 작업에는 하나의 브랜치 문맥
Foundry 작업의 브랜치는 모든 입력 및 출력 테이블에 동일하게 적용됩니다. 같은 작업에서 테이블마다 서로 다른 브랜치를 지정하는 방식은 현재 지원하지 않습니다. 실제로 사용할 입력 브랜치가 작업 브랜치와 다르면 검증 오류가 발생합니다.
브랜치가 참조하는 스키마나 파티션 명세는 main에서 실행하는 정리 작업도 삭제할 수 없습니다. 브랜치가 삭제되면 이 보호가 해제됩니다.
병합과 제한된 REST 작업
Iceberg의 cherry-pick 및 fast-forward는 스냅샷 참조만 처리하고 Foundry의 브랜치별 스키마 속성은 가져오지 않습니다. 이 방식으로 main에 병합해도 main의 기존 스키마가 남습니다. 브랜치의 스키마 변경을 반영하려면 코드를 병합한 뒤 Foundry 빌드 시스템으로 main을 다시 빌드합니다.
Foundry 브랜치 문맥에서 호출할 수 없는 권한 있는 REST 작업에는 다음이 포함됩니다.
dropNamespace
updateNamespace
dropTable
renameTable
commitTransaction
createView
dropView
renameView