기준: DEA-C01 시험 가이드오류 제보
데이터 엔지니어링에서 "느리다"와 "비싸다"는 대부분 같은 원인에서 나온다 — 불필요하게 많은 데이터를 읽기 때문이다. Athena, Redshift Spectrum, EMR, Glue는 모두 S3에서 읽은 데이터 양에 비례해 시간과 돈을 쓴다. 따라서 최적화의 본질은 단순하다: 읽어야 할 데이터를 최소화하는 것. 오늘은 그 네 가지 무기 — 파일 포맷, 압축, 파티셔닝, 파일 크기 — 를 다룬다. DEA-C01 시험 범위에 드는 영역이다.
파일 포맷은 크게 두 부류다.
| 분류 | 포맷 | 저장 방식 |
|---|---|---|
| 행 기반(row) | CSV, JSON, Avro | 한 행의 모든 컬럼을 연속 저장 |
| 열 기반(columnar) | Parquet, ORC | 같은 컬럼의 값들을 연속 저장 |
분석 쿼리는 보통 "수백 컬럼 중 몇 개"만 본다. SELECT region, SUM(amount)는 region과 amount 두 컬럼만 필요하다. 행 기반 포맷은 한 행을 통째로 저장하므로, 두 컬럼만 원해도 모든 컬럼을 읽어야 한다. 반면 열 기반(Parquet/ORC)은 필요한 컬럼만 골라 읽는다(column pruning). 100개 컬럼 중 2개만 읽으면 I/O가 50배 줄 수 있다.
-- Athena: Parquet으로 저장된 테이블에서 두 컬럼만 조회
-- → region, amount 컬럼 블록만 S3에서 읽어 스캔량 최소화
SELECT region, SUM(amount) AS total
FROM sales_parquet
WHERE year = 2026
GROUP BY region;💡 관련 이론: 열 기반 저장이 분석에서 빠른 이유는 I/O 절감뿐 아니라 압축 효율에도 있다. 같은 컬럼 값들은 데이터 타입과 분포가 비슷해(예: region 컬럼은 같은 문자열이 반복) 런-렝스 인코딩, 딕셔너리 인코딩 같은 기법이 극적으로 잘 먹힌다. 또 Parquet/ORC는 데이터 블록마다 min/max 통계를 메타데이터에 담아, 쿼리 조건에 맞지 않는 블록 전체를 읽지 않고 건너뛴다(predicate pushdown). 이 세 가지(컬럼 프루닝·고압축·통계 기반 스킵)가 열 포맷의 삼위일체다.
세 포맷의 용도는 다음과 같이 구분된다.
| 포맷 | 유형 | 강점 | 대표 용도 |
|---|---|---|---|
| Parquet | 열 기반 | Spark/Athena 생태계 표준, 폭넓은 호환 | 분석 쿼리 일반 |
| ORC | 열 기반 | Hive 생태계 최적, 강한 압축·인덱스 | Hive/EMR 중심 |
| Avro | 행 기반 | 스키마 진화, 쓰기 빠름 | 스트리밍 적재·스키마 변경 잦은 경우 |
시험 기준 직관: 분석 쿼리 최적화 = Parquet(또는 ORC), 스키마가 자주 바뀌는 행 단위 쓰기/스트리밍 = Avro. Athena·Redshift Spectrum·Glue가 Parquet을 가장 잘 다루므로, "비용 절감을 위한 포맷 선택"이 나오면 보통 Parquet이 정답이다.
⚠️ 함정: "Avro도 압축되는데 왜 분석엔 Parquet인가?"에서 헷갈린다. Avro는 행 기반이라 컬럼 프루닝이 안 된다 — 두 컬럼만 원해도 전체 행을 읽는다. Avro의 강점은 분석 속도가 아니라 스키마 진화(필드 추가/삭제를 안전하게 처리)와 빠른 쓰기다. 분석 스캔 비용 절감 문제에 Avro를 고르면 틀린다.
압축은 S3 저장 비용과 스캔 비용을 동시에 줄인다. 압축 코덱 선택의 핵심은 분할 가능성(splittable)이다.
| 코덱 | 압축률 | 속도 | 분할 가능 |
|---|---|---|---|
| Snappy | 중간 | 빠름 | (Parquet/ORC 내부에서) 예 |
| Gzip | 높음 | 느림 | 단독 텍스트에서는 아니오 |
| Zstd | 높음 | 빠름 | 예 |
| Bzip2 | 매우 높음 | 매우 느림 | 예 |
| LZO | 낮음 | 빠름 | 예(인덱스 시) |
분할 가능성이 왜 중요한가? 분산 엔진(Spark/Hive)은 큰 파일을 여러 워커가 나눠 병렬로 읽는다. 분할 불가능한 코덱(단독 Gzip 텍스트)은 파일 하나를 워커 하나가 통째로 읽어야 해서 병렬성을 죽인다. 그래서 거대한 단일 Gzip CSV는 최악의 조합이다.
실무 정석은 Parquet + Snappy다. Parquet 내부의 컬럼 청크 단위로 Snappy가 적용되며, 파일 자체는 블록 단위로 분할 가능해 병렬성과 압축을 모두 얻는다. 압축률을 더 원하면 Zstd가 좋은 현대적 대안이다.
파티셔닝(partitioning)은 데이터를 컬럼 값 기준 디렉터리로 물리적으로 분할 저장하는 것이다. 가장 흔한 것은 날짜 파티션이다.
s3://bucket/sales/
year=2026/month=06/day=25/part-0001.parquet
year=2026/month=06/day=26/part-0001.parquet
WHERE year=2026 AND month=06 AND day=26로 쿼리하면, 엔진은 해당 디렉터리만 읽고 나머지 날짜는 아예 열어보지도 않는다(partition pruning). 컬럼 프루닝이 "가로(컬럼) 방향" 절감이라면, 파티션 프루닝은 "세로(행) 방향" 절감이다.
-- 파티션 컬럼으로 필터 → 6월 26일 디렉터리만 스캔, 나머지 날짜 스킵
SELECT product_id, SUM(amount)
FROM sales
WHERE year = 2026 AND month = 6 AND day = 26
GROUP BY product_id;파티션 키는 쿼리에서 자주 필터하는 저(低)카디널리티 컬럼이어야 한다(날짜, 지역 등). 반대로 고카디널리티 컬럼(user_id 같은 수백만 값)으로 파티셔닝하면 과도한 파티션이 생겨 오히려 메타데이터 폭증과 작은 파일 문제를 일으킨다.
🎯 시나리오: Athena 비용이 폭증한 팀이 있다. 데이터는 거대한 단일 Gzip CSV로 S3에 쌓여 있고, 쿼리는 대부분 최근 7일·특정 지역만 본다. 최적화 처방은 (1) 포맷을 Parquet+Snappy로 변환(컬럼 프루닝+압축), (2) year/month/day로 파티셔닝(파티션 프루닝으로 날짜 범위 스킵), (3) 자주 쓰는 region을 보조 파티션 또는 정렬 키로, (4) 작은 파일은 적정 크기로 compaction. 이 조합으로 스캔량이 수십~수백 배 줄어 Athena 비용이 급감한다. Glue ETL 작업으로 CSV→Parquet 변환과 파티셔닝을 일괄 수행한다.
S3에 작은 파일이 수만 개 쌓이면, 데이터 총량이 작아도 쿼리가 극단적으로 느려진다. 이유는 파일마다 오버헤드가 붙기 때문이다 — 각 파일은 S3 LIST/GET 요청, 메타데이터 파싱, 태스크 스케줄링 비용을 일으킨다. 1KB 파일 10만 개는 1GB 파일 하나보다 압도적으로 느리고 비싸다.
작은 파일은 어디서 생기나?
repartition)으로 작은 출력 다수 생성해결책은 컴팩션(compaction) — 작은 파일들을 큰 파일로 병합하는 것이다.
# Spark: 출력 파티션 수를 줄여 작은 파일을 큰 파일로 통합
(df
.repartition(8) # 또는 coalesce로 출력 파일 수 제어
.write
.mode("overwrite")
.parquet("s3://curated/sales/"))AWS는 자동 도구도 제공한다. Firehose의 버퍼 크기/간격을 키워 더 큰 파일로 적재하고, Glue의 작은 파일 그룹화(groupFiles/groupSize)나 테이블 포맷의 자동 컴팩션을 활용한다.
적정 파일 크기는 보통 128MB~1GB 범위가 권장된다. 너무 크면(수 GB+) 분할·병렬성이 떨어지고 일부 워커만 일하며, 너무 작으면 오버헤드가 지배한다. 128MB는 우연이 아니라 HDFS의 기본 블록 크기에서 유래했다 — 하나의 블록이 하나의 처리 단위(태스크)에 대응하도록 설계된 값이다. Iceberg·Hudi·Delta 같은 오픈 테이블 포맷은 자동 컴팩션과 파일 크기 관리를 내장해 이 문제를 구조적으로 완화한다.
성능·비용 문제를 만났을 때의 점검 순서:
이 네 가지가 Athena 스캔 비용, Redshift Spectrum 비용, EMR/Glue 처리 시간을 모두 좌우한다.
성능·비용 최적화는 "읽을 데이터 최소화"로 요약된다. 열 기반 포맷(Parquet)은 컬럼 프루닝과 통계 기반 스킵으로 가로 방향을 줄이고, 파티셔닝은 안 읽을 디렉터리를 통째로 건너뛰어 세로 방향을 줄인다. 압축은 분할 가능한 코덱(Snappy/Zstd)을 골라 병렬성과 절감을 동시에 얻고, 작은 파일은 컴팩션으로 오버헤드를 없앤다. 거대 단일 Gzip CSV가 최악, Parquet+Snappy+날짜 파티셔닝+적정 파일 크기가 정석이다. 내일은 Week 5 전체를 종합 복습한다.
선택지를 클릭하면 정답·해설이 펼쳐집니다.
문제 1
Athena 쿼리가 대부분 100개 컬럼 중 3~4개만 조회하는데 데이터가 CSV로 저장돼 비용이 높다. 스캔 비용을 가장 효과적으로 줄이는 포맷 전환은?
문제 2
거대한 단일 Gzip 압축 CSV 파일을 EMR Spark가 처리하는데 병렬성이 전혀 나오지 않고 워커 하나만 일한다. 근본 원인은?
문제 3
매출 데이터를 year/month/day로 파티셔닝해 S3에 저장했다. WHERE year=2026 AND month=6 AND day=26 쿼리가 빠르고 저렴한 이유는?
문제 4
스트리밍 적재로 S3에 1~10KB짜리 파일이 하루 수십만 개씩 쌓이면서 쿼리가 점점 느려진다. 데이터 총량은 크지 않다. 가장 적절한 대응은?
문제 5
스키마(필드)가 자주 추가·변경되고 행 단위로 빠르게 쓰는 스트리밍 적재 단계에 가장 적합한 포맷은?