lakeFS와 Unity Catalog 함께 사용하기
Databricks Unity Catalog는 워크스페이스에 흩어진 데이터 자산을 팀이 탐색·거버넌스·쿼리하는 곳이에요. 이 통합은 lakeFS의 Delta Lake 테이블을 내보내 Unity Catalog에 외부 테이블로 등록해 줘요. lakeFS 브랜치마다 카탈로그의 스키마가 되어, 일반 SQL로 그 브랜치의 테이블 버전을 쿼리할 수 있고 데이터는 lakeFS 밖으로 복사되지 않아요.
출처: 문서
본문
lakeFS Enterprise v1.92.0부터 사용할 수 있어요. 무료 체험을 시작해 보세요.
Note
lakeFS v1.4.0부터 제공되는
lakefs/catalogexport/delta_exporter모듈(또는 그 별칭delta_exporter_v1)을 사용하는 훅은 지원 중단(deprecated)되었고 2026년 9월 1일부로 동작하지 않게 돼요. 이 페이지의 예제는delta_exporter_v2를 사용해요. 그 날짜 전에 기존 훅을 업데이트하세요.
개요
Databricks Unity Catalog는 워크스페이스에 흩어진 데이터 자산을 팀이 탐색하고, 거버넌스하고, 쿼리하는 곳이에요. Delta Lake 테이블을 lakeFS로 버전 관리하면, 브랜치에서 만든 테이블 버전은 Databricks에서 보이지 않아요. lakeFS를 실험에 유용하게 만드는 브랜치 격리가, 여러분의 결과를 소비자가 이미 사용 중인 카탈로그 바깥에도 두는 거예요.
Unity Catalog 통합은 lakeFS에서 Delta Lake 테이블을 내보내 Unity Catalog에 외부 테이블(external table)로 등록해서 이 간극을 메워요. lakeFS 브랜치마다 카탈로그의 스키마가 되므로, 소비자는 평범한 SQL로 그 브랜치 버전의 테이블을 쿼리하고 Catalog Explorer에서 다른 자산과 함께 탐색할 수 있어요. 데이터는 절대 lakeFS 밖으로 복사되지 않고, 등록된 테이블은 읽기 전용이에요.
내보내기는 Lua 훅으로 실행되고, 어떤 이벤트가 트리거할지 여러분이 정해요. post-commit으로 실행하면 브랜치 커밋마다 카탈로그를 새로 고치고, post-merge는 머지된 것만 게시하고, post-create-branch는 브랜치가 생기자마자 그 브랜치의 테이블을 등록해요.
테이블 내보내기 설정
필수 조건
훅은 내보낸 테이블 메타데이터를 오브젝트 스토어에 쓴 다음, SQL 웨어하우스를 통해 Unity Catalog에 테이블을 등록해요. 그래서 한쪽에는 동작하는 lakeFS 저장소, 다른 쪽에는 외부 테이블 생성이 허용된 Databricks 신원이 필요해요. 계속하기 전에 아래를 모두 준비하세요.
lakeFS와 스토리지
-
S3 또는 Azure ADLS Gen2를 백킹 스토리지로 쓰는, 활성 상태의 lakeFS 설치. 내보내려는 Delta Lake 테이블을 담고 있는 저장소가 있어야 해요.
-
그 Delta Lake 테이블과 훅 스크립트에 접근할 수 있는 lakeFS 자격 증명.
-
저장소의 스토리지 네임스페이스에 쓰기 권한이 있는 스토리지 자격 증명.
Databricks
훅은 테이블을 등록할 때마다 그 서비스 프린시펄로 동작하니, 다음 권한을 부여하세요:
| 권한 | 설정 위치 |
|---|---|
| 서비스 프린시펄: 자기 자신에 대한 Manager, Workspace access와 Databricks SQL access 활성화 | Admin console → Service principals → → Permissions → Grant access, 그리고 같은 페이지의 Configurations 탭에서 두 액세스 토글 |
| SQL 웨어하우스에 대한 Can use | SQL Warehouses → → Permissions |
| 카탈로그에 대한 USE CATALOG, USE SCHEMA, CREATE SCHEMA | Catalog → → Permissions → Grant |
| External Location에 대한 CREATE EXTERNAL TABLE | Catalog → External Data → External Locations → Create location |
테이블 디스크립터 정의하기
테이블 디스크립터는 내보내기 도구에게 테이블 데이터가 lakeFS 어디에 있는지, Unity Catalog에 어떻게 등록할지 알려주는 YAML 파일이에요. Unity Catalog는 3단계 네임스페이스로 테이블을 주소 지정하므로, 모든 내보내기는 catalog.schema.table로 결정돼요. 카탈로그와 테이블 이름은 디스크립터에서 오고, 스키마는 내보내지는 브랜치의 이름이에요. 그래서 main에서 내보낸 famous_people 테이블은 my-catalog-name.main.famous_people에 자리 잡고, 실험 브랜치에서 내보낸 같은 테이블은 그 브랜치 고유의 스키마로 옆에 나타나요.
테이블을 최소한 다음 필드로 정의하세요:
| 필드 | 설명 |
|---|---|
| name | Unity Catalog에 등록될 테이블 이름이에요. |
| type | 반드시 delta여야 해요. Delta Lake 내보내기를 선택해요. |
| catalog | 테이블을 만들 Unity Catalog 카탈로그의 이름이에요. |
| path | Delta Lake 테이블 데이터가 lakeFS에서 브랜치 루트 기준으로 어디에 있는지예요. |
다음을 famous-people-td.yaml로 저장하세요:
---
name: famous_people
type: delta
catalog: my-catalog-name
path: tables/famous-people
Tip
카탈로그 이름은 저장소 이름을 따라 지으세요. 스키마가 이미 브랜치 이름이므로, 저장소와 이름이 같은 카탈로그는 Unity Catalog 네임스페이스를 lakeFS 네임스페이스와 맞춰 줘요. 그래서
repo.branch.table이 양쪽에서 같게 읽혀요.
테이블 디스크립터는 반드시 저장소의 _lakefs_tables/ 프리픽스 아래에 저장해야 해요. 내보내기 도구가 디스크립터를 찾는 위치예요. 내보내기 도구는 그곳에 저장된 모든 디스크립터를 스스로 찾아내기 때문에, 테이블을 하나 더 내보내는 건 훅 설정을 고치는 게 아니라 디스크립터 파일을 하나 더 커밋하는 문제예요.
테이블 디스크립터를 _lakefs_tables/famous-people-td.yaml로 업로드하고 커밋하세요:
lakectl fs upload lakefs://repo/main/_lakefs_tables/famous-people-td.yaml -s ./famous-people-td.yaml && \
lakectl commit lakefs://repo/main -m "add famous people table descriptor"
Unity Catalog 내보내기 설정
내보내기 도구는 두 단계를 순서대로 실행하는 Lua 스크립트예요. Delta Lake 내보내기가 테이블 메타데이터를 스토리지 네임스페이스에 쓰고, Unity Catalog 내보내기가 그 결과를 받아 외부 테이블로 등록해요. 그다음 액션이 스크립트를 post-commit 이벤트에 묶어서 자동으로 실행되게 해요. 앞의 두 변형은 실행 시점에 _lakefs_tables/ 아래의 모든 테이블 디스크립터를 찾아내고, 세 번째는 액션의 args로 전달된 고정 테이블 목록을 내보내요. 스크립트가 짧아지고 카탈로그에 들어가야 할 테이블이 일부뿐일 때 유용해요.
unity_exporter.lua를 만드세요:
AWS S3Azure ADLS Gen2AWS S3 (고정 테이블 목록)
local databricks = require("databricks")
local lakefs = require("lakefs")
local extractor = require("lakefs/catalogexport/table_extractor")
local delta_exporter = require("lakefs/catalogexport/delta_exporter_v2")
local unity_exporter = require("lakefs/catalogexport/unity_exporter")
local descriptors = "_lakefs_tables"
local prefix = descriptors .. "/"
local entries = extractor.list_table_descriptor_entries(
lakefs,
action.repository_id,
action.commit_id
)
local table_names = {}
for _, e in ipairs(entries) do
table.insert(table_names, string.sub(e.path, #prefix + 1))
end
-- Describe the destination storage to export to:
local export_storage = {
type = "s3",
access_key_id = args.aws.access_key_id,
secret_access_key = args.aws.secret_access_key,
region = args.aws.region,
}
-- Export the Delta Lake log to the storage namespace:
local details = delta_exporter.export_delta_log(
action,
table_names,
export_storage,
nil,
descriptors
)
-- Register the exported tables in Unity Catalog:
local databricks_client = databricks.client(args.databricks_host, args.databricks_token)
local registration_statuses = unity_exporter.register_tables(
action,
descriptors,
details,
databricks_client,
args.warehouse_id
)
for t, status in pairs(registration_statuses) do
print("Unity Catalog registration for table \"" .. t .. "\" completed with status: " .. status .. "\n")
end
local azure = require("azure")
local databricks = require("databricks")
local lakefs = require("lakefs")
local extractor = require("lakefs/catalogexport/table_extractor")
local delta_exporter = require("lakefs/catalogexport/delta_exporter_v2")
local unity_exporter = require("lakefs/catalogexport/unity_exporter")
local descriptors = "_lakefs_tables"
local prefix = descriptors .. "/"
local entries = extractor.list_table_descriptor_entries(
lakefs,
action.repository_id,
action.commit_id
)
local table_names = {}
for _, e in ipairs(entries) do
table.insert(table_names, string.sub(e.path, #prefix + 1))
end
-- Describe the destination storage to export to:
local export_storage = {
type = "azure",
storage_account = args.azure.storage_account,
access_key = args.azure.access_key,
}
-- Export the Delta Lake log, rewriting paths to the abfss scheme:
local details = delta_exporter.export_delta_log(
action,
table_names,
export_storage,
nil,
descriptors,
azure.abfss_transform_path
)
-- Register the exported tables in Unity Catalog:
local databricks_client = databricks.client(args.databricks_host, args.databricks_token)
local registration_statuses = unity_exporter.register_tables(
action,
descriptors,
details,
databricks_client,
args.warehouse_id
)
for t, status in pairs(registration_statuses) do
print("Unity Catalog registration for table \"" .. t .. "\" completed with status: " .. status .. "\n")
end
local databricks = require("databricks")
local delta_exporter = require("lakefs/catalogexport/delta_exporter_v2")
local unity_exporter = require("lakefs/catalogexport/unity_exporter")
local descriptors = "_lakefs_tables"
-- Describe the destination storage to export to:
local export_storage = {
type = "s3",
access_key_id = args.aws.access_key_id,
secret_access_key = args.aws.secret_access_key,
region = args.aws.region,
}
-- Export the Delta Lake log to the storage namespace:
local details = delta_exporter.export_delta_log(
action,
args.table_defs,
export_storage,
nil,
descriptors
)
-- Register the exported tables in Unity Catalog:
local databricks_client = databricks.client(args.databricks_host, args.databricks_token)
local registration_statuses = unity_exporter.register_tables(
action,
descriptors,
details,
databricks_client,
args.warehouse_id
)
for t, status in pairs(registration_statuses) do
print("Unity Catalog registration for table \"" .. t .. "\" completed with status: " .. status .. "\n")
end
Lua 스크립트를 main 브랜치의 scripts/unity_exporter.lua로 업로드하고 커밋하세요:
lakectl fs upload lakefs://repo/main/scripts/unity_exporter.lua -s ./unity_exporter.lua && \
lakectl commit lakefs://repo/main -m "upload unity exporter script"
내보내기 액션 설정
main 브랜치에서 커밋이 완료될 때(post-commit) 위 스크립트를 실행하는 액션을 정의해요. 지원되는 훅 이벤트 중 무엇이든 내보내기를 구동할 수 있으니, 데이터가 게시 준비를 마치는 시점에 맞는 것을 고르세요. 검토된 데이터만 카탈로그에 넣길 원하는 팀은 보통 post-merge에서 main 브랜치로 내보내기를 합니다.
Warning
내보내기는
post-이벤트로 트리거하세요.pre-commit같은pre-이벤트는 변경이 확정되기 전에 실행되므로, 내보내기는 성공하지만 그 커밋이 추가하는 데이터가 빠진, 이전 모습 그대로의 테이블을 등록하게 돼요.
unity_exports_action.yaml을 만들되, 저장소의 백킹 스토리지에 맞는 스토리지 자격 증명을 넣으세요:
AWS S3Azure ADLS Gen2AWS S3 (고정 테이블 목록)
---
name: unity_exports
on:
post-commit:
branches: ["main"]
hooks:
- id: unity_export
type: lua
properties:
script_path: scripts/unity_exporter.lua
args:
aws:
access_key_id: <AWS_ACCESS_KEY_ID>
secret_access_key: <AWS_SECRET_ACCESS_KEY>
region: <AWS_REGION>
databricks_host: <DATABRICKS_HOST_URL>
databricks_token: <DATABRICKS_SERVICE_PRINCIPAL_TOKEN>
warehouse_id: <WAREHOUSE_ID>
---
name: unity_exports
on:
post-commit:
branches: ["main"]
hooks:
- id: unity_export
type: lua
properties:
script_path: scripts/unity_exporter.lua
args:
azure:
storage_account: <AZURE_STORAGE_ACCOUNT>
access_key: <AZURE_STORAGE_ACCESS_KEY>
databricks_host: <DATABRICKS_HOST_URL>
databricks_token: <DATABRICKS_SERVICE_PRINCIPAL_TOKEN>
warehouse_id: <WAREHOUSE_ID>
---
name: unity_exports
on:
post-commit:
branches: ["main"]
hooks:
- id: unity_export
type: lua
properties:
script_path: scripts/unity_exporter.lua
args:
aws:
access_key_id: <AWS_ACCESS_KEY_ID>
secret_access_key: <AWS_SECRET_ACCESS_KEY>
region: <AWS_REGION>
table_defs: # descriptor file names under _lakefs_tables/, without the .yaml extension
- famous-people-td
- my-second-table-td
- my-third-table-td
databricks_host: <DATABRICKS_HOST_URL>
databricks_token: <DATABRICKS_SERVICE_PRINCIPAL_TOKEN>
warehouse_id: <WAREHOUSE_ID>
액션 설정을 _lakefs_actions/unity_exports_action.yaml로 업로드하고 커밋하세요:
lakectl fs upload lakefs://repo/main/_lakefs_actions/unity_exports_action.yaml -s ./unity_exports_action.yaml && \
lakectl commit lakefs://repo/main -m "add unity export action"
Note
이 커밋부터
main에 대한 모든 커밋이 내보내기를 실행해요. 스크립트는 커밋에서 변경된 테이블로 내보내기를 좁히기 때문에, 이 커밋은 테이블을 건드리지 않으므로 아무것도 내보내지 않아요. 첫 진짜 내보내기는 다음 단계에서 만드는 커밋에서 일어나요.
lakeFS에 Delta Lake 테이블 쓰기
내보내기 도구와 액션이 준비됐으면, 테이블에 쓰는 행위 자체가 내보내기를 트리거해요. 원하는 엔진으로, 디스크립터의 path 필드가 가리키는 같은 경로에 테이블을 쓰세요. 아래 예제는 lakeFS S3 게이트웨이 위에서 Spark를 사용해요:
pyspark --packages "io.delta:delta-spark_2.12:3.0.0,org.apache.hadoop:hadoop-aws:3.3.4,com.amazonaws:aws-java-sdk-bundle:1.12.262" \
--conf spark.sql.extensions=io.delta.sql.DeltaSparkSessionExtension \
--conf spark.sql.catalog.spark_catalog=org.apache.spark.sql.delta.catalog.DeltaCatalog \
--conf spark.hadoop.fs.s3a.aws.credentials.provider='org.apache.hadoop.fs.s3a.SimpleAWSCredentialsProvider' \
--conf spark.hadoop.fs.s3a.endpoint='<LAKEFS_SERVER_URL>' \
--conf spark.hadoop.fs.s3a.access.key='<LAKEFS_ACCESS_KEY>' \
--conf spark.hadoop.fs.s3a.secret.key='<LAKEFS_SECRET_ACCESS_KEY>' \
--conf spark.hadoop.fs.s3a.path.style.access=true
data = [
('James','Bond','England','intelligence'),
('Robbie','Williams','England','music'),
('Hulk','Hogan','USA','entertainment'),
('Mister','T','USA','entertainment'),
('Rafael','Nadal','Spain','professional athlete'),
('Paul','Haver','Belgium','music'),
]
columns = ["firstname","lastname","country","category"]
df = spark.createDataFrame(data=data, schema = columns)
df.write.format("delta").mode("overwrite").partitionBy("category", "country").save("s3a://repo/main/tables/famous-people")
데이터를 커밋하면 post-commit 훅이 발동돼요:
lakectl commit lakefs://repo/main -m "add famous people data"
액션이 famous_people Delta Lake 테이블을 저장소의 스토리지 네임스페이스로 내보내고, Unity Catalog에 카탈로그 my-catalog-name, 내보내진 브랜치 이름을 딴 스키마 main, 테이블 이름 famous_people로 외부 테이블을 등록해요. 결과적으로 my-catalog-name.main.famous_people가 생기는 거예요. 실행이 끝나면 lakeFS UI에서 진행 상황을 따라갈 수 있어요:
내보낸 테이블 다루기
테이블이 등록되면 Unity Catalog의 다른 외부 테이블처럼 동작해요. 이미 쓰고 있는 Databricks 도구로 쿼리할 수 있고, Catalog Explorer에서 my-catalog-name.main.famous_people 아래로 탐색할 수 있어요. Databricks CLI에서 정의를 확인하려면 다음을 실행하세요:
databricks tables get my-catalog-name.main.famous_people
Databricks가 보는 것은 훅이 실행된 커밋 시점의 테이블이에요. 그래서 아직 커밋하지 않은 작업은 카탈로그에 나타나지 않고, 다음 커밋에서 테이블이 새로 고쳐져요. 내보내기는 읽기 전용이므로, 쓰기는 계속 lakeFS로 하고 카탈로그 최신화는 훅에 맡기세요. 내보내진 브랜치마다 자체 스키마를 얻지만, 액션의 branches 필터가 커버하는 브랜치만 내보내져요. 즉 새 브랜치는 어떤 액션이 그 브랜치를 커버하게 되는 순간 카탈로그에 나타나요.
Delta Lake 내보내기 호환성
내보내진 테이블은 쓰여질 때 갖췄던 Delta Lake 기능을 그대로 유지해요. 그래서 소비자가 Databricks에서 쿼리하는 내용은 lakeFS의 테이블과 똑같이 동작해요. 아래에서 설명하는 제외 사항을 빼면, Delta 프로토콜이 reader version 3과 writer version 7에서 정의하는 테이블 기능 전체가 지원돼요. Databricks는 각 기능이 요구하는 버전을 Delta Lake feature compatibility and protocols에 정리해 두었어요.
지원되는 테이블 기능
Reader-writer 기능은 테이블을 읽는 방식과 쓰는 방식 모두를 바꾸므로, 두 엔진이 모두 이해해야 해요:
| Reader-writer 기능 |
|---|
| columnMapping |
| deletionVectors |
| timestampNtz |
| typeWidening |
| v2Checkpoint |
| vacuumProtocolCheck |
| variantType |
| variantShredding |
| geospatial |
Writer-only 기능은 쓰기에만 영향을 주고, 이를 무시하는 리더도 올바른 데이터를 봐요:
| Writer-only 기능 |
|---|
| appendOnly |
| invariants |
| checkConstraints |
| changeDataFeed |
| generatedColumns |
| identityColumns |
| rowTracking |
| clustering |
| domainMetadata |
| inCommitTimestamp |
| checkpointProtection |
| allowColumnDefaults |
| materializePartitionColumns |
| collations |
내보내기 도구는 일부 기능이 최종 이름으로 확정되기 전에 엔진들이 쓰던 preview/development 철자도 받아들여요. typeWidening-preview, variantType-preview, variantShredding-preview, inCommitTimestamp-preview, collations-preview, geospatial-dev 등이 그 예예요. 그래서 오래된 엔진이 쓴 테이블도 여전히 내보낼 수 있어요.
설계상 제외되는 기능
나머지 기능은 의도적으로 제외돼요. lakeFS가 그런 종류의 테이블에 더 나은 경로를 제공하기 때문이거나, 테이블이 내보내기가 다루는 범위를 벗어나기 때문이에요:
| 제외되는 기능 | 이유 |
|---|---|
| icebergCompatV1, icebergCompatV2, icebergCompatV3, icebergWriterCompatV1, icebergNativeV4 | 이 기능들은 Delta 테이블을 Iceberg 리더에 노출하기 위해 존재해요. lakeFS는 Iceberg REST Catalog를 통해 Iceberg 테이블의 일급 지원 경로를 이미 갖고 있어요. Iceberg 호환을 위해 Delta 테이블을 내보내는 대신 그걸 사용하세요. |
| catalogManaged, catalogOwned-preview, coordinatedCommits-preview | 이것들은 카탈로그가 생명주기를 소유하는 테이블을 선언해요. lakeFS는 managed table을 지원하지 않고, external table만 내보낼 수 있어요. |
제외는 실행 단위가 아니라 테이블 단위예요. 내보내기 도구가 이런 기능을 선언한 테이블을 만나면 그 테이블만 건너뛰고 skipping <table>: unsupported table feature를 출력한 뒤 나머지를 계속해요. 그래서 호환되지 않는 테이블 하나가 전체 내보내기를 막지 않아요.
인식되지 않는 기능
위 두 목록 어디에도 없는 기능을 테이블이 선언할 수도 있어요. 내보내기 도구가 추적하는 것보다 새로운 Delta 기능을 쓴 엔진이 테이블을 썼을 때 일어나요. 이런 테이블도 그대로 내보내져요. 대부분의 기능은 내보내기가 올바른 로그를 만드는 데 특별한 처리가 필요 없거든요. 그리고 훅 로그에 그 테이블이 declares delta features not known to this exporter라고 기록돼요. 이는 테이블이 깨졌다는 신호가 아니라, 그 기능이 아직 검증되지 않았다는 신호로 받아들이세요.
어떤 기능을 사용 중인지 알려주시면 검토 후 지원 목록에 추가할게요.
제약 사항
-
내보내기는 AWS S3와 Azure ADLS Gen2에서만 지원돼요.
-
lakeFS의 Delta Lake 테이블은 단일 라이터(single writer)를 지원해요. Delta Lake 제약 사항에 설명되어 있어요.
더 읽을거리
각 Databricks 단계의 스크린샷이 포함된 동일 설정 워크스루는 lakeFS 블로그의 lakeFS + Unity Catalog Integration: Step-by-Step Tutorial을 참고하세요. Data Catalog Exports 가이드는 이 메커니즘을 공유하는 다른 내보내기 도구를 다루고, Lua 훅 레퍼런스는 훅 자체를 문서화하고 있어요.
더 알아보기 (Learn more)
공식 문서의 자세한 내용은 https://docs.lakefs.io/integrations/unity-catalog/에서 확인하실 수 있어요.