پرش به محتوای اصلی
پرش به محتوای مقاله

معماری Ractor در برابر خوشه‌های Puma در مدیریت حافظه

·۳۰ مرداد ۱۴۰۵۱۵ دقیقه مطالعه
سرور وب Ractor پرسرعت برای Ruby 4.0+: مبتنی بر Rack 3، با frontend Rust Tokio/Hyper و workerهای Ruby موازی Ractor.
سرور وب Ractor پرسرعت برای Ruby 4.0+: مبتنی بر Rack 3، با frontend Rust Tokio/Hyper و workerهای Ruby موازی Ractor.
اشتراک‌گذاری
واقعاً چه چیز جدید است؟

استفاده عملیاتی از Ractorها در یک سرور وب برای حذف کامل نیاز به Forking فرآیندها — این یعنی عبور از مدل سنتی مصرف حافظه در اکوسیستم روبین.

تصور کنید بتوانید بدون افزایش هزینه‌های زیرساختی، تعداد درخواست‌های دریافتی سرور خود را دو برابر کنید و هم‌زمان مصرف رم را به یک‌هفتم برسانید. این ادعای جسورانه، هسته اصلی معرفی Kino است؛ سرور وب جدیدی برای روبین ۴.۰ که در ۲۱ اوت ۲۰۲۶ منتشر شد. این سرور با بهره‌گیری از Ractorهای روبین ۴.۰ و یک استک شبکه با کارایی بالا در زبان Rust، موفق شده است محدودیت‌های قدیمی قفل جهانی ماشین مجازی (GVL) را دور بزند.

برای سال‌ها، توسعه‌دهندگان روبین با یک بن‌بست فنی مواجه بودند: GVL مانع از اجرای موازی کدهای روبین توسط چندین رشته (Thread) می‌شد. برای استفاده از تمام هسته‌های CPU، سرورهای تولیدی مانند Puma مجبور بودند از مدل «یک فرآیند برای هر هسته» (fork-per-core) استفاده کنند. در این مدل، هر فرآیند باید یک کپی کامل از برنامه را در حافظه داشته باشد. این معماری منجر به مصرف نجومی رم می‌شد، به‌ویژه زمانی که از فریم‌ورک‌های بزرگ استفاده می‌شد.

Kino این معماری را با جایگزینی لایه ورودی با زبان Rust و استفاده از کتابخانه‌های Tokio و Hyper برای مدیریت ورودی/خروجی شبکه تغییر داد. به جای تکثیر فرآیندها، این سرور درخواست‌ها را به Ractorها — مدل هم‌روندی مبتنی بر Actor در روبین — ارسال می‌کند. Ractorها شبیه به تکنسین‌های مستقلی هستند که هر کدام میز کار خود را دارند و بدون تداخل با هم کار می‌کنند. این رویکرد اجازه می‌دهد کد روبین به‌صورت واقعاً موازی در یک فرآیند کوچک اجرا شود، بدون آنکه حافظه سیستم توسط کپی‌های تکراری اشغال شود.

چرا اکنون Ractorها؟

روبین ۴.۰ قابلیت‌های Ractor را بازطراحی کرده تا این معماری عملی شود. بهبودهای کلیدی شامل معرفی Ractor::Port و shareable_proc و همچنین کاهش چشمگیر تداخل قفل‌ها (lock contention) است. اگرچه Ractorها در روبین ۴.۰ هنوز به‌طور رسمی در حالت آزمایشی هستند، اما هدف Kino این است که بهترین راه برای تجربه آن‌ها در حال حاضر و سرور پیشرو در زمان تثبیت کامل Ractorها باشد.

عملکرد و بنچمارک‌ها

بنچمارک‌ها روی یک سرور واقعی با مشخصات AWS c7a.2xlarge (۸ هسته AMD EPYC 9R14، ۱۶ گیگابایت رم، Amazon Linux 2023) و اجرای روبین ۴.۰.۵ به همراه YJIT انجام شده است. برای اندازه‌گیری سربار سرور (و نه منطق برنامه)، از یک برنامه Rack مصنوعی استفاده شد (متن ساده، بدنه ۱۰ کیلوبایتی، تابع فیبوناچی برای فشار به CPU و ۵ میلی‌ثانیه انتظار). این برنامه Ractor-shareable است و اجازه می‌دهد Kino در حالت :ractor اجرا شود.

