---
type: WebPage
title: Parquet가 현대 데이터 엔지니어링을 지배하는 이유 [영상]
description: ""
resource: https://news.hada.io/topic?id=35843
tags: []
timestamp: 2026-10-23T12:39:05.696729Z
---

URL Source: https://news.hada.io/topic?id=35843

Published Time: 2026-10-23T09:32:01+09:00

Markdown Content:
*   **Parquet는 같은 열의 값을 모아 저장해, 대규모 데이터에서 필요한 열만 빠르게 읽는 파일 형식**. CSV처럼 모든 줄을 훑고 텍스트를 숫자로 변환하는 작업을 줄여 분석과 모델 학습에 유리
*   **택시 운행 약 1,000만 건에서 평균 요금을 구하는 실험**에서 CSV보다 파일 크기가 6분의 1이었고, 읽기 속도는 **27배 빨랐음**
*   **반복되는 값은 사전과 반복 횟수, 적은 비트로 표현**하고 그 결과를 다시 압축. 같은 데이터라도 압축 방식과 행 정렬 순서에 따라 파일 크기와 읽기 속도가 달라짐
*   **행 그룹별 최솟값과 최댓값으로 조건에 맞지 않는 구간을 건너뜀**. 자주 필터링하는 열로 정렬하면 효과가 커지고, 잘못된 값 몇 개가 범위를 넓혀 불필요한 읽기를 유발할 수도 있음
*   **한 행씩 조회하거나 추가**하는 서비스에는 행 지향 저장, 많은 행의 일부 열을 읽는 **분석과 모델 학습**에는 열 지향 저장이 적합. Parquet 선택도 실제 읽기와 쓰기 방식에 따라 판단할 문제

* * *

## CSV와 Parquet의 읽기 성능 차이

*   택시 운행 약 **1,000만 건**의 평균 요금을 계산하는 함수는 거의 1GB인 CSV를 읽을 때 0.2초 미만이 걸렸음 
    *   같은 데이터를 담은 **Parquet**로 입력만 바꾸면 파일 크기는 6분의 1, 실행 시간은 7ms가 되어 27배 빨라짐
    *   언어 모델이 이런 함수를 쉽게 작성할 수 있어도, 데이터를 어떤 형식으로 저장할지는 사람이 선택함

*   **행 지향 저장**은 CSV, JSON Lines, Avro처럼 테이블을 행별로 저장하고, **열 지향 저장**은 Parquet, ORC, Feather처럼 열별로 저장함 
    *   예제 CSV의 한 줄에는 운행 한 건의 값 19개가 쉼표로 구분되어 있음
    *   값의 길이가 일정하지 않아 요금의 위치도 매번 달라지므로, 읽기 도구는 모든 줄의 문자를 읽고 쉼표를 세어 요금을 찾아야 함
    *   Parquet는 요금을 한곳에 모아 저장하므로 다른 열을 건너뛰고 요금 열만 읽을 수 있음

*   대부분의 **집계, 필터링, 학습 작업**은 많은 행에서 일부 열만 읽음
*   비교한 세 도구 중 CSV 읽기가 가장 빠른 것은 **Polars**였음 
    *   DuckDB는 Polars보다 3배 이상, pandas는 10배 이상 오래 걸렸음
    *   각 도구의 Parquet 읽기를 가장 빠른 CSV 읽기와 비교했으며, pandas조차 Parquet의 요금 열을 Polars의 CSV 읽기보다 7배 빠르게 읽었음
    *   세 도구 모두에서 속도가 개선되어 **파일 형식 자체의 효과**를 확인함

## 텍스트 파싱과 자료형 추론을 피하는 구조

*   CSV는 모든 값을 **텍스트**로 저장함 
    *   17.7달러는 `1`, `7`, `.`, `7`의 네 문자이며, 읽을 때마다 이를 숫자로 변환하는 **파싱**이 필요함
    *   열의 자료형도 저장하지 않아 읽기 도구는 기본적으로 처음 읽은 값으로 자료형을 추론하며, 이 추측은 틀릴 수 있음

