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

متاسفم، اما شما هنوز باید فکر کنید

هکرنیوز۱۴۰۵ مهر ۱۹, یکشنبه، ساعت ۱۷:۵۹حدود 9 دقیقه مطالعه

آدرس مقاله: https://itsallaboutthebit.com/i-am-sorry-but-you-sill-have-to-think/ آدرس نظرات: https://news.ycombinator.com/item?id=50043689 امتیاز: 211 # نظرات: 77

DHH، خالق چارچوب Ruby on Rails، تصمیم گرفت تا Campfire Once را از Ruby on Rails به Rust بازنویسی کند. یا بهتر است از یک کلاهک بخواهید که این کار را برای او انجام دهد، زیرا او نمی تواند خودش خواندن یا نوشتن کد Rust را تحمل کند. پس از آن، او بازنویسی هایی را به زبان های دیگر، مانند Elixir و Go اضافه کرد.

این برای من بسیار جالب است، زیرا بینش بسیار خوبی از نتایج شخصی است که از عوامل هوش مصنوعی بدون خواندن کد استفاده می کند. در این مورد، آن شخص نیز تجربه برنامه نویسی زیادی دارد. و نتایج... خوب، اگر امیدوارید بتوانید خواندن کد را متوقف کنید، یا به این زودی فکر نکنید، چندان دلگرم کننده نیست. مشکلات متعددی با بازنویسی‌ها وجود دارد، اما اجازه دهید با تفاوت‌های غیرکارکردی شروع کنیم، زیرا آنها به زیبایی چیزی را نشان می‌دهند که به نظر می‌رسد بسیاری از مردم متوجه نمی‌شوند: اگر درخواست شما به اندازه کافی مشخص نباشد، بسیاری از تصمیم‌ها یک سکه هستند.

این به این دلیل است که بسیاری از سؤالات یک پاسخ صحیح ندارند. آیا می خواهیم سازگاری با عقب را حفظ کنیم؟ آیا ما بیشتر به تأخیر اهمیت می دهیم یا توان عملیاتی؟ سیستم چه مقدار حافظه می تواند تحت بارگذاری استفاده کند؟ آیا از دست دادن اعلان‌های محتوای جدید پس از خرابی قابل قبول است؟ شما ممکن است به آنها یا حداقل برخی از آنها اهمیت ندهید، اما زمانی که یک LLM آنچه را که شما فکر می کنید می خواهید اجرا کند، به طور ضمنی پاسخ داده می شود.

اگر به بازنویسی‌ها دقیق‌تر نگاه کنید، به سرعت متوجه خواهید شد که سازگاری با عقب و سایر محدودیت‌ها متفاوت است. نسخه Rust سازگاری 100% به عقب را حفظ نمی کند، برای مثال توکن CSRF را حذف می کند تا ذخیره سازی آسان تر شود. همچنین Redis را به نفع صف‌های در حال پردازش، به عنوان مثال برای اعلان‌ها، حذف کرد. بازنویسی اکسیر به نسخه اصلی Rails بسیار نزدیکتر است. این در حال حاضر مقایسه را تقریبا بی فایده می کند، زیرا این تفاوت ها مستقل از زبان هستند.

این مانند یک کلاهک نیست که "بازنویسی در اکسیر" را شنیده باشد و به دلیل زبانی که استفاده شده است، سازگاری 100٪ به عقب را حفظ کند. اما حتی بهتر می شود!

اگر به کد نگاه می‌کردید، و بله، می‌دانم، ما دیگر نباید کد بخوانیم، به سرعت متوجه می‌شوید که بسیاری از چیزها عالی نیستند. به عنوان مثال، من افرادی را دیده ام که شکایت دارند که نسخه Elixir دارای یک فرآیند واحد است که به طور متوالی تمام جستارهای SQL را پردازش می کند، حتی اگر این موارد خواندنی باشند که می توانند همزمان اجرا شوند. این یک شکایت منصفانه است، اما به نظر می‌رسد DHH فکر می‌کند این بهترین ظاهر برای Elixir نیست که کلاهک نتواند کد عملکردی بنویسد. وقتی به نسخه Rust نگاه می کنم مطمئن نیستم که موافقم یا نه.

