Tech

Parquet 파일 구조와 관련 설정 값 이해하기

알 수 없는 사용자 2022. 5. 23. 11:41

Parquet는 아파치 하둡 에코 시스템에서 사용하는 칼럼 기반의 데이터 저장 형식입니다.

여러 인코딩과 압축을 지원함으로써 다양한 프로젝트에서 인기리에 사용 중입니다.

Parquet을 효과적으로 사용하는 데에는 많은 요소들을 살펴봐야 하지만, 이 번 글에서는 파일 구조와 관련된 설정값을 같이 보면서 Parquet을 보다 심층적으로 이해하는 시간을 가져보도록 하겠습니다.

 

Parquet의 파일 구조

Parquet은 Header, Blocks, Footer. 이 세 형식을 가집니다.

Header와 Footer는 Meta 정보이며, 각 1개씩만 존재합니다.

Blocks는 List 형식이며 여기에 실제 N 개의 데이터가 저장됩니다.

이를 정리해 보면 아래와 같습니다.

Parquet 파일 구조에서 Block-Row Group-Colunn Chunk-Page를 중점으로 다룹니다.

 

  • Header(Metadata) : Parquet 형식임을 나타내는 4byte의 Magic Number 정보가 있습니다.("PAR1")
  • Blocks(N 개의 블록) : 1개의 Block은 List<Row Group> -> List<Column Chunk> -> List<Page>의 형식을 취합니다.
  • Footer(Metadata) : 여러 메타 정보가 저장됩니다.

 

 

Header에 들어가는 내용은 '이 파일은 Parquet 형식이다'라는 시그너쳐이므로 크게 중요하지는 않습니다.

그러므로 이 글에서는 BlockFooter를 자세하게 살펴보도록 하겠습니다.

 

Block

  • Block(HDFS block)이란?
    • 여기서 말하는 Block은 HDFS의 Block을 의미합니다.
    • HDFS Block은 보통 기본 값 128MB로 된 저장 덩어리(chunk) 단위입니다.
    • 파일은 읽고 쓰는데 많은 비용이 들어가므로, 한 번에 읽을 때 단위를 정해 적절하게 이를 관리합니다.
    • 이 Block 사이즈가 작으면 읽기 수행 시 진입점이 많아지게 되면서 물리적으로 스캔 시 많은 시간이 소요됩니다. 
  • Row Group 
    • 1개의 Block에는 여러 개의 논리적인 Row 데이터가 저장됩니다.
    • 1 Block은 List<Row Group>와 같은 뜻입니다.
    • Block 사이즈를 조절하는 것은 Row Group의 개수를 조절하는 것과 같습니다.
    • HDFS Block이 하둡에서 처리하는 기본단위이므로 MapReduce 성능에 영향을 미칩니다.
  • Column Chunk
    • 1 Row Group은 N 개의 Column Chunk를 갖습니다.
    • Column Chunk는 특정 열의 데이터들로 구성되어 있습니다.
    • 파일에서 연속적으로 저장되므로 칼럼 단위로 데이터를 조회할 경우 높은 조회 성능을 냅니다.
    • Column Chunk는 I/O의 순차적 읽고 쓰기를 고려해 설계되었습니다.

Row 기반과 Column기반의 데이터가 저장되는 모습 https://www.slideshare.net/larsgeorge/parquet-data-io-philadelphia-2013

  •  Page
    • 1 Column Chunk는 N 개의 Page를 갖습니다.
    • Page는 Parquet 읽고 쓰는 최소 단위로서 압축 및 인코딩 성능이 고려되어 설계되었습니다.
    • 대부분의 Column은 남자/여자와 같은 동일한 값의 연속입니다.
    • 그렇기 때문에 이를 저장할 때 압축을 하게 되면 적은 용량으로 성능상의 이점을 볼 수 있습니다.
    • Page 단위로 압축되기 때문에, Page의 압축률을 높이는 것이 성능 향상의 주요 포인트입니다.
      이를 위해 Parquet에서는 Boolean을 제외한 대부분의 값을 Dictionary 인코딩을 사용합니다.
      https://dataninjago.com/2021/12/07/databricks-deep-dive-3-parquet-encoding/

      이렇게 사전을 통해 ProductA -> 0으로 치환하고 이 값을 다시 압축하면 높은 압축 효과를 보게 됩니다.

 

Footer

Parquet 파일 구조에서 Footer가 가장 중요한 이유는 파일을 저장할 때 Block의 Meta 정보를 Footer에 기록하기 때문입니다.

이 때 Footer에는 File Meta 1개와 N 개의 Block Meta(Row Group)가 기록됩니다.

struct FileMetaData {
  // File MetaData
  1: required i32 version
  2: required list<SchemaElement> schema;
  3: required i64 num_rows
  
  // Block MetaData
  4: required list<RowGroup> row_groups
}

이를 도식화하면 다음과 같습니다.

File Meta 정보와 N개의 Block 정보를 기록하고 있다.

 

Block Meta에는 각 block들의 위치(offset) 정보와 칼럼 정보 등이 기록되어 있으며, 이를 참조해 스토리지에 저장된 데이터(Block)를 스캔하게 됩니다.

 

 

 

 

지금까지 Parquet의 파일 구조를 개략적으로 알아보았습니다.

이제 이와 관련된 설정값을 보면서 의사 결정 포인트를 정리해 보겠습니다.

 

의사 결정 포인트

 

Property: parquet.block.size (기본값 128MB)

이 설정은 위에서 언급된 block(Row Group)의 사이즈를 정합니다.

block 크기가 크면 I/O 시에 많은 데이터를 가져오지만, 그와 함께 메모리 사용량도 늘어나게 됩니다.

한 block에 크기는 column의 값 분포도에 영향을 주므로 값이 큰 경우 정해진 사전 사이즈에서 소화를 못 시키실 수 있습니다.

정해진 사전 사이즈가 넘어가게 되면 사전을 만들지 않고 일반 텍스트로 그냥 저장되므로 성능에 큰 영향을 미칩니다.

 

Property: parquet.page.size (기본값 1MB)

이 설정은 페이지 크기를 설정합니다.

Page 크기는 압축률에 영향을 미칩니다.

Page 크기가 너무 작으면 압축 비용이 커져서 압축하는 의미가 없게 됩니다.

 

Property: parquet.dictionary.page.size (기본값 1MB)

사전의 크기를 설정합니다.

한 block의 칼럼 당 하나의 사전을 갖게 됩니다.

칼럼의 값이 중복이 적어서 정해진 사전 용 크기(1MB)를 넘어가게 되면 값을 그대로 저장하므로 성능 저하가 발생하게 됩니다.

 

Property: parquet.enable.dictionary (기본값 true)

사전 사용 여부를 설정하며, 기본 값은 true입니다.

위에서 언급한 대로, 사전의 크기를 넘어가는 경우 사용이 의미가 없기 때문에 UUID, 금액과 같이 사전을 만드는 의미가 없는 칼럼만 특별히 사용하지 않으려면 아래와 같이 설정합니다.

parquet.enable.dictionary#columnName=false

 

4개의 설정값을 살펴보면 모두 Column과 Page에 영향을 준다는 사실을 발견하게 됩니다.

중요한 것은 한 block의 Column 당 하나의 Dictionary를 사용하게 되므로 block의 사이즈를 줄이거나 늘려 보면서 적당한 사전의 크기를 찾는 것이 성능을 극대화하는 방법이라는 사실입니다.

 

 

이상 Parquet의 파일 구조와 저장 방식에 따른 설정값 변경으로 성능을 최대로 끌어올리는 방법에 대해 알아보았습니다.