*   **PyArrow와 Polars**를 함께 사용하는 예제에서는 자료형 추론으로 읽기가 실패함 
    *   PyArrow는 Apache Arrow의 공식 Python 라이브러리로, 대규모 데이터 처리, 저장, 이동을 위한 빠르고 메모리 사용량이 적은 도구를 제공함
    *   PyArrow가 CSV에 1달러 수수료를 소수점 없이 `1`로 쓰고, Polars가 첫 100행의 정수만 확인하면 해당 열을 정수로 취급함
    *   약 8만 행 뒤에서 0.3달러 수수료를 만나면 읽기가 실패함

*   Parquet는 파일에 **열별 자료형**을 저장하므로 추론이 필요 없고, 텍스트를 파싱하지 않고 바이트에서 숫자를 읽음

## 푸터가 제공하는 열 위치와 자료형

*   Parquet 파일은 **`PAR1` 4바이트**, 열 데이터, 푸터, 푸터 길이, 마지막 `PAR1` 순서로 구성됨
*   **푸터(footer)** 에는 각 열의 이름, 자료형, 데이터 시작 위치가 들어 있음 
    *   쓰기 도구는 데이터를 쓴 뒤에야 각 열의 시작 위치를 알 수 있으므로 푸터를 마지막에 기록함
    *   읽기 도구는 먼저 파일의 마지막 8바이트를 읽고, 이어서 푸터를 읽음

*   예제의 **158MB 파일**에서 푸터는 26KB이며, 간결한 이진 형식인 **Thrift**로 기록됨

## 세 가지 인코딩으로 반복 값을 줄이는 방법

*   한 열에는 같은 종류의 값이 모이고 반복도 많아, 원래 값을 정확히 복원할 수 있는 더 작은 표현인 **인코딩**을 적용하기 좋음
*   서로 다른 값이 5개뿐인 결제 유형 열에는 **세 가지 인코딩**을 적용함 
    *   **사전 인코딩(dictionary encoding)**: 다섯 값을 목록에 한 번씩 저장하고, 각 행에는 해당 값의 목록 내 위치를 기록함
    *   **런 길이 인코딩(run-length encoding)**: 같은 위치 값이 연속 반복되면 값 하나와 반복 횟수로 기록함
    *   **비트 패킹(bit-packing)**: 다섯 위치 값은 각각 3비트면 표현할 수 있으므로 64비트 대신 필요한 비트 수만 사용함

*   결제 유형을 일반적인 64비트 숫자로 저장하면 **76MB**지만, 세 인코딩을 적용하면 **2.2MB**로 줄어듦 
    *   이 예제에서 사전 인코딩만으로는 위치 값과 원래 값의 바이트 수가 같아 공간을 절약하지 못함
    *   원래 값을 32비트로 저장하더라도 세 인코딩을 함께 적용하면 공간을 94% 절약함

## 페이지 구성과 압축의 역할

*   Parquet는 각 열을 **페이지**로 나눔 
    *   예제의 한 그룹에는 결제 유형 약 100만 개가 있으며, 첫 페이지에 다섯 값의 사전을, 다음 53개 페이지에 위치 값을 저장함
    *   PyArrow는 기본적으로 한 페이지에 위치 값을 최대 2만 개 넣음
    *   각 페이지 앞의 짧은 헤더에는 인코딩 방식과 바이트 수가 들어 있음
    *   결제 유형 2만 개가 모두 같으면 하나의 런으로 표현되어 압축 전 데이터 크기가 13바이트에 불과함

*   **압축**은 인코딩과 별도 단계이며, 인코딩한 페이지의 바이트를 더 작게 만듦 
    *   **Zstandard**는 각 페이지를 독립적으로 압축하므로, 읽기 도구는 다른 페이지 없이 한 페이지만 압축 해제할 수 있음
    *   결제 유형 열은 인코딩 후 2.2MB에서 Zstandard 압축 후 1.2MB로 줄어듦

*   기본 압축기는 **Polars가 Zstandard**, PyArrow와 DuckDB가 Snappy임 
    *   예제에서 Zstandard의 쓰기 속도는 Snappy와 거의 같으면서 파일 크기는 약 20% 작았음
    *   Brotli는 더 작은 파일을 만들 수 있지만, PyArrow 기본 설정에서 읽기는 Zstandard보다 2.5배 오래 걸렸음
    *   같은 기본 설정 비교에서 Gzip 쓰기는 Zstandard보다 거의 30배 오래 걸렸음