زیرا در نسخه Rust برخی از عملیات پایگاه داده ناهمگام نیستند، که در برخی موارد ممکن است بدتر باشد. ببینید، در Rust، هنگام استفاده از یک زمان اجرا غیر همگام، زمان‌بندی پیشگیرانه نیست، بلکه مشارکتی است. اگر وظیفه ای نتیجه ندهد، هیچ کار دیگری نمی تواند روی همان thread کارگر اجرا شود. این در عمل به این معنی است که زمان صرف شده در کار باید تا حد امکان کوتاه باشد. برای مثال، وقتی یک پرس و جوی SQL را اجرا می‌کنید که 100 میلی‌ثانیه طول می‌کشد، نمی‌خواهید همه وظایف دیگر منتظر بمانند، زیرا به هر حال وظیفه صادرکننده پرس و جو منتظر است.

بنابراین، در حالت ایده‌آل باید از یک عملیات ورودی/خروجی غیرهمگام استفاده کنید که در زمان انتظار برای پاسخ از پایگاه داده به زمان اجرا تسلیم می‌شود. در Rust بازنویسی برخی از پرس‌و‌جوها روی رشته‌های کارگر اجرا می‌شوند، اما برخی به‌عنوان عملیات مسدودکننده در وظایف همگام‌سازی اجرا می‌شوند. قفل ها هم همینطور هنگام استفاده از زمان اجرا async، مطمئن ترین شرط استفاده از قفل های async مانند tokio::sync::Mutex است. اگر مطمئن هستید که قفل برای مدت کوتاهی نگه داشته می شود، استفاده از یک نسخه غیر همگام خوب است، اما اگر یک قفل همگام سازی را برای 10 میلی ثانیه نگه دارید، همه وظایف روی همان رشته منتظر آن هستند، در حالی که خود کار مسدود کردن هیچ کاری انجام نمی دهد.

بنابراین، متأسفم که به شما اطلاع می‌دهم که نه، احتمالا کدنویسی "حل نشده" است و شما هنوز باید بدانید که چه می‌کنید.

با جلوتر رفتن، مشخص شد که معیارهای شیب نیز شیب هستند، زیرا آنها فقط توان عملیاتی را اندازه گیری می کنند و سایر ویژگی های سیستم را نادیده می گیرند. برای مثال، زک دنیلز نرخ تحویل اعلان‌های پست جدید را تحت بار اندازه‌گیری کرد و نرخ تحویل موفقیت‌آمیز 1% را در نسخه Rust تحت بار سنگین کشف کرد. ظاهر عالی نیست معیارها سخت هستند. اما حتی معیار بهبودیافته ممکن است در واقع چیزی را که می‌خواهید آزمایش کنید، بسته به ویژگی‌های سیستم شما، آزمایش نکند. می بینید، هنگامی که یک سیستم را آزمایش می کنید، ممکن است بخواهید بر ویژگی های مختلف تحت بار تأکید کنید.

هر دو بنچمارک DHH و Zach معیارهای حلقه بسته بودند، بنابراین آنها در حال آزمایش "چند درخواست می تواند سیستم در مدت زمان مشخصی باشد؟" بودند. آزمایش از N کلاینت استفاده می کرد که هر کدام به محض دریافت پاسخ برای قبلی، پیام جدیدی ارسال می کردند. با این حال، در بسیاری از موارد، افزایش بار روی سیستم ممکن است ناشی از تعداد زیادی از کاربرانی باشد که یک عمل را همزمان انجام می‌دهند، که منتظر نمی‌مانند تا سایر کاربران اقدامات خود را به پایان برسانند. در این مورد شما نرخ ثابت ورود را ترجیح می دهید.

