چند روش برای بهینه سازی کدهای جنگو

امیرحسین بیگدلو 12 ماه قبل

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

 

 

 #  چرا به بهینه سازی کدهای جنگو نیاز داریم؟

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

 

جنگو معمولا سرعت بالایی دارد اما نمیتواند همه چیز را به تنهایی انجام دهد. ما باید به جنگو بگوییم که چه کاری را کجا باید انجام دهد و ابزارهایی برای انجام بهتر آن در اختیارش قرار دهیم. بسیاری از این روش‌ها مانند index کردن دیتابیس یا بهینه بودن کوئری‌های دیتابیس را میتوان از همان زمان اولیه توسعه در نظر داشت.

 

دوره پیشنهادی: دوره آموزش جنگو(django)

 

 #  بهینه کردن کوئری‌های دیتابیس

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

 

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

 

کد زیر یک نمونه بسیار بد از خواندن اطلاعات است. در کد زیر برای گرفتن اطلاعات مربوط به Alice و Bob دو بار به دیتابیس وصل میشویم:

bobs = Employee.objects.filter(first_name=”Bob”)
alices = Employee.objects.filter(first_name=”Alice”)

 

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

employees = Employee.objects.filter(first_name__in=[“Bob”, “Alice”])

bobs = employees.filter(first_name=”Bob”)
alices = employees.filter(first_name=”Alice”)

 

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

 

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

 

 

 #  استفاده از index در دیتابیس

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

 

 

 #  بدانید که به چه فیلدهایی نیاز دارید

اول از همه، همیشه مطمئن شوید که فقط اطلاعاتی را که واقعا نیاز دارید بگیرید و نه بیشتر. گرفتن اطلاعات بیشتر از مورد نیاز، باعث ایجاد مشکلات عملکرد در دیتابیس خواهد مخصوصا اگر در آینده تعداد کاربرها بیشتر شود. جنگو چند ابزار برای کمک در این مورد دارد. اگر فقط به چند فیلد جزئی نیاز دارید از متدها only یا defer استفاده کنید. این کار باعث میشود فشار کمتری به دیتابیس وارد شود. همچنین اگر اطلاعات را به شکل یک دیکشنری نیاز دارید، میتوانید از متدهای values یا values_list استفاده کنید.

 

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

 

 

 #  در اندازه بزرگ کار کنید

اگر قرار است اطاعات زیادی را آپدیت یا ایجاد کنید بهتر است بجای حلقه for از متدهای bulk_create یا bulk_update استفاده کنید.

 

پایتون یک متد به نام iter دارد که اگر قرار است همزمان هزاران دیتا را ایجاد یا آپدیت کنید بهتر است از این متد استفاده کنید. این متد دیتاها را تکه تکه کرده و ارسال میکند. در غیر اینصورت امکان کاهش سرعت دیتابیس وجود دارد. استفاده از querysetها در کنار حلقه for برای کار کردن همزمان با هزاران دیتا، همیشه باعث کاهش سرعت میشود. پس سعی کنید از iter استفاده کنید.

 

 

 #  بهینه کردن روابط جداول دیتابیس

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

 

فرض کنید اطلاعات هزاران کاربر را در queryset خود دارید و میخواهید از model دیگر آدرس آنها را دریافت کنید که اینکار زمانی زیادی را میگیرد. به مثال زیر دقت کنید:

employees = Employee.objects.all()

for employee in employees: 
  print(employee.address.street)

 

این کار باعث ایجاد یک queryset به ازای هر کاربر میشود. که اصلا کار درستی نیست. اما استفاده از select_related(“address”) میتواند یک join در دیتابیس ایجاد کرده و اطلاعات را فقط در یک queryset بگیرد. اینکار به شکل شگفت انگیزی تاثیر مثبت در عملکرد دیتابیس دارد.

 

حالا فرض کنید که مدل Address با یک رابطه چند به چند به مدل Employee وصل شده است. گرفتن تمام آدرس‌ها برای تمام کاربرها باعث میشود که در پشت صحنه جنگو دستور employee.addresses.all() را اجرا کند که در اینصورت به ازای هر کاربر یک queryset انجام خواهد شد. این دوباره باعث میشود که دیتابیس تحت فشار قرار گیرد. به جای آن میتوانید از prefetch_related(“addresses”) استفاده کنید که یک queryset جداگانه برای هر مدل آدرس ایجاد می کند و همه آدرس های مربوط به کارمندانی را که در QuerySet خود داریم واکشی می کند. در پس زمینه جنگو نتایج کش شده و سپس با یکدیگر ترکیب میشوند و باعث میشود فقط در یک queryset تمام اطلاعات را داشته باشیم.

 

 

 #  هیچ یک از این روش‌ها جواب نداد

اگر هنوز برنامه شما کند است پس باید عمیق‌تر در querysetهای خود بررسی کنید. نگاهی به دستور SQL که جنگو با QuerySet شما ایجاد کرده است می تواند به شما کمک کند تا ریشه مشکل را محدود کنید.

 

همچنین، به خاطر داشته باشید که ابزارهای زیادی وجود دارد که به شما کمک می کند عملکرد برنامه خود را ردیابی کنید - New Relic، Datadog، Splunk. چنین ابزارهایی به شما کمک می کنند تا ریشه مشکل را پیدا کنید.

 

 

 #  کندی در ادمین پنل جنگو

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

 

به طور پیش فرض، ادمین پنل جنگو از QuerySets معمولی و pagination استاندارد استفاده می کند. در موارد خاص، شما حتی می توانید خطای timeout error 504 را ببینید، زیرا داده های زیادی وجود دارد که حتی برای pagination نیز زیاد هستند. در این صورت می‌توانید چند ترفند انجام دهید مثلا خاموش کردن pagination (بنابراین جنگو نتایج را شمارش نمیکند) یا حتی صفحات template پیش‌فرض ادمین پنل را جایگزین کنید تا از Views و QuerySets خود استفاده کنید.

 

 

 #  نتیجه گیری

هر یک از این راه‌حل‌ها می‌تواند (اما نه لزوماً) به تنهایی تأثیر کمی بر برنامه شما داشته باشد، اما به یاد داشته باشید - اعداد و ارقام قدرت دارند. آنها را با هم ترکیب کنید و تفاوت بزرگی را خواهید دید.

 

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

 

 

مطالب مشابه



مونگارد