본문 바로가기
WIKI 기술 지식 베이스

lakeFS와 Apache Iceberg 함께 사용하기

원문 보기 위키 갱신

Apache Iceberg 테이블에는 브랜치를 만들거나, 검토하거나, 변경을 롤백하는 기능이 기본 내장되어 있지 않아요. 그래서 팀들은 마이그레이션을 테스트하거나 환경 사이에서 데이터를 승격시키려고 테이블을 복사하는 수밖에 없었죠. lakeFS는 명세를 준수하는 Apache Iceberg REST Catalog를 노출해서 이 문제를 해결해요. 표준 Iceberg 클라이언트라면 무엇이든 이 카탈로그를 통해 테이블을 만들고, 관리하고, 쿼리할 수 있고, 모든 테이블이 Git 같은 버전 관리를 얻어요. 데이터를 브랜치하고, 커밋하고, 머지하고, 롤백할 수 있게 되는 거죠.

lakeFS는 표준 Iceberg REST 카탈로그를 서빙해요: 클라이언트는 데이터 파일을 오브젝트 스토리지에 직접 읽고 쓰고, lakeFS는 테이블 메타데이터를 버저닝하며 브랜칭, 커밋, 머지를 제공해요.

Iceberg REST 명세를 따르기 때문에 lakeFS는 AWS Glue, Nessie, Hive Metastore 같은 다른 Iceberg 카탈로그, 그리고 폐기된 lakeFS HadoopCatalog의 드롭인 대체품이 될 수 있어요. 또한 lakeFS는 완전히 데이터 경로 밖에 있어서, 클라이언트는 데이터 파일을 하부 오브젝트 스토어에 직접 읽고 쓰고, lakeFS는 테이블 메타데이터만 버저닝해요.

출처: 문서

본문

lakeFS Team과 lakeFS Enterprise에서 프라이빗 프리뷰로 제공돼요. 무료 평가판을 시작하거나 문의하세요.

Quick Start

카탈로그는 lakeFS 서버의 /iceberg/api에서 서빙되고, OAuth2 토큰 엔드포인트는 /iceberg/api/v1/oauth/tokens에 있어요. lakeFS 액세스 키와 시크릿으로 인증하세요. 형식은 <access_key_id>:<secret_access_key>예요.

테이블은 <repository>.<branch>.<namespace>.<table>로 주소 지정되므로, 브랜치를 바꾸는 건 네임스페이스를 바꾸는 것만큼 간단해요:

Python (PyIceberg)

from pyiceberg.catalog.rest import RestCatalog

catalog = RestCatalog(name="my_catalog", **{
    "prefix": "lakefs",
    "uri": "https://lakefs.example.com/iceberg/api",
    "oauth2-server-uri": "https://lakefs.example.com/iceberg/api/v1/oauth/tokens",
    "credential": "AKIAlakefs12345EXAMPLE:abc/lakefs/1234567bPxRfiCYEXAMPLEKEY",
})

# List namespaces in a branch
catalog.list_namespaces(("repo", "main"))

# Query a table
catalog.list_tables("repo.main.inventory")
table = catalog.load_table("repo.main.inventory.books")
arrow_df = table.scan().to_arrow()

lakeFS 서버를 가리키는 카탈로그를 설정해요:

Spark

spark.sql.catalog.lakefs=org.apache.iceberg.spark.SparkCatalog
spark.sql.catalog.lakefs.type=rest
spark.sql.catalog.lakefs.prefix=lakefs
spark.sql.catalog.lakefs.uri=https://lakefs.example.com/iceberg/api
spark.sql.catalog.lakefs.oauth2-server-uri=https://lakefs.example.com/iceberg/api/v1/oauth/tokens
spark.sql.catalog.lakefs.credential=AKIAlakefs12345EXAMPLE:abc/lakefs/1234567bPxRfiCYEXAMPLEKEY
spark.sql("USE lakefs.my_repo.main.inventory")

// List available tables
spark.sql("SHOW TABLES").show()

// Query data with branch isolation
spark.sql("SELECT * FROM books").show()

// Switch to a feature branch
spark.sql("USE lakefs.my_repo.new_branch.inventory")
spark.sql("SELECT * FROM books").show()

Spark에서 lakeFS를 쓰는 다른 방법은 Spark 연동 가이드를 참고하세요.

Trino

# example: /etc/trino/catalog/lakefs.properties
connector.name=iceberg
iceberg.catalog.type=rest
iceberg.rest-catalog.uri=https://lakefs.example.com/iceberg/api
iceberg.rest-catalog.nested-namespace-enabled=true
iceberg.rest-catalog.security=OAUTH2
iceberg.rest-catalog.oauth2.credential=${ENV:LAKEFS_CREDENTIALS}
iceberg.rest-catalog.oauth2.server-uri=https://lakefs.example.com/iceberg/api/v1/oauth/tokens
-- List tables in the iceberg catalog
USE "repo.main.inventory"; -- <repository>.<branch or reference>.<namespace>
SHOW TABLES;

-- Query a table
SELECT * FROM books LIMIT 100;

-- Switch to a different branch
USE "repo.new_branch.inventory";
SELECT * FROM books;

스토리지 접근 설정을 포함한 전체 설정은 Trino / Presto 연동 가이드를 참고하세요.

DuckDB

LOAD iceberg;
LOAD httpfs;

CREATE SECRET lakefs_credentials (
    TYPE ICEBERG,
    CLIENT_ID 'AKIAlakefs12345EXAMPLE',
    CLIENT_SECRET 'abc/lakefs/1234567bPxRfiCYEXAMPLEKEY',
    OAUTH2_SERVER_URI 'https://lakefs.example.com/iceberg/api/v1/oauth/tokens'
);

ATTACH 'lakefs' AS main_branch (
    TYPE iceberg,
    SECRET lakefs_credentials,
    -- scope the catalog to a repository and branch (see "Relative Namespace Support" below)
    ENDPOINT 'https://lakefs.example.com/iceberg/relative_to/my-repo.main/api'
);

USE main_branch.inventory;
SELECT * FROM books;

세부 사항은 DuckDB 연동 가이드를 참고하세요.

다른 엔진을 찾으신다면 Dremio, Starburst Galaxy, Amazon Athena, AWS Glue Data Catalog용 전용 가이드를 참고하세요.

Iceberg 테이블을 위한 버전 관리

카탈로그를 통해 이뤄진 테이블과 네임스페이스 변경(테이블 생성·삭제, 새 스냅샷 커밋)은 lakeFS의 다른 변경과 마찬가지로 타겟 브랜치에 스테이징돼요. lakeFS 도구로 명시적으로 커밋해 브랜치 이력에 기록하세요. 그리고 여러 Iceberg 작업, 여러 테이블에 걸친 작업조차 하나의 lakeFS 커밋으로 묶을 수 있다는 점도 기억하세요:

import lakefs

repo = lakefs.repository("repo")

# Create a branch to work in isolation
branch = repo.branch("new_branch").create(source_reference="main")

# The table is immediately accessible on the new branch
table = catalog.load_table(f"repo.{branch.id}.inventory.books")

# ... modify the table using any Iceberg client ...

# Commit the staged catalog changes to lakeFS
branch.commit(message="Update inventory.books")

# Merge the changes back into main
branch.merge_into("main")

# Changes are now visible in main
main_table = catalog.load_table("repo.main.inventory.books")

충돌하는 테이블 변경의 머지