شما می خواهید به جای اینکه نرخ به سرعت پاسخگویی سیستم بستگی داشته باشد، درخواست ها را با نرخ ثابت ارسال کنید. اگر می‌خواهید بررسی کنید که تحویل رویدادها چقدر قابل اعتماد است، بهتر است همان تعداد رویداد را با هم مقایسه کنید. در اینجا، اگر به نتایج نگاه کنید، نشان می‌دهد که Rust 1% اعلان‌ها از 6-7k را ارائه می‌دهد، در حالی که Elixir 100% اعلان‌ها را از ~1.7k ارائه می‌دهد. چیزی که نشان می‌دهد این است که نسخه اکسیر با فشار برگشتی بهتر برخورد می‌کند، اما این تنها تا زمانی که تعداد درخواست‌ها سیستم را بیش از حد بارگذاری نکند، برقرار است.

اما بیایید برای یک لحظه معیارها را رها کنیم و در مورد معاوضه ها صحبت کنیم، زیرا برنامه نویسی همه چیز مبادله است. مطمئنا، شرایطی وجود دارد که یک ابزار یا یک راه حل به وضوح بهتر است، بدون هیچ گونه جنبه منفی، اما بسیار نادر است، به خصوص زمانی که به سیستم های پیچیده ای می رسید که باید قابل اعتماد باشند. من افراد زیادی از جامعه اکسیر را دیده ام که پس از مشاهده نرخ تحویل 1 درصدی نسخه Rust به نتایج مختلفی رسیده اند، بدون اینکه واقعا تلاش کنند چرا این اتفاق می افتد. اجماع عمومی؟ اکسیر فقط در همزمانی بهتر است!

در Rust زمان‌بند همکاری می‌کند، چطور می‌توانید این‌طور زندگی کنید؟ من اکسیر را دوست دارم و با موفقیت از آن در تولید استفاده کردم، اما گلوله نقره ای نیست. بله، Elixir (یا سایر زبان‌های مبتنی بر BEAM) در همزمانی بسیار خوب هستند، و مسلما نوشتن کد همزمان در Elixir به طور کلی آسان‌تر از Rust است، اما به قیمت نداشتن کنترل سطح پایین، استفاده از حافظه بالاتر و اغلب سرعت است. دلیلی برای وجود Rustler وجود دارد. و اعلام برتری زبان بر اساس یک معیار واحد بدون دانستن علت اصلی، ممکن است گمراه کننده باشد.

به یاد داشته باشید که چگونه ذکر کردم که تست استرس حلقه بسته تنها یکی از ویژگی های سیستم را نشان می دهد زیرا Rust 4 برابر بیشتر درخواست ها را پردازش می کند و بنابراین باید رویدادهای بیشتری را که از طریق WebSocket به مشتری ارسال می شود رسیدگی کند؟ من آزمایش را با نرخ تحویل ثابت با استفاده از نسخه Rust DHH و نسخه Zach's Elixir با اصلاحات مختلف تکرار کردم. در 100 POST/s، مشتریان 14% رویدادها را در Rust دریافت کردند. در اکسیر ~60% بود. هنوز بهتر است، درست است؟ نه به این سرعت! در این سطح ترافیک Rust هیچ خطای HTTP نداشت. زمان Elixir در حدود 23 درصد از درخواست‌های HTTP POST تمام شد.

و در مورد تاخیر چطور؟ بدترین تأخیر تحویل رویداد نزدیک به ۱۸۰ ثانیه بود. در Rust زمانی که تحویل‌ها تأخیر دارند، ارتباط مشتریان قطع می‌شود. در اتصال مجدد، یک سرویس گیرنده مرورگر آخرین پیام ها را دریافت می کند، که تا حد زیادی نیاز به رویدادهای از دست رفته را بی اعتبار می کند. به نظر شما چه چیزی UX بهتر است: مشتری بی سر و صدا در پس‌زمینه وصل شود و به‌روزرسانی‌های جدید را واکشی کند، یا به مدت 3 دقیقه منتظر به‌روزرسانی در مورد یک پیام جدید باشد؟ که فقط نشان می دهد که باز هم، یک معیار واحد کل داستان را بیان نمی کند.

