-
Parquet 알아보기BigData/Parquet 2023. 1. 5. 01:34728x90반응형
- 목차
관련된 글
https://westlife0615.tistory.com/333
https://westlife0615.tistory.com/445
소개.
먼저 Parquet 은 "파케이" 라고 발음됩니다.
항상 어떻게 발음하는지는 몰랐던 경험이 있어서 발음법이 어떻게 되는지 작성하면서 블로그 글을 시작하려고 합니다.
Parquet 는 파일 포맷의 하나입니다.
파일를 분류하는 기준이 여러가지 존재하는데요.
1. 바이너리 파일 vs 텍스트 파일
2. 칼럼 베이스 파일 vs 로우 베이스 파일
이 두가지 기준에 대해서 설명을 해보도록 하겠습니다.
왜냐하면 Parquet 는 위 2가지 관점에서 큰 의미를 가지는 파일입니다.
바이너리 파일 vs 텍스트 파일.
먼저, 텍스트 파일은 인간 친화적인 파일입니다.
즉, 사람이 쉽게 읽을 수 있는 모양의 파일을 뜻하죠.
일반적인 txt 파일이 가장 대표적이겠지만, 빅데이터를 담는 그릇으로써의 파일은 아닙니다.
데이터를 저장하는 텍스트 파일의 대표적인 케이스는 JSON 과 XML 입니다.
<JSON>{ "name" : "westlife", "age" : 30 }
<XML>
<?xml version="1.0" encoding="UTF-8" ?> <user> <name>westlife</name> <age>30</age> </user>
위 두가지 형태를 서로 치환가능한 형태로 작성해보았구요.
User 라는 데이터를 표현한 데이터입니다.
딱 보아도 문법만 이해한다면 쉽게 읽을 수 있는 데이터 포맷입니다.
이러한 파일은 텍스트 파일이라고 합니다.
반면, 바이너리 파일은 사람의 관점에서 가독성있는 파일이 아닙니다.
바이너리는 컴퓨터가 해독하기 쉬운 형태로 bit 로 구성되어 있습니다.
바이너리 파일의 대표적인 예시는 이미지나 동영상 파일 그리고 여러 프로그램의 실행 파일들입니다.
그리고 빅데이터 관점에서의 대표적인 케이스는 Parquet, Avro 등이 있는데요.
Avro 바이너리 파일의 내용을 한번 적어보겠습니다.
아래 예시는 어떤 Avro 파일을 hexdump 로 디코딩한 결과입니다.
사람의 눈으로 쉽게 해독할 수 없는 그럼 형태입니다.
hexdump test.avro 0000000 624f 016a 0a06 776f 656e 1872 6577 7473 0000010 696c 6566 3630 3531 6114 7276 2e6f 6f63 0000020 6564 0863 756e 6c6c 6116 7276 2e6f 6373 0000030 6568 616d 01e2 227b 616e 656d 7073 6361 0000040 2265 203a 6522 6178 706d 656c 612e 7276 0000050 226f 202c 7422 7079 2265 203a 7222 6365 0000060 726f 2264 202c 6e22 6d61 2265 203a 5522 0000070 6573 2272 202c 6622 6569 646c 2273 203a 0000080 7b5b 6e22 6d61 2265 203a 6e22 6c75 616c 0000090 6c62 2265 202c 7422 7079 2265 203a 6e22 00000a0 6c75 226c 5d7d 007d 5168 dc6c a5ed 16b8 00000b0 33a1 de93 98d6 bcec 8980 007a 5168 dc6c 00000c0 a5ed 16b8 33a1 de93 98d6 bcec
Parquet 는 위 Avro 파일처럼 바이너리 형식의 파일 포맷입니다.
이어질 내용에서 자세히 알아보도록 하겠습니다.
칼럼 베이스 파일 vs 로우 베이스 파일.
데이터를 관리하는 파일들은 칼럼 베이스인지 로우 베이스인지에 따라서 분류됩니다.
로우 베이스 파일에는 JSON, XML, CSV, Avro 들이 있습니다.
파일의 내용들 중, 데이터들이 보관된 모습을 보면 Row 단위로 줄지어져 있습니다.
<JSON>[ { "name": "westlife", "age": 30 }, { "name": "westlife1", "age": 31 }, { "name": "westlife2", "age": 32 }, { "name": "westlife3", "age": 33 } ]
<Avro File 의 일부분>00000000 4f 62 6a 01 06 0a 6f 77 6e 65 72 18 77 65 73 74 |Obj...owner.west| 00000010 6c 69 66 65 30 36 31 35 14 61 76 72 6f 2e 63 6f |life0615.avro.co| 00000020 64 65 63 08 6e 75 6c 6c 16 61 76 72 6f 2e 73 63 |dec.null.avro.sc| 00000030 68 65 6d 61 9e 02 7b 22 6e 61 6d 65 73 70 61 63 |hema..{"namespac| 00000040 65 22 3a 20 22 65 78 61 6d 70 6c 65 2e 61 76 72 |e": "example.avr| 00000050 6f 22 2c 20 22 74 79 70 65 22 3a 20 22 72 65 63 |o", "type": "rec| 00000060 6f 72 64 22 2c 20 22 6e 61 6d 65 22 3a 20 22 55 |ord", "name": "U| 00000070 73 65 72 22 2c 20 22 66 69 65 6c 64 73 22 3a 20 |ser", "fields": | 00000080 5b 7b 22 6e 61 6d 65 22 3a 20 22 6e 61 6d 65 22 |[{"name": "name"| 00000090 2c 20 22 74 79 70 65 22 3a 20 22 73 74 72 69 6e |, "type": "strin| 000000a0 67 22 7d 2c 20 7b 22 6e 61 6d 65 22 3a 20 22 61 |g"}, {"name": "a| 000000b0 67 65 22 2c 20 22 74 79 70 65 22 3a 20 22 69 6e |ge", "type": "in| 000000c0 74 22 7d 5d 7d 00 b7 bc a4 f7 87 42 1f a9 70 ab |t"}]}......B..p.| 000000d0 f6 a5 83 c2 63 a2 08 56 10 77 65 73 74 6c 69 66 |....c..V.westlif| 000000e0 65 3c 12 77 65 73 74 6c 69 66 65 31 3e 12 77 65 |e<.westlife1>.we| 000000f0 73 74 6c 69 66 65 32 40 12 77 65 73 74 6c 69 66 |stlife2@.westlif| 00000100 65 33 42 b7 bc a4 f7 87 42 1f a9 70 ab f6 a5 83 |e3B.....B..p....| 00000110 c2 63 a2 |.c.|
위 JSON 과 Avro 는 서로 호환되는 구조로 작성하였습니다.
JSON 파일은 Row 기준으로 JSON Object 들이 나열되어 저장되고,
Avro 또한 "westlife<.westlife1>.westlife2@.westlife3B.....B..p.....c." 와 같이 Row 의 값들이 나열되어 저장됩니다.
인코딩되어서 형태를 잘 파악할 순 없지만 Row-based 형식으로 저장되어 있네요.
칼럼 베이스의 파일은 로우 베이스 파일과 다르게 칼럼들을 기준으로 데이터가 저장됩니다.
칼럼 별로 데이터가 저장되는 영역 (또는 공간)이 달라집니다.
아래의 두 코드 예시는 Parquet 파일을 생성하는 코드와 parquet 파일의 내용입니다.import pandas as pd df = pd.DataFrame( data=[ ["Mark", "USA"], ["Kevin", "Canada"], ["Smith", "UK"], ["William", "Australia"] ], columns=["name", "country"] ) df.to_parquet("user.parquet", engine="pyarrow", index=False)
hexdump -C user.parquet 00000000 50 41 52 31 15 04 15 4a 15 4c 4c 15 08 15 00 12 |PAR1...J.LL.....| 00000010 00 00 25 40 04 00 00 00 4d 61 72 6b 05 00 00 00 |..%@....Mark....| 00000020 4b 65 76 69 6e 01 09 3c 53 6d 69 74 68 07 00 00 |Kevin..<Smith...| 00000030 00 57 69 6c 6c 69 61 6d 15 00 15 14 15 18 2c 15 |.William......,.| 00000040 08 15 10 15 06 15 06 1c 36 00 28 07 57 69 6c 6c |........6.(.Will| 00000050 69 61 6d 18 05 4b 65 76 69 6e 00 00 00 0a 24 02 |iam..Kevin....$.| 00000060 00 00 00 08 01 02 03 e4 00 26 d2 01 1c 15 0c 19 |.........&......| 00000070 35 00 06 10 19 18 04 6e 61 6d 65 15 02 16 08 16 |5......name.....| 00000080 c4 01 16 ca 01 26 70 26 08 1c 36 00 28 07 57 69 |.....&p&..6.(.Wi| 00000090 6c 6c 69 61 6d 18 05 4b 65 76 69 6e 00 19 2c 15 |lliam..Kevin..,.| 000000a0 04 15 00 15 02 00 15 00 15 10 15 02 00 00 00 15 |................| 000000b0 04 15 48 15 4a 4c 15 08 15 00 12 00 00 24 44 03 |..H.JL.......$D.| 000000c0 00 00 00 55 53 41 06 00 00 00 43 61 6e 61 64 61 |...USA....Canada| 000000d0 02 01 11 34 4b 09 00 00 00 41 75 73 74 72 61 6c |...4K....Austral| 000000e0 69 61 15 00 15 14 15 18 2c 15 08 15 10 15 06 15 |ia......,.......| 000000f0 06 1c 36 00 28 03 55 53 41 18 09 41 75 73 74 72 |..6.(.USA..Austr| 00000100 61 6c 69 61 00 00 00 0a 24 02 00 00 00 08 01 02 |alia....$.......| 00000110 03 e4 00 26 a6 04 1c 15 0c 19 35 00 06 10 19 18 |...&......5.....| 00000120 07 63 6f 75 6e 74 72 79 15 02 16 08 16 c2 01 16 |.country........| 00000130 c8 01 26 c4 03 26 de 02 1c 36 00 28 03 55 53 41 |..&..&...6.(.USA| 00000140 18 09 41 75 73 74 72 61 6c 69 61 00 19 2c 15 04 |..Australia..,..|
이어서 자세히 알아보겠지만,
위 Parquet 파일의 내용을 보면 Column 별로
name : Mark, Kevin ,Smith ,William
country : USA, Canada, UK, Australia
의 데이터가 나열되어 저장되있음을 알 수 있죠.Parquet 구조.
Parquet 파일의 내부 구조를 알아보려고 합니다.
크게 Header, Body, Footer 로 나뉩니다.
Header 는 다른 말로 File Metadata 라고 부르고,
Body 는 Row Group 이라는 영역으로 표현됩니다.
File Metadata (Header).
File Metadata 는 Parquet 파일의 헤더 영역입니다.
파일의 기본적인 정보를 담고 있습니다.
Parquet 파일의 Header 영역으로써 16 바이트의 영역을 할당받습니다.
- Magic Number
- File Format Version
- Metadata Offset
- Metadata Length
- Schema
Header - Magic Number.
Parquet 파일은 여타 파일들처럼 Magic Number 를 가집니다.
Java 의 ByteCode, Avro File 등은 파일이 시작하는 라인에 Magic Number 를 가집니다.
ByteCode 가 CAFEBABE 로 시작하고, Avro File 이 Obj/x01 로 시작하듯이
Parquet 또한 Magic Number 로 시작합니다.
Parquet 의 Magic Number 는 PAR1 이며, 이는 해당 파일이 Parquet 파일임을 증명합니다.
16진수로 50 41 52 31 로 표현됩니다.
Header - File Format Version.
Parquet File Format Version 은 말그대로 파일 포맷 버전인데요.
현재 (2023-10 기준) 에는 1.0, 2.4, 2.6 버전이 주로 사용됩니다.
File
아래 예시는 Parquet File Format Version 을 확인하는 코드입니다.
import pandas as pd import pyarrow as pa import pyarrow.parquet as pq data = { 'Name': ['Alice', 'Bob', 'Charlie'], 'Age': [25, 30, 22], 'City': ['New York', 'San Francisco', 'Los Angeles'] } df = pd.DataFrame(data) table = pa.Table.from_pandas(df) # Define the Parquet file schema parquet_schema = table.schema # Specify the file path for the Parquet file parquet_file = 'user.parquet' # Write the Arrow Table to a Parquet file with pq.ParquetWriter(parquet_file, parquet_schema, version='2.6') as writer: writer.write_table(table) print(f"Parquet file '{parquet_file}' created successfully.") import pyarrow.parquet as pq parquet_file_path = 'user.parquet' with pq.ParquetFile(parquet_file_path) as reader: file_metadata = reader.metadata schema = file_metadata.schema format_version = file_metadata.format_version print("Schema:") print(schema) print(f"format_version : {format_version}")
<실행 결과>format_version : 2.6
Header - Metadata offset & Length.
Parquet 파일은 Avro 의 Sync Marker 같은 섹션의 종료를 의미하는 수단이 없습니다.
"N 번째 라인부터 M 번째 라인까지가 헤더입니다." 라는 표기를 할 수 없는데,
Metadata 의 offset 과 length 로 이 문제를 해결합니다.
Metadata Offset 은 File Metadata 영역의 시작점을 의미합니다.
그리고 Metadata Length 는 File Metadata 영역의 길이를 의미합니다.
이 두 정보를 기반으로 File Metadata 영역의 시작과 끝을 파악할 수 있고,
Parquet Reader 라이브러리는 이 정보를 활용합니다.
Header - Summary Schema.
Parquet 파일은 Header 와 Footer 에 모두 Schema 정보가 존재합니다.
Header 의 Schema 는 요약된 Schema (Summary Schema) 정보를 가지고,
Footer 의 Schema 가 온전한 상태의 Schema 입니다.
Header 의 Schema 가 가지는 요약된 상태는 아래와 같습니다.
- Column Names
- Data Types
- Column Order
Summary Schema 가 필요한 이유는
Parquet 파일 조회시에 Serialized 된 데이터를 효율적으로 Deserialize 하기 위함입니다.
Row Group.
먼저 Row Group 의 구조부터 알아보도록 하겠습니다.
Row Group 의 구조는 아래와 같습니다.
Row Group 1 Column 1 Page 1 page 2 ... Column 2 Page 1 Page 2 ... ... Row Group 2 Column 1 Page 1 page 2 ... Column 2 Page 1 Page 2 ... ...
Row Group 은 Parquet 파일에서 실질적인 데이터가 저장되는 영역입니다.
하나의 Parquet 파일는 여러 개의 Row Group 들을 가질 수 있는데요.
이런 의미에서 Row Group 은 Row 들의 논리적인 단위라고 부르기도 합니다.
Parquet 파일은 Column Storage 이지만 Row 별로 큰 덩어리를 나누고,
그 Row 들을 Column 별로 저장합니다.
그래서 위의 Row Group 구조처럼 Row 들을 그룹짓게 됩니다.
Row Group 의 갯수와 사이즈를 직접적인 설정이 가능합니다.
아래의 예시는 Row Group 의 사이즈와 갯수를 설정하는 코드 예시입니다.
Row Group 의 사이즈를 1로 설정하였습니다.
그리고 3개의 데이터를 추가한 결과로 3개의 Row Group 이 생성됨을 확인할 수 있습니다.import pandas as pd import pyarrow as pa import pyarrow.parquet as pq data = { 'Name': ['Alice', 'Bob', 'Charlie'], 'Age': [25, 30, 22], 'City': ['New York', 'San Francisco', 'Los Angeles'] } df = pd.DataFrame(data) table = pa.Table.from_pandas(df) parquet_schema = table.schema parquet_file = 'user.parquet' # Write the Arrow Table to a Parquet file with pq.ParquetWriter(parquet_file, parquet_schema) as writer: writer.write_table(table, row_group_size=1) print(f"Parquet file '{parquet_file}' created successfully.") import pyarrow.parquet as pq parquet_file_path = 'user.parquet' file = pa.parquet.ParquetFile(parquet_file_path) with pq.ParquetFile(parquet_file_path) as reader: file_metadata = reader.metadata schema = file_metadata.schema num_row_groups = reader.num_row_groups print(f"num_row_groups : {num_row_groups}")
<실행 결과>num_row_groups : 3
Row Group 을 나눔으로써 얻을 수 있는 이점은
Parquet 데이터를 Row Group 별로 병렬 처리할 수 있습니다.Row Group -> Column.
Row Group 을 통해서 Row 들은 논리적으로 나뉘어집니다.
Parquet 파일의 설정을 통해서
"Row 100 개가 하나의 Row Group 을 구성하게 해줘." 라는 식의 설정을 거치면
100개의 Row 가 하나의 Row Group 을 구성합니다.
그리고 이 Row 들을 대상으로 Column 별로 나뉘게됩니다.
이렇게 나뉘어진 단위가 Row Group 의 Column 영역입니다.
예를 들어, Alice, Bob, Carol 에 해당하는 Row 가 3개 존재합니다.
그리고 Row Group 의 사이즈는 3개 이상이라고 가정하겠습니다.
그런 경우, Row Group 은 하나로 구성되고, Column 별로 value 들이 나열되어 저장됩니다.Rows -> {"name" : "Alice", "age" : 25} -> {"name" : "Bob", "age" : 30} -> {"name" : "Carol", "age" : 35} Row Group 1 Column 1 Alice,Bob,Carol Column2 25,30,35
Row Group 의 사이즈가 1인 경우에는 3개의 Row Group 이 생성됩니다.Rows -> {"name" : "Alice", "age" : 25} -> {"name" : "Bob", "age" : 30} -> {"name" : "Carol", "age" : 35} Row Group 1 Column 1 Alice Column2 25 Row Group 2 Column 1 Bob Column2 30 Row Group 3 Column 1 Carol Column2 35
Row Group -> Column -> Page.
Row Group 과 Column 까지 이해하셨다면, 이제 Page 라는 개념이 등장합니다.
Page 는 Column 영역을 구성하는 단위입니다.
Page 는 Column 의 value 들이 Sequential 하게 저장되는 실질적인 영역이구요.
아래 예시 코드와 같이 page 의 사이즈를 설정할 수 있습니다.import pyarrow as pa # Create a table table = pa.Table.from_pydict({ "name": ["Alice", "Bob", "Carol"], "age": [25, 30, 35] }) # Write the table to a Parquet file pa.parquet.write_table(table, "parquet_file.parquet", page_size=100000, compression="snappy")
만약, Row Group 의 사이즈가 3, Page 의 사이즈가 1인 경우,
아래와 같은 구성을 이룹니다.Rows -> {"name" : "Alice", "age" : 25} -> {"name" : "Bob", "age" : 30} -> {"name" : "Carol", "age" : 35} Row Group 1 Column 1 Page 1 Alice Page 2 Bob Page 3 Carol Column2 Page 1 25 Page 2 30 Page 3 35
<추후에 추가할 내용들>
- Footer
- Schema반응형'BigData > Parquet' 카테고리의 다른 글
[Parquet] Dictionary Encoding 알아보기 (0) 2024.03.24 [Apache Arrow] Pyarrow Table 알아보기 (0) 2024.03.08 Parquet Reader 알아보기 (0) 2023.12.08