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

کدنویسی هوش مصنوعی CI را به یک گلوگاه تبدیل کرده است، بنابراین ما برنامه خود را برای ادامه دادن دوباره کار کردیم

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

آدرس مقاله: https://linear.app/now/ci-bottleneck-reworked آدرس نظرات: https://news.ycombinator.com/item?id=49792067 امتیاز: 260 # نظر: 298

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

Agentها ارسال کد را به صورت تصاعدی سریع‌تر کرده‌اند، اما اعتبارسنجی این تغییرات به همان میزان ادامه پیدا نکرده است. هر روابط عمومی همچنان باید از CI عبور کند، بنابراین با تسریع توسعه، CI به یک گلوگاه تبدیل می‌شود و هزینه‌های زیرساختی را بالا می‌برد و توسعه‌دهندگان و عوامل را برای مدت طولانی‌تری منتظر بازخورد می‌گذارد.

در پیگیری ما برای عملکرد بیشتر CI در Linear، مدت زمان انتظار یک PR در CI و مدت زمان مصرف آن را بهینه کردیم. علی‌رغم اینکه مجموعه‌های آزمایشی ما از ابتدای سال تقریبا چهار برابر شده‌اند، زمان انتظار درخواست کشش را از بیش از 6 دقیقه به کمی بیش از 5 کاهش دادیم، در حالی که زمان دونده در هر آزمون را تقریبا به نصف کاهش دادیم.

پایگاه کد Linear در درجه اول TypeScript است، اما بسیاری از این بهینه سازی ها در سراسر زبان ها و زنجیره های ابزار اعمال می شود.

برخی از اولین دستاوردهای ما تقریبا نیازی به بهینه سازی خود CI ندارند. انتقال بارهای کاری از GitHub Actions به اجراکننده های شخص ثالث با CPUهای سریعتر، فضای ذخیره سازی با کارایی بالاتر و زیرساخت کش بهتر به ما ماشین های سریع تری را می دهد تا بتوانیم همان خط لوله را اجرا کنیم. در یک مقایسه مشابه دو روزه در دو طرف سوئیچ، مشاغل به طور متوسط ​​34٪ سریعتر اجرا می شوند، با برخی از بارهای کاری مانند tsc 52٪ کاهش می یابد.

به طور جداگانه، مدرن کردن زنجیره ابزار ما نیز نتیجه داد. با تغییر به tsgo، کامپایلر بومی TypeScript، میانه هفتگی چک tsc را 73 درصد کاهش داد، به اندازه‌ای بزرگ که گلوگاه را به طور کامل از کنترل تایپ خارج کند.

لینتینگ یکی دیگر از اهداف اولیه بود. تعداد انگشت شماری از قوانین لینت سفارشی ما به اطلاعات نوع TypeScript وابسته بودند، یا برای اعمال یک محدودیت یا اعمال یک اصلاح خودکار. این بدان معناست که هر اجرا پرز باید قبل از ارزیابی آن قوانین، گراف نوع کامل را بسازد، که باعث می‌شود پرده‌بندی به یکی از پرحافظه‌ترین کارهای CI ما تبدیل شود.

ما قوانین را برای استفاده از تجزیه و تحلیل استاتیک بر روی درخت نحو انتزاعی، شناسایی ساختارهای تابع مانند و الگوهای نگهبان بدون اطلاعات نوع بازنویسی کردیم. این به ESLint اجازه می دهد تا TypeScript را به طور کامل حذف کند و زمان پرز API را تا 68٪ و زمان پر کردن مخزن کامل را تا 55٪ کاهش دهد. استفاده از حافظه نیز به میزان قابل توجهی کاهش یافته است.

حذف وابستگی به اطلاعات نوع نیز انتقال بعدی ما به Oxlint را بسیار آسان‌تر کرد زیرا قوانینی که صرفا بر روی نحو عمل می‌کنند برای پورت ساده هستند. خود Oxlint دقیقه‌های صرف شده برای پرزهای CI را کاهش داد.

