Blog
pysparksparkpandasmigrationpandas-on-sparkdata-engineering

pandas 에서 PySpark 로 — 단일 머신 한계를 넘는 스케일아웃

메모리에 안 들어가는 데이터를 만난 pandas 사용자를 위한 전환 가이드. lazy vs eager 실행 모델 차이, 흔히 빠지는 함정(반복문·인덱스·collect), pandas API on Spark 로 코드 거의 그대로 분산 처리하는 법까지 정리합니다.

Data Dynamics2026年6月5日10 min read
This post is not yet translated. The original Korean version is shown below.

pandas 로 잘 돌던 분석 코드가 데이터가 커지면서 MemoryError 를 내기 시작합니다. 단일 머신 메모리에 데이터가 안 들어가는 순간, PySpark 로의 전환을 고민하게 됩니다. 그런데 pandas 와 Spark 는 실행 모델이 근본적으로 달라서, 익숙한 pandas 습관을 그대로 옮기면 느려지거나 오히려 메모리가 터집니다.

이 글은 pandas 사용자가 PySpark 로 넘어올 때 반드시 이해해야 할 차이, 흔히 빠지는 함정, 그리고 코드를 거의 바꾸지 않고 분산 처리하는 길(pandas API on Spark)을 정리합니다.

1. 가장 큰 차이 — Lazy vs Eager

pandas 는 즉시 실행(eager) 합니다. 한 줄 쓰면 그 자리에서 계산하고 결과를 메모리에 들고 있습니다. Spark 는 지연 실행(lazy) 합니다. 변환(transformation)을 쌓아두기만 하다가, 액션(action)을 만나야 한 번에 최적화해서 실행합니다.

# pandas: 각 줄이 즉시 실행
df2 = df[df.amount > 100]      # 바로 필터링됨
df3 = df2.groupby("user").sum() # 바로 집계됨
 
# PySpark: 변환은 계획만, 액션에서 실행
df2 = df.filter(F.col("amount") > 100)    # 아직 실행 안 됨
df3 = df2.groupBy("user").sum()            # 여전히 계획만
df3.show()                                  # ← 여기서 비로소 전체 실행
pandas (eager)PySpark (lazy)
실행 시점줄마다 즉시액션에서 한 번에
최적화없음Catalyst 가 전체 계획 최적화
디버깅중간 결과 즉시 확인액션 전엔 확인 불가

이 차이를 이해하면 "왜 에러가 엉뚱한 줄에서 나는지"(실제로는 액션에서 전체가 실행되며 터짐)를 알 수 있습니다.

2. 함정 ① 반복문 — 절대 행을 순회하지 마라

pandas 의 iterrows, apply 같은 행 순회 습관이 Spark 에서 최악입니다.

# pandas (이미 느리지만 동작)
for idx, row in df.iterrows():
    df.at[idx, "grade"] = compute(row)
 
# PySpark 에서 이걸 흉내내면? → collect 로 드라이버 메모리 폭발 + 분산 무력화
# 올바른 방법: 컬럼 연산(벡터화)
df = df.withColumn("grade",
    F.when(F.col("score") >= 90, "A").otherwise("B"))

Spark 의 힘은 컬럼 단위 분산 연산입니다. 행을 순회하는 순간 분산의 이점이 사라집니다. pandas 의 apply(axis=1) 도 마찬가지 — 컬럼 표현식이나 (불가피하면) pandas_udf 로 바꿔야 합니다(별도 글 "PySpark UDF가 느린 이유와 Pandas UDF").

3. 함정 ② collect / toPandas — 드라이버로 다 끌어오기

# 위험: 분산 데이터를 단일 드라이버 메모리로 → 큰 데이터면 OOM
result = spark_df.toPandas()
rows = spark_df.collect()
 
# 안전: 분산 저장하거나, 작게 줄인 뒤에만
spark_df.write.parquet("out")              # 익스큐터가 분산 저장
sample = spark_df.limit(1000).toPandas()   # 필요한 만큼만
agg = spark_df.groupBy("k").count().toPandas()  # 집계 후 작은 결과만

pandas 로 "돌아오고" 싶은 충동이 가장 위험합니다. 결과가 크면 절대 드라이버로 모으지 마세요(별도 글 "PySpark Executor OOM 정복"의 드라이버 OOM 참고).

4. 함정 ③ 인덱스 — Spark 에는 행 인덱스가 없다

pandas 의 핵심인 인덱스가 Spark 에는 없습니다. 순서·위치 기반 접근(df.iloc[5], df.loc[idx])이 안 됩니다.

# pandas: 위치/인덱스 접근
df.iloc[0]
df.set_index("id")
 
