엔지니어 블로그

[Spark] Spill로 인한 성능 저하 본문

Spark

[Spark] Spill로 인한 성능 저하

안기용 2025. 4. 6. 18:53

Spark의 대표적인 장애인 Spill을 알아보고 어떤 설정을 통해 성능 튜닝이 가능한지 알아보려고 합니다. Spill을 알아보기 전에 Shuffle,Partition의 개념을 먼저 알아보겠습니다.

Partition

Partition은 RDD를 구성하는 최소 단위 객체입니다. Spark의 성능,리소스 사용을 좌우하는 중요한 개념입니다. Partition은 여러 물리 노드에서 나누어 작업합니다. Spark의 최소 작업 단위를 Task라고 하고, 1개의 Task는 1개의 Partition에서 처리합니다. 또 1개의 Task는 1Core가 처리합니다. 즉, 1Task = 1Core = 1Partition입니다.
Partition의 수는 Spark 성능을 크게 좌우합니다. Partition의 수가 Core수를 결정하고 Partition의 크기는 Core당 필요한 메모리를 결정하기 때문입니다.
Partition을 늘리는 것은 Task당 필요 메모리를 줄이고 병렬화 정도를 높입니다

Shuffle

Shuffle은 특정 파티션 사이의 데이터가 재배치 될 때 일어나는 작업입니다. 특정 Transformation(groupBy,join등) 작업을 실행하면 다른 파티션의 데이터가 필요하기 때문에 Shuffle을 통해 찾게 되는 것 입니다.

Shuffle Spill

Spill은 Spark가 데이터를 처리하는 과정에서 처리 할 Partition이 너무 크거나, 메모리가 부족하면 발생합니다. Partition 사이즈, Shuffle 실행과 매우 깊은 연관이 있습니다. Shuffle Spill은 Disk,Memory 2종류로 나뉩니다.

구분 Memory Spill Disk Spill
정의  메모리 내 데이터가 할당된 공간을 초과해 스토리지 메모리와 충돌하거나 데이터를 밀어내는 현상 메모리에서 더 이상 데이터를 저장할 수 없을 때 디스크에 데이터를 기록하는 현상
발생 조건 - Execution Memory가 부족할 때 발생
- 작업 내에서 메모리 사용이 많거나 데이터가 반복적으로 사용되는 경우 
- Executor 전체 메모리가 부족할 때 발생
- Spark가 메모리에서 데이터를 처리할 수 없을 정도로 작업 규모가 크거나, Shuffle 데이터가 많을 때
영향 범위 - Executor 내부의 메모리 자원 간 충돌로 발생
예: Execution Memory ↔ Storage Memory 
- 전체 Executor 메모리가 부족해 데이터를 디스크로 Spill
발생 위치 메모리 상에서 Spark의 메모리 관리 로직 내에서 발생 메모리에서 데이터를 디스크로 이동할 때 발생
속도 영향 속도는 약간 느려질 수 있으나 디스크 I/O는 발생하지 않음 디스크 I/O가 발생하므로 작업 성능에 큰 영향을 미침

 

Spill이 발생하게 된다면 Task가 지연되고 최악의 경우 Spark가 종료 될 수 있습니다.

Spill Test

Spill에 대해 조금 더 잘 알아보기 위해 test 환경에서 Spill test를 진행 해봤습니다. 자원과 partition,row 수를 조정하면서 진행했으며, Shuffle이 발생하는 코드를 작성하여 진행했습니다.

Case1) Memory: 512m / Core: 1 / 10,000,000 row
Partitions =   16 | Rows = 1,000,000 | Time =    3.28 s
Partitions =   32 | Rows = 1,000,000 | Time =    2.21 s
Partitions =   64 | Rows = 1,000,000 | Time =    2.32 s
Partitions =  128 | Rows = 1,000,000 | Time =    3.00 s
Partitions =  256 | Rows = 1,000,000 | Time =    4.65 s
Case2) Memory: 512m / Core: 1 / 100,000,000 row
Partitions =   16 | Rows = 100,000,000 | Time =   68.77 s
Partitions =   32 | Rows = 100,000,000 | Time =   63.03 s
Partitions =   64 | Rows = 100,000,000 | Time =   62.00 s
Partitions =  128 | Rows = 100,000,000 | Time =   38.80 s
Partitions =  256 | Rows = 100,000,000 | Time =  758.41 s
Case3) Memory: 1024m / Core: 1 / 100,000,000 row
Partitions =   16 | Rows = 100,000,000 | Time =   61.02 s
Partitions =   32 | Rows = 100,000,000 | Time =   35.11 s
Partitions =   64 | Rows = 100,000,000 | Time =   42.18 s
Partitions =  128 | Rows = 100,000,000 | Time =   34.21 s
Partitions =  256 | Rows = 100,000,000 | Time =   41.26 s
결과

Case2 에서 Partition을 256으로 설정했을 때 유난히 작업 시간이 오래 걸리는 것이 보입니다. 이때 log를 확인해 보니 다음과 같았습니다.

ShuffleExternalSorter: Thread 27 spilling sort data of 20.0 MiB to disk (94  times so far)

1억 row, 256개의 파티션을 처리하기엔 메모리가 너무 적었고, OOM을 방지하기 위해 Spill이 발생한 것으로 보입니다.

결론

Case2를 보면 동일한 조건이지만 Partition가 적을때는 Spill이 발생하지 않았고, 오히려 128개까지는 처리 시간이 2배 가량 빨라지는 모습을 보였습니다. 또, Case3 에서 이전과 동일한 조건에 메모리 할당을 늘리니 해결 된 모습이 보입니다. 따라서 Spill 해결을 위해서는 적당한 Partition수와 Memory 할당을 늘리는 것이 필요합니다. 여기서 주의할 점은 Memory를 늘리면 해결하기는 수월하겠으나, 다수의 작업자가 사용하는 클러스터 환경이고, 여러 작업자가 Memory를 늘리게 되면 클러스터 전체의 사용성이 떨어지게 됩니다. 따라서 Memory 조정보다는 Partition 크기 조정을 우선적으로 검토할 필요가 있다고 생각합니다.

'Spark' 카테고리의 다른 글

[Spark] Lazy Evaluation  (0) 2025.04.06
[Spark] Partition  (0) 2025.03.26
[Spark] Spark 기본 동작  (0) 2025.03.25
Parquet란?  (0) 2025.03.21
[Spark] Spark Architecture  (0) 2025.02.25