lakeFS는 머지 동안 테이블 변경을 파일 작업으로 다뤄요. 테이블 메타데이터 파일은 일반 파일로 취급되고, 충돌하는 테이블 변경에는 특별한 머지 로직이 적용되지 않아요. 같은 테이블이 양쪽 브랜치에서 바뀌었다면 머지는 수동 해결이 필요한 충돌로 실패해요. 예외는 데이터 컴팩션(compaction)으로, Table Maintenance에서 설명하는 대로 lakeFS가 자동 해결할 수 있어요.

사용 사례

  • 격리된 데이터 개발: 여러 테이블에 걸친 스키마 변경, 마이그레이션, 백필을 테스트하는 브랜치를 만들고, 충돌 감지와 함께 안전하게 머지해요.

  • 다중 환경 관리: 환경(dev, staging, prod)을 브랜치로 표현하고, 자동화된 테스트가 게이트 역할을 하는 머지로 테이블 변경을 승격시켜요.

  • 협업: 여러 팀이 서로 다른 테이블 기능을 동시에 작업하고, 풀 리퀘스트로 데이터와 스키마 변경을 검토해요.

  • 거버넌스와 복구: 내장 커밋 로그가 누가 무엇을 어떻게 바꿨는지 기록하고, 세분화된 RBAC 정책이 접근을 제어하며, 원자적 롤백이 복구 시간을 줄여 줘요.

네임스페이스와 테이블

Iceberg 카탈로그의 네임스페이스는 "<repository>.<branch>.<namespace>(.<namespace>...)" 패턴을 따라요:

  • <repository>는 유효한 lakeFS 저장소 이름이어야 해요.

  • <branch>는 유효한 lakeFS 브랜치 이름이어야 해요.

  • <namespace> 구성 요소는 단위 구분자(unit separator)로 중첩할 수 있어요 (예: inventory.books).

예시:

  • my-repo.main.inventory

  • my-repo.feature-branch.inventory.books

Iceberg 카탈로그에서 사용하기 전에 저장소와 브랜치 구성 요소는 lakeFS에 이미 존재해야 해요.

카탈로그는 표준 Iceberg 네임스페이스 작업을 지원해요: 네임스페이스 생성, 나열, 삭제, 그리고 네임스페이스 안의 테이블 나열이요.

상대 네임스페이스 지원

일부 Apache Iceberg 클라이언트는 중첩 네임스페이스를 지원하지 않아요.

이런 클라이언트를 지원하려고 lakeFS REST 카탈로그는 상대 네임스페이스를 지정할 수 있게 해 줘요. 카탈로그 URL 엔드포인트의 일부로 부분 네임스페이스를 넘기는 방식이에요(보통 <repository>.<branch>):

https://lakefs.example.com/iceberg/relative_to/<repository>.<branch>/api

그러면 클라이언트가 넘기는 모든 네임스페이스는 URL의 네임스페이스에 대해 상대적으로 해석돼요. 예컨대 DuckDB는 <database>.<schema> 형태의 제한된 중첩만 허용하므로, 저장소와 브랜치로 범위가 지정된 엔드포인트를 쓰면 <namespace>.<table>만으로 테이블을 주소 지정할 수 있어요. 위의 DuckDB 예시와 DuckDB 연동 가이드에서 볼 수 있어요.

네임스페이스 제한

  • 저장소와 브랜치 이름은 lakeFS 네이밍 규칙을 따라야 해요.

  • 네임스페이스 구성 요소에는 중첩용 점(.) 외의 특수 문자를 쓸 수 없어요.

  • 네임스페이스 전체 경로 길이는 255자 미만이어야 해요.

  • 네임스페이스는 대소문자를 구분해요.

  • 빈 네임스페이스 구성 요소는 허용되지 않아요.

테이블 작업

Iceberg 카탈로그는 모든 표준 Iceberg 테이블 작업을 지원해요:

  • 스키마와 파티셔닝을 지정해 테이블 생성.

  • 테이블 스키마와 파티셔닝 갱신.

  • 테이블에 변경 커밋.

  • 테이블 삭제.

  • 네임스페이스 안의 테이블 나열.

인증과 권한 부여

인증

클라이언트는 <lakefs-endpoint>/iceberg/api/v1/oauth/tokens의 OAuth2 토큰 엔드포인트로 인증해요. 인증하려면 클라이언트가 lakeFS 액세스 키와 시크릿을 access_key:secret 형식의 credential로 제공해야 해요.

권한 부여

Iceberg REST 카탈로그는 자체 RBAC 액션과 리소스 집합을 정의하고 있어요. 아래에 설명돼요. lakeFS를 Iceberg와 함께 사용할 때는 권한을 Iceberg RBAC로 관리해야 해요 — 저장소나 객체 수준 권한만으로는 충분하지 않아요. Iceberg 카탈로그 권한은 관련 객체 권한보다 우선해요. 예컨대 저장소에 fs:ReadObject가 있어도 그 저장소의 테이블에 대한 catalog:ReadTable은 부여되지 않아요.

lakeFS RBAC에 대한 일반 정보는 RBAC 문서를 참고하세요.

리소스
리소스 ARN 구조
Iceberg Namespace arn:lakefs:catalog:::namespace/{repositoryId}/{namespace}
Iceberg Table arn:lakefs:catalog:::table/{repositoryId}/{namespace}/{table}
Iceberg View arn:lakefs:catalog:::view/{repositoryId}/{namespace}/{view}

ARN의 네임스페이스는 점으로 구분된 표기법을 사용해요 (예: my.namespace). 모든 경로 구성 요소에 와일드카드(*)가 지원돼요.

권한은 브랜치에 무관해요