# PySpark: 순서가 필요하면 명시적으로 정렬 + window
from pyspark.sql.window import Window
df = df.withColumn("row_num",
    F.row_number().over(Window.orderBy("created_at")))

Spark 데이터는 분산되어 순서가 없습니다. 순서가 필요하면 명시적으로 정렬하고, 행 번호가 필요하면 window 함수를 씁니다.

5. 함정 ④ 데이터 타입과 NULL

# pandas: NaN, 동적 타입
# PySpark: 명시적 스키마, null
 
# pandas 의 object 컬럼, mixed type → Spark 는 스키마가 엄격
# CSV 읽을 때 스키마 추론에 의존하지 말고 명시
schema = "id long, amount double, name string"
df = spark.read.schema(schema).csv("path", header=True)

6. API 매핑 — 손에 익은 연산들

작업pandasPySpark
필터df[df.x > 1]df.filter(F.col("x") > 1)
컬럼 추가df["y"] = ...df.withColumn("y", ...)
집계df.groupby("k").sum()df.groupBy("k").sum()
조인pd.merge(a, b, on="k")a.join(b, "k")
정렬df.sort_values("x")df.orderBy("x")
컬럼명 변경df.rename(...)df.withColumnRenamed(...)
결측 처리df.fillna(0)df.fillna(0) / F.coalesce
고유값df.x.unique()df.select("x").distinct()
행 수len(df)df.count() (액션!)

7. 가장 쉬운 길 — pandas API on Spark

코드를 거의 안 바꾸고 분산 처리하고 싶다면 pandas API on Spark(구 Koalas, pyspark.pandas)를 씁니다. pandas 와 거의 같은 API 를 Spark 위에서 실행합니다.

import pyspark.pandas as ps
 
# pandas 코드와 거의 동일!
psdf = ps.read_parquet("hdfs://.../big_data")
result = (psdf[psdf.amount > 100]
    .groupby("user")["amount"]
    .sum()
    .sort_values(ascending=False))
 
# 진짜 pandas 가 필요한 작은 결과만 변환
small = result.head(100).to_pandas()
순수 PySparkpandas API on Spark
코드 변경많음(API 다름)거의 없음
친숙함새 API 학습pandas 그대로
세밀 제어높음다소 추상화됨
적합신규/성능 criticalpandas 코드 이식

전환 전략: 기존 pandas 코드가 많다면 pandas API on Spark 로 먼저 이식해 동작시키고, 성능이 중요한 핫스팟만 순수 PySpark 로 다시 쓰는 점진적 접근이 현실적입니다.

8. 작은 데이터엔 Spark 가 과하다

반대 방향의 함정도 있습니다. 데이터가 작으면 Spark 가 오히려 느립니다. 셔플·JVM·스케줄링 오버헤드 때문입니다.

데이터 규모권장
수 GB 이하, 단일 머신 가능pandas (또는 Polars/DuckDB)
단일 머신 메모리 초과PySpark
pandas 코드 많은데 커짐pandas API on Spark

"큰 데이터 = Spark"가 항상 옳은 건 아닙니다. 단일 머신에 들어가면 pandas/Polars/DuckDB 가 더 빠르고 간단합니다.

9. 전환 체크리스트

  • lazy 실행 이해 — 액션 전엔 실행 안 됨
  • iterrows/apply(axis=1) → 컬럼 연산/pandas_udf
  • toPandas/collect 금지 — 분산 저장 또는 작게
  • 인덱스 의존 제거 → 정렬 + window
  • 스키마 명시 (추론 의존 줄이기)
  • 큰 pandas 코드는 pandas API on Spark 로 이식
  • 작은 데이터는 굳이 Spark 안 쓰기

10. 정리

차이/함정pandas 습관PySpark 정답
실행eagerlazy(액션에서 실행)
행 처리iterrows/apply컬럼 벡터 연산
결과 수집다 메모리분산 저장/limit
순서인덱스정렬 + window
이식pandas API on Spark

pandas 에서 PySpark 로의 전환은 "API 를 바꾸는 일"이 아니라 "사고방식을 바꾸는 일" 입니다. 즉시 실행·행 순회·전체 메모리 보유라는 pandas 의 전제가 분산 환경에서는 모두 반대가 됩니다. lazy 실행과 컬럼 연산을 이해하고, toPandas 의 유혹을 참으며, 기존 코드는 pandas API on Spark 로 점진적으로 옮기는 것 — 이 길이 단일 머신의 한계를 가장 매끄럽게 넘는 방법입니다.


이 글은 Spark 3.5 기준으로 작성되었습니다. pandas 기반 분석의 스케일아웃이나 데이터 파이프라인 전환이 필요하시면 언제든 문의해 주세요.

— Data Dynamics 엔지니어링 팀