پست اول، مقدمه ای بر Seastar استارت فریمورک Seastar توسط Avi Kivity… — An Inspired Engineer — TG.ME

پست اول، مقدمه ای بر Seastar

استارت فریمورک Seastar توسط Avi Kivity توی سال ۲۰۱۵ زده شد، هدف اصلی نوشتن الهام و کاربرد اولیه Seastar بازنویسی Cassandra بود و دیتابیس ScyllaDB رو توسعه دادن. حتی توی مقاله ‌ای که درباره ی ScyllaDB نوشتن گفتن که ادعای افزایش ده برابری پرفورمنس(نسبت به کدنویسی جاوا و استفاده از قفل‌ ها و روش‌های معمولی که برای ترد ها استفاده میشد) به کل برگرفته از قدرت استفاده از Seastar عه.

وقتی یه تیم بخواد یه دیتابیس با پرفورمنس خیلی بالا بسازه اگه بره سراغ فریمورکای عمومی و معمول، بخش زیادی از کارایی رو خرج قفل‌ها، همگام‌سازی بین تردها، تاخیرهای اشتراک حافظه و مدیریت تردها می‌کنه. برای همین ساخت یه فریمورک سطح پایین که بتونه I/O رو مستقیم کنترل کنه و بین هسته‌ ها مرزبندی دقیق داشته باشه، یه تصمیم منطقی و لازم بود.

حالا چرا پرفورمنسش بالاست؟!
بخش عمده‌ی افت پرفورمنس زمانی اتفاق می‌افته که CPU به‌جای اجرای دستور(حتی شده یه فورلوپ) تو حالت idle منتظر completion یه عملیات I/O یا آزاد شدن یه قفل بمونه.
هر نوع synchronization بین هسته‌ها چه از طریق mutex و چه cache line sharing باعث stall شدن pipeline و افت کارایی شدید میشه! درسته توی فریمورکای معمول مثل اسپرینگ بوت و... به چشم نمیاد ولی اگه قراره یه سیستم های پرفورمنس بسازیم اینجا اولین نقطه ایه که باید اصلاح بشه!
این تأخیرهای ظاهرا کوچک تو مقیاس بالا به latency بالا و عملکرد پایین ختم میشن.

یعنی فرض کنید ما یه برنامه ی مولتی ترد داریم که دارن روی یه پورت گوش میدن و منتظرن کانکشن جدید بیاد تا accept کنن و بعد شروع کنن به خوندن ازش و جواب دادن بهش! تا اینجا همه چی زیباست و خوب کار میکنه ولی مشکل اینجاست که همشون دارن کانکشن های جدید رو توی یه لیست مشترک نگهداری میکنن، ممکنه دوتا کلاینت توی یه لحظه وصل بشن و دوتا ترد همزمان بخوان لیست رو تغییر بدن پس مجبورن از قفل روی لیست استفاده کنن و بوم! اینجا پرفومنسمون ترکید! درسته این روش کار میکنه و خیلی هم زیباست و خیلی از فریمورکا دارن همین الان همینکارو میکنن ولی ما نمیخواییم که یه فریمورک عمومی باشیم! میخواییم یه فریمورک های پروفومنس بسازیم، پس چیکار کنیم؟ راه حل: معماری Shared Nothing

معماری Shared Nothing
قبلا اینجا صحبت کردم در موردش که چرا نیاز داریم
خب گفتم که هدفم zero context switch هست و باید برای اون معماری رو تغییر میدادم، حالا کاری که کردم اینه که اومدم یه io_context تعریف کردم که هر فید برای خودش، تایمراش و کانکشنایی که داره با این context کار میکنن، یعنی فرض کنین من میخوام با binance_feed وصل بشم به بایننس و مجبورم براش یه io context بسازم، هر io context یه ترد داره که میاد boost::asio::io_context رو روی یه ترد پین میکنه و روی اون اجرا میکنه.

حالا قسمت زیبای ماجرا اینجاست که این io context رو حتما باید به یه هسته ی cpu پین کنیم تا کرنل نیاد با عشق خودش بین هسته ها این ترد رو بازی بده، پس تا اینجا داریم که یه io context میسازیم و به یه هسته پینش میکنیم و بعد پاس میدیم به یه feed.


پس کاری که میکنیم اینه که میاییم جای اینکه یه ترد پول باز کنیم به تعداد هسته های ماشینمون ترد باز میکنیم و اونارو به هر هسته پین میکنیم، بعد روی هر هسته ایونت لوپ رو باز میکنیم و روی هر ترد به یک پورت واحد گوش میدیم(کرنل اینجا خودش هندل میکنه و اجازه میده از چندین ترد روی یه پورت گوش بدم که اینجا در موردش گفتم) و بعد هر کانکشنی که میاد با توجه به کانفیگ کرنل بین کانکشنا توزیع میشه و هر ترد هم لیست کانکشنایی که خودش نگهداشته رو داره، دیگه نیازی به این نیست که ترد ها بین همدیگه لیست کانکشن نگهدارن!
هیچی نباید بینشون رد و بدل بشه، مگر اینکه روی یه مدل از lock-free data structure باشه!

خلاصه اینکه Seastar اومده همه‌ی اون چیزایی که همیشه پرفورمنس رو می‌کشتن مثل قفل، context switch و اشتراک حافظه رو بندازه دور.
هر هسته کار خودشو می‌کنه، بدون اینکه مزاحم بقیه بشه و نتیجه‌اش یه فریمورکه که می‌تونه از هر CPU تا آخرین سیکلش استفاده کنه.

تو پست بعدی می‌رم سراغ این که Seastar دقیقاً چجوری این کارو می‌کنه، با reactor و futureهاش که مغز سیستمن

@knowpow
👍16❤13
October 9, 2025 2.9K 19