*   메모리에 있는 파일의 모든 열을 읽을 때 Zstandard 압축본은 비압축본보다 **약 24ms** 느렸음 
    *   하지만 약 **25Gbps보다 느린 네트워크**에서는 절약한 91MB를 전송하는 시간이 압축 해제 시간보다 길어져, 압축이 읽기 속도도 높임
    *   압축기는 애플리케이션의 읽기 속도, 쓰기 속도, 저장 크기 사이에서 균형을 맞춰 선택해야 함

## 정렬과 CSV 압축이 만드는 차이

*   **행의 정렬 순서**도 압축률에 영향을 줌 
    *   승차 시각으로 정렬하면 인접 시각 대부분의 차이가 몇 초 이내여서 많은 바이트가 같아지고, Zstandard가 잘 압축함
    *   정렬한 파일은 **144MB**, 행을 섞은 파일은 213MB였음

*   CSV에도 **Zstandard 압축**을 적용할 수 있으며, Python 3.14는 이를 표준 라이브러리에 포함함 
    *   예제 CSV는 188MB로 줄어들지만, Parquet의 158MB보다는 큼
    *   요금만 읽어도 모든 줄을 압축 해제하고 파싱해야 하므로, 압축 CSV의 읽기는 일반 CSV보다 5배 오래 걸림
    *   같은 열을 Parquet에서 읽는 데는 7ms가 걸림

## 행 그룹과 조건절 푸시다운

*   Parquet는 행을 **행 그룹(row group)** 으로 나누고, 각 그룹 안에서 열을 차례로 저장함 
    *   예제 파일에는 행 그룹이 10개 있음
    *   PyArrow는 각 행 그룹의 열마다 최솟값과 최댓값을 푸터에 기록함

*   2월 15일의 평균 요금을 구할 때, 읽기 도구는 각 그룹의 최초/최종 승차 시각을 확인하고 해당 날짜가 범위 밖이면 **그룹 전체를 읽지 않음**
    *   날짜 필터 같은 조건을 **조건절(predicate)** 이라 하며, 이를 읽기 도구에 전달하는 것이 조건절 푸시다운(predicate pushdown)임

*   승차 시각으로 정렬한 데이터에서는 해당 날짜가 포함된 **1개 그룹만** 읽지만, 섞인 데이터에서는 모든 그룹에 모든 날짜가 있어 10개를 전부 읽음 
    *   섞인 파일은 읽는 바이트가 20배 많고, 시간은 거의 2배 걸렸음
    *   쿼리가 가장 자주 필터링하는 열로 정렬하는 것이 중요함

*   소수의 **잘못된 값**도 행 그룹을 건너뛰지 못하게 만들 수 있음 
    *   예제에는 승차 시각이 2002년, 2008년, 2009년인 운행이 9건 있음
    *   뉴욕이 공개한 순서를 유지한 파일에서는 이 중 2건이 3월 운행 그룹에 들어가, 푸터 범위가 2002년 12월부터 2024년 3월까지 넓어짐
    *   그 결과 2월의 하루를 찾는데도 3월 운행 그룹 전체를 읽어야 함
    *   이런 값은 머신러닝 모델에 큰 영향을 주지 않더라도, **데이터 정제**가 엔지니어링 성능에는 중요할 수 있음

## 행 그룹 크기의 절충

*   **행 그룹 크기**는 쓰기 도구에서 설정하며, PyArrow는 약 100만 행, DuckDB와 Polars는 약 12만 행을 기본값으로 사용함
*   작은 행 그룹은 더 정밀하게 건너뛸 수 있지만, 각 그룹의 범위 정보가 추가되어 **푸터가 커짐**
    *   읽기 도구는 먼저 푸터 전체를 읽어야 함
    *   정렬한 예제에서 그룹당 1만 행으로 설정하면 그룹이 956개가 되고, 푸터는 26KB에서 2.2MB로 커짐

*   큰 행 그룹은 푸터가 작지만 **건너뛰기 정밀도**가 낮음 
    *   모든 운행을 한 그룹에 넣으면 하루를 찾는 데 27MB를 읽고 55ms가 걸림
    *   애플리케이션 요구에 따라 설정을 선택해야 하지만, 대체로 널리 쓰이는 도구의 기본값이 잘 작동함

