چالش‌های مدرن پردازش جریان کارایی و بهره‌وری

یک مهندس داده خوب

امروزه یک مهندس داده خوب، فقط کسی نیست که ابزارهای بیگ دیتا را بشناسد، بلکه مهندسی است که بداند کجا نباید از آن‌ها استفاده کند و چگونه می‌تواند با روش‌های جدید، کارایی بالاتر و هزینه کمتری داشته باشد. این رویکرد به‌ویژه با افزایش هزینه‌های پردازش داده و ظهور فناوری‌های کارآمدتر، بیش از پیش اهمیت پیدا کرده است.

اگر اخبار دنیای مهندسی داده را دنبال کنید، خواهید دید که بسیاری از شرکت‌ها به دلیل هزینه‌های بالای معماری‌های سنتی، به دنبال راه‌حل‌های جدیدتری هستند. نمونه‌هایی از این تغییرات عبارت‌اند از:

محبوبیت Polars و DuckDB به دلیل کارایی بالا و راه‌اندازی ساده.

  • رشد پروژه‌های مبتنی بر LakeHouse که هزینه ذخیره‌سازی داده‌های خام را کاهش داده و درعین‌حال امکان اجرای کوئری‌ها را فراهم می‌کنند.
  •  روندهای جدیدی مانند “بازنویسی کلان‌داده با Rust” که در راستای افزایش کارایی و کاهش هزینه‌ها مطرح شده است.

آدرس مقاله جریان بازنویسی با ٰزبان راست :    https://xuanwo.io/2024/07-rewrite-bigdata-in-rust

آدرس ریپوزیتوری مرتبط با این جریان :  https://github.com/rewrite-bigdata-in-rust/RBIR

یاروسلاو تکاچنکو اخیرا در مقاله ای با عنوان «بررسی چالش‌های نوین پردازش جریان» این موضوع را با تمرکز با فلینک و با مثال‌های مختلف توضیح داده است که در اینجا خلاصه آنرا با هم مرور می‌کنیم .


مقیاس‌پذیری لزوماً به معنی کارایی نیست

بسیاری از سیستم‌های توزیع‌شده مانند Hadoop، Spark، Kafka، Flink و غیره، در ابعاد مختلف بسیار قدرتمند هستند، اما هدف اصلی آن‌ها حل مشکل مقیاس‌پذیری بوده است، نه لزوماً افزایش کارایی. این دو مفهوم کاملاً متفاوت‌اند.

برای مثال: فقط به این دلیل که Flink می‌تواند هزاران تسک (Task Slot) را مدیریت کند، به این معنی نیست که هر تسک بهینه‌ترین محاسبات را انجام می‌دهد. در عمل، توسعه یک سیستم توزیع‌شده کارآمد بسیار دشوارتر است.

نمونه‌ای از یک مشکل عملی

یکی از کاربران Flink در Slack این مسئله را مطرح کرده است:

در حال حاضر دو گروه مصرف‌کننده Kafka داریم: گروه مصرف‌کننده خام که به راحتی مقیاس‌پذیر است و ورودی را از وسایل نقلیه دریافت می‌کند، و گروه مصرف‌کننده پردازشی که وظایفی مانند رمزگشایی و فیلتر کردن داده‌ها را انجام می‌دهد. مشکل اینجاست که وقتی بار کاری افزایش می‌یابد، گروه دوم به دلیل مصرف بالای پردازنده (CPU) کند می‌شود.

آیا جایگزینی گروه پردازشی با Apache Flink می‌تواند به ما کمک کند؟ آیا Flink توانایی پردازش این حجم از داده‌ها را با مصرف بهینه منابع دارد؟

پاسخ کوتاه این است: خیر!

این کار منطقی نیست، زیرا Flink برای این وظایف باید همان مقدار کار را انجام دهد که یک Kafka Consumer عادی انجام می‌دهد و در عمل وظایف بیشتری نیز بر عهده خواهد داشت، از جمله:

  • Checkpointing
  • سریال‌سازی و دسریال‌سازی اضافی
  • Shuffling داده‌ها

Flink در پردازش‌های Stateful مانند Joins و Aggregations عالی است، اما اگر یک Kafka Consumer دارید که فقط به رمزگشایی و فیلتر کردن ساده نیاز دارد، بهینه‌ترین راه حل این است که مصرف‌کننده فعلی را پروفایل و بهینه‌سازی کنید، نه اینکه آن را به Flink منتقل کنید.


مشکل دیگری که در استفاده از Flink SQL مشاهده شده است

در یک مثال واقعی دیگر، یکی از مهندسان داده که تمام پردازش خود را با Flink SQL انجام می‌داد، به مشکلی جدی در نرخ پردازش برخورد کرد:

