일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- spark streaming
- Schema Registry
- s3
- 컬럼 기반
- Data Engineer
- Data engineering
- 델타레이크
- 대용량 처리
- 카프카 구축
- 레드시프트
- 스파크
- spark
- 데이터 엔지니어
- 카프카
- 데이터 웨어하우스
- kafka rest api
- airflow
- Parquet
- 스파크 스트리밍
- Data Warehouse
- MySQL
- AWS
- 데이터
- Zookeeper
- Redshift
- delta lake
- kafka
- docker
- 에어플로우
- 데이터 엔지니어링
- Today
- Total
데이터 엔지니어 기술 블로그
[Apache Parquet] 공식 문서로 파케이 이해하기 본문
개발 동기
우리는 어떤 하둡 에코시스템에도 사용할 수 있고 압축과 컬럼 기반 데이터 표현의 이점을 만들기 위해 Parquet를 개발했습니다. Parquet는 처음부터 중첩된(nested) 데이터 구조를 위해 개발되었으며, Dremel의 논문에 작성된 record shredding and assembly algorithm을 사용합니다. 우리는 이 접근 방식이 중첩된 name space를 단순하게 붙이는 것보다 좋다고 믿고 있습니다.
https://github.com/julienledem/redelm/wiki/The-striping-and-assembly-algorithms-from-the-Dremel-paper
Parquet는 효율적인 압축과 인코딩 체계를 지원하도록 개발되었습니다. 여러 프로젝트에서 데이터에 올바른 압축과 인코딩 체계를 적용할 때 성능에 미치는 영향이 입증되었습니다. Parquet를 사용하면 컬럼 레벨에서 압축 체계를 지정할 수 있으며, 미래에 더 많은 인코딩이 추가될 것을 대비할 수 있습니다.
Parquet는 누구든지 사용할 수 있도록 개발되었습니다. 하둡 에코시스템은 데이터 프로세싱 프레임워크가 충분합니다. 우리는 편애하는 것은 좋아하지 않습니다. 우리는 그것이 효과적이라고 믿고 있고, 잘 구현된 컬럼 기반 스토리지 기반은 확장 비용과 어려운 종속성을 설정해야하는 비용 없이 모든 프레임워크에서 유용해야한다고 믿고 있습니다.
Modules
parquet-format 프로젝트는 Parquet 파일을 올바르게 읽기 위해 필요한 Thrift metadata 정의와 포맷 사양이 포함되어 있습니다.
https://github.com/apache/parquet-format
parquet-mr 프로젝트는 java 기반 유틸리티이며 여러 서브모듈이 포함되어 있습니다. 중첩된 컬럼 기반 데이터 스트림을 읽고 쓰기 위한 코어 컴포넌트들을 구현하고, core를 parquet format에 매핑하고, 하둡 Input/Output Formats, Pig loaders와 상호작용합니다.
parquet-cpp 프로젝트는 Parquet 파일을 읽고 쓰기 위한 C++ 라이브러리입니다.
parquet-rs 프로젝트는 Parquet 파일을 읽고 쓰기 위한 Rust 라이브러리입니다.
Building
- Java 리소스는 mvn package를 통해 빌드할 수 있으며, 현재 안정화 버전은 Maven Central에서 사용할 수 있습니다.
- C++ thrift 리소스는 make를 통해 생성할 수 있습니다.
- Thrift는 thrift에서 지원되는 모든 언어로 코드가 생성될 수 있습니다.
용어 사전
Block(hdfs block): hdfs 블록을 의미하며 Parquet를 설명할 때에도 의미는 변하지 않습니다. Parquet는 hdfs 위에서도 잘 동작하도록 설계되었습니다.
File: 파일에 대한 metadata를 포함하는 hdfs 파일입니다. 실제 데이터를 포함할 필요는 없습니다.
Row group: 데이터를 논리적으로 행으로 수평분할합니다. row group에 대해 물리적인 구조로 나뉘어졌다고 보장할 수 없습니다. row group은 dataset의 각 column에 대한 column chunk로 구성됩니다.
Column chunk: 특정 column에 대한 청크입니다. 청크는 특정 row group에 있으며 파일에서 연속적으로 있다고 보장할 수 있습니다.
Page: Column chunk는 page로 나뉩니다. page는 압축 및 인코딩 측면에서 개념적으로 분할할 수 없는 단위입니다. 인터리브되는(interleaved) 컬럼 청크에 여러 페이지 타입이 있을 수 있습니다.
Interleave: 읽고 쓸 때 성능을 높히기 위해 데이터가 서로 인접하지 않도록 배열하는 방법이다.
계층적으로, file은 하나 이상의 row group으로 구성됩니다. row group은 정확히 컬럼당 하나씩 column chunk가 포함됩니다. column chunk는 하나 이상의 page가 포함됩니다.
병렬화 단위(Unit of parallelization)
각 단계별 병렬처리를 어떤 단위로 하는지에 대한 이야기이다.
- MapReduce - File/Row Group
- IO - Column chunk
- Encoding/Compression - Page
파일 포맷(File format)
thrift 정의를 함께 읽으면 이해하기 쉽습니다.
https://github.com/apache/parquet-format/blob/master/src/main/thrift/parquet.thrift
4-byte magic number "PAR1"
<Column 1 Chunk 1 + Column Metadata>
<Column 2 Chunk 1 + Column Metadata>
...
<Column N Chunk 1 + Column Metadata>
<Column 1 Chunk 2 + Column Metadata>
<Column 2 Chunk 2 + Column Metadata>
...
<Column N Chunk 2 + Column Metadata>
...
<Column 1 Chunk M + Column Metadata>
<Column 2 Chunk M + Column Metadata>
...
<Column N Chunk M + Column Metadata>
File Metadata
4-byte length in bytes of file metadata
4-byte magic number "PAR1"
파케이 파일을 실제로 열어보면 PAR1이 처음과 끝에 실제로 있다.
위 예시에서는 N개의 column이 테이블에 있고 M개의 row group이 있습니다. 파일 메타데이터는 모든 컬럼 메타데이터의 시작 위치를 포함하고 있습니다. 메타데이터에 포함된 자세한 내용은 parquet.thrift 파일에서 볼 수 있습니다. 메타데이터는 single pass writing을 허용하기 위해 data 다음에 기록됩니다. Reader는 관심 있는 모든 column chunk를 찾기 위해서 먼저 파일 메타데이터를 읽습니다. 그리고 순차적으로 column chunk를 읽습니다.
왼쪽: parquet 파일의 위쪽 부분, 오른쪽: parquet 파일의 아래쪽 부분
Metadata
메타데이터 종류: 파일 메타데이터, 컬럼(청크) 메타데이터, 페이지 헤더 메타데이터
모든 thrift structure는 TCompactProtocol을 사용하여 직렬화됩니다.
Types
파일 포맷에서 지원하는 타입들은 디스크 스토리지에 미치는 영향을 가능하면 최소화하도록 설계되었습니다. 예를 들면 16-bit int 스토리지 포맷은 지원되지 않습니다. 왜냐하면 16-bit int는 32-bit int로 처리되기 때문입니다. 이런 방식은 포맷에 대한 Reader와 Writer를 구현하는 복잡성을 줄여줍니다.
지원되는 타입들
BOOLEAN: 1 bit boolean
INT32: 32 bit signed ints
INT64: 64 bit signed ints
INT96: 96 bit signed ints
FLOAT: IEEE 32-bit floating point values
DOUBLE: IEEE 64-bit floating point values
BYTE_ARRAY: arbitrarily long byte arrays.
Logical Types
논리 타입은 타입들을 확장할 때 사용됩니다. parquet는 primitive type을 해석하는 방법을 지정하여 저장될 수 있습니다. 이것은 primitive type을 최소한으로 유지하고 parquet의 효율적인 인코딩을 재사용합니다. 예를 들면 string은 UTF8 annotation이 있는 byte array로 저장이 됩니다. annotation은 데이터를 추가로 decode하고 해석하는 방법을 정의합니다. annotation은 파일 메타데이터에 ConvertedType으로 저장됩니다. 이 내용은 LogicalTypes.md에 문서화되어있습니다.
https://github.com/apache/parquet-format/blob/master/LogicalTypes.md
Nested Encoding
중첩 컬럼을 인코딩하기 위해 Parquet는 정의 레벨과 반복 레벨이 있는 Dremel encoding을 사용합니다. 정의 레벨은 컬럼 경로(path)에 정의된 optional field의 수를 지정합니다. 반복 레벨은 경로에서 값이 반복되는 반복 필드를 지정합니다. 최대 정의와 반복 레벨은 스키마에서 계산됩니다. 예를 들면 얼마나 많이 중첩이 되었는지에 대한 값입니다. 이것은 레벨을 저장하는 데 필요한 최대 비트 수를 정의합니다. 레벨들은 컬럼에 있는 모든 값에 대해 계산됩니다.
레벨에 대한 두 개의 인코딩은 BITPACKED와 RLE를 지원합니다. 이제는 RLE만 BITPACKED를 대체해서 사용됩니다.
Nulls
널은 정의 레벨에서 run-length로 인코딩됩니다. NULL 값은 데이터에서 인코딩되지 않습니다. 예를 들면 중첩되지 않은 스키마에서 1000개의 NULL이 있는 컬럼은 정의 레벨에서 run-length(0, 1000 times)로 인코딩 됩니다.
Data Pages
데이터 페이지의 경우 page header에 다음 3개의 정보가 연속적으로 인코딩됩니다.
- 정의 레벨 데이터
- 반복 레벨 데이터
- 인코딩된 값
헤더에 지정된 크기는 3개를 모두 결합한 것입니다. 데이터 페이지의 데이터는 항상 필요합니다. 정의 레벨과 반복 레벨은 스키마 정의에 따라 옵션입니다. 만약 컬럼이 중첩되지 않는다면 반복 레벨은 항상 1이기 때문에 인코딩하지 않습니다. required 데이터인 경우 정의 레벨은 스킵합니다. 예를 들면 컬럼이 중첩되지 않고 required인 경우에는 페이지에 있는 데이터는 인코딩된 값만 있습니다.
지원되는 모든 인코딩은 Encodings.md에 있습니다
https://github.com/apache/parquet-format/blob/master/Encodings.md
Column chunks
컬럼 청크는 연속적으로 쓰여진 페이지로 구성됩니다. 페이지는 공통 헤더를 공유하고 그것으로 Reader는 관심없는 페이지는 스킵합니다. 페이지의 데이터는 header를 따르며 압축되거나 인코딩 될 수 있습니다. 압축 및 인코딩은 페이지의 메타데이터에 쓰여있습니다.
Checksumming
데이터 페이지는 개별적으로 checksum될 수 있습니다. 이것은 HDFS 파일 레벨에서 비활성화여 단일 행 조회를 더 잘 지원할 수 있습니다.
Error recovery
- 파일 메타데이터가 손상되면 파일은 손실됩니다.
- 만약 컬럼 메타데이터가 손상되면 컬럼 청크를 잃습니다. 그러나 이 컬럼의 다른 Row group에 있는 컬럼 청크는 괜찮습니다.
- 페이지 헤더가 손상되면 해당 청크의 남아있는 페이지가 손실됩니다.
- 페이지 내의 데이터가 손상되면 페이지가 손실됩니다.
- 파일은 더 작은 row group에 대해 더 탄력적(resilient)입니다.
잠재적 확장(Potential extension)
더 작은 row group의 경우, 가장 큰 문제는 파일 메타데이터를 끝에 배치하는 것입니다. 만약 파일 메타데이터를 작성하는 동안 에러가 발생하면, 지금까지 작성된 모든 데이터를 읽을 수 없게 됩니다.
이것은 N번째 row group마다 파일 메타데이터를 작성하여 해결할 수 있습니다. 각 파일 메타데이터는 누적되며 지금까지 작성한 모든 row group을 포함합니다. sync marker를 사용하는 rc, avro파일에서 사용되는 전략과 결합하면 reader는 부분적으로 작성된 파일을 복구할 수 있습니다.
메타데이터와 컬럼 데이터 분리
parquet format은 메타데이터와 데이터를 분리하도록 명시적으로 디자인되었습니다. 이를 통해 컬럼별로 여러 파일로 분류할 수 있으며, 단일 메타데이터 파일에서 여러 parquet file을 참조할 수도 있습니다.
구성 옵션
Row group size
크기가 더 큰 row group은 더 큰 column chunk를 허용하므로 더 많은 순차적인 IO를 가능하게 합니다. 더 큰 그룹은 write path에서 더 많은 버퍼링이 필요합니다. 우리는 512MB ~ 1GB의 큰 row group을 권장합니다. 전체 row group을 읽어야 할 수 있으므로 하나의 HDFS 블록에 완전히 맞도록 합니다. 그러므로 HDFS 블록 사이즈는 더 크게 설정해야 합니다.
최적화된 읽기 설정은 다음과 같습니다.
- row groups: 1GB
- HDFS block size: 1GB
- 1 HDFS block per HDFS file
Data page size
데이터 페이지는 분할 할 수 없으므로 데이터 페이지가 작을수록 세분화된 읽기가 가능합니다. 페이지 크기가 클수록 페이지 헤더가 적으므로 공간 오버헤드가 적고, 잠재적으로 parsing 오버헤드가 줄어듭니다.
Note: sequential scan의 경우 한 번에 페이지를 읽지 않을 수 있습니다. 이것은 IO 청크가 아닙니다. 페이지 크기는 8KB로 권장합니다.
확장성
호환되는 확장에 대한 형식
File Version: 파일 메타데이터는 버전을 포함합니다.
Encodings: 인코딩은 enum으로 쓰여있으며 이후에 더 추가될 수 있습니다.
Page types: 페이지 타입은 더 추가될 수 있고 이후에도 안전하게 스킵될 수 있습니다.
'데이터 엔지니어링' 카테고리의 다른 글
[Delta Lake] 데이터 레이크하우스: 소개 및 예시 (2) | 2022.02.07 |
---|---|
[Trino] 트리노(프레스토) 기본 개념 이해 및 사용하기 (1) | 2022.02.04 |
[Apache Thrift] 아파치 쓰리프트 간단하게 이해하기 (0) | 2022.01.20 |
[🔥Spark] java.lang.AssertionError: assertion failed: Concurrent update to the log. Multiple streaming jobs detected 해결방법 (0) | 2021.11.04 |
[🔥Spark] StreamingQueryException: Cannot find earliest offsets of Set(topic-name-0) (0) | 2021.11.02 |