## Parquet의 기원과 두 가지 버전 체계

*   Google은 **2010년 Dremel**에서 레코드를 열별로 저장하는 시스템을 소개했고, Twitter와 Cloudera 엔지니어들이 2013년 같은 아이디어로 Hadoop용 Parquet를 만듦 
    *   Hadoop은 여러 머신의 로컬 디스크에 데이터를 저장하고 처리했으며, 디스크 읽기가 병목이었음
    *   필요한 열만 읽으면 디스크에서 가져오는 데이터량을 줄일 수 있음

*   **Parquet**라는 이름은 작은 나무 블록을 패턴으로 배치한 마루에서 왔으며, 파일의 행 그룹과 열 구조와 닮아 있음 
    *   2015년 Apache 최상위 프로젝트가 됨
    *   2017년 Zstandard와 페이지를 건너뛰기 위한 인덱스가 추가됨

*   “**버전 2**”는 서로 다른 두 버전 체계를 가리킬 수 있음 
    *   **형식 버전**은 나노초 타임스탬프처럼 파일이 담을 수 있는 자료형을 정하며, 두 형식 버전 모두 파일 앞뒤에 `PAR1`을 사용함
    *   **페이지 버전**은 페이지 배치를 정하며, 버전 2 페이지는 결측값 표시를 압축 영역 밖에 둠
    *   예제는 PyArrow 기본값인 형식 버전 2.6과 페이지 버전 1을 사용함

## 행 지향과 열 지향 저장을 선택하는 기준

*   **데이터베이스**는 체계적으로 조직한 데이터 모음이므로 CSV와 Parquet 파일도 이에 해당하지만, 운영체제에는 바이트일 뿐임 
    *   SQLite와 DuckDB처럼 이를 저장하고 읽는 프로그램은 **데이터베이스 관리 시스템**이며, 흔히 이 프로그램 자체를 데이터베이스라고 부름
    *   둘 다 전체 데이터베이스를 하나의 파일에 저장하지만, SQLite는 테이블을 행별로, DuckDB는 열별로 저장함

*   운행 한 건의 값 19개를 보여주는 앱에는 값들이 붙어 있는 **행 지향 저장**이 유리함 
    *   번호로 운행 한 건을 찾는 데 SQLite는 약 6μs가 걸리고, DuckDB는 29배 오래 걸렸음
    *   SQLite는 행 하나를 추가하고 저장하는 데 약 0.2ms가 걸림

*   **완성된 Parquet 파일**에는 PyArrow, Polars, DuckDB로 행을 추가할 수 없음 
    *   예제에 운행 한 건을 추가하려면 전체 파일을 다시 써야 하며, 쓰기에 거의 2초가 걸림

*   월별 평균 요금처럼 많은 행을 집계할 때는 **열 지향 저장**이 유리함 
    *   DuckDB는 필요한 두 열만 읽어 SQLite보다 49배 빠르게 응답함
    *   DuckDB는 모든 CPU 코어를, SQLite는 한 코어를 사용하지만, DuckDB를 한 코어로 제한해도 9배 빨랐음
    *   Parquet는 CSV의 6분의 1 크기이며 압축 CSV보다도 작음

*   **한 번에 한 행을 읽고 쓰는 앱**에는 행 지향 저장이, 많은 행을 함께 읽는 **분석과 학습**에는 열 지향 저장이 적합함

## 대규모 데이터셋과 테이블 관리로의 확장

*   여러 대형 기술 기업이 **Parquet**를 널리 사용하며, AI 모델 학습용 데이터셋도 많이 이 형식을 사용함 
    *   Hugging Face는 공개 데이터셋을 Parquet로 변환함
    *   뉴욕도 택시 데이터의 규모가 커 Parquet 파일로 공개함

*   Parquet가 확산되면서 **S3 같은 객체 스토리지**에 정리되지 않은 Parquet 파일이 많이 쌓이기 시작함 
    *   다음 단계는 **Iceberg**로 수천 개의 Parquet 파일을 하나의 테이블로 묶어 관리하는 것임
