Skip to main content

یادتان هست نتایج کنکور سازمان سنجش تا چند ساعت بعد از انتشار نتایج به دلیل شلوغی بیش از حد، قابل دسترس نبود؟ با وجود راه حل‌های فراوان متن باز، چرا بگذاریم چنین اتفاقی مجددا تکرار شود؟

در دنیای اینترنت، بخش اعظم خدمات وب و از طرفی پس زمینه‌ی اکثر خدمات موبایل، بر دوش وب سرور های عزیز و وب اپلیکیشن هاست. سرویس دهنده‌های وب به دلیل محبوبیت و کاربرد زیاد، برای تمامی سیستم های عامل و زبان‌های برنامه‌نویسی تولید شده‌اند. همینطور راه‌اندازی آنها بر روی رایانه‌ی شخصی شما بسیار ساده است و میتوانید تنها با نصب یک بسته‌ی نرم‌افزاری (به عنوان مثال XAMPP) مجموعه‌ی کامل و آماده‌به‌کاری را داشته باشید که شامل وب سرور Apache، زبان برنامه نویسی PHP و پایگاه داده‌ی MySQL است. اما این نصب ساده روی دستگاه شما،‌ به هیچ وجه پاسخگوی تعداد کاربر بالا برای یک سایت پر ترافیک نخواهد بود.

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

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

روند درخواست صفحه‌های پویا به صورت سنتی

مدتی پیش از این، به طور معمول از برنامه هایی مانند Apache به همراه افزونه‌ی php (معروف به modphp) (در کنار MySQL یا PostgreSQL به عنوان پایگاه داده) جهت ارایه ی سرویس استفاده میشد. بدین ترتیب که درخواست کاربر مراحل زیر را طی میکرد:روند درخواست به وب سرور و پردازش PHP

  1. مرورگر کاربر به Apache متصل میشود و درخواست را ارسال میکند.
  2.  برنامه ی Apache درخواست را دریافت میکند.
  3. در صورتی که فایل ثابتی مانند یک عکس درخواست شده باشد، آن را خودش تحویل میدهد.
  4. در صورتی که فایل غیر ثابتی مانند یک برنامه‌ی PHP فراخوانی شده باشد، آن را به modphp جهت پردازش تحویل میدهد.
  5. PHP درخواست را پردازش نموده و اطلاعات مورد نیاز را از پایگاه داده (عموما MySQL یا PostgreSQL) درخواست میدهد.
  6. MySQL به ازای هر درخواست، سوال (Query) مورد نظر را بررسی و جواب را محاسبه کرده و تحویل میدهد.
  7. نتیجه نهایی درخواست کاربر توسط PHP پردازش شده و به Apache تحویل میشود.

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

برنامه‌ی Apache که همگان سرویس دهی وب را با این اسم میشناسند، مدت‌ها به جامعه‌ی سروری خدمت کرده است اما این روزها ادمین سایت‌های پر بازدید به سمت نرم افزارهای سبک‌تری روی آورده اند. اولین مشکل این است که برنامه‌ی Apache پر از امکانات مختلف است، که همین قضیه میزان استفاده از حافظه و پردازنده را در آن بالا میبرد که در مقایسه، برنامه‌های سروری مانند nginx (که اِن جین ایکس خوانده میشود) یا Lighttpd بسیار سبک تر و سریع تر هستند و حافظه ی کمتری مصرف میکنند.

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

روش های استفاده از PHP، مزایا و معایب

وب سرورها، با سه روش زیر میتوانند از موتور پردازشگر PHP استفاده کنند. هر یک از این روشها، خوبی‌ها و بدی‌های خاص خودش را دارد که به آن میپردازیم.

۱- اتصال به صورت CGI

قدیمی ترین روش اتصال و استفاده از صفحه های پویا سی-جی-آی است که مفهوم بسیار ساده ایست و مختص زبان PHP به طور خاص نیست. این کلمه مختصر شده‌ی عبارت Common Gateway Interface است که به طور خلاصه برای وب سرور به این معنیست:

  1. فایل های ثابت عکس و متن و غیره را خودت به کاربر بده.
  2. برای بعضی آدرس ها، یک فایل اجرایی (CGI) را باز کن و محتوایی که آن فایل برایت تولید میکند را به کاربر بده.

