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행의 정수만 확인하면 해당 열을 정수로 취급함
푸터가 제공하는 열 위치와 자료형
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 파일을 하나의 테이블로 묶어 관리하는 것임
댓글과 토론