ARN 구조에 브랜치 구성 요소가 없어서, 권한 부여에는 저장소와 네임스페이스만 사용돼요. 즉, 주어진 저장소의 어떤 네임스페이스에 대한 접근을 허용하는 정책은 모든 브랜치에서 그 네임스페이스에 적용돼요. 예컨대 arn:lakefs:catalog:::table/my-repo/my.namespace/*에 catalog:ReadTable을 허용하는 정책은 클라이언트가 main, dev, 그 외 어느 브랜치로 접근하든 my.namespace의 테이블 읽기를 허용해요.

액션
액션 리소스 설명
catalog:CreateNamespace Namespace 네임스페이스를 만들어요
catalog:GetNamespace Namespace 네임스페이스 메타데이터를 가져와요
catalog:ListNamespaces Namespace 자식 네임스페이스를 나열해요
catalog:UpdateNamespace Namespace 네임스페이스 속성을 갱신해요
catalog:DeleteNamespace Namespace 네임스페이스를 삭제해요
catalog:ListTables Namespace 네임스페이스 안의 테이블을 나열해요
catalog:CreateTable Table 테이블을 만들어요
catalog:ReadTable Table 테이블 메타데이터를 읽어요
catalog:UpdateTable Table 테이블을 갱신하거나 이름을 바꿔요 (커밋, 스키마 등)
catalog:DeleteTable Table 테이블을 삭제해요
catalog:ReadTableData Table 테이블 데이터의 읽기 전용 자격 증명을 벤딩해요 (Credentials Vending 참고)
catalog:WriteTableData Table 테이블 데이터의 읽기·쓰기 자격 증명을 벤딩해요 (Credentials Vending 참고)
catalog:ListViews Namespace 네임스페이스 안의 뷰를 나열해요
catalog:CreateView View 뷰를 만들어요
catalog:ReadView View 뷰 메타데이터를 읽어요
catalog:UpdateView View 뷰를 갱신, 교체, 또는 이름 변경해요
catalog:DeleteView View 뷰를 삭제해요
목록 작업 필터링

네임스페이스 필터링:

ListNamespaces에서는 리소스 ARN에 와일드카드를 써서 사용자에게 보이는 네임스페이스를 제어할 수 있어요.

예시 — analytics로 시작하는 네임스페이스만 나열 허용:

{
  "statement": [
    {
      "effect": "allow",
      "action": ["catalog:ListNamespaces"],
      "resource": "arn:lakefs:catalog:::namespace/my-repo/analytics*"
    }
  ]
}

이 정책은 사용자가 my-repo에서 analytics로 시작하는 네임스페이스만 보게 해요. 같은 저장소의 다른 네임스페이스는 응답에서 걸러져요.

테이블과 뷰 필터링 (조건 사용):

ListTables와 ListViews에서는 네임스페이스 ARN만으로는 그 네임스페이스 안의 개별 테이블이나 뷰를 필터링할 수 없어요. 정책은 StringLike 조건 연산자와 catalog:TableName 또는 catalog:ViewName 필드를 사용해 어떤 엔티티가 보이는지 제어할 수 있어요.

조건 필드 지원 액션 설명
catalog:TableName catalog:ListTables 나열 중인 테이블의 이름
catalog:ViewName catalog:ListViews 나열 중인 뷰의 이름

예시 — 패턴에 일치하는 테이블만 나열 허용:

{
  "statement": [
    {
      "effect": "allow",
      "action": ["catalog:ListTables"],
      "resource": "arn:lakefs:catalog:::namespace/my-repo/analytics",
      "condition": {
        "StringLike": {
          "catalog:TableName": ["prod-*", "staging-*"]
        }
      }
    }
  ]
}

이 정책은 사용자가 my-repo의 analytics 네임스페이스에서 테이블을 나열할 때 prod- 또는 staging-으로 시작하는 테이블만 보게 해요. 패턴에 일치하지 않는 테이블은 응답에서 걸러져요.

예시 — 특정 뷰 나열 거부:

{
  "statement": [
    {
      "effect": "allow",
      "action": ["catalog:ListViews"],
      "resource": "arn:lakefs:catalog:::namespace/my-repo/my.namespace"
    },
    {
      "effect": "deny",
      "action": ["catalog:ListViews"],
      "resource": "arn:lakefs:catalog:::namespace/my-repo/my.namespace",
      "condition": {
        "StringLike": {
          "catalog:ViewName": ["secret_view"]
        }
      }
    }
  ]
}

이 정책은 사용자가 my.namespace 네임스페이스의 모든 뷰를 나열하게 하되, 명시적으로 거부된 secret_view는 예외로 해요.

예시 정책

모든 테이블과 네임스페이스에 대한 읽기 전용 접근:

{
  "statement": [
    {
      "effect": "allow",
      "action": [
        "catalog:ReadTable",
        "catalog:ReadTableData"
      ],
      "resource": "arn:lakefs:catalog:::table/*/*/*"
    },
    {
      "effect": "allow",
      "action": ["catalog:ListNamespaces", "catalog:GetNamespace"],
      "resource": "arn:lakefs:catalog:::namespace/*/*"
    }
  ]
}

테이블 데이터의 읽기 전용 자격 증명을 벤딩하려면 catalog:ReadTableData가 필요해요. catalog:ReadTable만으로는 테이블 메타데이터 접근만 가능하고 벤딩된 데이터 자격 증명은 얻지 못해요. 자세한 내용은 Credentials Vending을 참고하세요.

저장소의 모든 테이블에 대한 읽기·쓰기 접근 (데이터 자격 증명 포함):

{
  "statement": [
    {
      "effect": "allow",
      "action": [
        "catalog:ReadTable",
        "catalog:CreateTable",
        "catalog:UpdateTable",
        "catalog:ReadTableData",
        "catalog:WriteTableData"
      ],
      "resource": "arn:lakefs:catalog:::table/my-repo/*/*"
    }
  ]
}

lakeFS Enterprise v1.91.0 이하에서 업그레이드하는 경우

catalog:ReadTableData와 catalog:WriteTableData는 lakeFS Enterprise v1.92.0에서 자격 증명 벤딩과 함께 도입된 새 액션이에요. 업그레이드 전에 만들어진 모든 정책 — 커스텀 또는 영속화된 카탈로그 정책 포함 — 은 이 액션을 부여하지 않아요.

  • 내장 CatalogRead 정책에는 이미 catalog:ReadTableData가 포함되어 있고, CatalogReadWrite는 catalog:*를 사용해요. 이 관리형 정책에 붙은 주체들은 업그레이드 후 자동으로 벤딩을 얻어요.

  • 커스텀/영속화 정책은 자동으로 갱신되지 않아요. 검토해서 관련 statement에 catalog:ReadTableData(읽기 전용)와 catalog:WriteTableData(읽기·쓰기)를 추가하세요. catalog:ReadTable만으로는 메타데이터 접근만 가능하고 벤딩된 자격 증명은 얻지 못해요.

인라인 벤딩은 최선 노력(best-effort)이기 때문에 데이터 액션이 빠져도 카탈로그 작업은 실패하지 않아요. 클라이언트는 여전히 테이블 메타데이터를 로드하지만 조용히 자격 증명을 받지 못하고, 이는 연결 문제처럼 보일 수 있어요. 벤딩을 복구하려면 데이터 액션을 부여하세요 (그 전에는 직접 자격 증명 요청이 상태 코드 401을 반환해요).

특정 저장소의 테이블에 대한 전체 접근:

{
  "statement": [
    {
      "effect": "allow",
      "action": ["catalog:*"],
      "resource": "arn:lakefs:catalog:::table/my-repo/*/*"
    },
    {
      "effect": "allow",
      "action": ["catalog:*"],
      "resource": "arn:lakefs:catalog:::namespace/my-repo/*"
    }
  ]
}

특정 테이블을 제외한 모든 테이블 읽기 허용:

{
  "statement": [
    {
      "effect": "allow",
      "action": ["catalog:ReadTable"],
      "resource": "arn:lakefs:catalog:::table/my-repo/*/*"
    },
    {
      "effect": "deny",
      "action": ["catalog:ReadTable"],
      "resource": "arn:lakefs:catalog:::table/my-repo/my.namespace/secret_table"
    }
  ]
}

멀티테넌시 (Multi-tenancy)

lakeFS Enterprise 라이선스의 Tenants 기능이 필요해요

Iceberg REST 카탈로그는 lakeFS API, lakectl, SDK가 쓰는 것과 같은 X-LakeFS-Tenant 요청 헤더를 통해 활성 테넌트를 인식해요. 헤더가 설정되면 my-repo.main.analytics 같은 네임스페이스는 그 테넌트 안에서 my-repo를 해석하고, 카탈로그가 기록하는 모든 것은 그 테넌트의 저장소에 들어가요. 모든 응답은 같은 헤더로 해석된 테넌트를 되돌려 줘요.

헤더가 없으면 카탈로그는 루트 테넌트에서 동작하므로 기존 설정은 바뀌지 않아요.

헤더는 카탈로그 속성으로 설정해요. X-Iceberg-Access-Delegation에 쓰는 것과 같은 메커니즘이에요:

Spark

spark.sql.catalog.lakefs.header.X-LakeFS-Tenant=<tenant>

Python (PyIceberg)