پس مشخص است که فایل اجرایی میتواند هر چیزی (از جمله موتور پردازشگر PHP) باشد و وابسته به اینکه، کاربر سایت چه کسیست و یا عوامل دیگر، خروجی های متفاوتی تولید کند. به وضوح میتوان دید که اشکال اصلی این روش، اجرای یک فایل به ازای هر درخواست است که منابع پردازنده را در هر بار اجرا اشغال میکند. یعنی به ازای هر بار اجرا، فایل اجرایی CGI تعداد زیادی کار تکراری انجام میدهد تا به نقطه ای برسد که بتواند یک فایل PHP را بخواند و خروجی متناظر را تحویل بدهد و بعد بسته می‌شود. تکنولوژی CGI تقریبا منسوخ شده محسوب میشود و کمتر مورد استفاده قرار میگیرد.

۲- اتصال به صورت افزونه (ماژول) – modphp

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

یکی از مشکلات اصلی این روش، وجود اشتراک بین حافظه‌ی PHP و Apache ست که باعث میشود در صورت بروز مشکل در یکی از این دو، دیگری نیز تحت تاثیر قرار گیرد. حال این مشکل میتواند امنیتی باشد و یا یک اشکال (باگ) ساده در یکی از این دو نرم افزار. از طرفی جداسازی منابع پردازشی PHP از وب سرور در این روش ممکن نیست. ممکن است در سایت شما یک وب سرور کفایت کند اما منابع بیشتری برای پردازش PHP نیاز داشته باشید. این روش در سرورهای سبک تر مانند nginx پشتیبانی نمیشود.

یکی دیگر از مواردی که در این بخش ممکن است شما را با مشکل مواجه کند این است که پردازش های PHP تحت اجازه های کاربری همان وب سرور انجام میشوند. به عنوان مثال اگر برنامه‌ی apache تحت نام کاربری و مجوز www-data در حال اجرا باشد، فایلهایی که برنامه‌ی PHP شما میسازد نیز با همین نام کاربری ساخته میشود. این مورد برای سناریوهایی که چندین سایت را میخواهید به صورت ایزوله بر روی یک رایانه میزبانی کنید، مشکل زا خواهد بود.

۳- اتصال به صورت FastCGI

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

برنامه‌ی مسئول با یکی از برنامه هایی که سرش خلوت است، جواب مورد نیاز را تهیه کرده و برمیگرداند. در این روش، به جای اینکه تعداد مشخصی برنامه‌ی از پیش اجرا شده وجود داشته باشد، برنامه های مدیریتی مانند php-fpm میتوانند با توجه به تعداد درخواست ها، به صورت خودکار تعداد برنامه های پاسخگو را بیشتر کرده و روند پردازش را تسریع کنند. در این روش، شما میتوانید:

  • به تعداد دلخواه و با اجازه های کاربری مجزا، اقدام به تعریف نمودن برنامه های php-fpm کنید که این باعث میشود هر سایت به صورت ایزوله و با دسترسی های کاربری و امنیتی خاص خودش به فعالیت بپردازد.
  • به دلیل اینکه برنامه های پردازنده‌ی زبان از پیش اجرا می‌شوند، زمان کمتری برای پاسخ گویی به درخواست ها صرف میشود.
  • اگر مشکلی در یکی از پردازش های PHP پیش بیاید، هیچ تاثیری بر روی پردازنده های دیگر و یا وب سرور نمیگذارد و همان تک برنامه بسته خواهد شد.
  • با توجه به تعداد کاربران، به صورت پویا تعداد بیشتری برنامه‌ی پردازنده به صورت خودکار اجرا میشوند یا تعداد آنها کم میشود.
  • در صورت نیاز، پردازش PHP شما میتواند بر روی یک رایانه‌ی مجزا از وب سرور انجام شود.

مطالعات بیشتر : چرا nginx+phpfpm از apache+modphp سریع تر است؟

معرفی ترکیب بهینه برای سرویس دهی وب

تا اینجا وب سرور خود را از Apache به nginx تغییر دادیم تا منابع کمتری برای اولین لایه‌ی سرویس دهی مصرف کنیم. همچنین از آخرین روش استفاده از موتور پردازشگر PHP یعنی php-fpm بهره گرفتیم تا به صورت پویا و در محیطی قابل گسترش به کاربران سرویس دهی کنیم. شمای کلی معماری فعلی در شکل زیر آمده است.

معماری گسترش یافتی سرور وب ۱ - استفاده از nginx و php-fpm

بهینه سازی ۱ – استفاده از Varnish Cache در بیرونی‌ترین لایه

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

