
결론부터 말씀드리면, CSV 파일 하나의 용량이 커져서 열 때마다 버벅이거나 전체 컬럼 중 일부만 골라 쓰는 쿼리가 반복된다면 그때가 Parquet CSV 전환을 검토할 시점입니다. 컬럼 단위로 저장하는 구조 덕분에 용량은 눈에 띄게 줄고, 필요한 컬럼만 골라 읽는 작업에서는 읽기 속도 차이가 크게 벌어집니다. 다만 스키마가 자주 바뀌거나 작은 파일을 계속 append하는 로그성 파이프라인이라면 오히려 CSV 쪽이 다루기 편할 때도 있습니다. 아래에서 이 판단을 실제 코드로 확인해 보겠습니다.
CSV 용량이 어느 선을 넘으면 압박이 옵니다
CSV는 모든 값을 텍스트로 그대로 풀어서 저장하는 방식이라, 숫자든 날짜든 문자로 변환한 만큼 용량을 차지합니다. 반면 Parquet은 Apache 공식 프로젝트에서 밝히는 대로 컬럼 단위 저장 포맷이라서, 같은 컬럼 안에서 값이 반복되면 인코딩과 압축 효율이 CSV보다 훨씬 좋습니다.
체감상 압박이 오는 시점은 대략 이렇습니다.
- pandas로
read_csv할 때 메모리 부족(MemoryError)이 자주 뜨는 경우 - Git이나 S3에 CSV를 그대로 올려서 스토리지 비용이나 clone 시간이 부담되는 경우
- 같은 원본 CSV를 여러 팀이 각자 복사해 쓰면서 사본이 계속 늘어나는 경우
이럴 때 CSV를 Parquet으로 한 번만 변환해 두면, 이후 읽는 쪽에서는 용량 부담이 확 줄어듭니다. 다만 압축률은 데이터의 반복성에 따라 편차가 크므로, 숫자 하나로 “몇 % 줄어든다”고 단정하기는 어렵습니다. 컬럼 값이 반복되는 로그·거래 데이터일수록 효과가 크고, 무작위 문자열이 많은 데이터일수록 효과가 작습니다.
읽기 속도 차이는 어떤 쿼리에서 갈리나요?
핵심은 “컬럼 프루닝(column pruning)”입니다. CSV는 한 줄 전체를 읽어야 원하는 컬럼 하나를 뽑아낼 수 있는 구조라, 파일의 폭(컬럼 수)이 넓을수록 불필요한 파싱 비용이 커집니다. Parquet은 컬럼별로 물리적으로 분리 저장돼 있어서, columns 인자로 지정한 컬럼만 디스크에서 읽어옵니다.
여기에 더해 Parquet은 row group마다 최소·최댓값 통계를 저장해 두기 때문에, DuckDB 같은 엔진은 조건에 안 맞는 row group 자체를 건너뛸 수 있습니다. 반면 CSV는 이런 메타데이터가 없어서 조건에 안 맞아도 끝까지 훑어야 합니다.
읽기 속도 차이가 거의 없는 경우도 있습니다. 컬럼이 3~4개뿐인 좁은 테이블이거나, 매번 전체 컬럼을 다 쓰는 쿼리라면 컬럼 프루닝의 이점이 사라지므로 체감 차이는 크지 않습니다.