طبق این نتایج، Kino در سناریوهای با ورودی/خروجی (I/O) سبک، به‌طور مداوم از Puma پیشی می‌گیرد. در نقاط انتهایی متن ساده، Kino در حالت Ractor به ۲۵۰,۲۲۲ درخواست در ثانیه رسید، در حالی که Puma تنها ۱۱۸,۱۷۶ درخواست را مدیریت کرد. در واقع، هر یک از حالت‌های Kino در نقاط انتهایی I/O-light، بین ۱.۵ تا ۲.۱ برابر سریع‌تر از خوشه Puma است.

در وظایفی که فشار روی CPU است، شکاف عمیق‌تر شد. در تست‌های محاسباتی فیبوناچی، حالت Ractor در Kino حدود ۳۴٪ سریع‌تر از خوشه Puma بود و حتی بیش از ۵ برابر سریع‌تر از حالت Threaded خودِ Kino (که محدود به GVL است) عمل کرد. این تمرکز بر بهینه‌سازی‌های سطح پایین برای دستیابی به حداکثر توان عملیاتی، مشابه رویکردی است که در بهینه‌سازی‌های SIMD برای افزایش سرعت توکن‌سازی به کار گرفته شد تا گلوگاه‌های سخت‌افزاری برطرف شوند.

تنظیمات و توپولوژی

Kino اجازه می‌دهد توپولوژی سرور بر اساس پروفایل انتظار برنامه تنظیم شود. در حالی که پیش‌فرض ۸ ورکر با ۱ رشته در حالت Ractor است، افزایش تعداد ورکرها به ۳۲ (بیش از تعداد هسته‌های ۸ گانه) منجر به افزایش ۲۵ درصدی عملکرد در نقاط انتهایی /io و افزایش ۳۴ درصدی از طریق Kino.sleep شد. این موضوع ثابت می‌کند که اسلات‌های Kino در واقع رشته هستند و نه فرآیند؛ بنابراین وقتی برنامه زیاد در حالت انتظار است، افزایش تعداد ورکرها بدون خروج از یک فرآیند کوچک، توان عملیاتی را بازیابی می‌کند.

برتری در مدیریت حافظه

بهینگی حافظه جایی است که Kino تغییرات دراماتیکی ایجاد می‌کند. این معیار با استفاده از PSS (اندازه مجموعه متناسب) پس از بارگذاری مداوم اندازه‌گیری شد. برای یک برنامه بنچمارک ساده، Kino در حالت Ractor تنها از ۱۴۸ مگابایت رم استفاده کرد، در حالی که خوشه Puma به ۱,۰۶۸ مگابایت نیاز داشت؛ یعنی کاهش ۷ برابری مصرف حافظه. در حالت Threaded، این شکاف حتی بیشتر شد (۱۰۷ مگابایت در مقابل ۱,۰۶۸ مگابایت) که نشان‌دهنده کاهش ۱۰ برابری است.

این شکاف برای برنامه‌های ساده بسیار زیاد است زیرا آن‌ها عمدتاً از Heapهای خصوصی هر ورکر تشکیل شده‌اند که قابلیت اشتراک‌گذاری Copy-on-Write را ندارند. علاوه بر این، حالت Ractor مشکل «تکه تکه شدن آرنا» (arena-fragmentation) را که در حالت Threaded دیده می‌شود، حل می‌کند. بدون تنظیم MALLOC_ARENA_MAX=2 در حالت رشته‌ای، ۲۴ رشته که پاسخ‌های ۱۰ کیلوبایتی را از طریق یک glibc heap جابجا می‌کنند، می‌توانند مصرف رم را تا ۶۷۰ مگابایت افزایش دهند.

حتی در اجرای یک برنامه Hello World با فریم‌ورک Rails، شکاف همچنان قابل توجه است. چون Rails هنوز Ractor-shareable نیست، Kino آن را در حالت Threaded (به عنوان جایگزین) اجرا می‌کند. حتی در این حالت محدود، Kino از ۹۲ مگابایت رم استفاده کرد در حالی که Puma به ۳۸۹ مگابایت نیاز داشت (صرفه‌جویی ۴ برابری). دلیل کمتر بودن این فاصله در Rails این است که فریم‌ورک بزرگ Rails از طریق Copy-on-Write بین فورک‌های Puma به اشتراک گذاشته می‌شود.

زیرساخت‌های آماده برای تولید (Production)

Kino تنها یک نمونه اولیه نیست و شامل ویژگی‌های ضروری برای پایداری و امنیت در محیط عملیاتی است:

سخت‌سازی شبکه:

  • ورودی ایمن: شامل TLS (از طریق rustls)، محافظت در برابر حملات Slowloris و مهلت‌های زمانی (Deadlines) برای دست‌دادن TLS (۱۰ ثانیه).
  • سقف اتصالات: پیاده‌سازی max_connections (پیش‌فرض بر اساس ulimit -n) و max_body_size (پیش‌فرض ۵۰ مگابایت) برای جلوگیری از خطاهای ۴۱۳ یا اتمام منابع.
  • مهلت‌های درخواست: مهلت‌های ثابت برای قطع کلاینت‌های کند در ارسال هدر (۱۵ ثانیه) و آپلودهای متوقف شده در میانه بدنه (۳۰ ثانیه).

پایداری و نظارت:

  • بازیابی از خرابی: دارای سیستم نظارت بر خرابی با قابلیت بازسازی خودکار. یک Ractor کرش کرده بلافاصله پاسخ ۵۰۰ به درخواست‌های جاری می‌دهد و سپس دوباره ساخته می‌شود.
  • فشار معکوس (Backpressure): صف‌های محدود (پیش‌فرض queue_depth: 1024) در صورت سرریز، پاسخ ۵۰۳ ارسال می‌کنند. همچنین queue_timeout (پیش‌فرض ۵ ثانیه) تضمین می‌کند کلاینت‌ها در یک صف پر معلق نمانند.
  • تخلیه آرام (Graceful Drain): پشتیبانی از shutdown_timeout (پیش‌فرض ۳۰ ثانیه) برای پردازش درخواست‌های باقی‌مانده قبل از توقف کامل.

مانیتورینگ و مشاهده‌پذیری:

  • صفحه کنترل (Control Plane): یک control_bind اختصاصی (مثلاً "127.0.0.1:9293") که یک صفحه کنترل فقط-خواندنی را روی رشته‌ای مجزا ارائه می‌دهد. این بخش حتی اگر تمام ورکرهای روبین قفل شوند، پاسخگو می‌ماند.
  • متریک‌ها: ارائه /stats (به صورت JSON) و /metrics (فرمت متنی Prometheus). این بخش مواردی چون kino_requests_served_total و kino_queue_depth و هیستوگرام kino_request_queue_seconds را برای شناسایی اشباع ورکرها ردیابی می‌کند.
  • ردیابی هر اسلات: متریک‌ها به تفکیک هر اسلات توزیع می‌شوند. متریک busy_ms به اپراتورها اجازه می‌دهد یک ورکر گیر کرده را شناسایی کنند در حالی که بقیه بیکار هستند. اسلات یک ورکر کرش کرده هرگز بازیافت نمی‌شود، بنابراین شمارنده‌های آن متوقف شده و با هر بازسازی، سری متریک‌ها یکی افزایش می‌یابد.
  • پروب‌های سلامت: شامل /ready (۲۰۰ هنگام سرویس‌دهی، ۵۰۳ هنگام بوت یا تخلیه) و /live (۲۰۰ اگر فرآیند زنده باشد).

سیستم لاگینگ

Kino از یک لاگر async بومی مبتنی بر Rust استفاده می‌کند که توان عملیاتی آن ۲.۴ برابر بیشتر از ::Logger استاندارد روبین است (۱۴۹ هزار در مقابل ۶۳ هزار درخواست در ثانیه). این لاگر از طریق یک کانال بدون قفل (lock-free) به یک رشته flusher در Rust می‌نویسد تا رشته‌های درخواست هرگز منتظر mutex لاگ نمانند یا syscallهای write انجام ندهند.

  • لاگ‌های دسترسی: گزینه log_requests true برای هر درخواست یک خط در stdout چاپ می‌کند (شامل ۵۰۳ها). خطوط بر اساس وضعیت رنگی می‌شوند (۲xx سبز، ۳xx زرد، ۴xx زرشکی، ۵xx قرمز روشن).
  • Kino::Logger: یک زیرکلاس از ::Logger برای لاگ‌های برنامه که با Rails سازگار است (config.logger = Kino::Logger.new).
  • سازگاری با Ractor: شیء Kino::Logger::Device منجمد (frozen) و Ractor-shareable است، بنابراین یک دستگاه می‌تواند به تمام ورکرها سرویس دهد.

حل کابوس دیباگ Ractor

