본문 바로가기
컴퓨터 활용(한글, 오피스 등)/기타

Parquet 알아보기

by 3604 2025. 8. 22.
728x90

Parquet 알아보기

BigData/Parquet2023. 1. 5. 01:34

 

 

- 목차

소개.

바이너리 파일 vs 텍스트 파일.

칼럼 베이스 파일 vs 로우 베이스 파일.

Parquet 구조.

File Metadata (Header).

Header - Magic Number.

Header - File Format Version.

Header - Metadata offset & Length.

Header - Summary Schema.

Row Group.

Row Group -> Column.

Row Group -> Column -> Page.

 

관련된 글

https://westlife0615.tistory.com/333

Avro Serialization 알아보기.

- 목차 관련된 글 https://westlife0615.tistory.com/332 Avro File 알아보기 - 목차 소개. Avro 는 두가지 기능을 제공합니다. 첫번째는 직렬화 기능입니다. Avro Serialization Framework 로서 직렬화와 역직렬화를

westlife0615.tistory.com

https://westlife0615.tistory.com/445

Parquet Reader 알아보기

- 목차 소개. Parquet Reader 들이 어떠한 방식으로 Parquet 파일을 읽어들이는지 자세히 살펴보려고 합니다. Apache Arrow 기반의 라이브러리들은 많은 부분이 추상화되어 있어, Parquet 파일을 읽어들이는

westlife0615.tistory.com

소개.

먼저 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..

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라고 부르고,
BodyRow Group이라는 영역으로 표현됩니다.

File Metadata (Header).

File MetadataParquet 파일의 헤더 영역입니다.
파일의 기본적인 정보를 담고 있습니다.

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 로 시작합니다.
ParquetMagic 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

 

Parquet Reader 알아보기

BigData/Parquet2023. 12. 8. 05:37

 

 

- 목차

함께 보면 좋은 글.

소개.

Parquet File 생성.

Parquet Reader Parquet 메타데이터 읽기.

Parquet Reader Parquet Row Group 읽기.

Parquet Reader Row 읽기.

함께 보면 좋은 글.

https://westlife0615.tistory.com/50

Parquet 알아보기

- 목차 관련된 글 https://westlife0615.tistory.com/333 Avro Serialization 알아보기. - 목차 관련된 글 https://westlife0615.tistory.com/332 Avro File 알아보기 - 목차 소개. Avro 는 두가지 기능을 제공합니다. 첫번째는 직

westlife0615.tistory.com

소개.

Parquet Reader 들이 어떠한 방식으로 Parquet 파일을 읽어들이는지 자세히 살펴보려고 합니다.

Apache Arrow 기반의 라이브러리들은 많은 부분이 추상화되어 있어, Parquet 파일을 읽어들이는 방식이 명료하게 나타나지 않으며,

Python 관련 라이브러리들 또한 추상화된 부분이 많았습니다.

그래서 java org.apache.parquet parquet-hadoop 라이브러리를 사용할 예정입니다.

https://mvnrepository.com/artifact/org.apache.parquet/parquet-hadoop

Column 기반의 Parquet 파일이 어떠한 방식으로 저장되고 로드되는지 살펴보겠습니다.

Parquet File 생성.

먼저 테스트를 위한 Parquet File 을 생성합니다.

간단하게 생성하기 위해서 python 스크립트를 활용하겠습니다.

먼저 pyarrow 모듈을 설치합니다.

pip install pyarrowCopy

그리고 터미널을 열어 python3 인터프린터 내부로 진입합니다.

python3Copy

그리고 Parquet 파일을 생성합니다.

저는 /tmp/ 디렉토리 내부에 생성하였습니다.

import pandas as pd

import pyarrow as pa

import pyarrow.parquet as pq

 

# Create a Pandas DataFrame

data = {'name': [], 'city': []}

for i in range(0, 100000):

data["name"].append("name" + str(i))

data["city"].append("city" + str(i))

 

df = pd.DataFrame(data)

 

# Convert Pandas DataFrame to PyArrow Table

table = pa.Table.from_pandas(df)

 

# Specify the Parquet file path

parquet_file_path ='/tmp/user.parquet'

 

# Write the PyArrow Table to a Parquet file

pq.write_table(table, parquet_file_path, row_group_size=1000)

 

print(f'Parquet file written to: {parquet_file_path}')Copy

위 스크립트로 생성되는 user.parquet 파일은

name city 칼럼을 가집니다.

그리고 row_group_size 1000 으로 두어서, 100 개의 RowGroup 이 생성되도록 조작하였습니다.

생성된 Parquet 파일 구조는 아래와 같습니다.

100 개의 RowGroup 이 있구요.

RowGroup 내부에 Column 별로 데이터들이 존재하게 됩니다.

하나의 Column 내부에 저장되는 데이터들의 단위는 Page 라고하며,

RowGroup -> Column -> Page 로 이어지는 구성을 어떻게 읽어들이는지 알아보려고 합니다.

