"게으름"이라는 단어가 부정적인 의미를 가지고 있다는 데에는 거의 동의하지 않을 것입니다. 우리는 보통 열심히 일하려는 사람을 게으른 사람이라고 묘사합니다. 하지만 모든 게으름이 바람직하지 않은 것은 아니며, 특히 컴퓨터 과학 분야에서는 때로는 성실함보다 게으름을 선호하기도 합니다. 한 예가 게으른 평가입니다. 오늘은 게으른 평가가 Spark의 높은 성과에 왜 필수적인지, 그리고 Spark에서 게으른 평가가 어떻게 작동하는지 자세히 살펴보겠습니다.
게으른 평가의 정의부터 시작해 보겠습니다. 위키피디아에 따르면, 게으른 평가란 표현식 값이 필요할 때까지 평가를 지연시키는 평가 전략입니다. 즉, 표현식이 게으른 평가를 하면 그 값은 필요할 때까지 계산되지 않습니다. 왜 우리는 표현식을 만나자마자, 즉 열심히 평가하지 못하는지 궁금할 수 있습니다. 그 이유는 두 가지입니다:
우선, 게으른 평가는 Spark의 효율성을 높이는 데 도움을 줍니다. Spark는 촉매 최적화기를 사용해 데이터 연산을 가장 효율적으로 실행하는 방법을 자동으로 찾습니다. 만약 우리가 Spark에서 모든 표현식을 열심히 평가한다면, 촉매 최적화기는 모든 중간 표현식 계산을 위한 실행 계획을 별도로 최적화할 것입니다. 따라서 촉매 최적화기는 전역 최적 실행 계획을 생성할 수 없습니다. 반대로 게으른 평가 전략을 채택하고 모든 중간 표현식 계산을 건너뛸다면, 촉매 최적화 도구는 전체 연산 체인을 분석하여 전역 최적 실행 계획을 생성하는 데 필요한 정보를 얻을 수 있습니다.
쉬운 예로는 데이터프레임에 새로운 열을 추가하고 제거하는 것이 있습니다. 최종 표현식을 게으르게 평가하면 촉매 최적화 장치가 전체 연산 체인을 분석할 수 있습니다. 새로운 열의 추가와 제거는 연산 자체를 하지 않는 것과 같기 때문에, 촉매 최적화 장치는 Spark가 데이터를 추출할 때 초기 데이터프레임을 변경하지 않도록 지시하여 전체 과정을 매우 효율적으로 만듭니다. 하지만 중간 표현식을 포함해 모든 표현식을 열심히 평가한다면, 촉매 최적화 장치는 추가와 제거 작업을 제거할 충분한 정보를 갖지 못해 전반적인 비효율이 발생할 수 있습니다.

둘째, 게이 평가는 데이터를 저장하는 데 필요한 총 공간을 줄여줍니다. 만약 Spark가 모든 중간 표현식을 열심히 평가했다면, 이 표현식들이 어느 정도 공간을 차지해야 했습니다. 반면, 지연 평가는 중간 표현식을 저장하는 데 사용되는 임시 저장 공간을 줄이는 데 도움을 줍니다. 그 결과, 지연 평가는 Spark의 전체 공간 활용도를 향상시킵니다.
이제 슬쩍의 전반적인 효율성 향상에 슬쩍 평가가 중요하다는 점을 알렸으니, 이제 스파크에서 지연 평가가 어떻게 작동하는지 살펴보겠습니다. 모든 표현식을 열심히 평가하고 각 표현식에 대응하는 데이터를 계산하는 대신, 스파크는 데이터프레임을 사용해 데이터를 표현합니다. 이 데이터프레임들은 본질적으로 DAG(방향성 비순환 그래프)로, 모든 데이터 소스와 이 데이터프레임이 설명하는 데이터를 생성하는 데 필요한 수정 사항을 담고 있습니다. 보시다시피, 데이터프레임은 스파크가 게이지 평가를 실현하는 데 필수적입니다.

데이터프레임이 나타내는 데이터를 변경하기 위해 변환을 적용할 수 있습니다. 변환은 실제 수정을 하지 않고 데이터프레임을 수정하는 방법을 지정합니다. Spark는 데이터프레임에 액션을 호출하기 전까지 수정을 실행하지 않습니다. 변환과 달리, 액션은 적극적으로 평가됩니다. 액션은 데이터프레임이 나타내는 실제 데이터를 추출하는 과정을 포함합니다. (트랜스포메이션과 액션에 대해 더 알고 싶다면 이 블로그에서 확인할 수 있습니다.)
다른 예시를 살펴보겠습니다. 아래 플로우차트에서 보듯이, 우리는 세 개의 열을 가진 DataFrame(user_id, user_name, tenant_id)으로 시작합니다. 첫 번째 변환은 masked_user_name 열을 DataFrame에 추가하는 것입니다. 변환 후에는 네 개의 열(user_id, user_name, masked_user_name, tenant_id 네 열로 구성된 DataFrame이 됩니다. 그 다음 사용자 _name 열을 제거하는 또 다른 변환을 수행하면, 세 개의 열을 가진 DataFrame이 됩니다: user_id, masked_user_name, tenant_id. 마지막으로, DataFrame을 두 열로 구성된 다른 DataFrame(tenant_id과 tenant_name)과 연결합니다. 지금까지 많은 계산을 한 것처럼 보일 수 있습니다. 하지만 실제로는 DataFrame 변환만 수행했을 뿐이며, 평가는 게으르게 평가되기 때문입니다. 평가는 all() 메서드가 최종 DataFrame에서 호출된 후에야 시작되며, 이 과정이 전체 과정에서 유일한 동작입니다.

요약하자면, 게이 평가는 Spark가 시간과 공간 성능을 최적화하는 데 도움을 주고, Spark는 DataFrames와 변환을 통해 게으른 평가를 달성합니다. 이 글이 Spark와 Lazy 평가에 대한 이해를 높이는 데 도움이 되길 바랍니다.
추가 자료: Catalyst Optimizer : Spark SQL의 힘 Spark SQL의 Catalyst Optimizer 지향 비순환 그래프 동작, 좁은 변환, 광변환 깊이 탐구