catalog = RestCatalog(name="lakefs", **{
    "uri": "https://lakefs.example.com/iceberg/api",
    "prefix": "lakefs",
    "oauth2-server-uri":
        "https://lakefs.example.com/iceberg/api/v1/oauth/tokens",
    "credential": "<lakefs-access-key>:<lakefs-secret-key>",
    "header.X-LakeFS-Tenant": "<tenant>",
})

Go (iceberg-go)

cat, err := rest.NewCatalog(ctx, "lakefs", "https://lakefs.example.com/iceberg/api",
    rest.WithCredential("<lakefs-access-key>:<lakefs-secret-key>"),
    rest.WithAdditionalProps(iceberg.Properties{"header.X-LakeFS-Tenant": "<tenant>"}),
)

호출자는 그 테넌트의 멤버여야 해요. 테넌트 검사에 실패한 요청은 다음으로 응답해요:

상태 오류 타입 언제
400 InvalidTenant 헤더 값이 제대로 된 형식의 테넌트 이름이 아니에요.
404 NotFound 테넌트가 존재하지 않거나, 호출자가 그 멤버가 아니에요. 두 경우는 구분할 수 없어요.
401 Unauthorized 라이선스에 Tenants 기능이 포함되어 있지 않아요.
501 NotImplemented 인증 백엔드가 테넌트를 지원하지 않아요.

클라이언트는 자체 오류 타입으로 이를 표면화해요. PyIceberg는 테이블 작업 전에 카탈로그가 설정을 로드할 때 404를 던져요.

자격 증명 벤딩 (Credentials Vending)

lakeFS는 Iceberg REST 카탈로그 클라이언트에게 단기적이고 테이블 범위의 오브젝트 스토어 자격 증명을 벤딩(발급)할 수 있어요. 이 덕분에 저장소의 물리적 스토리지용 영구 자격 증명을 Spark, Trino, PyIceberg 등 호환 클라이언트에 설정할 필요가 없어져요.

Iceberg 클라이언트는 lakeFS 자격 증명만 아는 상태인데, REST 카탈로그가 클라이언트가 읽고 써야 하는 물리적 오브젝트 스토어 위치를 반환하는 경우를 해결해 주는 거죠.

자격 증명 벤딩은 현재 S3를 지원하고, 추가 스토리지 백엔드 지원이 계획되어 있어요.

정보

자격 증명 벤딩은 클라이언트 쪽 영구 오브젝트 스토어 자격 증명의 필요를 없애 줄 뿐, 데이터를 lakeFS S3 게이트웨이로 프록시하지 않아요. 클라이언트는 여전히 공표된 물리적 스토리지 엔드포인트에 네트워크로 접근할 수 있어야 해요.

동작 방식

클라이언트는 자신의 lakeFS 자격 증명으로 카탈로그에 인증해요. S3에서 lakeFS는 그 다음:

  • 테이블 데이터에 대한 접근을 승인해요.

  • AWS STS AssumeRole을 호출해요.

  • STS 세션을 그 테이블의 데이터와 lakeFS 관리 메타데이터 접두사로 제한해요.

  • 임시 S3 자격 증명과 만료 시각을 Iceberg 클라이언트에게 반환해요.

  • 클라이언트는 물리적 S3 엔드포인트에 직접 접근해요.

벤딩된 자격 증명이 담는 권한은 호출자의 접근에 따라 달라져요. 읽기 전용 세션은 다음을 허용해요:

s3:GetObject
s3:ListBucket

읽기·쓰기 세션은 추가로 다음을 허용해요:

s3:PutObject
s3:DeleteObject
s3:AbortMultipartUpload
s3:ListMultipartUploadParts

생성되는 세션 정책은 이 액션들을 테이블의 데이터와 메타데이터 접두사로 제한해요.

자격 증명 벤딩은 최선 노력(best-effort)이에요. 예컨대 호출자에게 관련 테이블 데이터 권한이 없거나, 위치가 저장소의 스토리지 네임스페이스 밖이거나, 백엔드가 벤딩을 지원하지 않아 자격 증명을 벤딩할 수 없을 때에도, 카탈로그 작업은 성공하고 테이블 메타데이터를 자격 증명 없이 반환해요. Iceberg 클라이언트는 벤딩된 자격 증명의 요청과 갱신을 스스로 처리해요.

제한

  • 벤딩은 현재 S3만 지원하고, Azure와 GCS 같은 추가 백엔드 지원이 계획되어 있어요.

  • 자격 증명은 저장소의 스토리지 네임스페이스 안의 위치에 벤딩돼요. 외래(foreign) 또는 외부(external) 위치로 등록된 테이블은 그 위치의 자격 증명을 받지 못해요.

설정

자격 증명 벤딩은 스토리지 백엔드별로 정의되는 서버 설정이에요. S3 백엔드에서 활성화하려면 다음 blockstore 설정에 전용 role_arn을 설정하세요:

blockstore:
  type: s3
  s3:
    credentials_vending:
      role_arn: arn:aws:iam::123456789012:role/lakefs-iceberg-vending
      session_duration: 1h
      external_id: optional-external-id
      endpoint: https://s3.customer-facing.example.com
필드 필수 동작
role_arn Yes 벤딩을 활성화하고 각 세션에서 assume할 역할을 식별해요. 기본값은 비어 있고, 이는 벤딩을 비활성화해요.
session_duration No STS 세션 수명. 기본값: 1h. 최소: 15m. 더 짧게 설정하면 lakeFS가 시작을 거부해요.
external_id No STS AssumeRole에 전달돼요. 보통 역할의 신뢰 정책이 요구할 때 필요해요.
endpoint No 클라이언트에게 반환되는 S3 엔드포인트를 덮어써요. lakeFS와 클라이언트가 서로 다른 내부/외부 주소를 쓸 때 유용해요.

멀티 스토리지 백엔드 설치에서는 blockstores.stores[].s3 아래의 각 관련 S3 항목 안에 credentials_vending을 설정하세요. 모든 credentials_vending 필드와 대응하는 LAKEFS_ 접두사 환경 변수는 lakeFS Enterprise 설정 레퍼런스를 참고하세요.

AWS 사전 준비 (S3)

role_arn을 설정하면 벤딩이 켜지지만, AWS 쪽 설정도 필요해요. 다음 단계를 완료하세요:

  • role_arn이 참조하는 IAM 역할을 만들거나 식별하고, 그 역할의 identity policy에 벤딩에 필요한 S3 작업을 부여해요. 읽기는 s3:GetObject와 s3:ListBucket, 쓰기는 s3:PutObject, s3:DeleteObject, s3:AbortMultipartUpload, s3:ListMultipartUploadParts예요. lakeFS가 생성하는 인라인 세션 정책은 이 권한을 축소만 할 수 있고, 역할에 없는 권한을 추가할 수는 없어요.

  • lakeFS 서버의 AWS 신원이 역할을 assume하도록 허용해요. role_arn에 대한 sts:AssumeRole을 부여하면 돼요.

  • 역할의 신뢰 정책을 lakeFS 런타임 신원을 신뢰하도록 설정해요. external_id가 설정되어 있다면 신뢰 정책의 조건이 그 값과 일치해야 해요.

  • session_duration을 제한 안에 유지해 역할의 MaxSessionDuration이나 관련 STS 역할 체이닝 한도를 넘지 않게 하세요.

네트워크 접근 가능성도 확인하세요: lakeFS 서버는 STS에 도달할 수 있어야 하고, Iceberg 클라이언트는 물리적/고객 대면 S3 엔드포인트에 도달할 수 있어야 해요.

lakeFS 권한 부여

