ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Parquet 알아보기
    BigData/Parquet 2023. 1. 5. 01:34
    728x90
    반응형

    - 목차

     
     
     

    관련된 글

    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..<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 라고 부르고,
    BodyRow 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
Designed by Tistory.