같은 데이터라도 어떤 파일 포맷으로 저장하느냐에 따라 쿼리 속도가 100배, 비용이 10배 차이 난다. 데이터 분석가가 "왜 이 쿼리가 5분이나 걸리죠?"라고 물으면, 답은 종종 "데이터가 CSV로 저장돼 있어서"다. CSV를 Parquet으로 바꾸는 한 줄짜리 변환이 가장 효과적인 최적화일 때가 많다.
오늘은 데이터 엔지니어가 매일 마주하는 파일 포맷들 — CSV, JSON, Parquet, ORC, Avro — 을 비교하고, 그 차이의 본질인 컬럼형(columnar) vs 행형(row-based) 저장을 이해하며, 시간이 지나며 데이터 구조가 바뀌는 현실을 다루는 **스키마 진화(schema evolution)**를 본다.
| 포맷 | 구조 | 사람이 읽나 | 압축 | 주 용도 |
|---|---|---|---|---|
| CSV | 행형, 텍스트 | O | 약함 | 단순 교환, 레거시 |
| JSON | 행형, 텍스트 | O | 약함 | API, 중첩 구조, 로그 |
| Parquet | 컬럼형, 바이너리 | X | 강함 | 분석(OLAP)의 표준 |
| ORC | 컬럼형, 바이너리 | X | 강함 | Hive 생태계 분석 |
| Avro | 행형, 바이너리 | X | 중간 | 스트리밍, 스키마 진화 |
크게 세 부류다. **텍스트 기반(CSV/JSON)**은 사람이 읽기 쉽고 호환성이 좋지만 비효율적이다. **컬럼형 바이너리(Parquet/ORC)**는 분석 쿼리에 최적화돼 있다. **행형 바이너리(Avro)**는 쓰기와 스키마 진화에 강해 스트리밍·이벤트에 쓰인다.
💡 관련 이론: 포맷 선택은 워크로드 따라간다. "쓰기가 잦고 한 레코드 전체를 자주 다루는가(write-heavy)" → 행형(Avro). "읽기 위주로 특정 컬럼만 집계하는가(read-heavy, analytical)" → 컬럼형(Parquet/ORC). 이 한 줄이 시험에서 포맷을 고르는 기준이다.
이것이 오늘의 핵심이다. 같은 표를 디스크에 어떻게 늘어놓느냐의 차이다.
원본 테이블
id | name | amount
1 | Kim | 100
2 | Lee | 200
3 | Park | 300
행형(row-based) 저장 — CSV, Avro
[1,Kim,100][2,Lee,200][3,Park,300]
한 행을 통째로 모아둔다
컬럼형(columnar) 저장 — Parquet, ORC
[1,2,3][Kim,Lee,Park][100,200,300]
같은 컬럼끼리 모아둔다
SELECT SUM(amount) 같은 분석 쿼리를 보자. 행형은 amount를 읽으려면 id·name까지 전부 스캔해야 한다. 컬럼형은 [100,200,300] 블록만 읽으면 끝이다. 필요한 컬럼만 읽으므로 I/O가 극적으로 줄어든다. 이를 컬럼 프루닝(column pruning)이라 한다.
게다가 같은 컬럼은 데이터 타입과 값이 비슷해 압축이 훨씬 잘 된다. amount 컬럼은 전부 숫자라 효율적으로 압축되지만, 행 단위로 섞인 타입은 그렇지 않다.
-- Athena 비용은 "스캔한 데이터량"에 비례한다
-- CSV: amount만 필요해도 모든 컬럼을 스캔 → 비싸고 느림
-- Parquet: amount 컬럼만 스캔 → 싸고 빠름
SELECT region,
선택지를 클릭하면 정답·해설이 펼쳐집니다.
문제 1
`SELECT SUM(amount) FROM orders GROUP BY region` 같은 분석 쿼리에서 Parquet 같은 컬럼형 포맷이 행형보다 빠르고 저렴한 가장 근본적인 이유는?
문제 2
원시 로그가 JSON으로 S3에 들어오고 있다. 이후 Athena로 자주 대규모 집계 분석을 할 예정일 때, 데이터 엔지니어가 취할 가장 효과적인 최적화는?
문제 3
스키마가 자주 변경되는 Kafka/스트리밍 이벤트 파이프라인에서, 쓰기 효율과 스키마 진화 지원이 강점이라 널리 쓰이는 행형 바이너리 포맷은?
문제 4
운영 중인 주문 이벤트 스키마에 `coupon_code` 필드를 추가하면서 기존에 쌓인 옛 데이터도 계속 안전하게 읽으려 한다. 가장 안전한 스키마 진화 방법은?
문제 5
프로듀서가 새 스키마를 등록할 때 기존과의 호환성을 검증해, 호환을 깨는 변경을 배포 전에 차단하는 거버넌스 장치는?
반대로 OLTP처럼 "한 행 전체를 읽거나 쓰는" 작업은 행형이 유리하다. 컬럼형은 한 행을 재구성하려면 여러 컬럼 블록을 모아야 하기 때문이다.
💡 관련 이론: 컬럼형 포맷은 컬럼 프루닝 외에도 **프레디킷 푸시다운(predicate pushdown)**과 파티션 프루닝으로 빨라진다. Parquet은 데이터 블록마다 min/max 등 통계를 저장해,
WHERE amount > 500같은 조건에 안 맞는 블록을 아예 읽지 않고 건너뛴다. 컬럼 프루닝(가로로 덜 읽기) + 프레디킷 푸시다운(세로로 덜 읽기)의 결합이 OLAP 성능의 핵심이다.
AWS 분석 스택(Athena, Redshift Spectrum, Glue, EMR)에서 Parquet이 기본 권장 포맷이다. 이유를 정리하면:
# 흔한 최적화: 원시 CSV/JSON을 Parquet으로 변환 (Glue/Spark)
df = spark.read.json("s3://raw/events/")
df.write.partitionBy("year","month","day") \
.parquet("s3://processed/events/")
# 이후 Athena 쿼리가 빨라지고 스캔 비용이 급감ORC는 Parquet과 거의 동일한 컬럼형 장점을 가지며 Hive/Hadoop 생태계에서 특히 강하다. AWS에서는 둘 다 잘 지원되지만 Parquet이 더 널리 쓰인다.
Avro는 행형 바이너리인데도 중요하게 다뤄진다. 핵심 강점이 스키마 진화이기 때문이다. Avro는 데이터와 함께 스키마를 명시적으로 다루며, 읽을 때의 스키마(reader)와 쓸 때의 스키마(writer)가 달라도 호환되도록 설계됐다. 그래서 스키마가 자주 바뀌는 스트리밍·이벤트 파이프라인(Kafka 등)에서 표준처럼 쓰인다.
스키마 진화란 시간이 지나며 데이터 구조가 바뀌는 현실을 안전하게 다루는 것이다. 예를 들어 주문 이벤트에 나중에 coupon_code 필드가 추가될 수 있다. 이때 옛 데이터와 새 데이터를 모두 읽을 수 있어야 한다.
스키마 진화의 호환성 종류
하위 호환(backward) : 새 스키마로 옛 데이터를 읽을 수 있다
→ 필드 삭제, 기본값 있는 필드 추가
상위 호환(forward) : 옛 스키마로 새 데이터를 읽을 수 있다
→ 필드 추가
완전 호환(full) : 양방향 모두 가능
안전한 진화의 원칙: 필드를 추가할 땐 기본값(default)을 주고, 함부로 삭제·이름변경하지 않는다. 기본값이 있으면 그 필드가 없는 옛 데이터를 읽어도 기본값으로 채워져 깨지지 않는다.
💡 관련 이론: 스키마 진화를 조직 차원에서 강제하는 것이 **스키마 레지스트리(Schema Registry)**다. 프로듀서가 새 스키마를 등록할 때 기존과 호환되는지 검증해, 호환을 깨는 변경을 배포 전에 차단한다. AWS Glue Schema Registry가 이 역할을 하며, Kinesis·MSK와 통합된다. 데이터 계약(data contract)을 코드로 강제하는 거버넌스 장치다.
시험과 실무에서 포맷을 고르는 흐름이다.
사람이 직접 읽거나 외부와 교환? → CSV / JSON
분석(집계)용으로 S3에 적재? → Parquet (AWS 기본 권장)
Hive/Hadoop 생태계 중심 분석? → ORC
스트리밍·이벤트, 스키마 자주 변경? → Avro
중첩·반정형 구조(API 응답, 로그)? → JSON (→ 이후 Parquet 변환)
현실의 전형적 패턴은 **"원시 데이터는 JSON/CSV로 들어오지만, 처리 단계에서 Parquet으로 변환해 분석 영역에 적재"**하는 것이다. 들어올 때의 편의(텍스트)와 분석할 때의 효율(컬럼형)을 모두 취하는 방법이다.
오늘의 핵심 세 가지. 첫째, 포맷의 본질적 차이는 **컬럼형(Parquet/ORC) vs 행형(CSV/Avro)**이며, 분석 쿼리는 컬럼만 읽는 컬럼형이 압도적으로 빠르고 싸다. 둘째, Parquet이 AWS 분석의 사실상 표준으로, 컬럼 프루닝·압축·프레디킷 푸시다운으로 Athena 비용을 급감시킨다. 셋째, 스키마 진화는 데이터 구조 변경을 안전하게 다루는 것이며, 기본값 있는 필드 추가가 안전한 원칙이고 스키마 레지스트리로 강제한다.
다음 글에서는 Week 1에서 다룬 기초들 — 역할·파이프라인, 배치/스트리밍, 서비스 지도, 데이터 포맷 — 을 한데 엮어 복습한다.