자격 증명 벤딩은 일반 카탈로그 작업에 쓰이는 메타데이터 액션과 별개인 테이블 데이터 액션에 의존해요. 테이블 데이터 액션은 읽기 전용 또는 읽기·쓰기 자격 증명이 벤딩되는지를 결정하고, 메타데이터 액션은 테이블과 그 물리적 위치를 해석하는 것을 통제해요:

액션 결과
catalog:ReadTableData 테이블 데이터의 읽기 전용 자격 증명을 벤딩해요.
catalog:WriteTableData 테이블 데이터의 읽기·쓰기 자격 증명을 벤딩해요. 쓰기가 먼저 검사되므로 별도의 읽기 데이터 권한은 필요하지 않아요.

사용자에게는 요청된 카탈로그 작업을 위한 일반 메타데이터 액션도 여전히 필요해요. 예: catalog:ReadTable, catalog:CreateTable, catalog:UpdateTable. 이 데이터 액션을 포함한 예시 정책과 기존 설치의 업그레이드 안내는 Authorization 섹션을 참고하세요.

다른 카탈로그 권한과 마찬가지로, 테이블 리소스 ARN은 브랜치에 무관해요.

클라이언트 설정

Iceberg 클라이언트를 설정해 벤딩된 자격 증명을 요청하세요:

Spark

Spark는 Hadoop S3A에 정적 오브젝트 스토어 자격 증명을 쓰는 대신 Iceberg S3FileIO를 사용해야 해요.

spark.sql.catalog.lakefs=org.apache.iceberg.spark.SparkCatalog
spark.sql.catalog.lakefs.type=rest
spark.sql.catalog.lakefs.uri=https://lakefs.example.com/iceberg/api
spark.sql.catalog.lakefs.oauth2-server-uri=https://lakefs.example.com/iceberg/api/v1/oauth/tokens
spark.sql.catalog.lakefs.credential=<lakefs-access-key>:<lakefs-secret-key>
spark.sql.catalog.lakefs.prefix=lakefs
spark.sql.catalog.lakefs.header.X-Iceberg-Access-Delegation=vended-credentials
spark.sql.catalog.lakefs.io-impl=org.apache.iceberg.aws.s3.S3FileIO

Python (PyIceberg)

catalog = RestCatalog(name="lakefs", **{
    "uri": "https://lakefs.example.com/iceberg/api",
    "prefix": "lakefs",
    "oauth2-server-uri":
        "https://lakefs.example.com/iceberg/api/v1/oauth/tokens",
    "credential": "<lakefs-access-key>:<lakefs-secret-key>",
    "header.X-Iceberg-Access-Delegation": "vended-credentials",
})

Trino

connector.name=iceberg
iceberg.catalog.type=rest
iceberg.rest-catalog.uri=https://lakefs.example.com/iceberg/api
iceberg.rest-catalog.prefix=lakefs
iceberg.rest-catalog.security=OAUTH2
iceberg.rest-catalog.oauth2.server-uri=https://lakefs.example.com/iceberg/api/v1/oauth/tokens
iceberg.rest-catalog.oauth2.credential=<lakefs-access-key>:<lakefs-secret-key>
iceberg.rest-catalog.vended-credentials-enabled=true
fs.native-s3.enabled=true

S3 호환 서비스에서는 클라이언트에 맞는 S3 엔드포인트, 리전, path-style 옵션을 유지하세요.

문제 해결

증상 가능한 원인과 해결
카탈로그 작업은 성공하지만 클라이언트가 자격 증명을 받지 못함 호출자에게 catalog:ReadTableData / catalog:WriteTableData가 없어요. 적절한 데이터 액션을 부여하세요 (Authorization 참고).
벤딩이 상태 코드 500으로 실패 STS AssumeRole이 실패했어요. lakeFS 신원이 role_arn을 assume할 수 있는지, 역할의 신뢰 정책이 lakeFS를 신뢰하는지(external_id가 있다면 일치하는지), 역할의 identity policy가 필요한 S3 액션을 허용하는지 확인하세요.
클라이언트는 자격 증명을 받지만 S3를 읽거나 쓰지 못함 클라이언트가 물리적/고객 대면 S3 엔드포인트에 도달하지 못해요. 네트워크 연결과, 있다면 설정된 endpoint 오버라이드를 확인하세요.
벤딩이 상태 코드 403으로 실패 요청된 테이블 위치가 저장소(또는 데이터셋 소스)의 스토리지 네임스페이스 밖이에요. 예컨대 외래/외부 테이블 위치요. 그런 위치에는 자격 증명이 벤딩되지 않아요.
벤딩이 상태 코드 406으로 실패 스토리지 백엔드가 벤딩을 지원하지 않아요(현재 S3만 지원), 또는 S3 벤딩이 꺼져 있어요. 관련 S3 blockstore에 credentials_vending.role_arn을 설정하세요.

테이블 유지보수 (Table Maintenance)

데이터 파일 컴팩션

lakeFS 카탈로그에서는 데이터 파일 컴팩션 작업(RewriteDataFiles 등) 이후에도 데이터 무결성이 유지돼요. 그런 작업은 데이터 파일을 삭제하지 않기 때문이에요 (새 스냅샷과 매니페스트 파일만 만들어요).

하지만 불필요한 머지 충돌을 피하려고, 컴팩션 작업을 snapshotProperty()로 표시하기를 권해요. 그러면 lakeFS가 컴팩션 커밋을 가진 브랜치를 머지할 때 충돌을 자동으로 해결할 수 있어요.

정보

만료되거나 삭제된 스냅샷은 lakeFS가 어떤 스냅샷을 유지할지 판단할 수 없어서 머지 충돌로 이어질 가능성이 매우 높아요. 여기에는 Tables Cleanup으로 만료된 스냅샷도 포함돼요 — cleanup이 관련 스냅샷을 만료시키기 전에 컴팩션된 브랜치를 머지하세요.

요구 사항
  • Iceberg v1.5 이상과 함께 Spark Java API(SQL 프로시저 아님)를 사용해요

  • snapshotProperty()로 다음 속성과 값을 지정해 컴팩션 작업을 표시해요:

SparkActions.get(spark)
    .rewriteDataFiles(table)
    .snapshotProperty("lakefs.compaction.operation-id", "rewrite-data-files")
    .execute();
충돌 해결

브랜치를 머지할 때, 양쪽 브랜치가 같은 테이블에 대한 컴팩션 커밋을 가지고 최대 한 브랜치에만 컴팩션이 아닌 데이터 변경이 있다면 lakeFS는 자동으로 충돌을 해결해요. 이 덕분에 작업을 잃지 않고 컴팩션된 브랜치를 안전하게 머지할 수 있어요.

정보

만료되거나 삭제된 스냅샷은 lakeFS가 어떤 스냅샷을 유지할지 판단할 수 없어서 머지 충돌로 이어질 가능성이 매우 높아요.

팁

머지 충돌을 최소화하려고 컴팩션된 브랜치를 자주 머지하세요.

팁

"main"이 아닌 Iceberg 브랜치("Refs")에서는 데이터 변경이 무시되므로, lakeFS에서 브랜치를 만들 때 Iceberg에서 브랜치를 만드는 건 피하는 게 좋아요.

자동 충돌 해결을 끄려면 iceberg.ignore_compaction_commits 설정 플래그를 false로 설정하세요 (기본값은 true).

Tables Cleanup

