PySpark UDF의 Arrow 최적화 — 설정과 UDAF 대안
Apache Spark Window Function Frame 경계 및 PySpark Arrow 최적화 공식 문서 종합 (웹 리서치, 2026) — Apache Spark 공식 문서(pyspark.sql.Window, user_guide/sql/arrow_pandas) + Databricks 블로그 Apache Spark 공식 문서 Apache Arrow in PySpark 사용자 가이드
이 모듈을 다 읽으면
- Arrow가 JVM-Python 간 데이터 전송 오버헤드를 줄이는 방식(zero-copy)을 설명할 수 있다
- 일반 Python UDF에도 Arrow 최적화를 켤 수 있다는 것과 그 설정을 판단할 수 있다
- 그룹별 집계·변환에 applyInPandas가 어떻게 쓰이는지 설명할 수 있다
spark-learn-26/39에서 다룬 'pandas UDF가 일반 UDF보다 빠른 이유'를 넘어, Arrow가 실제로 어떻게 그 속도를 내는지, 그리고 일반(스칼라) Python UDF에도 Arrow를 적용하는 방법과 UDAF에 해당하는 그룹별 집계 대안을 정리한다.
Arrow의 zero-copy — 왜 직렬화 오버헤드가 줄어드는가
spark-learn-26에서 다룬 것처럼 일반 Python UDF는 JVM과 별도 Python 프로세스 사이를 오가며 데이터를 직렬화/역직렬화해야 해 오버헤드가 크다. Apache Arrow는 언어에 무관한 공통 컬럼형 메모리 포맷을 정의해, JVM 쪽과 Python 쪽이 같은 메모리 레이아웃을 그대로 공유해서 읽을 수 있게 한다(zero-copy) — 데이터를 다른 포맷으로 변환해 복사하는 과정 자체를 생략하는 것이다. pandas UDF는 이 Arrow 포맷으로 데이터를 배치(batch) 단위로 주고받고, Python 쪽에서 pandas Series/DataFrame으로 그 배치를 벡터화 연산하므로, 행 하나하나를 개별 함수 호출하는 일반 UDF보다 훨씬 빠르다.
핵심 포인트
- Arrow는 JVM과 Python이 공유하는 컬럼형 메모리 포맷 — zero-copy로 변환/복사 오버헤드를 없앤다
- pandas UDF는 Arrow로 배치 단위 데이터를 주고받고 pandas로 벡터화 연산해 행 단위 일반 UDF보다 훨씬 빠르다
일반 스칼라 UDF에도 Arrow를 켤 수 있다
spark.sql.execution.arrow.pyspark.enabled는 toPandas()나 createDataFrame() 같은 Spark DataFrame ↔ pandas DataFrame 간 변환에 Arrow를 쓸지 정하는 설정이다. 이와 별개로, pandas UDF가 아닌 일반(스칼라) Python UDF에도 Arrow 기반 배치 전송을 적용할 수 있는 spark.sql.execution.pythonUDF.arrow.enabled 설정(또는 개별 UDF 등록 시 useArrow 파라미터)이 있다 — 코드를 pandas UDF로 다시 작성하지 않고도 기존 스칼라 UDF의 데이터 전송 구간만 Arrow로 가속할 수 있다는 뜻이다. 다만 이 최적화는 어디까지나 '전송'을 가속하는 것이지, 여전히 각 값을 하나씩 처리하는 함수 자체의 실행 방식(벡터화가 아님)은 그대로라는 점은 구분해서 이해해야 한다.
핵심 포인트
- spark.sql.execution.arrow.pyspark.enabled: DataFrame↔pandas 변환(toPandas/createDataFrame)에 Arrow 적용
- spark.sql.execution.pythonUDF.arrow.enabled(또는 useArrow 파라미터): 일반 스칼라 Python UDF의 데이터 전송 구간도 Arrow로 가속 — 단, 함수 자체의 벡터화 여부와는 별개
UDAF 대안 — applyInPandas로 그룹별 집계·변환하기
전통적인 타입 세이프 UDAF(Aggregator API, Scala/Java 전용)는 그룹별로 커스텀 집계 로직을 짤 때 쓰던 방식이지만, PySpark 환경에서는 groupBy(...).applyInPandas(func, schema)가 그 역할을 대신하는 경우가 많다 — '분할(split, 그룹별로 나눔) → 적용(apply, 각 그룹에 커스텀 Python 함수 실행) → 결합(combine, 결과를 다시 합침)'이라는 split-apply-combine 패턴을 구현한다. 각 그룹이 통째로 하나의 pandas DataFrame으로 Python 쪽에 전달되므로, 단순 합계/평균을 넘어 그룹 내부에서 복잡한 로직(여러 컬럼을 함께 보는 조건부 계산 등)이 필요한 집계·변환에 적합하다.
핵심 포인트
- applyInPandas(func, schema)는 split(그룹분할)-apply(커스텀함수)-combine(결과병합) 패턴으로 UDAF와 유사한 역할을 한다
- 각 그룹이 통째로 pandas DataFrame으로 전달돼, 단순 집계를 넘어 복잡한 그룹 내부 로직이 필요할 때 적합하다