배치 처리 최적화 도구은(는) 무엇을 하나요?
Spark, Polars, DuckDB, pandas를 사용한 배치 데이터 처리 작업을 메모리 효율성, 병렬 처리, 비용 절감에 중점을 두고 최적화합니다.
활성화 조건
다음 경우에 활성화: "배치 처리", "Spark 최적화", "Polars", "DuckDB", "pandas 성능", "데이터 프레임", "셔플 최적화", "파티션 스큐", "메모리 최적화", "메모리 부족"
다음 경우에는 사용하지 마세요: 실시간 스트리밍 → streaming-pipeline-architect | 웨어하우스 SQL 튜닝 → data-warehouse-optimizer | 파이프라인 오케스트레이션 → airflow-dag-orchestrator
빠른 시작
- 올바른 도구 선택 — 단일 노드 분석에는 DuckDB, 데이터 프레임에는 Polars, 분산 환경에는 Spark
- 먼저 프로파일링 — 최적화 전에 병목 지점(셔플, 스큐, 메모리)을 식별합니다.
- 초기에 데이터 축소 — 파이프라인에서 가능한 한 빨리 필터링하고 칼럼을 선택합니다.
- 셔플 피하기 — 작은 테이블은 브로드캐스트하고, 데이터를 미리 파티셔닝하며, 맵-사이드 조인을 사용합니다.
- 리소스 적절히 할당 — 실행자 메모리/코어를 실제 데이터 크기에 맞춥니다.
핵심 기능
| 영역 | 기술 |
|---|---|
| 분산 | Apache Spark 3.5+, Dask, Ray |
| 단일 노드 | DuckDB 1.1+, Polars 1.x, pandas 2.2+ |
| 파일 포맷 | Parquet, Arrow IPC, Delta Lake, Iceberg |
| 최적화 | AQE (Spark), 지연 평가 (Polars), 컬럼 스캔 |
| 클라우드 | Databricks, EMR, Dataproc, 서버리스 Spark |
아키텍처 패턴
도구 선택 결정 트리
데이터 크기?
├─ < 10 GB → DuckDB (SQL) 또는 Polars (DataFrame)
│ 단일 머신, 설정 불필요, 가장 빠른 반복
│
├─ 10-100 GB → Polars (지연) 또는 DuckDB (디스크 오버플로우)
│ 여전히 단일 머신이지만 디스크로 오버플로 가능
│
└─ > 100 GB → Spark (분산)
다중 노드 클러스터, 셔플 기반 조인
복잡도?
├─ SQL 중심 → DuckDB (분석을 위한 가장 빠른 SQL 엔진)
├─ DataFrame → Polars (pandas보다 10배 빠름, 지연 평가)
└─ 복잡한 ML → Spark + MLlib 또는 Spark + Ray
Spark 최적화 패턴
from pyspark.sql import SparkSession
import pyspark.sql.functions as F
spark = SparkSession.builder \
.config("spark.sql.adaptive.enabled", "true") \
.config("spark.sql.adaptive.coalescePartitions.enabled", "true") \
.config("spark.sql.adaptive.skewJoin.enabled", "true") \
.getOrCreate()
# 좋음: 작은 차원 테이블 브로드캐스트 (< 100MB)
from pyspark.sql.functions import broadcast
result = large_df.join(broadcast(small_dim_df), "key")
# 좋음: 조건자 푸시다운 — 조인 전 필터링
orders = spark.read.parquet("s3://data/orders/") \
.filter(F.col("order_date") >= "2026-01-01") \
.select("order_id", "customer_id", "amount") # 칼럼 정리
# 나쁨: 대규모 데이터셋에 collect() 사용 — 드라이버 OOM 발생
# all_data = large_df.collect() # 절대 사용하지 마세요
# 좋음: 파티셔닝된 출력 작성
result.repartition(200) \
.write.mode("overwrite") \
.partitionBy("order_date") \
.parquet("s3://output/results/")
Polars 지연 평가
import polars as pl
# 지연 모드: 쿼리 계획을 세우고 최적화한 후 실행
result = (
pl.scan_parquet("data/orders/*.parquet") # 지연 스캔
.filter(pl.col("order_date") >= "2026-01-01")
.join(
pl.scan_parquet("data/customers/*.parquet"),
on="customer_id",
how="inner"
)
.group_by("region")
.agg([
pl.col("amount").sum().alias("total_revenue"),
pl.col("order_id").n_unique().alias("order_count"),
])
.sort("total_revenue", descending=True)
.collect() # 최적화된 계획 실행
)
# Polars가 자동으로 최적화: 조건자 푸시다운, 프로젝션 푸시다운,
# 조인 재정렬 — 모두 지연 평가를 통해 자동 수행
안티패턴
- 5GB 이상 데이터에 pandas 사용 — pandas는 모든 데이터를 메모리에 로드합니다. 중간 규모 데이터는 Polars(지연) 또는 DuckDB, 대규모는 Spark를 사용하세요.
- 드라이버로 collect 수행 — 대규모 Spark 데이터프레임에서
df.collect()또는df.toPandas()는 OOM을 유발합니다. 먼저 집계하세요. - 파티션 스큐 무시 — 다른 파티션보다 10배 많은 데이터를 가진 하나의 파티션이 전체 작업의 병목이 됩니다. AQE 또는 살팅(salting)을 사용하세요.
- 모든 칼럼 읽기 — 항상 필요한 칼럼만 선택하세요. Parquet 컬럼 포맷은 사용되지 않는 칼럼을 완전히 건너뜁니다.
- 작은 출력 파일 — 너무 많은 작은 출력 파일(< 128MB)은 다운스트림 읽기를 느리게 만듭니다. 쓰기 전에 coalesce를 수행하세요.
품질 체크리스트
- 도구가 데이터 크기에 맞음 (DuckDB/Polars < 100GB, Spark > 100GB)
- 칼럼이 초기에 정리됨 (필요한 것만 선택)
- 필터가 스캔 수준으로 푸시됨 (조건자 푸시다운)
- 조인에서 작은 테이블 브로드캐스트 (< 100MB)
- Spark AQE 활성화 (적응형 쿼리 실행)
- 대규모 데이터셋에
collect()사용 없음 (collect 전에 집계) - 출력 파일 크기 128MB-1GB (쓰기 전 coalesce/repartition)
- 파티션 스큐 모니터링 및 완화 (살팅 또는 AQE)
- 작업 프로파일링: Spark UI 스테이지, Polars
.explain(), DuckDBEXPLAIN ANALYZE - 메모리 적절히 할당: 실행자 메모리 >= 가장 큰 파티션의 2배