دسته‌ها
یادداشت‌ فنی

دیر رسیدن، فرقی با هرگز نرسیدن ندارد!

آیا از کندی پایپ‌لاین‌های خود رنج می‌برید؟ fail شدن پایپ‌لاین‌ها امان شما را بریده‌اند؟ آیا به خاطر ضعیف‌بودن شبکه رانرها یا لود زیاد روی آن‌ها، پایپ‌لاین‌های شما حتی dependencyهای پروژه را نیز نمی‌توانند دانلود کنند؟ اگر پاسختان به این سوالات آری‌ست، احتمالا این مطلب برای شماست!

مدتی بود که ما در سینماتیکت، تقریبا از تمامی مشکلات بالا رنج می‌بردیم و کله‌هایمان ملول شده بود! دانلود base image داکرفایل‌ها به مشکل می‌خورد، npm نمی‌توانست dependencyهای پروژه را دانلود کند و اعتراف می‌کرد که مشکل از من است، checksum پیش‌نیاز‌ها در pip اشتباه بود و همه‌ی این‌ها، باعث می‌شدند نه داکر ایمیج‌ها بیلد شوند و نه تست‌ها اجرا. از خود مشکلات هم که بگذریم، گه‌گداری بودن آن‌ها، خبر از مشکل در جایی خارج از داکرفایل‌ها و پایپ‌لاین‌ها می‌داد.

بروز خطا در اجرای پایپ‌لاین‌ها
بروز خطا در اجرای پایپ‌لاین‌ها

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

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

روشن بود که باید تا جای ممکن از دانلود چندباره‌ی پکیج‌ها جلوگیری کنیم. بخشی از پایپ‌ها که بیشتر از سایر قسمت‌ها شبکه را درگیر می‌کنند، همین دانلود و نصب پکیج‌های موردنیاز هر پروژه است. ایده ساده بود: باید پکیج‌ها کش می‌شدند. اما چطور؟ چگونه کش‌ها را آپدیت کنیم؟ چگونه از آن‌ها روی تمامی برنچ‌ها و پایپ‌‌لاین‌های پروژه استفاده کنیم؟

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

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

حالا به اجرای فرایند توضیح‌داده‌شده در یک پروژه‌ی پایتونی می‌پردازیم. داکرفایل می‌تواند به شکل زیر باشد:

داکرفایل با چند استیج

همانطور که در داکرفایل پیداست، در استیج اول، یعنی base، پکیج‌های مورد نیاز پروژه نصب شده‌اند. چه پکیج‌های سیستمی و چه پکیج‌های پایتون. در استیج‌های بعدی به کدها پرداخته شده و فایل‌های لازم به داخل داکر ایمیج انتقال داده شده‌اند.

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

بیلد داکر ایمیج پروژه می‌تواند به شکل زیر انجام شود:

دستورات پایپ‌لاین

ابتدا وجود Cache Image را روی رجیستری بررسی می‌کنیم. اگر Image وجود داشته باشد که دریافت می‌شود، در غیر این صورت، باید ساخته شود.

در خط بعد، Cache Image ساخته می‌شود. اگر در قسمت قبل Image دریافت شده‌باشد، به خاطر استفاده از همان Image به عنوان منبع کش (--cache-from)، به سرعت انجام می‌شود. اگر هم Image دریافت نشده‌باشد، این‌جا ساخته می‌شود. در دستور سوم، برای ساخت Image اصلی از Cache Image به عنوان مرجع کش استفاده می‌کنیم تا قسمت نصب نیازمندی‌های پروژه، به ازای هر بار ساخت Image اجرا نشود. به این شکل می‌توان تا حد زیادی در ترافیک شبکه و زمان اجرای پایپ‌لاین‌ها صرفه‌جویی کرد.

تمام شد؟ تاثیرگذار هم بود؟

بعد از اعمال این تغییرات روی پایپ‌لاین‌ها و داکرفایل‌هایمان، باید بررسی می‌کردیم که آیا واقعا این تغییرات تاثیر به سزایی روی عملکرد و سرعت پایپ‌لاین‌ها می‌گذارند یا خیر. اگر از کاهش چشم‌گیر تعداد پایپ‌لاین‌هایی که به خاطر مشکلات شبکه با شکست مواجه شده‌بودند بگذریم، زمان اجرای پایپ‌لاین‌هایی که از کش استفاده می‌کردند، روی پروژه‌های پایتونی تا ۱۰ برابر سریع‌تر شدند!

این قصه سر دراز دارد …

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

ساختار هرمی پیشنهادی برای داکرفایل‌ها

به طور مثال، نصب نیازمندی‌های سیستمی و پکیج‌های مورد نیاز، باید در ابتدای داکرفایل باشند. چرا که dependencyهای پروژه کم‌تر تغییر می‌کنند. اما انتقال کدها به داخل Image، باید در دستورات انتهایی داکرفایل باشد. چرا که تغییر کدها، متداول‌تر است.

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

از محمدحسین بهمنی

Trying to be good at something

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد.