-
[Parquet] Dictionary Encoding 알아보기BigData/Parquet 2024. 3. 24. 10:26728x90반응형
- 목차
들어가며.
이번 글에서는 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, False
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 입니다.
표현상 가장 큰 자리수를 가지는 4가 3자리를 가지므로 이를 기준으로 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()
아래의 출력 결과는 생성된 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.parquet
반응형'BigData > Parquet' 카테고리의 다른 글
[Apache Arrow] Pyarrow Table 알아보기 (0) 2024.03.08 Parquet Reader 알아보기 (0) 2023.12.08 Parquet 알아보기 (0) 2023.01.05