با زیرساخت‌های اساسی و بررسی‌های فردی سریع‌تر، ما کوچک‌نمایی کردیم تا به CI به عنوان یک سیستم نگاه کنیم. که توجه ما را به مشاغل کوچکی که جلوی هر چیز دیگری می نشستند جلب کرد. هر اجرا با بررسی مسیرهایی که یک PR لمس کرده است و اینکه آیا این تست‌ها قبلا برای ورودی‌های یکسان گذرانده‌اند یا خیر شروع می‌شود. ما این چک‌ها را در سطح شغلی می‌بندیم، بنابراین کار نادیده گرفته شده هرگز دونده را محفوظ نمی‌دارد، اما همین امر آنها را مستقیما در مسیر بحرانی قرار می‌دهد. هیچ‌کدام از هشت قطعه تست API نمی‌توانند تا زمانی که به پایان برسند شروع شوند، و همین امر باعث می‌شود تاخیرهای کوچک نیز به‌طور نامتناسبی مهم باشد.

بسیاری از گردش‌های کاری ما با یک کار تشخیص تغییر شروع می‌شوند که تصمیم می‌گیرد چه چیزی در آینده اجرا شود. به عنوان مثال، بررسی می کند که آیا یک تفاوت حاوی مهاجرت پایگاه داده است یا خیر و سیگنالی را که برای زمان بندی بررسی های CI پایگاه داده مربوطه استفاده می شود، خروجی می دهد. این شغل‌ها درخت کامل کار را بررسی می‌کردند، حتی اگر فقط به زیر مجموعه کوچکی از آن نیاز داشتند. ما عمق واکشی را محدود کردیم، که کندترین این دروازه‌ها را از 94 ثانیه به 20 ثانیه رساند، و پرداخت را به طور کامل از مشاغلی که هرگز به درخت کار نیاز نداشتند حذف کردیم، و زمان صرف شده برای آن‌ها را از 27 ثانیه به 7 کاهش دادیم.

برای رویدادهای commit push و ادغام صف، جایی که باید مسیرها را متفاوت کنیم، متوجه شدیم که یک پرداخت پراکنده و بدون حباب با سابقه محدود کافی است و 11 ثانیه فرد دیگر صرفه جویی می کند.

پس از تعویض زیرساخت های دونده زیربنایی، متوجه شدیم که زمان پرداخت ما (با اقدامات/تسویه حساب) در مشاغلمان طولانی تر شده است و گاهی اوقات متوقف می شود. از آنجا که رانرهای شخص ثالث خارج از شبکه GitHub می نشینند، برای دسترسی به GitHub به یک پیوند IP مستقیم متکی هستند. ارائه‌دهنده هنگ‌ها را به تخریب متناوب در آن پیوند ردیابی کرد. بسیاری از گردش‌های کاری ما با پرداخت شروع می‌شوند، بنابراین واکشی متوقف شده می‌تواند کل اجرای CI را به تاخیر بیندازد.

برای مقاوم بودن در برابر ناپایداری شبکه، اقدامات/پرداخت را با یک کنش ترکیبی از خود جایگزین کردیم که با backoff مجددا امتحان شد، و GIT_HTTP_LOW_SPEED_LIMIT و GIT_HTTP_LOW_SPEED_TIME را تنظیم کردیم تا یک اتصال متوقف شده پس از حدود 30 ثانیه قطع شود، به جای اینکه gouts را متوقف کند و همچنین از آن استفاده می کند. یک دیسک چسبنده نتیجه، تعداد دفعات بسیار کمتری بود، جایی که یک کار در مسیر بحرانی در حالت بیکار و منتظر پایان پرداخت بود.