Iceberg의 내장 유지보수 절차(스냅샷 만료, 고아 파일 삭제)는 lakeFS 버저닝을 인식하지 못해서 비활성 상태로 유지해야 해요(Unsupported Operations 참고). 그들이 원래 확보했을 스토리지를 되찾으려고, lakeFS는 Iceberg Cleanup을 제공해요. lakeFS Enterprise Spark metadata client에 포함된 Spark 잡으로, lakeFS 브랜치, 태그, 보존된 커밋, 스테이징된 변경이 여전히 참조하는 것은 아무것도 지우지 않으면서 카탈로그가 관리하는 테이블의 스토리지 발자국을 줄여요.

cleanup 실행은 두 부분으로 이루어져요:

  • 스냅샷 만료: 모든 브랜치에서 보존 정책보다 오래된 테이블 스냅샷을 테이블 메타데이터에서 제거하고, 잘린 메타데이터는 브랜치에 스테이징돼요 (다른 카탈로그 변경처럼 커밋하세요).

  • 스토리지 청소: 카탈로그 관리 스토리지에서 유지되는 어떤 테이블이나 뷰 — 어떤 브랜치 HEAD, 태그, 저장소의 가비지 컬렉션 규칙이 유지하는 커밋, 스테이징된 변경 — 로도 참조되지 않는 파일은 삭제 후보로 표시된 뒤 삭제돼요. 안전 나이보다 새 파일은 절대 삭제되지 않아 진행 중인 쓰기를 보호해요.

요구 사항
  • 저장소에 가비지 컬렉션 규칙이 설정되어 있어야 해요. 그 규칙이 유지하는 커밋이 cleanup이 유지할 과거 테이블 버전을 정의해요.

  • Spark 클러스터가 저장소의 스토리지 네임스페이스에 대한 읽기, 쓰기, 삭제 접근 권한이 필요해요 (자세한 내용은 this 참고).

  • cleanup을 실행하는 lakeFS 사용자는 아래 나열된 권한이 필요해요 (표준 GC 집합에 관련 카탈로그 권한을 더한 것).

예시 cleanup 정책

{
  "statement": [
    {
      "effect": "allow",
      "action": [
        "retention:GetGarbageCollectionRules",
        "retention:PrepareGarbageCollectionCommits",
        "retention:PrepareGarbageCollectionUncommitted",
        "fs:ReadRepository",
        "fs:ListBranches",
        "fs:ListTags",
        "fs:ListObjects"
      ],
      "resource": "arn:lakefs:fs:::repository/my-repo"
    },
    {
      "effect": "allow",
      "action": ["fs:ReadObject", "fs:WriteObject"],
      "resource": "arn:lakefs:fs:::repository/my-repo/object/*"
    },
    {
      "effect": "allow",
      "action": ["fs:ReadConfig"],
      "resource": "*"
    },
    {
      "effect": "allow",
      "action": ["catalog:ReadTable", "catalog:UpdateTable"],
      "resource": "arn:lakefs:catalog:::table/my-repo/*/*"
    }
  ]
}
cleanup 실행하기
CLIENT_VERSION=0.25.0
spark-submit --class io.treeverse.gc.IcebergCleanup \
  --conf spark.hadoop.lakefs.api.url=https://lakefs.example.com/api/v1 \
  --conf spark.hadoop.lakefs.api.access_key=<LAKEFS_ACCESS_KEY> \
  --conf spark.hadoop.lakefs.api.secret_key=<LAKEFS_SECRET_KEY> \
  --conf spark.hadoop.lakefs.iceberg.catalog.uri=https://lakefs.example.com/iceberg/api \
  --conf spark.hadoop.fs.s3a.access.key=<S3_ACCESS_KEY> \
  --conf spark.hadoop.fs.s3a.secret.key=<S3_SECRET_KEY> \
  http://treeverse-clients-us-east.s3-website-us-east-1.amazonaws.com/lakefs-spark-client-enterprise/${CLIENT_VERSION}/lakefs-spark-client-enterprise-assembly-${CLIENT_VERSION}.jar \
  <repository-name> [region]

잡 인자는 저장소 이름이고, S3에서는 선택적 리전이에요. spark.speculation은 반드시 비활성 상태를 유지해야 해요 (중복 실행되는 추측 태스크가 메타데이터 커밋을 이중 적용할 수 있어요). 켜져 있으면 잡은 실행을 거부해요.

설정

다음 속성들은 Hadoop 설정으로 전달해요. 즉 spark.hadoop. 접두사를 붙여요:

속성 기본값 설명
lakefs.iceberg.catalog.uri (required) lakeFS Iceberg REST 카탈로그의 URI, 예: https://lakefs.example.com/iceberg/api.
lakefs.iceberg.cleanup.max_snapshot_age_days 5 이보다 오래된 스냅샷은 만료돼요. 0이면 keep 하한까지 보호되지 않은 모든 스냅샷을 만료시켜요.
lakefs.iceberg.cleanup.min_snapshots_to_keep 2 테이블 메타데이터 파일당 유지하는 최소 스냅샷 수. 1보다 커야 해요.
lakefs.iceberg.cleanup.safety_age_seconds 259200 (3 days) 이보다 새 파일은 절대 삭제되지 않아요. 최소: 43200 (12 hours).
lakefs.iceberg.cleanup.dry_run false 실행이 무엇을 만료·삭제했을지 보고하고, 아무것도 바꾸지 않아요.
lakefs.iceberg.cleanup.do_stage true false이면 만료는 보고서에서 미리 보기만 하고, 테이블 메타데이터는 잘리거나 스테이징되지 않아요.
lakefs.iceberg.cleanup.do_mark true 스냅샷을 만료하고 삭제 후보를 표시해요. 청소 전용 실행이면 false로 하세요.
lakefs.iceberg.cleanup.do_sweep true 표시된 후보를 삭제해요. 표시 전용 실행이면 false로 하세요.
lakefs.iceberg.cleanup.mark_id 청소 전용 실행에서 실행할 mark의 ID. 표시하는 동안에는 설정하면 안 돼요.
lakefs.iceberg.cleanup.sweep_parallelism 3 삭제 단계의 병렬도예요.

do_stage는 표시 실행에만 적용돼요: 청소 전용 실행(do_mark=false)은 스냅샷 만료를 완전히 건너뛰고 지정된 mark를 실행하기만 해요. dry_run=true를 설정하면 do_stage와 do_sweep이 모두 강제로 꺼져서, 드라이런은 저장소의 테이블이나 스토리지를 수정하지 않고 삭제 후보만 보고해요.

실행 모드
  • 전체 실행 (기본값): 스냅샷 만료, 참조되지 않는 파일 표시, 그리고 삭제를 단일 실행에서 해요.

  • 드라이런 (dry_run=true): 실행 보고서만 기록해요 (<storage-namespace>/_lakefs/retention/iceberg/ 아래). 테이블 메타데이터는 그대로이고 아무것도 삭제되지 않아요. 드라이런의 mark는 이후 실행이 청소할 수 없어요.

  • 표시 전용 (do_sweep=false): 스냅샷을 만료하고 검토를 위해 삭제 후보를 기록해요. 실행의 로그와 보고서가 그 결과 mark ID를 알려 줘요.

  • 청소 전용 (do_mark=false와 mark_id): 이전 mark의 후보를 삭제해요. mark는 여전히 안전 나이보다 젊어야 해요. 실패했거나 중단된 청소는 같은 방식으로 재개되어 이미 삭제된 것은 건너뛰어요.

테이블별 제어

