Spark, Dask, Ray — 분산 워크로드를 어느 프레임워크에 맡길까

Apache Spark, Dask, Ray의 실행 모델과 스케줄링 방식을 비교하고 ETL·분산 학습·실시간 서빙·시뮬레이션 워크로드별 선택 기준을 정리한다.

2026-08-12 · 최초 발행 2025-12-12

같은 클러스터에 "분산 처리 붙여야 한다"는 요구가 들어와도, 그 요구가 대규모 조인인지 Pandas 코드 확장인지 하이퍼파라미터 탐색인지에 따라 정답이 갈린다. Apache Spark, Dask, Ray는 셋 다 여러 노드로 연산을 흩뿌린다는 목표는 같지만 스케줄링 단위와 생태계가 근본적으로 다르기 때문에, 워크로드를 먼저 규정하지 않고 프레임워크부터 고르면 나중에 되돌리는 비용이 크다.

프레임워크가 스케줄링을 다루는 방식

Spark는 JVM 기반 RDD/DataFrame 엔진 위에서 Stage/Task DAG를 정적으로 최적화한다. 카탈리스트 옵티마이저가 쿼리 계획을 세우고, SQL·MLlib·Structured Streaming이 같은 엔진을 공유하기 때문에 대규모 셔플과 조인에 특화돼 있다.

Dask는 순수 Python 생태계 친화적인 동적 태스크 그래프(DAG) 기반이다. 지연 실행과 워커 메모리 상황을 고려한 어댑티브 스케줄링을 쓰며, Pandas·NumPy·SciPy 호환성이 높아 함수 조합의 유연성이 크다.

Ray는 태스크·액터 모델 기반 범용 분산 런타임이다. 오브젝트 스토어를 매개로 미세 단위 스케줄링과 상태ful 액터 관리를 하며, RLlib·Tune·Serve 같은 ML/서빙 파운데이션이 그 위에 올라간다.

클러스터·리소스는 어떻게 관리되나

셋 다 Kubernetes/YARN 같은 외부 매니저나 내장 런처를 쓸 수 있고, CPU/GPU·메모리·가속기 할당과 격리·쿼터 관리가 필요하다는 점은 같다. 차이는 프리엠션 대응에서 드러난다. 오토스케일링과 스팟/프리엠티브 인스턴스 조합으로 비용을 낮추려면 세 프레임워크 모두 체크포인트·재시도 전략이 필수인데, Spark는 executor 단위 재시작이, Ray는 액터 재시작과 오브젝트 재배치가, Dask는 워커 재스케줄링과 부분 재계산(partial recompute)이 각각의 복구 경로다.

데이터 접근은 HDFS/S3/오브젝트 스토리지/DB 커넥터를 공통으로 지원하며, 데이터 지역성을 우선한 배치로 네트워크 비용을 낮춘다. Exactly-once 처리가 필요하면 Iceberg/Delta/Hudi 같은 트랜잭션 싱크와 배리어·2단계 커밋을 활용한다.

아키텍처 흐름

작업 제출DAG 생성·최적화리소스 요청컨테이너·팟 할당태스크 배치·우선순위데이터 접근상태·체크포인트 보고메트릭·로그 전송실패 감지재시도 / 스펙큘레이티브 실행결과 수집·커밋사용자 코드Spark / Dask / Ray드라이버 / 스케줄러작업 DAG클러스터 관리자Kubernetes / YARN / Mesos워커 노드 집합스토리지HDFS / S3 / DB메타스토어 / 코디네이션ZK / Etcd / Redis모니터링Prometheus / Grafana출력 싱크Delta / Iceberg / Kafka

워크로드별로 갈라 쓰는 실무 사례

대용량 ETL/ELT는 Spark SQL과 Delta/Iceberg로 배치 변환·머지를 처리하고 스키마 진화·ACID 트랜잭션을 붙이는 게 정석이다. 중규모 CSV/Parquet 전처리는 Dask로 끝낸 뒤 Spark 클러스터로 오프로딩하는 하이브리드 구성도 흔하다.

머신러닝 분산 학습·튜닝에서는 Ray Tune으로 하이퍼파라미터를 탐색하고 Ray Train이나 Spark MLlib로 분산 학습을 붙인다. GPU 자원 스케줄링과 혼합정밀도가 이 축의 관건이다. Dask-ML로 사이킷런 호환 분산 피팅을 할 수도 있는데, 이때는 데이터 분할·셔플 비용 관리가 중요해진다.

실시간 처리·서빙에서는 Spark Structured Streaming이 Kafka→레이크하우스 스트림 ETL을 exactly-once로 보장하고, Ray Serve는 온라인 추론 서비스에서 모델 버전 롤링 업데이트와 A/B 라우팅을 담당한다. 대규모 시뮬레이션·탐색적 분석은 Ray Actors로 에이전트 기반 시뮬레이션을 병렬화하거나, Dask로 대형 배열·이미지의 메모리 외(out-of-core) 연산을 처리하는 식이다.

프레임워크 비교