لازم نیست هر شغلی در مسیر حیاتی وجود داشته باشد. ما نشانگرهای کش را به عنوان بخشی از بررسی نهایی قبل از ادغام می‌نوشتیم، که به این معنی بود که درخواست کشش می‌تواند حتی پس از گذراندن آزمایش‌هایش در صف ادغام قرار گیرد. ما آن رایت را به شغلی منتقل کردیم که پس از اتمام قطعات آزمایشی اجرا می‌شود، اما هیچ دروازه‌ای ندارد، برای هر درخواست کشش API و ورود صف ادغام، 42 ثانیه از مسیر ادغام فاصله می‌گیرد.

این تغییرات با هم، تقریبا یک دقیقه از بررسی مورد نیاز برای درخواست‌های کشش API در موارد از دست دادن حافظه پنهان را گرفت، در حالی که شروع‌های دونده را نیز کاهش داد.

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

قطعات تست API ما هر کدام 7 تا 8 ثانیه را صرف نصب یک مشتری Postgres با apt در هر اجرا کردند. ما آن را به یک تصویر پایه CI کوچک حاوی Node و کلاینت منتقل کردیم، بنابراین هر قطعه می تواند از محیطی که آماده اجرا بود شروع شود. ما بعدا هدرهای ساخت بومی مورد نیاز را به تصویر اضافه کردیم، پس از اینکه متوجه شدیم که دانلود آنها در حین راه‌اندازی ممکن است گهگاه آویزان شود و دم را کوتاه کند.

پایگاه کد Linear یک monorepo است که به عنوان یک فضای کاری pnpm مدیریت می شود. گردش کار تست API ما کل فضای کاری را نصب می کرد حتی اگر فقط به بسته API و وابستگی های آن نیاز داشت. محدود کردن نصب به بسته API ما نصب pnpm را از 44-73 ثانیه به 16-18 ثانیه کاهش می دهد. ما همین الگو را برای کارهای مجاور API اعمال کردیم، که هر کدام مخزن کامل را نصب می‌کردند و یک کش وابستگی را آپلود می‌کردند که بعدا اجرا می‌شد تقریبا هرگز مورد استفاده قرار نمی‌گرفت.

ما همچنین caching node_modules را آزمایش کردیم و متوجه شدیم که بازسازی سریعتر است. کلید حافظه پنهان به فایل قفلی که اغلب در حال تغییر است وابسته بود، و حتی بازیابی حافظه پنهان حدود 28 ثانیه طول می کشد، در حالی که برای نصب فیلتر شده این زمان تقریبا 7.5 ثانیه طول می کشد. حافظه نهان صرفه جویی در زمان و تغییرپذیری را بدون اینکه مزیت قابل تشخیصی به ما بدهد اضافه می کرد.

این سه تغییر با هم، زمان راه اندازی هر شارد را تقریبا 44 درصد از 110-140 ثانیه به 67-73 ثانیه کاهش دادند.

فراتر از این، اشکال دیگری از راه‌اندازی مکرر وجود داشت که می‌توانستیم به‌کلی از آن اجتناب کنیم.

برخی از کارهای راه‌اندازی فقط زمانی باید تکرار شوند که ورودی‌های آن تغییر کنند. برای مثال، کانتینرهای API ما، تاریخچه انتقال کامل پایگاه داده را در هر اجرا دوباره پخش می‌کردند، حتی زمانی که یک PR طرح را تغییر نداده بود. برای آن موارد، ما به جای آن به بارگیری یک اسنپ شات طرحواره تولید شده و فایل بوت استرپ روی آوردیم و تنظیمات پایگاه داده را از تقریبا 12 ثانیه به 1-2 ثانیه در هر ظرف کاهش دادیم.