현재 스냅샷과 Iceberg 브랜치 또는 태그 ref가 참조하는 모든 스냅샷은 절대 만료되지 않아요. 그 외에도 테이블 소유자는 표준 Iceberg 테이블 속성으로 잡의 보존 정책을 재정의할 수 있어요:

  • lakefs-gc.enabled=false는 테이블을 스냅샷 만료 대상에서 제외해요.

  • history.expire.min-snapshots-to-keep과 history.expire.max-snapshot-age-ms(테이블 또는 Iceberg ref별)는 잡 정책보다 더 많이 보존할 때 보호를 올려 줘요.

실행 보고서

모든 실행은 저장소의 스토리지 네임스페이스 아래 _lakefs/retention/iceberg/expire/<run-id>/와 _lakefs/retention/iceberg/sweep/<run-id>/에 산출물을 기록해요. 계획된 만료, 테이블별 결과, 삭제 후보, 삭제 결과의 Parquet 데이터셋과, 실행의 설정·상태·횟수를 기록하는 summary.json이 함께 있어요. 청소 실행의 ID는 청소 전용 실행이 실행하는 mark ID예요.

제한
  • 외부 테이블: 포인터가 lakeFS 관리 위치 밖의 메타데이터를 참조하는 테이블(예: 외부 metadataLocation으로 등록됨)은 건너뛰어져요 — 만료도 청소도 되지 않고, 실행 보고서에서 skipped로 집계돼요. 다른 관리 테이블의 디렉터리 안에서 그것이 참조하는 파일은 이 건너뛰기로 보호되지 않아요.

  • 임베드된 매니페스트를 가진 format-v1 테이블: 스냅샷이 매니페스트 목록을 직접 임베드하는(manifest-list 파일 포인터 대신 manifests 배열) v1 테이블 메타데이터는 지원되지 않고 실행을 중단시켜요. 이 레이아웃은 Iceberg 0.8 이전의 것이에요. cleanup을 실행하기 전에 그런 테이블을 다시 작성하세요.

  • 컴팩션 충돌 해결: 컴팩션 커밋에 대한 자동 충돌 해결은 아직 만료된 스냅샷을 처리하지 못해요. cleanup이 관련 스냅샷을 만료시키기 전에 컴팩션된 브랜치를 머지하세요. 그렇지 않으면 그런 머지가 충돌할 수 있어요.

지원되지 않는 작업

Iceberg의 내장 테이블 유지보수 작업 — Iceberg core가 제공하는 Spark 프로시저와 SparkActions — 은 lakeFS 카탈로그에 대해 지원되지 않아요:

대신 lakeFS가 제공하는 유지보수 잡인 lakeFS Iceberg Cleanup을 실행하세요. 이 잡은 유지된 lakeFS 커밋이 참조하지 않게 되면 오래된 스냅샷을 안전하게 만료하고 참조되지 않는 파일 — 오래된 메타데이터 파일과 삭제된 테이블의 스토리지 포함 — 을 삭제해요.

위험

데이터 손실을 막으려고, 클라이언트는 다른 모든 cleanup 작업을 반드시 비활성 상태로 유지해야 해요:

  • 고아 파일 삭제 비활성화.

  • 재작성 시 remove-dangling-deletes를 false로 설정.

  • 스냅샷 만료 비활성화.

참고

history.expire.min-snapshots-to-keep과 history.expire.max-snapshot-age-ms 테이블 속성은 Tables Cleanup이 유지하는 것도 올려요. cleanup이 잘라 주길 기대하는 테이블에는 지나치게 큰 값을 피하세요.

제한

  • Table Maintenance:

    • 세부 사항은 Table Maintenance 섹션을 참고하세요
  • 고급 기능:

    • 서버 쪽 쿼리 플래닝

    • 테이블 이름 변경

    • 테이블 위치 갱신 (커밋 사용)

  • lakeFS Iceberg REST 카탈로그는 현재 Amazon S3, S3 호환 스토리지(MinIO 등), Google Cloud Storage, Azure Blob Storage에서 동작하도록 테스트되어 있어요.

  • Iceberg v3 테이블 포맷은 현재 지원되지 않아요 (가장 흔한 v2와 v1은 지원돼요).

로드맵

다음 기능들이 이후 릴리스를 위해 계획되어 있어요:

  • Table Import:

    • 다른 카탈로그에서 기존 Iceberg 테이블 가져오기 지원

    • 대규모 마이그레이션을 위한 벌크 임포트 기능

  • Azure Storage Support

  • 고급 기능:

    • Views API 지원

    • 테이블 트랜잭션

  • 고급 버저닝 기능

    • 충돌하지 않는 테이블 업데이트 머지

동작 원리

내부적으로 lakeFS Iceberg REST 카탈로그는 각 테이블의 메타데이터 파일을 추적해요. 이것은 보통 테이블 포인터라고 불려요.

이 포인터는 저장소의 스토리지 네임스페이스 안에 저장돼요.

요청이 들어오면 카탈로그는 테이블의 완전 수식 네임스페이스 <repository>.<reference>.<namespace>.<table_name>을 검사해 지정한 참조에서 그 특별한 포인터 파일을 읽고, 메타데이터 파일의 하부 오브젝트 스토어 위치를 클라이언트에게 반환해요. 테이블이 만들어지거나 갱신될 때 lakeFS는 스토리지 네임스페이스 안에 새 메타데이터 파일을 만들고, 그 메타데이터 파일을 요청된 브랜치의 현재 포인터로 등록해요. 이 변경은 lakeFS에 스테이징되고, 브랜치에 커밋할 수 있어요.

이 접근법은 Iceberg의 기존 메타데이터와 스냅샷의 불변성 위에 세워져 있어요: lakeFS의 커밋은 메타데이터 파일을 캡처하고, 그 메타데이터 파일은 매니페스트 목록, 매니페스트 파일, 관련된 모든 데이터 파일을 캡처해요.

Iceberg와 lakeFS가 둘 다 어떤 파일이 어느 버전에 속하는지 추적하는 "이중 부기(double booking)"를 피할 뿐만 아니라, 기존 Iceberg 도구 생태계와의 카탈로그 확장성과 호환성도 크게 좋아져요.

예시: Iceberg 테이블 읽기

Iceberg 테이블에서 읽는 것이 어떤 모습인지 간단화한 예시예요:

sequenceDiagram
    Actor Iceberg Client
    participant lakeFS Catalog API
    participant lakeFS
    participant Object Store

    Iceberg Client->>lakeFS Catalog API: get table metadata("repo.branch.table")
    lakeFS Catalog API->>lakeFS: get('repo', 'branch', 'table')
    lakeFS->>lakeFS Catalog API: physical_address
    lakeFS Catalog API->>Iceberg Client: object location ("s3://.../metadata.json")
    Iceberg Client->>Object Store: GetObject
    Object Store->>Iceberg Client: table data

예시: Iceberg 테이블 쓰기

Iceberg 테이블에 쓰는 것이 어떤 모습인지 간단화한 예시예요:

sequenceDiagram
    Actor Iceberg Client
    participant lakeFS Catalog API
    participant lakeFS
    participant Object Store
    Iceberg Client->>Object Store: write table data
    Iceberg Client->>lakeFS Catalog API: Iceberg commit
    lakeFS Catalog API->>lakeFS: stage('new table pointer')
    lakeFS->>Object Store: PutObject("metdata.json")
    lakeFS Catalog API->>Iceberg Client: Iceberg commit done
    Iceberg Client->>lakeFS: lakeFS commit('repo','branch', message)

관련 리소스

추가 읽을거리

폐기됨: Iceberg HadoopCatalog

경고

