چگونه Rubocop Linting را در CI خود 22 برابر افزایش دادیم

در Jobber، ما از صف ادغام GitHub به عنوان راهی برای انجام بررسیهای اضافی روی کدهایی که در شرف ادغام هستند استفاده میکنیم – و میخواهیم این مرحله صف ادغام سریع باشد (هدف زیر پنج دقیقه است).
ما متوجه شدیم که اجرای پردههای Rubocop ما در صف ادغام بسیار مفید خواهد بود، بهویژه زمانی که تغییرات قوانین یا قوانین سفارشی جدید اضافه شده است. مشکل این است که مرحله پرده زدن در بزرگترین پایگاه کد ما تقریباً 7 دقیقه طول می کشد – برای هدف صف ادغام ما بسیار طولانی است.
بررسی حافظه پنهان
روش فراخوانی Rubocop در CI با دستور زیر بود:
bundle exec rubocop
اما در مورد حافظه پنهان چطور؟ بدون مدیریت صریح دادههای مشاغل قبلی، Rubocop در هر اجرای CI از ابتدا شروع میکند. آیا از کش پشتیبانی می کند و آیا می توانیم از آن استفاده کنیم؟
به نظر می رسد که Rubocop در واقع یک پیاده سازی کش قوی دارد که از تمام کارهای سنگین، از جمله باطل کردن حافظه پنهان، مراقبت می کند:
اجراهای بعدی قادر به بازیابی این اطلاعات و ارائه اطلاعات ذخیره شده به جای بازرسی مجدد فایل خواهند بود. این کار در صورتی انجام می شود که حافظه پنهان فایل همچنان معتبر باشد، در صورتی که هیچ تغییری در موارد زیر وجود نداشته باشد:
محتویات فایل بازرسی شده
پیکربندی RuboCop برای فایل
گزینه های داده شده به rubocop، به استثنای برخی موارد که هیچ ارتباطی با تخلفات گزارش شده ندارند
نسخه روبی برای فراخوانی rubocop استفاده می شود
نسخه برنامه rubocop (یا به طور دقیق، هر چیزی که در کد منبع برنامه rubocop فراخوانی شده است)
کش بر اساس تعداد فایل ها به طور خودکار هرس می شود:
هر بار که یک فایل تغییر می کند، تخلفات آن تحت یک کلید جدید در حافظه پنهان ذخیره می شود. این بدان معنی است که حافظه پنهان تا زمانی که کاری برای متوقف کردن آن انجام دهیم به رشد خود ادامه خواهد داد. پارامتر پیکربندی AllCops: MaxFilesInCache محدودیتی را تعیین میکند و زمانی که تعداد فایلهای موجود در حافظه نهان از آن حد بیشتر شود، قدیمیترین فایلها بهطور خودکار از حافظه پنهان حذف میشوند.
این شگفت انگیز است – یک استراتژی ابطال کش به خوبی فکر شده! نکته دوم مربوط به ذخیره شدن تغییرات فایل تحت یک کلید جدید واقعاً به ما کمک نمی کند – مکانیسم کش CI تغییر ناپذیر است.
استفاده از حافظه پنهان Rubocop در CI
ما نمیتوانیم مستقیماً از Rubocop بپرسیم که قرار است چه کاری انجام دهد (هیچ API برای رفتار حافظه پنهان آن وجود ندارد)، بنابراین چگونه میتوانیم به طور قطعی یک کلید حافظه پنهان برای حافظه پنهان جریان متقابل تغییرناپذیر خود ایجاد کنیم که در مرحله قفل با کش Rubocop تغییر میکند. منطق ابطال؟
ابطال دوره ای
آیا میتوانیم این مشکل را کنار بگذاریم و حافظه پنهان را به صورت دورهای دوباره تولید کنیم؟ شاید روزانه، یا هفتگی، و استفاده مجدد از آن در تمام اجراهای CI؟ مطمئن! این مطمئنا کمک خواهد کرد – اما محدودیت های زیر را دارد:
-
با تغییر فایلها، بازدیدهای حافظه پنهان به مرور زمان کاهش مییابد. احتمالاً مشکلی نیست مگر اینکه بخش بزرگی از پایگاه کد در دوره حافظه پنهان اصلاح شود (چیزی مانند رفع خودکار پرده، یا refactor / تغییر نام).
-
اگر Rubocop تصمیم گرفت حافظه نهان را باطل کند، تا زمانی که دوره کش بعدی رخ دهد، بلافاصله به مدت زمان پرده سازی تمام قد باز خواهید گشت. رایج ترین عامل برای این تغییر در پیکربندی Rubocop است.
-
اولین اجرا بعد از هر دوره کش تمام طول خواهد بود.
-
کوتاه کردن دوره حافظه نهان برای کاهش برخی از مسائل فوق، اثر جانبی افزایش میزان ذخیره سازی کش مصرف شده توسط پروژه شما دارد.
باطل کردن پویا هوشمند
چه می شود اگر بتوانیم منطق لغو اعتبار کش داخلی Rubocop را با منطق بی اعتباری کش CI ادغام کنیم؟ محدودیت ها تبدیل به یک نقطه گلوله می شوند:
- با تغییر فایلها، بازدیدهای حافظه پنهان به مرور زمان کاهش مییابد. احتمالاً مشکلی نیست مگر اینکه بخش بزرگی از پایگاه کد در دوره حافظه پنهان اصلاح شود (چیزی مانند رفع خودکار پرده، یا refactor / تغییر نام).
توجه داشته باشید که سرویس CI معمولاً پس از حداکثر چند روز یک حافظه پنهان منقضی می شود. در مورد ما این هر 15 روز یکبار اتفاق میافتد، و بنابراین یک “تنظیم مجدد” طبیعی وجود دارد که با تغییر فایلها، کاهش ضربه آهسته حافظه پنهان را در طول زمان کاهش میدهد.
در اینجا آمده است که چگونه جابر با منطق Rubocop، ابطال کش CI ما را تقویت می کند!
-
قبل از بازیابی دایرکتوری کش rubocop (
~/.cache/rubocop_cache
)، یک فایل اختصاصی را با استفاده از دستور و پیکربندی مشابهی که مرحله پرده سازی کامل استفاده می کند، پر کنید. -
آنچه را Rubocop در دایرکتوری کش نوشته است بررسی کنید، و کلید کش خود را به عنوان هش از آن اطلاعات تولید کنید – در این مرحله، الگوی بازیابی، اجرا، تداوم را به طور معمول ادامه دهید.
در اینجا نحوه دریافت متنی که میخواهید هش کنید آمده است – با فرض اینکه از فایلی استفاده کردهاید که احتمال تغییر آن برای شناسایی شما بسیار کم است، این در اصل یک کلید حافظه پنهان Rubocop را نشان میدهد:
$ find ~/.cache/rubocop_cache -type f
/home/circleci/.cache/rubocop_cache/c21eac4b5c1ceb0445943396a341eadb756f46cf/7a1221dfb74d1bb683162bcc22951148cd32f1c9
خروجی آن به یک فایل (rubocop_cache_key
) و آن را هش کنید، آن را با سایر کلیدهای محیطی ترکیب کنید، و یک کلید کش قوی دریافت می کنید!
نمونه کلید حافظه پنهان:
rubocop-v1-{{ arch }}-ruby_<< pipeline.parameters.ruby_version >>-{{ checksum "rubocop_cache_key" }}
بخش کلید حافظه پنهان | شرح |
---|---|
rubocop |
توصیف کننده کلید حافظه پنهان – این یکی برای اهداف روبوکوپی منحصر به فرد در نظر گرفته شده است. |
v1 |
شماره نسخه دستی – هنگامی که مشکلات غیرمنتظره ای وجود دارد و می خواهید یک راه مستقیم برای باطل کردن حافظه پنهان دارید، این را افزایش دهید. |
{{ arch }} |
نماد CircleCI برای معماری، مانند arch1-linux-amd64-6_85 . |
ruby_<< pipeline.parameters.ruby_version >> |
نسخه روبی – سعی نکنید حافظه پنهان را در نسخه های روبی به اشتراک بگذارید. Rubocop تقریباً در این مورد نیز حافظه نهان را باطل میکند، اما در مورد ما، گردش کار راهاندازی ما نسخه Ruby را شناسایی میکند و آن را بهعنوان پارامتر خط لوله بهعنوان پارامتر خط لوله ارسال میکند تا بتوانیم آن را نیز بپزیم. |
{{ checksum "rubocop_cache_key" }} |
این هم بخش “هوشمند” و هم “پویا” است – بر اساس منطق بی اعتبارسازی هوشمند Rubocop است و پویا است زیرا متن را مستقیماً تحت کنترل منبع هش نمی کند. برای نحوه تولید به مثال های زیر مراجعه کنید rubocop_cache_keyfile . |
همه اش را بگذار کنار هم
بنابراین اکنون یک کلید کش مناسب داریم – به نظر می رسد که در یک گردش کار CircleCI استفاده شده است (در زیر یک نمونه جزئی از یک فایل پیکربندی CircleCI است)؟
references:
detect_rubocop_cache_key: &detect_rubocop_cache_key
run:
name: Detect rubocop cache key
command: bundle exec rubocop example.rb >/dev/null 2>&1 && find ~/.cache/rubocop_cache -type f > rubocop_cache_key && cat rubocop_cache_key
restore_rubocop_cache: &restore_rubocop_cache
restore_cache:
name: Restore rubocop cache
keys:
- &rubocop_cache_key rubocop-v1-{{ arch }}-ruby_<< pipeline.parameters.ruby_version >>-{{ checksum "rubocop_cache_key" }}
jobs:
lint_rubocop:
- *bundle_install
- *detect_rubocop_cache_key
- *restore_rubocop_cache
- run
name: Rubocop linting
command: bundle exec rubocop
- save_cache:
name: Save rubocop cache
key: *rubocop_cache_key
paths:
- ~/.cache/rubocop_cache
یادداشت برای پروژه های بسیار بزرگ
اگر تعداد فایل های شما نزدیک به 20 هزار باشد، می خواهید تنظیم کنید MaxFilesInCache
حداکثر تعداد فایل شما به اضافه درصدی برای جبران کمبود حافظه پنهان (فایل ها در طول زمان تغییر می کنند، بین باطل شدن حافظه پنهان).
پتانسیل بهبود بیشتر
هنگامی که میزان کاری را که CI خود برای پردهسازی انجام میدهد بهینه کردید، میتوانید از طریق موازیسازی آن کار سود بیشتری به دست آورید – یا از نوع چند رشتهای یا مقیاس افقی (هر دو مقدار کار یکسانی را شامل میشوند، اما اعمال نفوذ دارند. سخت افزار بیشتری برای تکمیل آن کار سریعتر – معمولاً با هزینه پولی).
نتایج بهبود عملکرد
قبل از ذخیره، پرده زدن 476 ثانیه طول کشید.
پس از کش کردن، پر کردن 22 ثانیه طول می کشد.
نتیجه (476 / 22 = 21.6
): 22 برابر سریعتر – به راحتی به اندازه کافی سریع برای اجرای یک بررسی کامل پرده در صف ادغام ما!
درباره جابر
تیمهای فنآوری فوقالعاده Jobber ما در سراسر پرداختها، زیرساختها، هوش مصنوعی/ML، گردش کار تجاری و ارتباطات فعالیت میکنند. ما روی پشته های فناوری پیشرفته و مدرن با استفاده از React، React Native، Ruby on Rails و GraphQL کار می کنیم.
اگر میخواهید بخشی از فرهنگ کار مشترک باشید، به کسبوکارهای کوچک خدمات خانگی کمک کنید تا مقیاسشان را افزایش دهند و تأثیر مثبتی بر جوامع ما ایجاد کنند، سپس برای کسب اطلاعات بیشتر از سایت مشاغل ما دیدن کنید!