هفت چک مستقل هر کدام یک رانر را شروع می‌کردند، مخزن را بررسی می‌کردند و قبل از انجام تنها چند ثانیه کار مفید، وابستگی‌ها را نصب می‌کردند. ما آنها را در دو شغل ادغام کردیم و سپس هفت وظیفه را همزمان در داخل آنها اجرا کردیم. این باعث شد تعداد دفعاتی که همان سربار راه‌اندازی را پرداخت کرده‌ایم از هفت به دو بار کاهش دهیم. براساس استفاده ژوئن، این تغییر تقریبا 87000 دقیقه دونده در ماه صرفه جویی کرد که معادل 11.8٪ از کل استفاده از CI ما است.

با کاهش هزینه ثابت هر قطعه آزمایشی، می‌توانستیم مجموعه API را با شدت بیشتری موازی کنیم. این بزرگترین و یکی از پرتکرارترین بخش‌هایی بود که در جریان کار ما اجرا می‌شد، بنابراین پیشرفت‌ها در آنجا تأثیر زیادی بر زمان ادغام داشتند.

Vitest، اجرای آزمایشی که ما برای مجموعه‌های آزمایشی TypeScript خود استفاده می‌کنیم، کار را به‌جای طول مدت آزمایش‌های فردی، بر اساس فایل توزیع می‌کند. این بدان معناست که چند فایل آزمایشی غیرمعمول بزرگ می‌توانند بر یک خرده تسلط داشته باشند و به طور مؤثری تکمیل کل مجموعه را متوقف کنند، حتی زمانی که سایر فایل‌ها خیلی زودتر به پایان می‌رسند.

ما آن فایل‌های بزرگ را با حفظ ساختار تست‌ها به فایل‌های کوچک‌تر و متمرکزتر تقسیم کردیم، سپس پیکربندی‌های مختلف shard و runner را ارزیابی کردیم. ما قبلا در اوایل سال از سه به چهار قطعه رسیده بودیم. حرکت به هشت، کار مهم را تقریبا 19٪ سریعتر و 19٪ ارزانتر در معیار اولیه ما کرد. یک هفته پس از تغییر، کندترین قطعه از 5.25 دقیقه به 4.33 دقیقه کاهش یافت.

Vitest معمولا هر فایل آزمایشی را ایزوله می کند، که برای ما به معنای بازسازی موجودیت، GraphQL و نمودار تزئینی در هر قطعه آزمایشی است. ما یک پروژه انتخابی ویتست را با ایزوله: نادرست معرفی کردیم که به فایل‌های امن اجازه می‌دهد تا یک رجیستری ماژول را در هر کارگر به اشتراک بگذارند.

این بزرگترین بهبود عملکرد واحد ما بود که تقریبا 17٪ از پس انداز ماهانه در حجم ما ارزش داشت. کندترین قطعه از حدود 300-379 ثانیه به حدود 195 ثانیه کاهش یافت، در حالی که کل زمان دونده API-shard از حدود 32.8 به 22 دقیقه در هر اجرا کاهش یافت.

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

اشتراک گذاری بیشتر فقط زمانی جواب می دهد که هزینه ثابت برای هر خرده کم باشد، زیرا دوبرابر کردن تعداد خرده ها نیز زمان گردش کار صرف شده برای راه اندازی را دو برابر می کند. بهینه‌سازی‌های راه‌اندازی که قبلا به آنها اشاره کردیم همان چیزی است که هشت قطعه را کاربردی کرده است. با سرعت 110 تا 140 ثانیه در هر قطعه، 8 قطعه 15 تا 19 دقیقه زمان دونده را صرف راه اندازی می کنند، که بیشتر از خود تست هاست. راه‌اندازی اکنون حدود 40 ثانیه است، بنابراین هشت قطعه زمان کل راه‌اندازی کمتری را نسبت به چهار مورد قبلی صرف می‌کنند، در حالی که آزمایش‌ها را دوبرابر موازی می‌کنند.

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

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

AI coding has made CI a bottleneck, so we reworked ours to keep up

Article URL: https://linear.app/now/ci-bottleneck-reworked Comments URL: https://news.ycombinator.com/item?id=49792067 Points: 260 # Comments: 298

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