HadoopCatalog와 다른 파일시스템 기반 카탈로그는 현재 Apache Iceberg 커뮤니티에서 권장되지 않고, 동시성과 도구 관련 여러 제한이 있어요.

그래서 이 섹션에서 설명하는 HadoopCatalog는 이제 폐기(deprecated)됐고 추가 업데이트를 받지 않아요.

설정

Maven

lakeFS 커스텀 카탈로그를 설치하려면 다음 Maven 의존성을 사용해요:

<dependency>
<groupId>io.lakefs</groupId>
<artifactId>lakefs-iceberg</artifactId>
<version>0.1.4</version>
</dependency>

Iceberg와 함께 패키지 목록에 lakefs-iceberg jar를 포함하세요. 예:

.config("spark.jars.packages", "org.apache.iceberg:iceberg-spark-runtime-3.3_2.12:1.3.0,io.lakefs:lakefs-iceberg:0.1.4")

설정

PySpark

Spark SQL 카탈로그를 설정해요:

.config("spark.sql.catalog.lakefs", "org.apache.iceberg.spark.SparkCatalog") \
.config("spark.sql.catalog.lakefs.catalog-impl", "io.lakefs.iceberg.LakeFSCatalog") \
.config("spark.sql.catalog.lakefs.warehouse", f"lakefs://{repo_name}") \
.config("spark.sql.catalog.lakefs.cache-enabled", "false")

S3A Hadoop FileSystem을 lakeFS 연결 정보로 설정해요. 이것들은 여러분의 S3 자격 증명이 아니라 lakeFS 엔드포인트와 자격 증명이라는 점에 유의하세요.

.config("spark.hadoop.fs.s3.impl", "org.apache.hadoop.fs.s3a.S3AFileSystem") \
.config("spark.hadoop.fs.s3a.endpoint", "https://example-org.us-east-1.lakefscloud.io") \
.config("spark.hadoop.fs.s3a.access.key", "«redacted:AKIA…»") \
.config("spark.hadoop.fs.s3a.secret.key", "wJalrXUtnFEMI/K3MDENG/bPxRfiCYEXAMPLEKEY") \
.config("spark.hadoop.fs.s3a.path.style.access", "true")

Spark Shell

spark-shell --conf spark.sql.catalog.lakefs="org.apache.iceberg.spark.SparkCatalog" \
    --conf spark.sql.catalog.lakefs.catalog-impl="io.lakefs.iceberg.LakeFSCatalog" \
    --conf spark.sql.catalog.lakefs.warehouse="lakefs://example-repo" \
    --conf spark.sql.catalog.lakefs.cache-enabled="false" \
    --conf spark.hadoop.fs.s3.impl="org.apache.hadoop.fs.s3a.S3AFileSystem" \
    --conf spark.hadoop.fs.s3a.endpoint="https://example-org.us-east-1.lakefscloud.io" \
    --conf spark.hadoop.fs.s3a.access.key="«redacted:AKIA…»" \
    --conf spark.hadoop.fs.s3a.secret.key="wJalrXUtnFEMI/K3MDENG/bPxRfiCYEXAMPLEKEY" \
    --conf spark.hadoop.fs.s3a.path.style.access="true"

HadoopCatalog로 Iceberg 테이블 사용하기

테이블 만들기

메인 브랜치에 테이블을 만들려면 다음 문법을 사용해요:

CREATE TABLE lakefs.main.db1.table1 (id int, data string);
테이블에 데이터 넣기
INSERT INTO lakefs.main.db1.table1 VALUES (1, 'data1');
INSERT INTO lakefs.main.db1.table1 VALUES (2, 'data2');
브랜치 만들기

이제 테이블 생성을 메인 브랜치에 커밋할 수 있어요:

lakectl commit lakefs://example-repo/main -m "my first iceberg commit"

그다음 브랜치를 만들어요:

lakectl branch create lakefs://example-repo/dev -s lakefs://example-repo/main
브랜치에서 변경하기

이제 브랜치에서 변경할 수 있어요:

INSERT INTO lakefs.dev.db1.table1 VALUES (3, 'data3');
테이블 쿼리하기

브랜치에서 테이블을 쿼리하면 넣은 데이터가 보여요:

SELECT * FROM lakefs.dev.db1.table1;

결과:

+----+------+
| id | data |
+----+------+
| 1  | data1|
| 2  | data2|
| 3  | data3|
+----+------+

하지만 메인 브랜치에서 테이블을 쿼리하면 새 변경은 보이지 않아요:

SELECT * FROM lakefs.main.db1.table1;

결과:

+----+------+
| id | data |
+----+------+
| 1  | data1|
| 2  | data2|
+----+------+
기존 Iceberg 테이블을 Hadoop Catalog로 마이그레이션하기

원본 테이블에서 lakeFS로 증분 복사를 통해 이뤄져요.

  • 새 lakeFS 저장소를 만들어요: lakectl repo create lakefs://example-repo <base storage path>

  • 소스 iceberg 테이블과 타겟 lakeFS 카탈로그와 상호작용할 수 있는 Spark 세션을 시작해요. 버킷별 설정을 쓰는 Hadoop과 S3 세션, lakeFS 카탈로그의 예시예요:

SparkConf conf = new SparkConf();
conf.set("spark.hadoop.fs.s3a.path.style.access", "true");

// set hadoop on S3 config (source tables we want to copy) for spark
conf.set("spark.sql.catalog.hadoop_prod", "org.apache.iceberg.spark.SparkCatalog");
conf.set("spark.sql.catalog.hadoop_prod.type", "hadoop");
conf.set("spark.sql.catalog.hadoop_prod.warehouse", "s3a://my-bucket/warehouse/hadoop/");
conf.set("spark.sql.extensions", "org.apache.iceberg.spark.extensions.IcebergSparkSessionExtensions");
conf.set("spark.hadoop.fs.s3a.bucket.my-bucket.access.key", "<AWS_ACCESS_KEY>");
conf.set("spark.hadoop.fs.s3a.bucket.my-bucket.secret.key", "<AWS_SECRET_KEY>");

// set lakeFS config (target catalog and repository)
conf.set("spark.sql.catalog.lakefs", "org.apache.iceberg.spark.SparkCatalog");
conf.set("spark.sql.catalog.lakefs.catalog-impl", "io.lakefs.iceberg.LakeFSCatalog");
conf.set("spark.sql.catalog.lakefs.warehouse", "lakefs://example-repo");
conf.set("spark.sql.extensions", "org.apache.iceberg.spark.extensions.IcebergSparkSessionExtensions");
conf.set("spark.hadoop.fs.s3a.bucket.example-repo.access.key", "<LAKEFS_ACCESS_KEY>");
conf.set("spark.hadoop.fs.s3a.bucket.example-repo.secret.key", "<LAKEFS_SECRET_KEY>");
conf.set("spark.hadoop.fs.s3a.bucket.example-repo.endpoint"  , "<LAKEFS_ENDPOINT>");
  1. lakeFS에 스키마를 만들고 데이터를 복사해요. spark-sql로 복사하는 예시예요:
-- Create Iceberg Schema in lakeFS
CREATE SCHEMA IF NOT EXISTS <lakefs-catalog>.<branch>.<db>
-- Create new iceberg table in lakeFS from the source table (pre-lakeFS)
CREATE TABLE IF NOT EXISTS <lakefs-catalog>.<branch>.<db> USING iceberg AS SELECT * FROM <iceberg-original-table>

더 알아보기 (Learn more)

공식 문서: lakeFS Iceberg 문서