چگونه یک برنامه جنگو را برای خدمت به یک میلیون کاربر مقیاس کنیم؟

ای کاش برنامه جنگو شما می توانست میلیون ها بازدید را انجام دهد؟ این پست مجموعهای از مقالات، کتابها و ویدیوهایی است که در مورد چگونگی به حداکثر رساندن یک برنامه جنگو به حداکثر تواناییهایش خواندهام، حتی برخی از این توصیهها را خودم اجرا کردهام.
همچنین زمان خوبی است که به یاد داشته باشید که اگر برنامه شما به تازگی شروع به کار کرده است، احتمالاً نباید در مورد عملکرد آن وسواس داشته باشید… هنوز.
پرس و جوهای کند را در جنگو کاهش دهید
همانطور که می دانید دسترسی به پایگاه داده معمولا گلوگاه اکثر برنامه ها است. **مهمترین اقدامی که باید انجام داد کاهش تعداد پرس و جوها و تاثیر هر یک از آنهاست. شما می توانید تاثیر پرس و جوهای خود را تا 90 درصد کاهش دهید و من اغراق نمی کنم.
نوشتن کدی که چندین پرس و جو در پایگاه داده و همچنین جستجوهای بسیار گران قیمت ایجاد می کند، بسیار رایج است.
با استفاده از django-debug-toolbar چه پرس و جوهایی در برنامه شما ایجاد می شود و آنها را کاهش دهید یا کارآمدتر کنید:
- select_related() برای جلوگیری از جستجوهای متعدد در کلید خارجی یا روابط یک به یک
- prefetch_related() برای جلوگیری از جستجوی بیش از حد در روابط چند به چند یا چند به یک
- django_annotate() برای اضافه کردن اطلاعات به هر شی در یک پرس و جو. من یک ورودی دارم که در آن تفاوت بین حاشیه نویسی و جمع را توضیح می دهم.
- django_aggregate() برای پردازش تمام اطلاعات از یک پرس و جو به یک داده واحد (جمع بندی، میانگین ها).
- شیء Q برای پیوستن به پرس و جوها توسط OR یا AND مستقیماً از پایگاه داده.
- F-Expressions** برای انجام عملیات در سطح پایگاه داده به جای کد پایتون.
Django debug tool bar showing the SQL queries of a Django request
](images/django-debug-tool-bar-numero-queries.png)
مثال استفاده با select_related.
# review/views.py
from .models import Review
def list_reviews(request):
queryset = Review.objects.filter(product__id=product_id).select_related('user')
# We're preventing a new query everytime we access review.user
# ...
گانیکورن را به درستی پیکربندی کنید
Gunicorn پر استفاده ترین سرور Python WSGI HTTP برای برنامه های جنگو است. اما ناهمزمان نیست، در نظر بگیرید که آن را با یکی از همتایان ناهمزمان خود ترکیب کنید: هایپر کورن یا یووی کورن. دومی کارگران گانیکورن را اجرا می کند.
گانیکورن را به درستی پیکربندی کنید
اطمینان حاصل کنید که با توجه به تعداد هستههای پردازنده خود از گونیکورنهای درست استفاده میکنید. آنها توصیه می کنند کارگران را روی (2 x تعداد هسته ها) + 1 تنظیم کنید. طبق مستندات، با 4 تا 12 کارگر می توانید از صدها تا هزاران درخواست در ثانیه ارائه دهید ، بنابراین باید برای یک وب سایت در مقیاس متوسط تا بزرگ کافی باشد.
عملکرد سریال سازهای خود را بهبود بخشید
اگر از DRF استفاده میکنید و از کلاسهای عمومی آن برای ایجاد سریالساز استفاده میکنید، ممکن است دقیقاً بهترین عملکرد را نداشته باشید. کلاسهای عمومی برای سریالسازها اعتبارسنجی دادهها را انجام میدهند، که اگر فقط میخواهید دادهها را بخوانید میتواند بسیار زمانبر باشد.
حتی اگر به یاد داشته باشید که فیلدهای خود را به عنوان read_only علامت گذاری کنید، سریالسازهای DRF سریعترین نیستند، ممکن است بخواهید Serpy، Marshmallow را بررسی کنید. موضوع کاملاً گسترده است، اما با این ایده بمانید که در سریالسازهای جنگو زمینههای پیشرفت زیادی وجود دارد.
من این مقاله را برای شما می گذارم که توضیح می دهد چگونه برخی از توسعه دهندگان توانستند هزینه زمانی سریال سازی را تا 99٪ کاهش دهند.
در نماهای خود از صفحه بندی استفاده کنید
احتمالاً کاملاً واضح به نظر می رسد، اما من احساس می کنم باید به آن اشاره کنم: اگر کاربر شما فقط چند رکورد اول را مفید می بیند، نیازی به بازگرداندن کل جدول پایگاه داده ندارید.
استفاده کنید صفحه نویس شی ارائه شده توسط جنگو، یا نتایج جستجو را به چند مورد محدود کنید.
DRF همچنین گزینه ای برای صفحه بندی نتایج شما دارد، آن را بررسی کنید.
# review/views.py
from django.views.generic import ListView
from .models import Review
class ReviewList(ListView):
model = Review
paginate_by = 25
context_object_name = 'review_list'
از شاخص ها در مدل های خود استفاده کنید
پرس و جوهای پیچیده تر خود را درک کنید و سعی کنید برای آنها فهرست ایجاد کنید. این فهرست جستجوهای شما را در جنگو سریعتر میکند، اما همچنین ایجاد و بهروزرسانی اطلاعات جدید را اندکی کند میکند، علاوه بر اینکه فضای بیشتری را در پایگاه داده شما اشغال میکند. سعی کنید تعادل سالمی بین سرعت و فضای ذخیره سازی استفاده شده ایجاد کنید.
from django.db import models
class Review(models.Model):
created = models.DateTimeField(
auto_now_add=True,
db_index=True,
)
برای جستجوهای خود از فهرست ها استفاده کنید
اگر برنامه شما به شدت از جستجوهای اطلاعاتی استفاده می کند، به جای پیاده سازی کد خود، از یک موتور جستجوی کارآمد مانند Solr استفاده کنید.
گزینه های بسیاری در دسترس است:
- ElasticSearch
- سولر
- هووش
- ژاپیان
میان افزار بلااستفاده را حذف کنید
هر میان افزار به معنای یک مرحله اضافی در هر درخواست وب است، بنابراین حذف تمام میان افزارهایی که استفاده نمی کنید به معنای بهبود جزئی در سرعت پاسخگویی برنامه شما خواهد بود.
در اینجا چند میان افزار رایج وجود دارد که همیشه مورد استفاده قرار نمی گیرند: پیام ها، صفحات مسطح و محلی سازی، نه، منظورم موقعیت جغرافیایی نیست، بلکه ترجمه محتوا با توجه به زمینه محلی است.
MIDDLEWARE = [
# ...
'django.contrib.messages.middleware.MessageMiddleware',
'django.contrib.flatpages.middleware.FlatpageFallbackMiddleware',
'django.middleware.locale.LocaleMiddleware'
]
ذخیره در جنگو
هنگامی که زمان پاسخگویی برنامه شما با مشکل مواجه می شود، باید تمام نتایج زمان بر و پر منابع را در حافظه پنهان نگه دارید.
اگر میخواهید در سیستم ذخیرهسازی اطلاعات عمیقتری داشته باشید، من یک پست در مورد کش کردن در جنگو با استفاده از memcached دارم که میتوانید برای بررسی عمیقتر آن را بررسی کنید.
اگر صفحه شما مدل های زیادی دارد و به ندرت تغییر می کنند، منطقی نیست که هر بار به پایگاه داده دسترسی داشته باشید تا با هر درخواست HTTP جدید آنها را درخواست کنید. فقط پاسخ آن درخواست را در حافظه پنهان قرار دهید و زمان پاسخ شما بهبود می یابد، به این ترتیب هر بار که همان محتوا درخواست می شود، نیازی به درخواست یا محاسبات جدید به پایگاه داده نخواهد بود، بلکه مقدار مستقیماً از حافظه
از جمله گزینه های موجود عبارتند از:
- Memcached
- ردیس
- کش پایگاه داده
- کش سیستم فایل
# settings.py
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
'LOCATION': '127.0.0.1:11211',
}
}
کش جنگو در سطوح مختلف، از کل سایت گرفته تا بازدیدها یا حتی قطعات کوچک اطلاعات، قابل تنظیم است.
# myapp/views.py
from django.shortcuts import render
from django.views.decorators.cache import cache_page
@cache_page(60*15)
def my_view(request):
return render(request, 'myapp/template.html', {
'time_consuming_data': get_time_consuming_data()
})
توجه داشته باشید که حافظه پنهان memcached (memcached، redis) یک روش ذخیره سازی زودگذر است، در صورت راه اندازی مجدد یا خاموش شدن سیستم، کل حافظه پنهان ناپدید می شود.
از کرفس برای کارهای ناهمزمان استفاده می کند
گاهی اوقات گلوگاه به عهده اشخاص ثالث است. هنگامی که ایمیلی ارسال می کنید یا اطلاعاتی را از شخص ثالث درخواست می کنید، راهی ندارید که بدانید درخواست شما چقدر طول می کشد، اتصال کند یا سرور بیش از حد اشباع شده می تواند شما را منتظر پاسخ نگه دارد. هیچ فایده ای ندارد که کاربر را ده ها ثانیه منتظر بمانید تا یک ایمیل ارسال شود، به او پاسخ ارسال کنید و ایمیل را به یک صف انتقال دهید تا بعداً پردازش شود. کرفس محبوب ترین راه برای انجام این کار است.
نمی دانم از کجا شروع کنم، من چند پست دارم که در آنها نحوه اجرای وظایف ناهمزمان با کرفس و جنگو را توضیح می دهم.
# myapp/views.py
from celery import shared_task
@shared_task
def send_order_confirmation(order_pk):
email_data = generate_data_for_email(order_pk)
send_customized_mail(**email_data)
جداول را در پایگاه داده خود پارتیشن بندی کنید
زمانی که جدولهای شما از میلیونها رکورد فراتر رود، هر جستجو از طریق کل پایگاه داده انجام میشود و این فرآیند زمان بسیار زیادی را میگیرد. چگونه می توانستیم این را حل کنیم؟ با تقسیم جداول به قطعات به طوری که هر جستجو در یکی از قسمت ها انجام شود، به عنوان مثال، یک جدول برای داده های یک سال قبل (یا دوره ای که ترجیح می دهید)، دیگری برای داده های دو سال قبل و غیره تا اولین داده
دستورالعمل های اجرای پارتیشن بندی به پایگاه داده ای که استفاده می کنید بستگی دارد. اگر از postgres استفاده می کنید، این ویژگی فقط برای نسخه های Postgres بالاتر از 10 موجود است. می توانید از django-postgres-extra برای پیاده سازی آن ویژگی های اضافی که در ORM جنگو یافت نمی شوند، استفاده کنید.
پیاده سازی بسیار گسترده است و نیاز به ورود کامل دارد. یک مقاله عالی وجود دارد که نحوه پیاده سازی پارتیشن بندی Postgresql در جنگو را توضیح می دهد.
همچنین به دنبال کپی های پایگاه داده برای خواندن فایل ها باشید، بسته به معماری برنامه خود، می توانید چندین نسخه برای خواندن و یک Master برای نوشتن پیاده سازی کنید. این رویکرد یک موضوع کامل است و خارج از محدوده یک پست کوتاه است، اما اکنون می دانید که به دنبال چه چیزی باشید.
استفاده از CDN (شبکه تحویل محتوا)
ارائه تصاویر و فایل های ایستا می تواند بخش مهمی از برنامه شما را مختل کند. تولید محتوای پویا شما می توانید وظیفه ارائه محتوای ثابت را به یک شبکه تحویل محتوا (CDN) محول کنید.
علاوه بر بهره مندی از موقعیت های جغرافیایی CDN ها؛ یک سرور در همان کشور (یا قاره) به عنوان کاربر شما منجر به پاسخ سریعتر می شود.
گزینه های CDN زیادی در دسترس هستند، از جمله محبوب ترین گزینه ها می توان به AWS، Azure، Digital Ocean، Cloud Flare و غیره اشاره کرد.
عادی سازی
گاهی اوقات پرس و جوهای زمان اجرا بسیار پرهزینه ای وجود دارد که با اضافه کردن اطلاعات تکراری و افزونگی قابل حل هستند. برای مثال، تصور کنید میخواهید تعداد محصولاتی را که عبارت «برای کودکان» را در صفحه اصلی خود دارند، برگردانید، اجرای یک پرس و جو که کلمه را جستجو میکند و سپس یک شمارش را اجرا میکند، نسبتاً ساده است. اما اگر 10000 یا 100000 یا 1000000 محصول داشته باشید، هر بار که می خواهید به مقدار شمارش دسترسی داشته باشید، پایگاه داده شما کل جدول را مرور می کند و داده ها را شمارش می کند.
به جای انجام یک شمارش، می توانید آن عدد را در پایگاه داده یا حافظه ذخیره کنید و مستقیماً آن را برگردانید، برای به روز نگه داشتن آن می توانید از یک شمارش دوره ای استفاده کنید یا با هر اضافه آن را افزایش دهید.
البته این مشکل را به همراه دارد که اکنون دادههای بیشتری برای نگهداری دارید، نه اینکه با هم جفت شوند، بنابراین ** فقط در صورتی باید از این گزینه برای حل مشکلات عملکرد جنگو استفاده کنید که گزینههای دیگر را تمام کردهاید.
count = my_model.objects.filter(description__icontains="para niños").count()
# ... denormalizing
count = my_count.objects.get(description="para niños") # Each row of the my_count model contains a description and the total results.
total_count = count.total
تاثیر افزونه های شخص ثالث را بررسی کنید
گاهی اوقات وب سایت ما تقریباً عالی کار می کند، اما افزونه های شخص ثالث مانند ابزارهای تجزیه و تحلیل فیس بوک، گوگل، افزونه های یکپارچه سازی چت رسانه های اجتماعی بر عملکرد برنامه ما تأثیر می گذارند. با استفاده از async، defer یا سایر ویژگیهای HTML، در ترکیب با جاوا اسکریپت، یاد بگیرید که چگونه بارگذاری آنها را به تأخیر بیندازید یا آنها را تغییر دهید تا تأثیر آنها کاهش یابد.
اگر موارد فوق غیرممکن است، گزینه های جایگزین را ارزیابی کنید یا آنها را حذف کنید.
استفاده از مترجم دیگری را برای بهبود عملکرد جنگو در نظر بگیرید
همه چیز مربوط به پایگاه داده نیست، گاهی اوقات مشکل در خود کد پایتون است.
علاوه بر مفسر معمولی پایتون، مفسری که به طور پیشفرض در وبسایت رسمی پایتون ارائه میشود، مفسرهای دیگری نیز وجود دارند که مطمئناً عملکرد بهتری را به شما ارائه میدهند.
Pypy یکی از آنهاست که وظیفه بهینه سازی کد پایتون را با تجزیه و تحلیل نوع اشیایی که با هر اجرا ایجاد می شود را بر عهده دارد. این گزینه برای برنامه هایی ایده آل است که جنگو مسئول بازگرداندن نتیجه ای است که عمدتاً با استفاده از کد پایتون پردازش شده است.
اما همه چیز فوق العاده نیست. مفسرهای شخص ثالث، از جمله pypy، معمولاً 100٪ با تمام کدهای پایتون سازگار نیستند، اما با اکثر آنها سازگار هستند، بنابراین، درست مانند گزینه قبلی. **استفاده از مترجم شخص ثالث نیز باید یکی از آخرین گزینه هایی باشد که برای حل مشکل عملکرد جنگو خود در نظر می گیرید.
تنگناها را به زبان سطح پایین با Swig بنویسید
اگر همه موارد بالا را امتحان کرده اید و هنوز یک برنامه با تنگنا دارید، احتمالاً بیش از حد از پایتون استفاده می کنید و به سرعت زبان دیگری نیاز دارید. اما نگران نباشید، لازم نیست کل برنامه خود را در C یا C++ دوباره انجام دهید. Swig به شما امکان می دهد ماژول هایی را به زبان های C، C++، Java، Go یا سایر زبان های سطح پایین تر ایجاد کنید و آنها را مستقیماً از پایتون وارد کنید.
آیا می خواهید بدانید که چقدر تفاوت بین پایتون و یک زبان کامپایل شده مانند go وجود دارد؟ در پست خود Python vs Go سرعت هر دو زبان را مقایسه می کنم
اگر گلوگاهی دارید که ناشی از محاسبات پرهزینه ریاضی است، که نشان دهنده عدم سرعت پایتون به عنوان یک زبان تفسیری است، ممکن است بخواهید گلوگاه را به زبان سطح پایین بازنویسی کنید و سپس آن را با استفاده از پایتون فراخوانی کنید. به این ترتیب شما سهولت استفاده از پایتون را با سرعت یک زبان سطح پایین خواهید داشت.
ORM ها و چارچوب های جایگزین
بسته به پیشرفت برنامه خود، ممکن است بخواهید سریعتر از جنگو به فریمورک دیگری مهاجرت کنید. ORM جنگو دقیقاً سریعترین موجود نیست و در زمان نگارش، ناهمزمان نیست. ممکن است بخواهید sqlalchemy، ponyorm را امتحان کنید.
یا اگر برنامه شما در سطح پایگاه داده خیلی پیچیده نیست، ممکن است بخواهید پرس و جوهای sql خود را بنویسید و آنها را با فریمورک دیگری ترکیب کنید.
روند فعلی جداسازی فرانت اند و باطن است، بنابراین جنگو به همراه جنگو Rest Framework برای ایجاد API استفاده می شود، بنابراین اگر برنامه های شما شامل ایجاد یک API است، ممکن است بخواهید FastAPI را در نظر بگیرید، اگر آن را نمی دانید. ، به پست من نگاهی بیندازید که در آن اصول اولیه FastAPI را توضیح می دهم.
پاداش: برنامه های کاربردی با بیش از 63000 مدل
یک سخنرانی در djangocon2019 وجود دارد که در آن سخنران توضیح میدهد که چگونه توانستند با یک برنامه با 63000 نقطه پایانی که هر کدام مجوزهای متفاوتی داشتند، مقابله کنند.
https://www.youtube.com/watch?v=O6-PbTPAFXw
امتیاز: وبلاگ های فنی
پینترست و اینستاگرام دو سایت غول پیکری هستند که با انتخاب جنگو به عنوان باطن خود شروع به کار کردند. شما می توانید اطلاعاتی در مورد بهینه سازی و مشکلات بسیار خاص در وبلاگ های فنی آنها پیدا کنید.
وبلاگ اینستاگرام پستی به نام کارایی وب سرویس در اینستاگرام با پایتون دارد که در آن برخی از مشکلاتی که هنگام مدیریت 500 میلیون کاربر با آن مواجه میشوند و نحوه رفع آنها را توضیح میدهند.
در اینجا پیوندهای وبلاگ های زیر آمده است:
منابع:
- راهنمای قطعی جنگو: توسعه وب درست توسط آدریان هولواتی و جیکوب کاپلان ماس انجام شده است
- دو اسکوپ جنگو 1.8 توسط دنیل روی گرینفلد و آدری روی گرینفلد
- Django با اجرای عالی توسط پیتر بامگارتنر و یان مالت