یکی از بزرگ‌ترین موانع پذیرش Ractorها، خطای ترسناک Ractor::IsolationError است. Kino دستور kino --check را معرفی کرده است که برنامه را تحلیل کرده و دقیقاً مواردی که مانع حالت Ractor می‌شوند را لیست می‌کند.

به جای یک خطای کلی، این ابزار مسدودکننده‌های خاص را شناسایی می‌کند:

  • متغیرهای تصاحب شده (Captured Variables): نام متغیر و محل تعریف آن را ذکر می‌کند.
  • متغیرهای نمونه (Instance Variables): آن‌ها را از طریق مسیر (path) شناسایی می‌کند.
  • تله‌های سطح کلاس: برنامه‌هایی که تست Ractor.shareable? را پاس می‌کنند اما در اولین درخواست به دلیل متغیرهای نمونه در سطح کلاس خطا می‌دهند، شناسایی می‌شوند.

این ابزار به توسعه‌دهندگان اجازه می‌دهد اشیاء غیرقابل اشتراک را بدون رمزگشایی دستی قوانین ایزولاسیون VM روبین پیدا و منجمد کنند. همچنین با خروجی ۰ یا ۱، برای خط لوله‌های CI مناسب است.

توازن در Rails

برای کاربران Rails یک نکته وجود دارد. چون فریم‌ورک Rails در حال حاضر Ractor-shareable نیست، نمی‌تواند در حالت موازی Ractor در Kino اجرا شود. در این سناریو، خوشه Puma از نظر توان عملیاتی برنده است (حدود ۴.۶ برابر بیشتر درخواست سرو می‌کند) زیرا می‌تواند از تمام ۸ هسته استفاده کند، در حالی که حالت Threaded در Kino محدود به یک GVL است.

با این حال، این توازن صرفاً بین «توان عملیاتی» در مقابل «حافظه» است. Kino همچنان ۴ برابر کمتر از Puma رم مصرف می‌کند. توسعه‌دهندگان اشاره کرده‌اند که پس از رفع موانع در لایه‌های بالادستی Rails (که در doc/rails-on-ractors.md ردیابی می‌شود)، حالت Ractor در Rails می‌تواند شکاف عملکرد را پر کند و در عین حال مصرف رم پایین را حفظ نماید.

پیاده‌سازی فنی

Kino به گونه‌ای طراحی شده که جایگزینی مستقیم (drop-in) برای Puma باشد. از توپولوژی مشابه (تعداد ورکر ضربدر تعداد رشته) و یک DSL پیکربندی آشنا استفاده می‌کند. این سرور به صورت یک gem بومی پیش‌کامپایل شده برای لینوکس (x86_64/aarch64، glibc و musl) و macOS (arm64) عرضه می‌شود، به این معنی که کاربران برای استفاده در محیط تولید نیازی به نصب کامپایلر Rust ندارند. در پلتفرم‌های دیگر، gem هنگام نصب کامپایل می‌شود و به Rust toolchain و clang/libclang نیاز دارد.

پیکربندی پیشرفته و هوک‌ها

Kino کنترل عمیقی روی چرخه حیات ورکر از طریق چندین هوک فراهم می‌کند:

  • زمینه ورکر (Worker-context): هوک after_worker_boot (قبل از سرویس‌دهی) و after_request_complete (مسیر داغ، بعد از هر پاسخ). در حالت :ractor این‌ها باید Ractor.shareable_proc باشند.
  • زمینه اصلی (Main-context): هوک after_boot (برای سیگنال‌های آمادگی مثل sd_notify) و on_worker_exit (برای ثبت علت‌های کرش).
  • مدیریت خطا: هوک on_error اجازه ادغام با ردیاب‌های خطا را پس از ارسال پاسخ ۵۰۰ به کلاینت می‌دهد.

برای زمان‌بندی‌های با دقت بالا، Kino.sleep ارائه شده که GVL را آزاد کرده و از ساعت سیستم‌عامل استفاده می‌کند تا از مشکل بیدار شدن دیرهنگام (late-wake) در Ractorهای استاندارد MRI جلوگیری کند. همچنین مکانیزم «قرنطینه» (quarantine) وجود دارد: اگر درخواستی از quarantine_timeout (پیش‌فرض ۶۰ ثانیه) فراتر رود، Kino یک ورکر جایگزین می‌سازد تا ظرفیت را بازیابی کند، بدون اینکه ورکر گیر کرده را به اجبار بکشد (که در روبین ناامن است). quarantine_max تعداد کل این جایگزینی‌ها را محدود می‌کند تا از نشت حافظه بی‌نهایت جلوگیری شود.