스키마가 자주 바뀌는 데이터의 딜레마
CSV는 별도의 스키마가 없습니다. 열마다 타입을 강제하지 않고, 읽는 쪽(pandas의 타입 추론 등)이 알아서 해석하는 구조입니다. 그래서 컬럼을 추가하거나 순서를 바꿔도 파일 자체는 별문제 없이 굴러갑니다. 위키백과 Apache Parquet 문서에도 설명돼 있듯, Parquet은 파일 내부에 스키마를 함께 저장하는 자기 기술적(self-describing) 포맷입니다.
이 차이가 실무에서 문제가 되는 지점은 아래와 같습니다.
| 상황 | CSV | Parquet |
|---|---|---|
| 컬럼 추가 | 그냥 이어 쓰면 됨 | 새 파일은 스키마가 달라져 병합 시 처리 필요 |
| 컬럼 타입 변경(문자→숫자 등) | 읽는 쪽에서 알아서 재해석 | 기존 파일과 타입 충돌 가능 |
| 여러 파일을 한 번에 읽기 | 헤더만 같으면 대체로 문제없음 | 스키마가 다르면 통합 읽기에서 에러 발생 가능 |
로그 파이프라인처럼 필드가 수시로 추가·삭제되는 데이터라면, 매번 스키마를 맞춰 쓰는 비용이 Parquet 전환의 이점을 깎아 먹을 수 있습니다. 이런 경우엔 스키마가 안정된 이후로 전환 시점을 미루는 편이 낫습니다.
pandas와 DuckDB로 직접 비교해 보세요
말로만 설명하기보다 직접 돌려보는 편이 확실합니다. pandas 2.x 기준으로 to_parquet을 쓰려면 pyarrow 패키지가 설치돼 있어야 합니다.
import pandas as pd
import time, os
df = pd.read_csv("sales.csv")
df.to_parquet("sales.parquet", engine="pyarrow", compression="snappy")
print("CSV 용량(MB):", os.path.getsize("sales.csv") / 1024 / 1024)
print("Parquet 용량(MB):", os.path.getsize("sales.parquet") / 1024 / 1024)
start = time.time()
pd.read_csv("sales.csv", usecols=["date", "amount"])
print("CSV 컬럼 2개 읽기(초):", time.time() - start)
start = time.time()
pd.read_parquet("sales.parquet", columns=["date", "amount"])
print("Parquet 컬럼 2개 읽기(초):", time.time() - start)
데이터 크기와 하드웨어에 따라 절댓값은 달라지지만, 대체로 아래와 같은 패턴이 나타납니다. 컬럼 수가 많은 테이블일수록, 그리고 CSV 용량이 클수록 격차가 벌어지는 방향입니다.
CSV 용량(MB): 812.4
Parquet 용량(MB): 194.7
CSV 컬럼 2개 읽기(초): 6.82
Parquet 컬럼 2개 읽기(초): 0.41

DuckDB를 쓰면 pandas로 로딩하지 않고 파일을 바로 쿼리할 수도 있습니다.
import duckdb
duckdb.sql("""
SELECT date, SUM(amount)
FROM 'sales.parquet'
GROUP BY date
""").show()
DuckDB는 row group 통계를 참고해 조건에 안 맞는 블록을 건너뛰기 때문에, 같은 쿼리를 CSV로 직접 스캔하는 것보다 유리한 경우가 많습니다.
Parquet이 손해를 보는 경우도 있을까요?
있습니다. 대표적으로 세 가지 상황에서는 전환 효과가 떨어지거나 오히려 번거로워집니다.
작은 파일이 아주 많은 경우입니다. Parquet은 파일 내부에 스키마·통계 등 메타데이터를 함께 저장하기 때문에, 파일 하나당 크기가 몇 KB 수준이면 메타데이터 오버헤드 비중이 커집니다. 수만 개의 작은 로그 조각을 그때그때 저장하는 구조라면 CSV나 JSON Lines 쪽이 더 가볍습니다.
사람이 직접 열어봐야 하는 경우도 마찬가지입니다. CSV는 메모장이나 엑셀로 바로 열리지만, Parquet은 바이너리 포맷이라 전용 도구(pandas, DuckDB, 파케이 뷰어 등)가 있어야 내용을 확인할 수 있습니다. 협업 상대가 비개발 직군이라면 이 부분이 실제 장벽이 됩니다.
한 가지 헷갈리는 지점도 짚고 넘어가겠습니다. “압축했으니까 pandas 로딩도 당연히 빨라지겠지” 하고 pd.read_parquet("sales.parquet")을 컬럼 지정 없이 그대로 쓰면, 전체 컬럼을 다 읽어오기 때문에 컬럼 프루닝의 이점이 사라집니다. 속도 이득을 보려면 columns 인자를 꼭 지정하거나, DuckDB·PyArrow의 Dataset API처럼 필요한 부분만 읽는 방식을 써야 합니다.
지금 쓰는 파이프라인의 컬럼 수부터 세어보세요
Parquet CSV 전환 여부는 결국 세 가지 질문으로 정리됩니다. 파일 용량이 부담스러운가, 매번 일부 컬럼만 골라 쓰는가, 스키마가 안정적인가. 세 질문에 모두 “그렇다”에 가깝다면 전환 효과가 확실히 체감될 가능성이 높습니다.
당장 해볼 수 있는 다음 단계는 간단합니다. 지금 자주 쓰는 CSV 파일 하나를 골라 위 코드로 Parquet 변환본을 만들어 보고, 평소 즐겨 쓰는 쿼리(컬럼 몇 개만 뽑아 집계하는 쿼리)를 두 포맷으로 각각 돌려 시간을 재보시기 바랍니다. 그 결과 하나가 전환 여부를 정하는 가장 확실한 근거가 됩니다.