Parquet Reader Parquet 메타데이터 읽기.

저는 java 11 버전에서 테스트를 진행하고 있구요.

아래 모듈의 Parquet Reader 를 사용합니다.

// https://mvnrepository.com/artifact/org.apache.parquet/parquet-hadoop

implementation group: 'org.apache.parquet', name: 'parquet-hadoop', version: '1.13.1'Copy

package org.example;

 

import org.apache.hadoop.conf.Configuration;

import org.apache.hadoop.fs.Path;

import org.apache.parquet.hadoop.ParquetFileReader;

import org.apache.parquet.hadoop.metadata.BlockMetaData;

import org.apache.parquet.hadoop.metadata.ParquetMetadata;

import org.apache.parquet.schema.MessageType;

 

import java.io.IOException;

 

public class ParquetTest{

public static void main(String[] args)throws IOException {

Path path =new Path("/tmp/user.parquet");

ParquetFileReader parquetFileReader = ParquetFileReader.open(new Configuration(), path);

ParquetMetadata parquetMetadata = parquetFileReader.getFooter();

MessageType schema = parquetMetadata.getFileMetaData().getSchema();

 

for (int i =0; i < parquetMetadata.getBlocks().size(); i++) {

BlockMetaData metaData = parquetMetadata.getBlocks().get(i);

System.out.println("RowCount " + i + ": " + metaData.getRowCount());

System.out.println("RowIndexOffset " + i + ": " + metaData.getRowIndexOffset());

System.out.println("schema " + i + ": " + schema.toString());

}

 

parquetFileReader.close();

}

}ColumnDescriptor columnDescriptor = schema.getColumns().get(columnIndex);

PageReader pageReader = pageReadStore.getPageReader(columnDescriptor);

ColumnReader colReader = colReadStore.getColumnReader(columnDescriptor);

long valueSize = pageReader.getTotalValueCount();

for (int i =0; i < valueSize; i++) {

String value = colReader.getBinary().toStringUsingUTF8();

System.out.println(value);

colReader.consume();

}

}

 

}

parquetFileReader.close();

}

}

아래 결과는 생략된 버전이긴하지만,

Column row_group_size 만큼씩 읽어들입니다.

name0

name1

name2

name3

name4

name5

name6

name7

name8

name9

city0

city1

city2

city3

city4

city5

city6

city7Copy

 

[Parquet] Dictionary Encoding 알아보기

BigData/Parquet2024. 3. 24. 10:26

 

 

- 목차

들어가며.

Encoding ?

Lookup Table.

Cardinality.

Bit Packing.

Dictionary Encoding 실습.

들어가며.

이번 글에서는 Parquet Dictionary Encoding 에 대해서 알아보려고 합니다.

Dictionary Encoding 은 큰 길이의 String Number 와 같은 숫자로 표현할 수 있는 방식입니다.

예를 들어, "Python", "Java" "Golang" 과 같인 3개의 텍스트를 Unicode 로써 표현하게 되면

문자의 길이만큼 4byte * length 의 용량을 차지하게 되겠죠 ?

하지만 이를 나열 순서에 따라 0, 1, 2 로 표현하게 되면 데이터의 압축이 가능해집니다.

이러한 방식으로 문자열을 bit 레벨로 압축시켜 표현하는 방식을 Dictionary Encoding 이라고 부릅니다.

Encoding ?

인코딩이란 원본 데이터를 변형하는 것을 의미합니다.

다만 단순한 변형이 아니라"효율적으로 데이터를 저장하기 위해서"또는"안전하게 데이터를 관리하기 위해서"인코딩이라는 데이터 변형을 적용합니다.

, 저장의 효율성과 보안적인 측면에서 인코딩이 사용됩니다.

Parquet 과 같은 데이터 파일은 저장의 효율성을 위해서 인코딩을 적용합니다.

, 저장되는 파일의 사이즈를 최소화하는데에 초점을 둡니다.

Dictionary Encoding 또한 Parquet 파일에서 사용하는 인코딩 방식 중의 하나입니다.

Lookup Table.

Dictionary Encoding 은 내부적으로 Lookup Table 을 생성합니다.

""또는""의 값을 가지는성별과 같이Categorical Data를 대상으로 Dictionary Encoding 을 적용할 수 있습니다.

"" -> 0, "" -> 1 과 같이인코딩함으로써 4바이트를 요구하는 Unicode 를 단 1비트로 표현할 수 있게 됩니다.

이로써 1/32 가량의 데이터 압축을 할 수 있죠.

, 이러한 경우에 Lookup Table 을 생성해여"" -> 0, "" -> 1의 매칭되는 메타데이터를 기록해야합니다.

Cardinality.

Dictionary Encoding 을 적용하기 위해서는 Cardinality 의 개념을 아는 것이 중요합니다.

Cardinality 의 의미는"집합의 크기"라고 합니다.

우리가 수학의 "집합과 원소" 파트에서 배우길 집합은

