스파크 쿼리
스파크 쿼리 (Spark Queries)
이 문서에서는 스파크에서 아이스버그 테이블을 쿼리하는 방법을 알려드릴게요. SQL과 DataFrame으로 데이터를 읽고, SQL 함수, 타임 트래블, 증분 읽기, 그리고 다양한 메타데이터 테이블을 사용한 테이블 검사 방법까지 폭넓게 살펴볼게요.
출처: 문서
본문
스파크에서 아이스버그를 사용하려면 먼저 스파크 카탈로그를 구성해요. 아이스버그는 데이터 소스와 카탈로그 구현에 아파치 스파크의 DataSourceV2 API를 사용해요.
SQL로 쿼리하기 (Querying with SQL)
스파크에서 테이블은 카탈로그 이름을 포함하는 식별자를 사용해요.
SELECT * FROM prod.db.table; -- catalog: prod, namespace: db, table: table
history, snapshots 같은 메타데이터 테이블은 아이스버그 테이블 이름을 네임스페이스로 사용할 수 있어요.
예를 들어 prod.db.table의 files 메타데이터 테이블에서 읽으려면:
SELECT * FROM prod.db.table.files;
| content | file_path | file_format | spec_id | partition | record_count | file_size_in_bytes | column_sizes | value_counts | null_value_counts | nan_value_counts | lower_bounds | upper_bounds | key_metadata | split_offsets | equality_ids | sort_order_id |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | s3:/.../table/data/00000-3-8d6d60e8-d427-4809-bcf0-f5d45a4aad96.parquet | PARQUET | 0 | {1999-01-01, 01} | 1 | 597 | [1 -> 90, 2 -> 62] | [1 -> 1, 2 -> 1] | [1 -> 0, 2 -> 0] | [] | [1 -> , 2 -> c] | [1 -> , 2 -> c] | null | [4] | null | null |
| 0 | s3:/.../table/data/00001-4-8d6d60e8-d427-4809-bcf0-f5d45a4aad96.parquet | PARQUET | 0 | {1999-01-01, 02} | 1 | 597 | [1 -> 90, 2 -> 62] | [1 -> 1, 2 -> 1] | [1 -> 0, 2 -> 0] | [] | [1 -> , 2 -> b] | [1 -> , 2 -> b] | null | [4] | null | null |
| 0 | s3:/.../table/data/00002-5-8d6d60e8-d427-4809-bcf0-f5d45a4aad96.parquet | PARQUET | 0 | {1999-01-01, 03} | 1 | 597 | [1 -> 90, 2 -> 62] | [1 -> 1, 2 -> 1] | [1 -> 0, 2 -> 0] | [] | [1 -> , 2 -> a] | [1 -> , 2 -> a] | null | [4] | null | null |
스파크 SQL 함수 (Spark SQL functions)
아이스버그는 각 아이스버그 카탈로그에 SQL 함수를 추가해서, 쿼리에서 변환 결과를 검사하고 아이스버그 파티션 변환과 일치하는 필터를 작성할 수 있게 해줘요. 이 함수들은 아이스버그 카탈로그를 통해서만 사용 가능해요. 스파크 내장 카탈로그에는 등록되지 않아요.
참고 (Note)
4.2.0 이전의 스파크는 세션 카탈로그에서 V2Function을 지원하지 않아요. spark_catalog이 org.apache.iceberg.spark.SparkSessionCatalog로 구성돼 있어도 SELECT spark_catalog.system.bucket(16, id) 같은 쿼리는 실패해요. 자세한 내용은 SPARK-54760 (apache/spark#53531)을 참고해주세요. 아이스버그 SQL 함수를 사용하려면 org.apache.iceberg.spark.SparkCatalog로 구성된 카탈로그를 통해 호출해주세요.
이 함수들을 호출할 때는 system 네임스페이스를 사용해요.
SELECT system.iceberg_version();
SELECT system.bucket(16, id), system.days(ts)
FROM prod.db.table;
카탈로그를 명시하고 싶다면 함수에 카탈로그 이름으로 한정을 붙여요.
SELECT prod.system.bucket(16, id)
FROM prod.db.table;
정보 (Info)
PARTITIONED BY 절은 year(ts)와 month(ts) 같은 단수 변환 표현식을 사용해요. SQL 함수는 system.years(ts)와 system.months(ts)를 사용해요.
| 함수 | 지원 입력 타입 | 반환 타입 | 예시 |
|---|---|---|---|
| system.iceberg_version() | 없음 | string | SELECT system.iceberg_version(); |
| system.bucket(numBuckets, col) | date, tinyint, smallint, int, bigint, timestamp, timestamp_ntz, decimal, string, binary | int | SELECT system.bucket(16, id) FROM prod.db.table; |
| system.years(col) | date, timestamp, timestamp_ntz | int | SELECT system.years(ts) FROM prod.db.table; |
| system.months(col) | date, timestamp, timestamp_ntz | int | SELECT system.months(ts) FROM prod.db.table; |
| system.days(col) | date, timestamp, timestamp_ntz | date | SELECT * FROM prod.db.table WHERE system.days(ts) = date('2025-03-01'); |
| system.hours(col) | timestamp, timestamp_ntz | int | SELECT system.hours(ts) FROM prod.db.table; |
| system.truncate(width, col) | tinyint, smallint, int, bigint, decimal, string, binary | col과 같은 타입 | SELECT system.truncate(4, data) FROM prod.db.table; |
모든 변환 함수는 NULL 입력에 대해 NULL을 반환해요.
system.years, system.months, system.days, system.hours는 추출된 달력 필드가 아니라 아이스버그 변환 값을 반환해요. 예를 들어 system.years는 1970-01-01 이후의 연수를, system.months는 1970-01 이후의 개월 수를, system.hours는 1970-01-01T00:00 이후의 시간 수를 반환해요. system.days는 입력의 날짜 부분을 나타내는 date 값을 반환해요(date 입력은 같은 값을 그대로 반환하고, timestamp는 시간 컴포넌트를 버려요).
숫자 입력의 경우 system.truncate(width, col)은 width의 가장 가까운 배수로 내림해요. string과 binary 입력의 경우 처음 width 문자나 바이트를 유지해요.
이 함수들은 아이스버그가 값을 어떻게 변환하는지 검사하거나, 파티션 변환과 정렬되는 쿼리 및 행 수준 연산의 필터를 작성할 때 특히 유용해요.
SQL로 타임 트래블 쿼리 (Time travel Queries with SQL)
스파크는 TIMESTAMP AS OF 또는 VERSION AS OF 절을 사용한 SQL 쿼리에서 타임 트래블을 지원해요. VERSION AS OF 절은 long 스냅샷 ID나 string 브랜치·태그 이름을 포함할 수 있어요.
정보 (Info)
참고: 브랜치나 태그의 이름이 스냅샷 ID와 같다면, 타임 트래블에 선택되는 스냅샷은 주어진 스냅샷 ID를 가진 스냅샷이에요. 예를 들어 '1'이라는 이름의 태그가 ID 2인 스냅샷을 참조하는 경우를 생각해볼게요. version 여행 절이 VERSION AS OF '1'이면 ID 1인 스냅샷으로 타임 트래블해요. 이것이 원하지 않는다면 'snapshot-1' 같은 잘 정의된 프리픽스로 태그나 브랜치 이름을 바꿔주세요.
-- time travel to October 26, 1986 at 01:21:00
SELECT * FROM prod.db.table TIMESTAMP AS OF '1986-10-26 01:21:00';
-- time travel to snapshot with id 10963874102873L
SELECT * FROM prod.db.table VERSION AS OF 10963874102873;
-- time travel to the head snapshot of audit-branch
SELECT * FROM prod.db.table VERSION AS OF 'audit-branch';
-- time travel to the snapshot referenced by the tag historical-snapshot
SELECT * FROM prod.db.table VERSION AS OF 'historical-snapshot';
또한 FOR SYSTEM_TIME AS OF와 FOR SYSTEM_VERSION AS OF 절도 지원돼요.
SELECT * FROM prod.db.table FOR SYSTEM_TIME AS OF '1986-10-26 01:21:00';
SELECT * FROM prod.db.table FOR SYSTEM_VERSION AS OF 10963874102873;
SELECT * FROM prod.db.table FOR SYSTEM_VERSION AS OF 'audit-branch';
SELECT * FROM prod.db.table FOR SYSTEM_VERSION AS OF 'historical-snapshot';
타임스탬프는 Unix 타임스탬프(초)로도 제공될 수 있어요.
-- timestamp in seconds
SELECT * FROM prod.db.table TIMESTAMP AS OF 499162860;
SELECT * FROM prod.db.table FOR SYSTEM_TIME AS OF 499162860;
브랜치나 태그는 메타데이터 테이블과 비슷한 branch_
SELECT * FROM prod.db.table.`branch_audit-branch`;
SELECT * FROM prod.db.table.`tag_historical-snapshot`;
("-"가 있는 식별자는 유효하지 않으므로 백쿼트로 이스케이프해야 해요.)
브랜치나 태그가 있는 식별자는 VERSION AS OF와 함께 사용할 수 없다는 점에 유의해주세요.
타임 트래블 쿼리의 스키마 선택 (Schema selection in time travel queries)
이전 섹션에서 언급한 다양한 타임 트래블 쿼리는 스냅샷의 스키마나 테이블의 스키마를 사용할 수 있어요.
-- time travel to October 26, 1986 at 01:21:00 -> uses the snapshot's schema
SELECT * FROM prod.db.table TIMESTAMP AS OF '1986-10-26 01:21:00';
-- time travel to snapshot with id 10963874102873L -> uses the snapshot's schema
SELECT * FROM prod.db.table VERSION AS OF 10963874102873;
-- time travel to the head of audit-branch -> uses the table's schema
SELECT * FROM prod.db.table VERSION AS OF 'audit-branch';
SELECT * FROM prod.db.table.`branch_audit-branch`;
-- time travel to the snapshot referenced by the tag historical-snapshot -> uses the snapshot's schema
SELECT * FROM prod.db.table VERSION AS OF 'historical-snapshot';
SELECT * FROM prod.db.table.`tag_historical-snapshot`;
예를 들어 시간이 지나며 스키마를 진화시키는 테이블을 고려하고, 각 타입의 타임 트래블 쿼리가 어떻게 스키마를 선택하는지 살펴볼게요.
-- snapshot S1: initial schema (id, status)
CREATE TABLE prod.db.orders (
id BIGINT,
status STRING
) USING iceberg;
INSERT INTO prod.db.orders VALUES (1, 'NEW'), (2, 'PAID');
-- record snapshot S1's snapshot_id and committed_at timestamp
-- e.g. snapshot_id = 101, committed_at = '2025-01-01 10:00:00'
-- snapshot S2: add a new column "total" and write new data
ALTER TABLE prod.db.orders ADD COLUMN total DOUBLE;
INSERT INTO prod.db.orders VALUES (3, 'PAID', 100.0);
-- now S2 is the current snapshot with schema (id, status, total)
특정 스냅샷이나 타임스탬프를 선택하는 타임 트래블 쿼리는 스냅샷의 스키마를 사용해요.
-- uses the snapshot schema of S1: columns (id, status)
SELECT * FROM prod.db.orders VERSION AS OF 101;
SELECT * FROM prod.db.orders TIMESTAMP AS OF '2025-01-01 10:00:00';
두 쿼리 모두에서 결과는 id와 status만 있어요. total 컬럼은 S1 스키마에 없고, 현재 테이블 스키마에 total이 포함돼 있어도 보이지 않아요.
이제 S1을 참조하는 브랜치와 태그를 만들어요.
-- branch "audit_branch" points to snapshot S1
ALTER TABLE prod.db.orders CREATE BRANCH audit_branch AS OF VERSION 101;
-- tag "first_load" also points to snapshot S1
ALTER TABLE prod.db.orders CREATE TAG first_load AS OF VERSION 101;
브랜치를 쿼리하면 스파크는 테이블의 현재 스키마를 사용해요.
-- uses the table schema: columns (id, status, total)
SELECT * FROM prod.db.orders VERSION AS OF 'audit_branch';
-- equivalent identifier form
SELECT * FROM prod.db.orders.`branch_audit_branch`;
이 쿼리들에서 결과에는 (id, status, total) 컬럼이 있어요. S1의 행에 대해서는 total이 NULL로 반환되는데, 그 행들이 쓰여졌을 당시에는 그 컬럼이 존재하지 않았기 때문이에요.
태그를 쿼리하면 스파크는 태그가 참조하는 스냅샷의 스키마를 사용해요.
-- uses the snapshot schema of S1: columns (id, status)
SELECT * FROM prod.db.orders VERSION AS OF 'first_load';
-- equivalent identifier form
SELECT * FROM prod.db.orders.`tag_first_load`;
이 쿼리들은 id와 status만 반환해요. 태그는 특정 스냅샷에 바인딩되고 그 스냅샷의 스키마를 사용하므로, 테이블의 현재 스키마가 진화했어도 그렇기 때문이에요.
DataFrame으로 쿼리하기 (Querying with DataFrames)
테이블을 DataFrame으로 로드하려면 table을 사용해요.
val df = spark.table("prod.db.table")
DataFrameReader와 카탈로그 (Catalogs with DataFrameReader)
경로와 테이블 이름은 스파크의 DataFrameReader 인터페이스로 로드할 수 있어요. 테이블이 로드되는 방식은 식별자가 어떻게 지정되는지에 따라 달라져요. spark.read.format("iceberg").load(table) 또는 spark.table(table)을 사용할 때 table 변수는 아래 나열된 여러 형식을 취할 수 있어요.
- file:///path/to/table: 주어진 경로에서 HadoopTable 로드
- tablename: currentCatalog.currentNamespace.tablename 로드
- catalog.tablename: 지정된 카탈로그에서 tablename 로드
- namespace.tablename: current catalog에서 namespace.tablename 로드
- catalog.namespace.tablename: 지정된 카탈로그에서 namespace.tablename 로드
- namespace1.namespace2.tablename: current catalog에서 namespace1.namespace2.tablename 로드
위 목록은 우선순위 순서예요. 예를 들어 일치하는 카탈로그는 어떤 네임스페이스 해석보다 우선해요.
DataFrame으로 타임 트래블 쿼리 (Time travel Queries with DataFrame)
DataFrame API에서 특정 테이블 스냅샷이나 특정 시점의 스냅샷을 선택하려면 아이스버그는 네 가지 스파크 읽기 옵션을 지원해요.
- snapshot-id: 특정 테이블 스냅샷 선택
- as-of-timestamp: 밀리초 단위 타임스탬프에서 현재 스냅샷 선택
- branch: 지정된 브랜치의 헤드 스냅샷 선택. 현재 브랜치는 as-of-timestamp와 결합할 수 없다는 점에 유의해주세요.
- tag: 지정된 태그와 연결된 스냅샷 선택. 태그는 as-of-timestamp와 결합할 수 없어요.
// time travel to October 26, 1986 at 01:21:00
spark.read
.option("as-of-timestamp", "499162860000")
.format("iceberg")
.load("path/to/table")
// time travel to snapshot with ID 10963874102873L
spark.read
.option("snapshot-id", 10963874102873L)
.format("iceberg")
.load("path/to/table")
// time travel to tag historical-snapshot
spark.read
.option(SparkReadOptions.TAG, "historical-snapshot")
.format("iceberg")
.load("path/to/table")
// time travel to the head snapshot of audit-branch
spark.read
.option(SparkReadOptions.BRANCH, "audit-branch")
.format("iceberg")
.load("path/to/table")
증분 읽기 (Incremental read)
추가된 데이터를 증분으로 읽으려면:
- start-snapshot-id: 증분 스캔에 사용되는 시작 스냅샷 ID (배타적).
- end-snapshot-id: 증분 스캔에 사용되는 끝 스냅샷 ID (포함). 선택 사항이에요. 생략하면 현재 스냅샷이 기본값이 돼요.
// get the data added after start-snapshot-id (10963874102873L) until end-snapshot-id (63874143573109L)
spark.read
.format("iceberg")
.option("start-snapshot-id", "10963874102873")
.option("end-snapshot-id", "63874143573109")
.load("path/to/table")
정보 (Info)
현재 append 연산의 데이터만 가져와요. replace, overwrite, delete 연산은 지원할 수 없어요. 증분 읽기는 V1과 V2 format-version 모두에서 동작해요. 증분 읽기는 스파크의 SQL 구문에서 지원되지 않아요.
테이블 검사 (Inspecting tables)
테이블의 이력, 스냅샷 및 기타 메타데이터를 검사하기 위해 아이스버그는 메타데이터 테이블을 지원해요.
메타데이터 테이블은 원래 테이블 이름 뒤에 메타데이터 테이블 이름을 붙여서 식별해요. 예를 들어 db.table의 이력은 db.table.history로 읽어요.
이력 (History)
테이블 이력을 보려면:
SELECT * FROM prod.db.table.history;
| made_current_at | snapshot_id | parent_id | is_current_ancestor |
|---|---|---|---|
| 2019-02-08 03:29:51.215 | 5781947118336215154 | NULL | true |
| 2019-02-08 03:47:55.948 | 5179299526185056830 | 5781947118336215154 | true |
| 2019-02-09 16:24:30.13 | 296410040247533544 | 5179299526185056830 | false |
| 2019-02-09 16:32:47.336 | 2999875608062437330 | 5179299526185056830 | true |
| 2019-02-09 19:42:03.919 | 8924558786060583479 | 2999875608062437330 | true |
| 2019-02-09 19:49:16.343 | 6536733823181975045 | 8924558786060583479 | true |
정보 (Info)
이것은 롤백된 커밋을 보여줘요. 예시에는 같은 부모를 가진 두 스냅샷이 있고, 하나는 현재 테이블 상태의 조상이 아니에요.
메타데이터 로그 항목 (Metadata Log Entries)
테이블 메타데이터 로그 항목을 보려면:
SELECT * from prod.db.table.metadata_log_entries;
| timestamp | file | latest_snapshot_id | latest_schema_id | latest_sequence_number |
|---|---|---|---|---|
| 2022-07-28 10:43:52.93 | s3://.../table/metadata/00000-9441e604-b3c2-498a-a45a-6320e8ab9006.metadata.json | null | null | null |
| 2022-07-28 10:43:57.487 | s3://.../table/metadata/00001-f30823df-b745-4a0a-b293-7532e0c99986.metadata.json | 170260833677645300 | 0 | 1 |
| 2022-07-28 10:43:58.25 | s3://.../table/metadata/00002-2cc2837a-02dc-4687-acc1-b4d86ea486f4.metadata.json | 958906493976709774 | 0 | 2 |
스냅샷 (Snapshots)
테이블의 유효한 스냅샷을 보려면:
SELECT * FROM prod.db.table.snapshots;
| committed_at | snapshot_id | parent_id | operation | manifest_list | summary |
|---|---|---|---|---|---|
| 2019-02-08 03:29:51.215 | 57897183625154 | null | append | s3://.../table/metadata/snap-57897183625154-1.avro | { added-records -> 2478404, total-records -> 2478404, added-data-files -> 438, total-data-files -> 438, spark.app.id -> application_1520379288616_155055 } |
스냅샷을 테이블 이력과 조인할 수도 있어요. 예를 들어 이 쿼리는 각 스냅샷을 쓴 애플리케이션 ID와 함께 테이블 이력을 보여줘요.
select
h.made_current_at,
s.operation,
h.snapshot_id,
h.is_current_ancestor,
s.summary['spark.app.id']
from prod.db.table.history h
join prod.db.table.snapshots s
on h.snapshot_id = s.snapshot_id
order by made_current_at;
| made_current_at | operation | snapshot_id | is_current_ancestor | summary[spark.app.id] |
|---|---|---|---|---|
| 2019-02-08 03:29:51.215 | append | 57897183625154 | true | application_1520379288616_155055 |
| 2019-02-09 16:24:30.13 | delete | 29641004024753 | false | application_1520379288616_151109 |
| 2019-02-09 16:32:47.336 | append | 57897183625154 | true | application_1520379288616_155055 |
| 2019-02-08 03:47:55.948 | overwrite | 51792995261850 | true | application_1520379288616_152431 |
엔트리 (Entries)
데이터와 삭제 파일 모두에 대한 테이블의 현재 매니페스트 엔트리를 보여줘요.
SELECT * FROM prod.db.table.entries;
| status | snapshot_id | sequence_number | file_sequence_number | data_file | readable_metrics |
|---|---|---|---|---|---|
| 2 | 57897183625154 | 0 | 0 | {"content":0,"file_path":"s3:/.../table/data/00047-25-833044d0-127b-415c-b874-038a4f978c29-00612.parquet","file_format":"PARQUET","spec_id":0,"record_count":15,"file_size_in_bytes":473,"column_sizes":{1:103},"value_counts":{1:15},"null_value_counts":{1:0},"nan_value_counts":{},"lower_bounds":{1:},"upper_bounds":{1:},"key_metadata":null,"split_offsets":[4],"equality_ids":null,"sort_order_id":0} | {"c1":{"column_size":103,"value_count":15,"null_value_count":0,"nan_value_count":null,"lower_bound":1,"upper_bound":3}} |
참고:
- entries 테이블의 컬럼은 매니페스트 엔트리 필드에 대응해요: status: 추가·삭제 추적에 사용, snapshot_id: 파일이 추가되거나 제거된 스냅샷의 ID, sequence_number: 스냅샷 간 변경 순서에 사용, file_sequence_number: 파일이 언제 추가됐는지 나타냄, data_file: 데이터 파일에 대한 메타데이터를 포함하는 struct (데이터 파일 필드 참조)
- readable_metrics 컬럼은 data_file 컬럼에서 파생된 확장된 컬럼 수준 통계의 사람이 읽기 쉬운 맵을 제공해서, 파일 수준 통계를 검사하고 디버깅하기 더 쉽게 해줘요.
파일 (Files)
테이블의 현재 파일을 보려면:
SELECT * FROM prod.db.table.files;
| content | file_path | file_format | spec_id | record_count | file_size_in_bytes | column_sizes | value_counts | null_value_counts | nan_value_counts | lower_bounds | upper_bounds | key_metadata | split_offsets | equality_ids | sort_order_id | readable_metrics |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | s3:/.../table/data/00042-3-a9aa8b24-20bc-4d56-93b0-6b7675782bb5-00001.parquet | PARQUET | 0 | 1 | 652 | {1:52,2:48} | {1:1,2:1} | {1:0,2:0} | {} | {1:,2:d} | {1:,2:d} | NULL | [4] | NULL | 0 | {"data":{"column_size":48,"value_count":1,"null_value_count":0,"nan_value_count":null,"lower_bound":"d","upper_bound":"d"},"id":{"column_size":52,"value_count":1,"null_value_count":0,"nan_value_count":null,"lower_bound":1,"upper_bound":1}} |
| 0 | s3:/.../table/data/00000-0-f9709213-22ca-4196-8733-5cb15d2afeb9-00001.parquet | PARQUET | 0 | 1 | 643 | {1:46,2:48} | {1:1,2:1} | {1:0,2:0} | {} | {1:,2:a} | {1:,2:a} | NULL | [4] | NULL | 0 | {"data":{"column_size":48,"value_count":1,"null_value_count":0,"nan_value_count":null,"lower_bound":"a","upper_bound":"a"},"id":{"column_size":46,"value_count":1,"null_value_count":0,"nan_value_count":null,"lower_bound":1,"upper_bound":1}} |
| 0 | s3:/.../table/data/00001-1-f9709213-22ca-4196-8733-5cb15d2afeb9-00001.parquet | PARQUET | 0 | 2 | 644 | {1:49,2:51} | {1:2,2:2} | {1:0,2:0} | {} | {1:,2:b} | {1:,2:c} | NULL | [4] | NULL | 0 | {"data":{"column_size":51,"value_count":2,"null_value_count":0,"nan_value_count":null,"lower_bound":"b","upper_bound":"c"},"id":{"column_size":49,"value_count":2,"null_value_count":0,"nan_value_count":null,"lower_bound":2,"upper_bound":3}} |
| 1 | s3:/.../table/data/00081-4-a9aa8b24-20bc-4d56-93b0-6b7675782bb5-00001-deletes.parquet | PARQUET | 0 | 1 | 1560 | {2147483545:46,2147483546:152} | {2147483545:1,2147483546:1} | {2147483545:0,2147483546:0} | {} | {2147483545:,2147483546:s3:/.../table/data/00000-0-f9709213-22ca-4196-8733-5cb15d2afeb9-00001.parquet} | {2147483545:,2147483546:s3:/.../table/data/00000-0-f9709213-22ca-4196-8733-5cb15d2afeb9-00001.parquet} | NULL | [4] | NULL | NULL | {"data":{"column_size":null,"value_count":null,"null_value_count":null,"nan_value_count":null,"lower_bound":null,"upper_bound":null},"id":{"column_size":null,"value_count":null,"null_value_count":null,"nan_value_count":null,"lower_bound":null,"upper_bound":null}} |
| 2 | s3:/.../table/data/00047-25-833044d0-127b-415c-b874-038a4f978c29-00612.parquet | PARQUET | 0 | 126506 | 28613985 | {100:135377,101:11314} | {100:126506,101:126506} | {100:105434,101:11} | {} | {100:0,101:17} | {100:404455227527,101:23} | NULL | NULL | [1] | 0 | {"id":{"column_size":135377,"value_count":126506,"null_value_count":105434,"nan_value_count":null,"lower_bound":0,"upper_bound":404455227527},"data":{"column_size":11314,"value_count":126506,"null_value_count": 11,"nan_value_count":null,"lower_bound":17,"upper_bound":23}} |
정보 (Info)
content는 데이터 파일이 저장하는 콘텐츠의 종류를 나타내요:
- 0 - 데이터 (Data)
- 1 - 포지션 삭제 (Position Deletes)
- 2 - 이퀄리티 삭제 (Equality Deletes)
데이터 파일만 또는 삭제 파일만 보려면 각각 prod.db.table.data_files와 prod.db.table.delete_files를 쿼리해요. 모든 추적된 스냅샷에 걸친 모든 파일, 데이터 파일, 삭제 파일을 보려면 각각 prod.db.table.all_files, prod.db.table.all_data_files, prod.db.table.all_delete_files를 쿼리해요.
매니페스트 (Manifests)
테이블의 현재 파일 매니페스트를 보려면:
SELECT * FROM prod.db.table.manifests;
| content | path | length | partition_spec_id | added_snapshot_id | added_data_files_count | existing_data_files_count | deleted_data_files_count | added_delete_files_count | existing_delete_files_count | deleted_delete_files_count | partition_summaries |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | s3://.../table/metadata/45b5290b-ee61-4788-b324-b1e2735c0e10-m0.avro | 4479 | 0 | 6668963634911763636 | 8 | 0 | 0 | 0 | 0 | 0 | [[false,null,2019-05-13,2019-05-15]] |
참고:
- manifests 테이블의 partition_summaries 컬럼 안의 필드는 매니페스트 리스트 안의 field_summary struct에 해당하며, 순서는 contains_null contains_nan lower_bound upper_bound예요.
- contains_nan은 null을 반환할 수 있는데, 이는 파일 메타데이터에서 이 정보를 사용할 수 없다는 뜻이에요. 이는 보통 contains_nan이 채워지지 않는 V1 테이블에서 읽을 때 발생해요.
파티션 (Partitions)
테이블의 현재 파티션을 보려면:
SELECT * FROM prod.db.table.partitions;
| partition | spec_id | record_count | file_count | total_data_file_size_in_bytes | position_delete_record_count | position_delete_file_count | equality_delete_record_count | equality_delete_file_count | last_updated_at(μs) | last_updated_snapshot_id |
|---|---|---|---|---|---|---|---|---|---|---|
| {20211001, 11} | 0 | 1 | 1 | 100 | 2 | 1 | 0 | 0 | 1633086034192000 | 9205185327307503337 |
| {20211002, 11} | 0 | 4 | 3 | 500 | 1 | 1 | 0 | 0 | 1633172537358000 | 867027598972211003 |
| {20211001, 10} | 0 | 7 | 4 | 700 | 0 | 0 | 0 | 0 | 1633082598716000 | 3280122546965981531 |
| {20211002, 10} | 0 | 3 | 2 | 400 | 0 | 0 | 1 | 1 | 1633169159489000 | 6941468797545315876 |
참고:
- 파티셔닝되지 않은 테이블의 경우 partitions 테이블에는 partition과 spec_id 필드가 없어요.
- partitions 메타데이터 테이블은 현재 스냅샷에 데이터 파일이나 삭제 파일이 있는 파티션을 보여줘요. 하지만 삭제 파일은 적용되지 않으므로, 어떤 경우에는 모든 데이터 행이 삭제 파일로 삭제 표시돼도 파티션이 보일 수 있어요.
포지션 삭제 파일 (Positional Delete Files)
테이블의 현재 스냅샷에서 모든 포지션 삭제 파일을 보려면:
SELECT * from prod.db.table.position_deletes;
| file_path | pos | row | partition | spec_id | delete_file_path |
|---|---|---|---|---|---|
| s3:/.../table/data/00042-3-a9aa8b24-20bc-4d56-93b0-6b7675782bb5-00001.parquet | 1 | 0 | {20211001, 11} | 0 | s3:/.../table/data/00191-1933-25e9f2f3-d863-4a69-a5e1-f9aeeebe60bb-00001-deletes.parquet |
모든 메타데이터 테이블 (All Metadata Tables)
이 테이블들은 현재 스냅샷에 특화된 메타데이터 테이블들의 합집합이고, 모든 스냅샷에 걸친 메타데이터를 반환해요.
위험 (Danger)
"all" 메타데이터 테이블은 메타데이터 파일이 둘 이상의 테이블 스냅샷에 속할 수 있으므로 데이터 파일이나 매니페스트 파일당 한 행 이상을 만들 수 있어요.
모든 데이터 파일 (All Data Files)
테이블의 모든 데이터 파일과 각 파일의 메타데이터를 보려면:
SELECT * FROM prod.db.table.all_data_files;
| content | file_path | file_format | spec_id | partition | record_count | file_size_in_bytes | column_sizes | value_counts | null_value_counts | nan_value_counts | lower_bounds | upper_bounds | key_metadata | split_offsets | equality_ids | sort_order_id | readable_metrics |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | s3://.../dt=20210102/00000-0-756e2512-49ae-45bb-aae3-c0ca475e7879-00001.parquet | PARQUET | 0 | {20210102} | 14 | 2444 | {1 -> 94, 2 -> 17} | {1 -> 14, 2 -> 14} | {1 -> 0, 2 -> 0} | {} | {1 -> 1, 2 -> 20210102} | {1 -> 2, 2 -> 20210102} | null | [4] | null | 0 | {"id":{"column_size":94,"value_count":14,"null_value_count":0,"nan_value_count":null,"lower_bound":1,"upper_bound":2},"data":{"column_size":17,"value_count":14,"null_value_count": 0,"nan_value_count":null,"lower_bound":20210102,"upper_bound":20210102}} |
| 0 | s3://.../dt=20210103/00000-0-26222098-032f-472b-8ea5-651a55b21210-00001.parquet | PARQUET | 0 | {20210103} | 14 | 2444 | {1 -> 94, 2 -> 17} | {1 -> 14, 2 -> 14} | {1 -> 0, 2 -> 0} | {} | {1 -> 1, 2 -> 20210103} | {1 -> 3, 2 -> 20210103} | null | [4] | null | 0 | {"id":{"column_size":94,"value_count":14,"null_value_count":0,"nan_value_count":null,"lower_bound":1,"upper_bound":3},"data":{"column_size":17,"value_count":14,"null_value_count": 0,"nan_value_count":null,"lower_bound":20210103,"upper_bound":20210103}} |
| 0 | s3://.../dt=20210104/00000-0-a3bb1927-88eb-4f1c-bc6e-19076b0d952e-00001.parquet | PARQUET | 0 | {20210104} | 14 | 2444 | {1 -> 94, 2 -> 17} | {1 -> 14, 2 -> 14} | {1 -> 0, 2 -> 0} | {} | {1 -> 1, 2 -> 20210104} | {1 -> 3, 2 -> 20210104} | null | [4] | null | 0 | {"id":{"column_size":94,"value_count":14,"null_value_count":0,"nan_value_count":null,"lower_bound":1,"upper_bound":3},"data":{"column_size":17,"value_count":14,"null_value_count": 0,"nan_value_count":null,"lower_bound":20210104,"upper_bound":20210104}} |
모든 삭제 파일 (All Delete Files)
모든 스냅샷에서 테이블의 삭제 파일과 각 파일의 메타데이터를 보려면:
SELECT * FROM prod.db.table.all_delete_files;
| content | file_path | file_format | spec_id | partition | record_count | file_size_in_bytes | column_sizes | value_counts | null_value_counts | nan_value_counts | lower_bounds | upper_bounds | key_metadata | split_offsets | equality_ids | sort_order_id | readable_metrics |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | s3:/.../table/data/00081-4-a9aa8b24-20bc-4d56-93b0-6b7675782bb5-00001-deletes.parquet | PARQUET | 0 | {20210102} | 1 | 1560 | {2147483545:46,2147483546:152} | {2147483545:1,2147483546:1} | {2147483545:0,2147483546:0} | {} | {2147483545:,2147483546:s3:/.../table/data/00000-0-f9709213-22ca-4196-8733-5cb15d2afeb9-00001.parquet} | {2147483545:,2147483546:s3:/.../table/data/00000-0-f9709213-22ca-4196-8733-5cb15d2afeb9-00001.parquet} | NULL | [4] | NULL | NULL | {"data":{"column_size":null,"value_count":null,"null_value_count":null,"nan_value_count":null,"lower_bound":null,"upper_bound":null},"id":{"column_size":null,"value_count":null,"null_value_count":null,"nan_value_count":null,"lower_bound":null,"upper_bound":null}} |
| 2 | s3:/.../table/data/00047-25-833044d0-127b-415c-b874-038a4f978c29-00612.parquet | PARQUET | 0 | {20210103} | 126506 | 28613985 | {100:135377,101:11314} | {100:126506,101:126506} | {100:105434,101:11} | {} | {100:0,101:17} | {100:404455227527,101:23} | NULL | NULL | [1] | 0 | {"id":{"column_size":135377,"value_count":126506,"null_value_count":105434,"nan_value_count":null,"lower_bound":0,"upper_bound":404455227527},"data":{"column_size":11314,"value_count":126506,"null_value_count": 11,"nan_value_count":null,"lower_bound":17,"upper_bound":23}} |
모든 엔트리 (All Entries)
데이터와 삭제 파일 모두에 대해 모든 스냅샷에서 테이블의 매니페스트 엔트리를 보여주려면:
SELECT * FROM prod.db.table.all_entries;
| status | snapshot_id | sequence_number | file_sequence_number | data_file | readable_metrics |
|---|---|---|---|---|---|
| 2 | 57897183625154 | 0 | 0 | {"content":0,"file_path":"s3:/.../table/data/00047-25-833044d0-127b-415c-b874-038a4f978c29-00612.parquet","file_format":"PARQUET","spec_id":0,"record_count":15,"file_size_in_bytes":473,"column_sizes":{1:103},"value_counts":{1:15},"null_value_counts":{1:0},"nan_value_counts":{},"lower_bounds":{1:},"upper_bounds":{1:},"key_metadata":null,"split_offsets":[4],"equality_ids":null,"sort_order_id":0} | {"c1":{"column_size":103,"value_count":15,"null_value_count":0,"nan_value_count":null,"lower_bound":1,"upper_bound":3}} |
모든 매니페스트 (All Manifests)
테이블의 모든 매니페스트 파일을 보려면:
SELECT * FROM prod.db.table.all_manifests;
| content | path | length | partition_spec_id | added_snapshot_id | added_data_files_count | existing_data_files_count | deleted_data_files_count | added_delete_files_count | existing_delete_files_count | deleted_delete_files_count | partition_summaries | reference_snapshot_id |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | s3://.../metadata/a85f78c5-3222-4b37-b7e4-faf944425d48-m0.avro | 6376 | 0 | 6272782676904868561 | 2 | 0 | 0 | 0 | 0 | 0 | [{false, false, 20210101, 20210101}] | 57897183625154 |
참고:
- manifests 테이블의 partition_summaries 컬럼 안의 필드는 매니페스트 리스트 안의 field_summary struct에 해당하며, 순서는 contains_null contains_nan lower_bound upper_bound예요.
- contains_nan은 null을 반환할 수 있는데, 이는 파일 메타데이터에서 이 정보를 사용할 수 없다는 뜻이에요. 이는 보통 contains_nan이 채워지지 않는 V1 테이블에서 읽을 때 발생해요.
참조 (References)
테이블의 알려진 스냅샷 참조를 보려면:
SELECT * FROM prod.db.table.refs;
| name | type | snapshot_id | max_reference_age_in_ms | min_snapshots_to_keep | max_snapshot_age_in_ms |
|---|---|---|---|---|---|
| main | BRANCH | 4686954189838128572 | 10 | 20 | 30 |
| testTag | TAG | 4686954189838128572 | 10 | null | null |
DataFrame으로 검사 (Inspecting with DataFrames)
메타데이터 테이블은 DataFrameReader API로 로드할 수 있어요.
// named metastore table
spark.read.format("iceberg").load("db.table.files")
// Hadoop path table
spark.read.format("iceberg").load("hdfs://nn:8020/path/to/table#files")
메타데이터 테이블로 타임 트래블 (Time Travel with Metadata Tables)
타임 트래블 기능으로 테이블의 메타데이터를 검사하려면:
-- get the table's file manifests at timestamp Sep 20, 2021 08:00:00
SELECT * FROM prod.db.table.manifests TIMESTAMP AS OF '2021-09-20 08:00:00';
-- get the table's partitions with snapshot id 10963874102873L
SELECT * FROM prod.db.table.partitions VERSION AS OF 10963874102873;
메타데이터 테이블은 DataFrameReader API로도 타임 트래블을 사용해서 검사할 수 있어요.
// load the table's file metadata at snapshot-id 10963874102873 as DataFrame
spark.read.format("iceberg").option("snapshot-id", 10963874102873L).load("db.table.files")