بیایید ببینیم به جای این همه کار، با خرج دادن کمی ذکاوت میتوانیم مشکل را حل کنیم یا نه. عموما کاربرانی که برای اولین به سایت شما مراجعه میکنند، با صفحات ثابت و مشابهی روبرو هستند. پس لزومی ندارد اگر شما مطلب جدیدی در هر ۵ دقیقه روی سایت منتشر نمیکنید، به ازای هر کاربری که ثانیه به ثانیه به سایت وارد میشود، یک بار پردازشگر PHP را درگیر کنید. نرم افزار Varnish برای چنین شرایطی بسیار مناسب است. این برنامه با توجه به آدرس ها و تعاریفی که به آن میدهید، جلوی وب سرور شما یک لایه‌ی مخفی از دید کاربر ایجاد میکند و درخواست های تکراری را خودش و از روی حافظه (Ram+Disk) با سرعت بالا پاسخ میدهد.

بنابراین وقتی تیزر شما پخش میشود، فقط برای اولین کاربر، صفحه‌ی نخستین سایت شما پردازش شده و باقی کاربران همین نسخه‌ی پردازش شده را بدون دخالت nginx یا php-fpm و از دستان varnish عزیز دریافت میکنند.

معماری گسترش یافتی سرور وب ۱ - جایگذاری varnish در لایه بیرونی

بهینه سازی ۲ – استفاده از memcached برای ذخیره سازی Session ها

صورت مساله ۱: با تقریب خوبی تمامی برنامه های تحت وب، کاربران خود را با استفاده از کوکی یا نشست (Session) شناسایی میکنند. به طور پیش فرض به ازای هر کاربر جدیدی که به سایت مجهز به نشست شما مراجعه می‌کند، یک فایل برای ذخیره کردن اطلاعات کاربر بر روی سیستم فایل شما ایجاد می‌شود. با زیاد شدن تعداد کاربران (مخصوصا اگر تاریخ انقضای هر نشست زیاد باشد)، تعداد زیادی فایل (آن هم در یک فولدر) ایجاد شده است که سرعت دسترسی به اطلاعات نشست هر کاربر را تا حد زیادی کند میکند.

صورت مساله ۲: شما دو سیستم مجزا راه اندازی کرده اید که پاسخ درخواست های PHP کاربران را پردازش کند. هر یک از این سیستم ها اطلاعات نشست کاربران را بر روی هارد خود نگهداری میکند. بنابراین اگر یک کاربر درخواستی به سیستم اول ارسال کند، و درخواست دوم به سیستم دوم محول شود، اطلاعات جداگانه‌ای برای یک کاربر در این دو سیستم ذخیره میشود. بنابراین می‌بایست به دنبال راهی باشیم که نشست های کاربران همگی در یک مکان ذخیره شود و هر دو سیستم از این منبع یکتا استفاده کنند.

شاید بد نباشد بدانید که PHP راه های بسیار زیادی غیر از نگهداری فایل، برای مدیریت نشست ها پشتیبانی میکند. اگر میزان اطلاعاتی که در هر نشست نگهداری میکنید زیاد نباشد، نگهداری این اطلاعات بر روی حافظه‌ی اصلی (RAM) یکی از سریعترین و بهترین روش ها محسوب می‌شود. ابزاری که برای اینکار استفاده می‌شود، memcached نام دارد. پس از نصب memcached در یک سیستم مشترک که همه‌ی سرورهای مربوط به PHP به آن دسترسی داشته باشند، میتوانید در تنظیمات PHP، ذخیره سازی اطلاعات نشست را به memcached محول کنید.

معماری گسترش یافتی سرور وب ۱ - استفاده از memcached جهت ذخیره نشست ها

بهینه سازی ۳ – جدا سازی و ارتقای منابع سخت افزاری

همیشه میتوانید با هزینه‌ی مالی بیشتر، سخت افزار خود را ارتقا بدهید. اما سخت‌افزارهایی که وحشتناک قوی باشند، وحشتناک گران هم هستند. بنابراین یک حرکت اقتصادی خود برای ارتقای سخت افزاری معماری شما این است که بخش های مختلف آن را بر روی رایانه‌های جداگانه راه اندازی و اجرا کنید. اولین و مهم ترین جداسازی که تمامی سایت ها میبایست انجام دهند، جدایی پایگاه داده از سیستم پردازشی وب است. نوع عملیاتی که در پایگاه داده انجام می شود به شدت با دیسک در ارتباط است و این موضوع، منابع کمی که برنامه های دیگر از جمله PHP از دیسک نیاز دارند را تحت تاثیر قرار میدهد. از طرفی اختصاص فضای جداگانه به MySQL به روند های پشتیبان گیری و بهینه تر عمل کردن این سرویس هم کمک میکند. البته در تصویر زیر من Varnish را هم جدا کرده ام تا بهینه سازی بعدی به عنوان پخش کننده‌ی بار (Load Balancer) راحت تر انجام شود.

