برای فهمیدن اینکه در یک برنامهی اسپارک دقیقا چه اتفاقهایی میافتد، باید با تعدادی از مفاهیم کلیدی برنامههای اسپارک آشنا شویم.
Application:
برنامهی کاربر در اسپارک که با APIهای آن نوشته شده است. این شامل یک Driver Program و Executorها بر روی کلاستر است.
SparkSession:
یک آبجکت است که نقطهی آغازین برای تعامل با امکانات اسپارک بهحساب میآید و اجازهی برنامهنویسی با استفاده از APIها را میدهد. اگر از Shell استفاده کنید، خود اسپارک یک SparkSession برای شما ایجاد میکند، در حالیکه در یک برنامهی اسپارک خودتان باید SparkSession را بسازید.
Job:
یک محاسبات موازی که شامل چند تسک است و با فراخوانی Spark Actionها مانند ()save و ()collect شروع به کار میکند.
Stage:
هر جاب به مجموعههای کوچکی از تسکها تقسیم میشود که به آنها Stage گفته میشود. هر کدام از آنها به دیگری متکی است.
Task:
یک واحد از کار یا اجرا که به Spark Executor ارسال میشود.
حال کمی بیشتر در این مفاهیم عمیق میشویم.
در مرکز هر برنامهی اسپارک(Spark Application)، Spark Driver قرار دارد که آبجکت SparkSession را میسازد. وقتی از Shell استفاده میکند، این درایور بخشی از Shell است و آبجکت SparkSession خودش ساخته میشود. میتوان با استفاده از متغیر spark به آن دسترسی پیدا کرد.
درایور طی تعامل با Spark Shell، برنامهی اسپارک را تبدیل به یک یا چند جاب میکند. سپس هر جاب را تبدیل به یک DAG میکند. این در اصل Spark Execution Plan یا برنامهی اجرای اسپارک است که در آن هر گرهی درون DAG میتواند یک یا چند Stage باشد.
بر اساس اینکه چه عملیاتی میتواند بهصورت موازی یا سریالی انجام شود، Stageها بهعنوان گرههای DAG ساخته میشوند. همهی عملیات اسپارک نمیتوانند در یک Stage انجام شود، پس باید به چندین Stage تقسیم شوند.
هر Stage شامل Taskهایی است(یک واحد از عملیات اجرا) که بعد در هر کدام از Executorها پخش میشوند؛ هر تسک به یک هسته نگاشت میشود و بر روی یک پارتیشن از داده کار میکند. بههمین خاطر، یک Executor با ۱۶ هسته میتواند حداقل ۱۶ تسک داشته باشد و روی حداقل ۱۶ پارتیشن از داده کار کند. این باعث میشود اجراهای اسپارک فوقالعاده موازی شوند!
تمام عملیات اسپارک به ۲ دسته تقسیم میشوند: Transformations و Actions. همانطور که از نام Transformation معلوم است، اینها یک Spark DataFrame را تبدیل به یک DataFrame جدید میکنند، بدون آنکه DataFrame اصلی را تغییر دهند(به آن خاصیت immutability میدهد) به عبارت دیگر، وقتی یک عملیات مانند ()select یا ()filter فراخوانی میشود، DataFrame اصلی تغییری نمیکند بلکه نتیجهی ترنسفورم شده بهعنوان یک DataFrame جدید باز میگردد.
تمام ترنسفورمها Laze Evaluated هستند؛ یعنی نتایج همان لحظه محاسبه نمیشوند بلکه بهعنوان یک Lineage ذخیره میشوند. یک Lineage به اسپارک اجازه میدهد که بعدا در برنامهی اجرا، یک سری ترنسفورمها را دوباره مرتبسازی کند، آنها را یکی کند یا آنها را تبدیل به Stageهایی بکند تا عملیات بهینه انجام شود. Lazy Evaluation استراتژی اسپارک برای به تاخیر انداختن عملیات اجرا تا زمانی است که یک Action فراخوانی شده است یا دادهها لمس شدهاند (از روی دیسک خواندن یا به روی دیسک نوشتن)
یک Action باعث به حرکت افتادن Lazy Evalutaion تمام ترنسفورمهای ذخیره شده میشود. در شکل زیر، تمام ترنسفورمهای T ذخیره شدهاند تا زمانی که Action A فراخوانی میشود. هر کدام از ترنسفورمهای T یک DataFrame جدید تولید میکنند.
این Lazy Evaluation باعث میشود تا اسپارک با نگاه کردن به ترنسفورمهایی که پشت سر هم آمدهاند، کوئریها را [با ترتیب مناسب و] بهینه اجرا کند. Lineage و Immutability به ما Fault Tolerance میدهند. چون اسپارک هر ترنسفورم را در Lineage ذخیره میکند و در این ترنسفورمها، DataFrame بدون تغییر باقی میماند، در صورت بروز هر گونه خطا، میتواند با دوباره اجرا کردن Lineage ذخیره شده، حالت اصلی را دوباره تولید کند.
تمام Actionها و Transformationها در یک Query Plan هستند. تا زمانی که یک Action فراخوانی نشده است، هیچ چیزی در Query Plan اجرا نمیشود.
یک نکتهی بسیار خوب Lazy Evaluation این است که اسپارک میتواند محاسبات کوئری شما را نگاه کند و مشخص کند که چطور میتواند آن را بهینه کند. این بهینهسازی میتواند بهصورت متصل کردن یا پایپلاین کردن بعضی عملیات و اختصاص دادن آنها به یک Stage باشد یا اینکه آنها را با توجه به نیاز داشتن به جابجایی داده در کلاسترها، به چند Stage تبدیل میکند.
این Transformationها میتوانند به دو دستهی Narrow Dependencies یا Wide Dependencies تقسیم شوند.
هر ترنسفورمی که یک پارتیشن خروجی میتواند از یک پارتیشن ورودی محاسبه شود، یک Narrow Transformation است.
بعضی توابع مانند ()groupBy اسپارک را ملزم به انجام Wide Transformation میکند یعنی Data Shuffle(حرکت داده) باید در هر کدام از پارتیشنهای Executorها اتفاق بیافتد. در این Transformationها، توابع، به خروجی دیگر پارتیشنها برای محاسبهی نتیجهی نهایی احتیاج دارند.
اسپارک یک محیط گرافیکی به نام Spark UI ارائه میدهد که میتوانید برنامهها، جابها، استیجها و تسکها را مانیتور کنید. بسته به اینکه اسپارک چگونه دیپلوی میشود، درایور یک Web UI را تولید میکند که بهصورت دیفالت بر روی پورت ۴۰۴۰ است.