انطباق با Rack 3 و سازگاری

Kino برای اکوسیستم مدرن روبین ساخته شده و کاملاً با Rack 3 سازگار است. مجموعه تست‌های آن هر برنامه را تحت Rack::Lint روی سوکت‌های واقعی اجرا می‌کند تا پشتیبانی از موارد زیر تضمین شود:

  • بدنه درخواست‌های استریم از طریق rack.input (فقط رو به جلو).
  • بدنه‌های پاسخ استریم دوطرفه (enumerable و callable).
  • هدرهای حروف کوچک و چند مقداری.
  • معناشناسی صحیح HEAD/204.

لازم به ذکر است که قابلیت full hijack عمداً حذف شده زیرا در Rack 3 اختیاری است. برای ادغام با ابزارهای موجود، Kino::Logger یک زیرکلاس واقعی از ::Logger است و در هر جایی که Rails انتظار لاگر دارد (مانند ActiveSupport::BroadcastLogger) قابل استفاده است.

این حرکت به سمت سرورهای روبین با پشتیبانی Rust، آینده‌ای را نشان می‌دهد که در آن VM روبین منطق برنامه را مدیریت می‌کند و یک لایه بومی (native) کارهای سنگین هم‌روندی و شبکه را بر عهده می‌گیرد. این رویکرد بهینه‌سازی لایه‌های زیرین برای مدیریت بهینه داده‌ها، یادآور نوآوری‌هایی است که در موتور Rayforce برای ترکیب تحلیل‌های ستونی و گراف دیده می‌شود تا کارایی عملیاتی به حداکثر برسد. برای توسعه‌دهندگان، این به معنای توانایی مقیاس‌پذیری عمودی روی نمونه‌های ابری کوچک‌تر و ارزان‌تر بدون قربانی کردن عملکرد است.

برای شروع تجربه، می‌توانید kino را به Gemfile خود اضافه کرده و دستور bundle exec kino --check را اجرا کنید تا ببینید آیا برنامه Rack شما برای انقلاب Ractor آماده است یا خیر.

چرا این موضوع مهم است؟

این ابزار هزینه عملیاتی سرورهای روبین را به‌ویژه در مقیاس‌های کوچک و متوسط به‌شدت کاهش می‌دهد. تخصص تیم توسعه در ترکیب Rust و Ruby، استانداردی جدید برای کاهش اثر GVL بدون از دست دادن سادگی توسعه ایجاد کرده است.

تأثیر برای ایران

برای توسعه‌دهندگان ایرانی که از سرورهای با منابع محدود (VPS) استفاده می‌کنند، این ابزار امکان میزبانی برنامه‌های سنگین‌تر روی سخت‌افزارهای ارزان‌تر را فراهم می‌کند.

·نگاه ما
تحریریه دات‌هوش

جایگزینی مدل Fork-per-core با Ractorها در Kino، نشان‌دهنده تغییر پارادایم از «تکثیر برای مقیاس» به «اشتراک برای کارایی» است. این رویکرد ثابت می‌کند که برای عبور از محدودیت GVL در روبین، لزوماً نیاز به تغییر در هسته زبان نیست، بلکه می‌توان با یک لایه مدیریت شبکه در Rust، بهره‌وری سخت‌افزار را به حداکثر رساند.

منابع

این گزارش با خط‌لولهٔ خودکار دات‌هوش از منابع معتبر جهانی تدوین و زیر نظر تحریریه منتشر شده است. روش کار ما

گفتگو

پنج‌شنبه‌های هوش‌محور

بسته‌ی هفتگی دات‌هوش

۵ خبر، ۲ ابزار، ۱ پرامپت در هر شماره. به‌زودی راه‌اندازی می‌شود — هر پنج‌شنبه صبح.

خبر کلیدی
ابزار کاربردی
پرامپت حرفه‌ای
تحلیل پژوهش
به‌زودی
زاویه‌ی ایرانی
به‌زودی
تمرین این هفته
به‌زودی

راهنماهای دات‌هوش

راهنماهای کاربردیِ دات‌هوش برای کار با هوش مصنوعی — از همین‌جا شروع کنید:

دات‌هوش

راهنمای فارسی هوش مصنوعی — با نگاه به ایران

اخبار روزانه، معرفی ابزارها و مدل‌ها، و آموزشِ کار با هوش مصنوعی؛ همیشه با این پرسش که از ایران چه چیزی کار می‌کند و چه چیزی نه.