برنامه من از ۱۰ تاپیک ورودی Kafka داده دریافت کرده و آن‌ها را پردازش می‌کند تا یک تاپیک خروجی تولید کند. عملیات شامل فیلتر کردن و نرمال‌سازی پیام‌ها است (فیلترهایی بر اساس مقادیر فیلدها و عملیات substring ساده).

  • ۹ تاپیک اول بین چند صد تا چند هزار پیام بر ثانیه تولید می‌کنند و هرکدام ۴ تا ۱۰ پارتیشن دارند.
  • یک تاپیک بزرگ‌تر ۱۵۰ هزار پیام در ثانیه تولید می‌کند و دارای ۵۰۰ پارتیشن است.
  • خروجی مورد انتظار باید ۶۰ هزار پیام بر ثانیه باشد تا تأخیر نداشته باشیم.

اما با وجود ۲۰ پاد (Pod)، ۱۲۰ سطح موازی‌سازی (Parallelism) و ۴ اسلات (Task Slot) در هر TaskManager، تنها ۲۰ هزار پیام بر ثانیه پردازش می‌شود و تاپیک بزرگ‌تر را به خوبی مصرف نمی‌کنیم.

🚨 مشکل کجاست؟

  1. Flink SQL امکان تنظیم موازی‌سازی دقیق برای Kafka Sources را نمی‌دهد. در نتیجه مجبوریم موازی‌سازی را روی تمامی تاپیک‌ها افزایش دهیم که باز خود باعث هدررفت منابع در تاپیک‌های کوچک‌تر می‌شود.
  2. وجود یک Join در SQL باعث افت عملکرد شده بود، زیرا بسیاری از پردازش‌های Join در Flink SQL بهینه نیستند.

بهره‌گیری از Rust برای بهینه‌سازی پردازش جریان

یکی از پیشرفت‌های اخیر در پردازش جریان، استفاده از زبان Rust برای بهینه‌سازی عملکرد و بهره‌وری سیستم‌ها است. Apache DataFusion Comet نمونه‌ای از این پیشرفت است.

✅ DataFusion Comet چیست؟
این پروژه مجموعه‌ای از عملگرهای Spark را با زبان Rust و موتور پردازش Arrow/DataFusion بازنویسی کرده است که نتیجه آن، افزایش سرعت تا ۲ برابر و کاهش هزینه پردازشی بوده است.

🚀 چرا Rust؟

  • مدیریت حافظه کارآمد: بدون نیاز به Garbage Collection
  • اجرای همزمان (Concurrency) پیشرفته: بدون شرایط رقابتی (Race Condition)
  • کارایی بالا: Rust در پردازش‌های سنگین و موازی بهینه‌تر از Java و Python عمل می‌کند.

در آینده نزدیک، انتظار داریم که بسیاری از پردازش‌های جریانی سنگین از Rust استفاده کنند، مشابه آنچه که Alibaba با Fluss (ذخیره‌سازی جریانی ستونی) و Flash (موتور Flink بهینه‌سازی شده با وکتورایزیشن) انجام داده است. همچنین، پردازش LakeHouse به جای Kafka می‌تواند هزینه‌ها را به شدت کاهش دهد، زیرا خواندن داده از یک جدول Iceberg یا Delta Lake بسیار کارآمدتر از پردازش همان مقدار داده در Kafka است.


کارایی = کاهش هزینه‌ها

چرا کارایی مهم است؟ چون کارایی مستقیماً به هزینه‌ها مرتبط است.

برای مثال، متا (Meta) یک تغییر کوچک در کد خود ایجاد کرد:

یک کاراکتر ” & ” در کد اضافه شد تا مقدار به جای کپی، به صورت مرجع (Reference) استفاده شود.

نتیجه؟ صرفه‌جویی معادل ۱۵۰۰۰ سرور در سال 😲

جمع‌بندی

🚀 مقیاس‌پذیری به معنی کارایی نیست!
📉 کارایی پایین = هزینه بالاتر
🔥 استفاده از Rust و تکنیک‌های بهینه‌سازی پایگاه‌های داده، آینده پردازش جریان را متحول خواهد کرد!

اگر در حال توسعه یک سیستم پردازش جریانی هستید، قبل از تغییر تکنولوژی، کارایی سیستم فعلی را بررسی کنید. در بسیاری از موارد، تغییر ساده‌ای مانند استفاده از پردازش‌های ستونی، بهینه‌سازی مصرف Kafka یا استفاده از Rust می‌تواند کارایی را چند برابر افزایش دهد بدون اینکه هزینه‌های زیرساختی شما را بالا ببرد.

نوشته های مشابه