تجربه: همانطور که گفته شد بیشترین منبع مورد نیاز برای سرورهای MySQL خواندن ها و نوشتن های اتفاقی (Random Write/Read) بر روی دیسک است. این روزها، هارد دیسک های SSD این کار را با هزینه‌ای اندک بسیار بهتر از هارد های سنتی انجام می‌دهند. پس استفاده از یک برند خوب SSD به صورت موازی (Raid Mirror) یک راه حل خوب برای منابع MySQL است.

معماری گسترش یافتی سرور وب ۱ - جداسازی و ارتقای سخت افزارها

بهینه سازی ۴ – پخش بار محاسباتی بین چند سرور

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

معماری گسترش یافتی سرور وب ۱ - تقسیم بار توسط HAProxy

 

بهینه سازی ۵ – اگر فایلها تغییر کنند چه اتفاقی می‌افتد؟

تا جایی که فایلهای سرور شما تغییری نمیکند یا فایلی از کاربران دریافت نمیکنید، مشکلی با معماری بالا وجود ندارد. اما اگر میخواهید فایل از کاربران دریافت کنید یا فایلهای بین سرورهای پردازنده مشترک باشند، نگاهی به طراحی زیر (با استفاده از GlusterFS) بیاندازید. البته اگر تعداد فایلها و دریافت آنها زیاد باشد، بهترین کار راه اندازی (یا استفاده از) نوعی از سیستم توزیع محتوا یا CDN است که کاربران مستقیما برای دریافت فایلها با سرورهای CDN در ارتباط باشند.

معماری گسترش یافتی سرور وب ۱ - استفاده از GlusterFS برای اشتراک فایلها

بهینه سازی ۶ – وقتی یک پایگاه داده کافی نیست

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

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

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

معماری گسترش یافتی سرور وب ۱ - چند پایگاه داده MySQL در حالت Replication

 

پیوندهای مرتبط و مطالعات بیشتر

  1. صفحه‌ی پروژه‌ی NGINX
  2. صفحه‌ی پروژه‌ی PHP-FPM
  3. صفحه‌ی پروژه‌ی Varnish-Cache
  4. صفحه‌ی پروژه‌ی HAProxy
  5. وارنیش کش چیست؟ – رضا سروری
  6. آموزش نصب وب سرور Nginx بر روی سیستم عامل CentOS – پارس‌پک
  7. آموزش نصب و کانفیگ NGINX و PHP-FPM در UBUNTU 13.04 – کامپایلر دات آی-آر
  8. راهنمای انگلیسی – نصب NGINX و PHP-FPM بر روی اوبونتو ۱۳.۰۴
  9. راهنمای انگلیسی – نصب NGINX و PHP-FPM بر روی CentOS 6
  10. راهنمای انگلیسی – استفاده از ایندکس برای بهبود MySQL
  11. راهنمای انگلیسی – راه اندازی و تنظیم GlusterFS

اگر مقاله‌ی خوبی در این رابطه نوشته‌اید که دوست دارید در این لیست نمایش داده شود لطفا در بخش نظرات ذکر کنید.

علی نادعلیزاده

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

۱۰ Comments

  • محسن گفت:

    ممنون ، خیلی اطلاعات خوبی بود

  • جاوید گفت:

    بدون اغراق
    بهترین مقاله فارسی زبانی که من در حوزه وب خوندم.
    ممنون از وقتی که گذاشتیدو این مطلب رو منتشر کردید.

  • محمد گفت:

    خیلی ممنون از زحمتی که کشیدید و تجربه خودتون رو به ما منتقل کردید. بسیار مفید بود

  • پوریا گفت:

    بسیار عالی.. من در پیاده سازی memcache هنوز مشکل دارم ولی خیلی خوب مطالب فراوانی رو تو یه مقاله فارسی جا دادی که جای تشکر و امیدواری داره.

  • Soroush گفت:

    بسیار عالی، قشنگ چند روز حتما وقت برده ازت و چیز خوب کاملی شده.

  • امیرحسین گفت:

    پروژه های نوپا اگر از ابتدا با چنین دیدی ساخته بشن حتما در ادامه راه با مشکلات کمتری رو به رو میشن. ممنون که تجربه های ارزشمندت رو به اشتراک گذاشتی ؛)

  • رامتین مهدیزاده گفت:

    بهینه سازی شماره ۵ کجاست ؟

  • محمد گفت:

    خیلی ممنون که تجربه خود را برای ما بیان کردید. امیدوارم دیگران هم برای شما چنین باشن .
    با تشکر فراوان

  • سینا بهارلویی گفت:

    خیلی خوب بود! این بهینه سازی ها و نحوه ی ایجاد کردنشون یه جا ندیده بودم انقدر خوب