📊 데이터공학

Parquet

Apache Parquet

컬럼 지향 파일 포맷. 빅데이터 분석 표준.

상세 설명

Apache Parquet는 2013년 Cloudera와 Twitter가 공동 개발한 컬럼 지향(columnar) 파일 포맷입니다. 빅데이터 분석에서 사실상 표준으로, Spark, Pandas, Polars, DuckDB, 주요 클라우드 서비스(AWS Athena, Google BigQuery, Snowflake)가 네이티브로 지원합니다.

컬럼 기반 저장은 분석 쿼리에 최적입니다. SELECT name, salary FROM employees에서 age, department 컬럼은 읽지 않아 I/O가 감소합니다. 동일 타입 데이터가 연속 저장되어 딕셔너리 인코딩, RLE(Run-Length Encoding), delta encoding의 압축 효율이 극대화됩니다.

Row Group(기본 128MB) 단위로 데이터를 분할하고, 각 Row Group에 컬럼별 min/max 통계가 저장됩니다. WHERE age > 30 쿼리 시 통계를 확인해 조건에 맞지 않는 Row Group을 스킵합니다(Predicate Pushdown). 스키마가 파일에 내장되어 있어 별도 스키마 관리가 불필요합니다.

Python에서는 PyArrow 또는 fastparquet 라이브러리로 읽고 씁니다. Pandas DataFrame을 Parquet으로 저장하면 CSV 대비 5-10배 작고, 읽기 속도도 빠릅니다. 데이터 레이크 구축 시 JSON, CSV 대신 Parquet 사용이 권장됩니다.

코드 예제

import pandas as pd
import pyarrow as pa
import pyarrow.parquet as pq

# Pandas DataFrame 생성
df = pd.DataFrame({
    'user_id': range(1000000),
    'name': ['user_' + str(i) for i in range(1000000)],
    'age': [20 + i % 50 for i in range(1000000)],
    'signup_date': pd.date_range('2020-01-01', periods=1000000, freq='T'),
    'is_active': [i % 2 == 0 for i in range(1000000)]
})

# Parquet 파일로 저장 (기본 snappy 압축)
df.to_parquet('users.parquet', index=False)

# 압축 방식 지정 (gzip, snappy, zstd, lz4)
df.to_parquet('users_gzip.parquet', compression='gzip')

# 특정 컬럼만 읽기 (I/O 최소화)
df_subset = pd.read_parquet('users.parquet', columns=['user_id', 'name'])

# 필터 조건 적용 (Predicate Pushdown)
df_filtered = pd.read_parquet(
    'users.parquet',
    filters=[('age', '>=', 30), ('is_active', '==', True)]
)

# PyArrow로 직접 제어
table = pa.Table.from_pandas(df)

# 파티션 저장 (Hive 스타일)
pq.write_to_dataset(
    table,
    root_path='users_partitioned',
    partition_cols=['is_active']
)
# 결과: users_partitioned/is_active=true/part-0.parquet
#       users_partitioned/is_active=false/part-0.parquet

# 파티션된 데이터셋 읽기
dataset = pq.ParquetDataset('users_partitioned')
df_partitioned = dataset.read().to_pandas()

# 메타데이터 확인
parquet_file = pq.ParquetFile('users.parquet')
print(f"Row Groups: {parquet_file.metadata.num_row_groups}")
print(f"Columns: {parquet_file.schema.names}")
print(f"Total Rows: {parquet_file.metadata.num_rows}")

# 컬럼별 통계 확인
for i in range(parquet_file.metadata.num_row_groups):
    rg = parquet_file.metadata.row_group(i)
    for j in range(rg.num_columns):
        col = rg.column(j)
        if col.statistics:
            print(f"Column {col.path_in_schema}: "
                  f"min={col.statistics.min}, max={col.statistics.max}")

# Spark에서 Parquet 사용
# spark.read.parquet("users.parquet")
# df.write.partitionBy("is_active").parquet("output_path")

실무에서 이렇게 말해요

시니어: "데이터 레이크에 CSV로 쌓던 거 Parquet으로 바꾸니까 Athena 쿼리 비용이 80% 줄었어요. 컬럼 프루닝 덕분이에요."

주니어: "파티셔닝은 어떤 기준으로 하나요?"

시니어: "쿼리에서 자주 필터링하는 컬럼으로 해요. 날짜별로 파티션하면 WHERE date='2024-01-01' 쿼리가 해당 파티션만 스캔해요."

면접관: "데이터 파이프라인에서 파일 포맷 선택 기준은?"

지원자: "분석 워크로드는 Parquet, 로그 스트리밍은 Avro를 사용합니다. Parquet은 컬럼 프루닝과 Predicate Pushdown으로 분석 쿼리가 빠르고, Avro는 스키마 진화가 용이해 스트리밍에 적합합니다. 데이터 레이크는 Parquet으로 통일해서 Spark, Athena, Pandas에서 일관되게 접근합니다."

리뷰어: "Row Group 크기가 너무 작아요. 파일당 Row Group 수백 개면 메타데이터 오버헤드가 커져요."

개발자: "write 옵션에서 row_group_size를 늘리면 되나요?"

리뷰어: "네, 또는 작은 파일을 병합해서 저장하세요. 128MB 정도가 적당해요."

주의사항

더 배우기