Apache Iceberg REST Catalog 실전 운영: Polaris 1.7.0과 멀티엔진 거버넌스
Iceberg 도입 초기에는 엔진마다 카탈로그를 따로 운영하는 경우가 많았다. Spark는 Hive Metastore, Flink는 별도 JDBC 카탈로그, Trino는 직접 S3 파일 탐색. 이 구성에서는 동일 테이블을 여러 엔진이 동시에 수정하면 메타데이터가 충돌하고, 어떤 엔진이 어떤 데이터에 접근했는지 추적이 불가능하다.
REST Catalog는 이 문제를 카탈로그 레이어에서 해결하려는 시도다. 2026년 8월 2일 출시된 Apache Polaris 1.7.0은 프로덕션 운영에 필요한 핵심 기능들을 추가했다.
REST Catalog가 해결하는 문제
기존 카탈로그(Hive Metastore, Glue)는 조회 서비스다. 테이블 위치를 알려주지만, 쓰기 조정이나 접근 제어는 엔진이 각자 처리한다.
REST Catalog는 조정 프로토콜로 진화하고 있다.
- 모든 엔진이 동일한 HTTP API를 통해 메타데이터를 읽고 쓴다.
- 카탈로그 서버가 커밋을 검증하고, 충돌을 감지하고, 자격 증명을 발급한다.
- 감사 로그가 카탈로그 서버 한 곳에서 생성된다.
Credential Vending: 멀티엔진 거버넌스의 핵심
전통적인 접근 방식에서는 엔진이 S3 버킷 전체에 접근할 수 있는 자격 증명을 가진다. 누가 무엇을 읽었는지 카탈로그 레벨에서 알 수 없다.
Credential Vending은 Polaris가 엔진에게 범위가 제한된 임시 자격 증명을 발급하는 방식이다.
GET /v1/oauth/tokens
→ {
"access-token": "eyJ...",
"token-type": "Bearer",
"expires-in": 3600
}
GET /v1/namespaces/{ns}/tables/{table}
→ {
"metadata-location": "s3://bucket/path/metadata.json",
"config": {
"s3.access-key-id": "<임시 키>",
"s3.secret-access-key": "<임시 시크릿>",
"s3.session-token": "<STS 세션>",
"s3.region": "us-east-1"
}
}이 임시 자격 증명은 특정 테이블 경로에만 접근 가능하고, 만료 시간이 있다. Polaris는 어떤 엔진이 어떤 테이블에 언제 접근했는지 기록할 수 있다.
GCS에서의 Workload Identity Federation
GCS는 STS 기반 임시 토큰을 직접 지원하지 않는다. Polaris 1.7.0은 Workload Identity Federation을 통해 이를 해결한다.
- Polaris가 워크로드에 서비스 계정 토큰을 발급한다.
- 워크로드가 GCP WIF 엔드포인트에서 짧은 수명의 GCS 토큰으로 교환한다.
- 교환된 토큰은 특정 GCS 버킷/경로에만 접근 가능하다.
주의: 토큰 교환 레이턴시(200-400ms)가 추가되므로 짧은 쿼리가 많은 경우 프리페치 전략이 필요하다.
멱등성 쓰기: Polaris 1.7.0의 핵심 추가 기능
네트워크 타임아웃이나 엔진 재시작으로 커밋이 실제로 성공했는지 알 수 없는 상황이 발생한다. 재시도하면 중복 커밋이 발생할 수 있다.
설정
# polaris.properties
polaris.idempotency.enabled=true
polaris.idempotency.ttl=PT5M # 5분 TTL (ISO-8601 duration)
polaris.idempotency.store=entity-property # 또는 external-kv사용 방법
커밋 API 호출 시 Idempotency-Key 헤더를 포함한다:
POST /v1/namespaces/prod/tables/events/commit
Idempotency-Key: spark-job-20260828-001-attempt-3
Content-Type: application/json
{
"requirements": [...],
"updates": [...]
}Polaris는 5분 내 동일 키로 들어온 요청을 첫 번째 응답과 동일하게 처리한다. 첫 커밋이 성공했다면 중복 요청은 성공 응답을 반환한다. 첫 커밋이 실패했다면 재시도가 실제로 실행된다.
entity-property 방식은 Polaris 자체 메타데이터 저장소에 키를 저장하므로 외부 의존성이 없다. 처리량이 높은 경우 external-kv(Redis 등)를 사용하면 멱등성 키 조회 레이턴시를 줄일 수 있다.
쓰기 충돌 처리
REST Catalog 스펙은 낙관적 동시성 제어를 기반으로 한다. 커밋 요청에 현재 메타데이터 위치를 포함하고, 서버가 변경되었으면 409를 반환한다.
POST /v1/namespaces/{ns}/tables/{table}/commit
{
"requirements": [
{
"type": "assert-current-schema-id",
"current-schema-id": 3
},
{
"type": "assert-ref-snapshot-id",
"ref": "main",
"snapshot-id": 8841234567
}
],
"updates": [...]
}
→ HTTP 409 Conflict
{"error": {"type": "CommitConflictException", ...}}충돌 처리 전략
| 상황 | 권장 전략 |
|---|---|
| Spark 배치 잡 + Flink 스트리밍 동시 쓰기 | 파티션 분리 또는 브랜치/태그 격리 |
| 스키마 진화 중 여러 팀 동시 작업 | 스키마 변경은 단일 소유자 팀이 담당 |
| 재시도 루프에서 스타베이션 위험 | 지수 백오프 + 최대 재시도 횟수 제한 |
| DELETE 후 INSERT 패턴 | COW vs MOR 테이블 타입 재검토 |
REST Catalog 스펙은 재시도 동작을 의무화하지 않는다. 엔진마다 재시도 정책이 다르므로 여러 엔진이 같은 파티션에 동시 쓰기를 하면 한 엔진이 계속 패배할 수 있다. 운영 환경에서는 쓰기 파티션을 엔진별로 분리하거나, Iceberg 브랜치를 활용하는 것이 안전하다.
오펀 파일 정리: v2 삭제 매니페스트 주의사항
Copy-on-Write(COW) 테이블에서는 오래된 데이터 파일이 오펀으로 남는다. Merge-on-Read(MOR) 테이블에서는 이에 더해 삭제 매니페스트(v2 equality/positional delete files)가 쌓인다.
Iceberg v2 포맷을 쓰는 경우 오펀 파일 정리 시 삭제 매니페스트를 포함해야 한다.
# PySpark로 오펀 파일 정리
from pyspark.sql import SparkSession
spark = SparkSession.builder.getOrCreate()
# 오펀 파일 목록 확인 (삭제 전 필수)
spark.sql("""
CALL catalog.system.remove_orphan_files(
table => 'prod.events',
older_than => TIMESTAMP '2026-08-21 00:00:00',
dry_run => true
)
""").show()
# 실제 삭제
spark.sql("""
CALL catalog.system.remove_orphan_files(
table => 'prod.events',
older_than => TIMESTAMP '2026-08-21 00:00:00',
dry_run => false
)
""")주의: older_than 기준을 너무 최근으로 설정하면 현재 진행 중인 쓰기 트랜잭션의 파일을 삭제할 위험이 있다. 최소 24시간 이상 여유를 두는 것을 권장한다.
자주 발생하는 안티패턴
안티패턴 1: 카탈로그 없이 직접 S3 경로 참조
# 잘못된 방식: REST Catalog를 우회
spark.read.parquet("s3://bucket/warehouse/prod/events/data/")
# 올바른 방식: 카탈로그를 통해 접근
spark.read.table("rest_catalog.prod.events")직접 S3 경로를 읽으면 Iceberg 스냅샷 격리가 깨진다. 읽는 시점에 진행 중인 쓰기의 파일이 보일 수 있다.
안티패턴 2: 메타데이터 파일을 수동으로 수정
REST Catalog는 메타데이터 파일 위치를 관리한다. S3에서 직접 metadata.json을 수정하면 Polaris의 상태와 불일치가 발생한다. 스키마 변경, 파티션 변경은 반드시 카탈로그 API를 통해 수행한다.
안티패턴 3: 오래된 스냅샷 무제한 보관
스냅샷이 쌓이면 메타데이터 파일이 커지고, 플래닝 시간이 늘어난다.
-- 7일 이상 된 스냅샷 만료
CALL catalog.system.expire_snapshots(
table => 'prod.events',
older_than => TIMESTAMP '2026-08-21 00:00:00',
retain_last => 5
);안티패턴 4: 커밋 재시도 없이 운영
네트워크 불안정 환경에서 커밋 실패 시 재시도 없이 잡을 실패 처리하면 데이터 손실이 발생한다. Polaris 1.7.0의 멱등성 키와 재시도 로직을 함께 구성해야 한다.
Polaris 1.7.0 배포 설정
# docker-compose.yml (개발 환경)
services:
polaris:
image: apache/polaris:1.7.0
environment:
- POLARIS_PERSISTENCE_TYPE=postgres
- POLARIS_PERSISTENCE_HOST=postgres
- POLARIS_PERSISTENCE_PORT=5432
- POLARIS_PERSISTENCE_DATABASE=polaris
- POLARIS_CREDENTIAL_VENDING_ENABLED=true
- POLARIS_IDEMPOTENCY_ENABLED=true
- POLARIS_IDEMPOTENCY_TTL=PT5M
ports:
- "8181:8181"
postgres:
image: postgres:16
environment:
POSTGRES_DB: polaris
POSTGRES_USER: polaris
POSTGRES_PASSWORD: changemeSpark 연결 설정:
spark.sql.catalog.rest_catalog=org.apache.iceberg.spark.SparkCatalog
spark.sql.catalog.rest_catalog.type=rest
spark.sql.catalog.rest_catalog.uri=http://polaris:8181/api/catalog
spark.sql.catalog.rest_catalog.credential=client_id:client_secret
spark.sql.catalog.rest_catalog.warehouse=s3://my-bucket/warehouse운영 체크리스트
초기 설정
- [ ] Polaris 메타데이터 저장소로 PostgreSQL 또는 DynamoDB 선택 (SQLite는 개발 전용)
- [ ] Credential Vending 활성화 및 IAM 역할 범위 설정
- [ ] 멱등성 TTL을 최대 잡 실행 시간 × 2 이상으로 설정
쓰기 충돌 관리
- [ ] 동시 쓰기 엔진별 파티션 격리 정책 수립
- [ ] 커밋 재시도 로직에 지수 백오프 + 최대 횟수 제한 적용
- [ ] 충돌 발생률 모니터링 (높으면 파티션 전략 재검토)
유지보수
- [ ] 스냅샷 만료 스케줄 설정 (7일 보관 기준)
- [ ] 오펀 파일 정리 스케줄 설정 (older_than: 현재 - 24시간 이상)
- [ ] 메타데이터 파일 크기 모니터링 (10MB 초과 시 경고)
모니터링
- [ ] 커밋 지연 시간 (p99 > 500ms 이면 메타데이터 저장소 점검)
- [ ] 멱등성 키 캐시 히트율
- [ ] 자격 증명 발급 실패율
요약
Apache Iceberg REST Catalog는 멀티엔진 데이터 레이크의 메타데이터 관리를 단순한 조회 서비스에서 조정 프로토콜로 바꾼다. Polaris 1.7.0이 추가한 멱등성 쓰기와 Credential Vending은 프로덕션 운영에서 반복적으로 나타나는 두 문제—재시도 중복과 엔진별 접근 제어—를 카탈로그 레이어에서 해결한다. 실제 운영에서는 쓰기 충돌 관리, 오펀 파일 정리, 스냅샷 만료를 체계적으로 구성해야 안정적인 멀티엔진 환경을 유지할 수 있다.
References
- https://github.com/apache/polaris/releases/tag/apache-polaris-1.7.0
- https://iceberg.apache.org/docs/latest/rest-catalog/
- https://iceberg.apache.org/spec/
- https://polaris.apache.org/
- https://iceberg.apache.org/docs/latest/configuration/