بازگشت به مبادلات آیا می دانید چرا نسخه Rust این همه پیام را تحت بار سنگین ارسال می کند؟ از کانال tokio::sync::broadcast برای پخش رویدادها به مشتریان متصل استفاده می کند. ممکن است لازم باشد رویدادی که در مورد یک پیام چت جدید اطلاع رسانی می کند به چندین اتصال ارسال شود، بنابراین کاملا منطقی است. یکی از ویژگی های کانال پخش این است که با یک ظرفیت تعیین شده محدود می شود. اگر گیرنده نتواند به اندازه کافی سریع پیام ها را مدیریت کند، گیرنده یک خطای RecvError::Laged دریافت می کند (برای اطلاعات بیشتر در مورد تاخیر به اسناد مراجعه کنید). ظرفیت پخش در بازنویسی Rust روی 256 تنظیم شد.

در Elixir، فرآیند GenServer تحویل رویدادها را مدیریت می کند و به طور پیش فرض صندوق پست GenServer محدودیتی ندارد. یک تغییر خط در Rust:

نرخ تحویل را در 100 req/s از 14% به ~90% افزایش می دهد. الان از اکسیر هم بهتره، نه؟ نه واقعا. من فکر می کنم که این نسخه در واقع بدتر است زیرا در این مورد بهتر است سریع شکست بخورید و مشتری را مجبور به اتصال مجدد کنید تا اینکه کارها را بسیار آهسته انجام دهید. می دانید بعد از افزایش ظرفیت چه چیز دیگری تغییر کرد؟ تأخیر تحویل رویداد pMAX از 11 ثانیه به بیش از 130 ثانیه افزایش یافت، مشابه رفتار Elixir، که به نظر من بسیار بدتر از قطع ارتباط مشتریان عقب مانده است.

اگر از من بپرسید، حتی 11 ثانیه بسیار طولانی است و اگر گیرنده نمی تواند رویداد را سریعتر از آن بگذراند، بهتر است رویداد را رها کنید. این نشان می‌دهد که تعیین محدودیت‌های معقول در سیستم چقدر مهم است، و در واقع، Elixir فقط به طور خودکار تمام مشکلات همزمانی را خارج از جعبه برطرف نمی‌کند. همچنین، اگر خودتان محدودیت‌هایی تعیین نکنید، احتمالا با محدودیت‌های خارجی مواجه خواهید شد. در طول تست استرس 100 req/s، نسخه اکسیر به 1.8 گیگابایت حافظه استفاده کرد. نکته دیگری که باید در مورد آن فکر کنید: آیا بهتر است برخی از پیام‌ها را رها کنید یا OOM را بکشید؟ معامله آف تمام راه.

اکسیر عالی است، اما به طور جادویی همه مشکلات شما را حل نمی کند. مهم نیست از چه زبانی استفاده می کنید، باید به حالت های شکست و مبادلات فکر کنید.

پس امروز چه آموختیم؟ هنوز باید انتقادی فکر کنید. خوب است بدانید که دارید چه کار می کنید. بر اساس یک معیار واحد فرضیات عجولانه نسازید. اگر می خواهید یک سیستم قابل اعتماد داشته باشید، باید بدانید که چه زمانی باید شکست بخورید. همچنین، معیارها سخت هستند.

خواندن متن کامل در هکرنیوزبه زبان اصلی، در سایت ناشر باز می‌شود
متن اصلی (انگلیسی)

I'm sorry, but you still have to think

Article URL: https://itsallaboutthebit.com/i-am-sorry-but-you-still-have-to-think/ Comments URL: https://news.ycombinator.com/item?id=50043689 Points: 211 # Comments: 77

همه‌ی اخبار فناوری