항목 Spark Dask Ray
성능(대규모 셔플) 매우 우수, Tungsten/Whole-stage 코드 생성 최적화 중간, Python 오버헤드 존재 중간~우수, 태스크 미세 스케줄링에 유리
확장성 수천 노드 검증 사례 다수 수백~소수천 워커 실무 사례 수천 노드, 초미세 태스크 대량 처리 적합
일관성/결정성 강함, SQL/ACID 테이블과 궁합 우수 데이터프레임 연산 결정성 양호 태스크·액터 상태 관리 유연, 패턴에 따라 상이
안정성/복구 성숙, 스펙큘레이티브·체크포인트 체계 확립 안정성 양호, 워커 메모리 압력 관리 중요 내결함성 우수, 액터 재시작·오브젝트 재배치 지원
운영 편의 생태계 풍부, JVM·튜닝 학습곡선 존재 Python 친화, 간결한 배포 범용 런타임, ML/서빙까지 일관 운영 가능

환경·데이터 특성에 따라 결과가 달라질 수 있어 표만 보고 결정하기보다 PoC로 지표를 검증하는 편이 안전하다.

분산 워크로드 관리 절차

처리량·지연·비용·신뢰성 목표를 정의하고 배치/스트리밍/온라인 여부를 구분하는 데서 시작한다. 소스·싱크와 데이터 크기·키 분포·스큐를 분석해 데이터 지역성 전략을 세우고, 코어/메모리/GPU 산정과 노드 타입 조합으로 클러스터를 사이징한다. 우선순위 큐와 페어 셰어·캐파시티 스케줄링 정책을 정하고, 장시간 태스크는 분할과 체크포인트 주기를 미리 설계한다. 키 스큐는 살팅(Salting)이나 스큐 조인으로 완화하고 적정 파티션 수를 잡는다. 재시도·스펙큘레이티브 실행·아이들 타임아웃과 배리어·2단계 커밋, 멱등적 싱크 설계로 신뢰성을 확보한 뒤, 메트릭·트레이스·로그를 표준화해 SLA 위반·실패율·백프레셔를 알림으로 잡아낸다. 오토스케일링과 스팟 혼합, 캐시·로컬 디스크 활용, 스토리지 클래스·압축·파일 크기 정렬로 비용을 최적화하고, IAM 최소권한·네트워크 정책·비밀관리·데이터 마스킹·감사 로그로 보안·규정 준수를 챙긴다.

프레임워크별 빠른 시작 코드

전제조건은 Python 3.10+와 쿠버네티스 또는 VM 클러스터, 클라우드 오브젝트 스토리지다. 버전 예시는 Spark 3.5.x, Dask 2024.10+, Ray 2.9+ 수준이며 최신 정보는 별도로 확인해야 한다.

Spark (PySpark)

from pyspark.sql import SparkSession

spark = SparkSession.builder \
    .appName("etl-example") \
    .getOrCreate()

df = spark.read.parquet("s3://bucket/input/")  # IAM/자격 구성 필요
result = df.filter("event_ts >= '2025-01-01'") \
           .groupBy("user_id").count()

result.write.mode("overwrite").format("delta") \
      .save("s3://bucket/output/delta_table")

spark.stop()

운영 참고: spark.sql.shuffle.partitions, 동적 할당(spark.dynamicAllocation.enabled) 조정.

Dask

from dask.distributed import Client
import dask.dataframe as dd

client = Client(address="tcp://scheduler:8786")
df = dd.read_parquet("s3://bucket/input/")
result = df[df.event_ts >= "2025-01-01"].groupby("user_id").size().compute()
print(result.head())

운영 참고: 워커 메모리 제한(--memory-limit), 어댑티브 스케줄링(client.adaptive) 사용.

Ray

import ray
ray.init(address="auto")  # Ray cluster

@ray.remote
def f(x):
    return x * x

results = ray.get([f.remote(i) for i in range(1_000)])
print(sum(results))

운영 참고: ray autoscaler, placement group으로 GPU/NUMA 배치 제어.

운영 모범사례와 트레이드오프

저장소·레이크하우스는 컬럼형 포맷(Parquet)과 테이블 포맷(Delta/Iceberg)을 채택하고 소파일 컴팩션을 정기화하는 게 모범사례지만, 강한 일관성·ACID를 얻는 대가로 쓰기 지연과 메타데이터 오버헤드를 감수해야 한다. 리소스·스케일링은 오토스케일링과 스팟 혼합, 네임스페이스·큐 단위 노이즈 격리, 스큐 태스크 분리가 기본이며 비용 절감과 프리엠션 위험·복구 시간 증가는 맞바꾸는 관계다. 셔플·네트워크는 외부 셔플 서비스와 로컬 SSD로 I/O를 빠르게 만들 수 있지만 비용과 운영 복잡성이 함께 늘어난다. 보안·비밀관리는 OIDC/IAM 역할 위임과 네트워크 폴리시, KMS·하드닝 이미지가 필요하고 이는 성능 오버헤드와 운영 비용을 동반한다. 관측성은 RED/USE 지표와 추적 상관관계(trace-id), 에러 예산 기반 조정이 유용하지만 저장·처리 비용이 함께 늘어난다는 점을 감안해야 한다.

분산컴퓨팅ApacheSparkDaskRay워크로드관리