"어떤 명확한 조건을 만족시키는 서로 다른 대상들의 모임"라고 배웁니다.

이를 데이터의 관점에서 보았을 때, Unique 한 데이터들의 모임이라고 볼 수 있을 것 같습니다.

, Cardinality Unique 한 데이터의 갯수를 뜻하구요.

성별과 같은 데이터는 남/녀 라는 두가지 데이터를 가지므로 Low Cardinality.

이름의 성씨과 같이 김////... 등 많은 수의 값을 가지는 경우에는 High Cardinality 라고 볼 수 있습니다.

Categorical Data 를 대상으로 Cardinality 를 적용하곤 합니다.

Cardinality Dictionary Encoding 을 서로 Trade-Off 관계를 갖습니다.

Dictionary Encoding 을 통해서 저장되는 파일의 사이즈가 줄어들긴 합니다.

하지만 High Cardinality 인 데이터는 Lookup Table 의 사이즈가 굉장히 커지는 단점을 가집니다.

그래서 이는 데이터의 중복이 없는 상태이기 때문에 Dictionary Encoding 과 같은 방식으로 인코딩하는 것이 오히려 낭비일 수 있습니다.

Bit Packing.

Bit Packing 이라는 Parquet 의 또 다른 Encoding 방식이 존재합니다.

이름처럼 Bit 단위로 Packing 을 적용합니다.

일반적인 데이터가 바이트 단위로 묶여지는 것과 달리 Bit 단위로 데이터가 관리됩니다.

Bit Packing 은 정보를 Byte 단위가 아닌 Bit 단위로 표현하는 인코딩/압축 방식입니다.

예를 들어, 아래와 같은 4개의 Bool 데이터는 프로그래밍 언어마다 다르지만 최소 1Byte 를 메모리에 차지합니다.

True, False, True, FalseCopy

Bool 자료형이 1 Byte 라고 한다면, 이는 00000001 (b) 또는 00000000 (b) 로 표현되겠죠.

그리고 이 4개의 Bool 타입 데이터가 디스크에 저장되면 4Bytes 를 차지하게 됩니다.

이를 Bit Packing 으로 인코딩하게 되면 4 bit 만으로 저장이 가능합니다.

1010 -> True, False, True, False 로 표현할 수 있기 때문이죠.

또 다른 예시로 1, 2, 3, 4 Bit Packing 방식으로 인코딩할 수 있습니다.

이들의 Binary 표현은 00000001, 00000010, 00000011, 00000100 와 같죠.

이를 3자리로 표현하면 1, 10, 11, 100 입니다.

표현상 가장 큰 자리수를 가지는 43자리를 가지므로 이를 기준으로 Bit Packing 을 수행합니다.

그래서 001010011100 -> 001, 010, 011, 100 으로 표현이 가능하죠.

이 또한 Parquet 에서 활용하는 인코딩 방식 중 하나입니다.

Dictionary Encoding 실습.

마지막으로 Dictionary Encoding 으로 인해서 파일이 압축되는 결과를 확인해보겠습니다.

2개의 Parquet 파일을 생성하구요.

각각 1천만개의 텍스트를 생성합니다.

첫번째 파일은"Andy"라는 텍스트를 1천만건 생성을 하구요. 이는 Dictionary Encoding 으로 큰 효율을 볼 수 있습니다.

두번째 파일은"And0" ~ "And9"까지의 데이터를 생성합니다.

10개의 Cardinality 를 가지게 됩니다.

from pyspark.sql import SparkSession, Row

from pyspark.sql.types import StructType, StringType, StructField

 

spark = SparkSession.builder.appName("parquet-dictionary-encoding").master("local[*]").config("spark.driver.bindAddress", "localhost").getOrCreate()

 

rows1 = [Row(name="Andy") for _ in range(10000000)]

schema = StructType([StructField("name", StringType(), False)])

df1 = spark.createDataFrame(rows1, schema)

df1.coalesce(1).write.mode("overwrite").parquet("/tmp/parquet1")

 

rows2 = [Row(name=f"And{i % 10}") for i in range(10000000)]

df2 = spark.createDataFrame(rows2, schema)

df2.coalesce(1).write.mode("overwrite").parquet("/tmp/parquet2")

 

spark.stop()Copy

아래의 출력 결과는 생성된 2개의 파일의 위치와 크기를 나타냅니다.

1번째 파일은 27K, 2번째 파일은 272K 의 크기를 가집니다.

, Low Cardinality 를 가질수록 큰 압축률을 가지게 됩니다.

[ 128] .

├── [ 192] parquet1

   ├── [ 0] _SUCCESS

   └── [ 27K] part-00000-a20e1c94-5607-4dbf-b457-09d2e9ec2468-c000.snappy.parquet

└── [ 192] parquet2

├── [ 0] _SUCCESS

└── [272K] part-00000-9bbfb13d-51eb-43d0-9223-61e0b768b196-c000.snappy.parquetCopy

출처: https://westlife0615.tistory.com/category/BigData

728x90