کارایی جنگو در عمل: اول N+1 را بکشید، بعد کش کنید
وقتی سایتی با جنگو کند بهنظر میرسد، تیمها اغلب سراغ worker بیشتر،
اینستنس بزرگتر Postgres یا CDN میروند. اینها کمک میکنند—اما بیشترین
بازدهی تقریباً همیشه در لایه ORM است. راهنماهای پروداکشن ۲۰۲۵–۲۰۲۶ همین
ترتیب را تکرار میکنند، بیدلیل نیست.
۱. قبل از «بهینهسازی»، کوئریها را ببینید
در محیط لوکال django-debug-toolbar را نصب کنید و پنل SQL صفحات لیست را
ببینید. در staging کوئریهای کند را لاگ کنید یا با connection.queries در
یک management command موقت بررسی کنید. آنچه اندازه نمیگیرید، درست
نمیکنید.
یک قاعده سرانگشتی: صفحه ساده باید در حدود تکرقمی کوئری بماند. جهشها
معمولاً یعنی N+1 یا کوئری تصادفی داخل حلقه یا تگ قالب.
۲. N+1 را با ابزار درست prefetch درست کنید
select_related— کلید خارجی / one-to-one (همانJOINدر SQL).prefetch_related— FK معکوس و many-to-many (کوئری جدا، سپس join در
پایتون). برای M2M، prefetch را به JOINهای غولپیکر با ردیف تکراری ترجیح دهید.
posts = (
Post.objects.filter(status="published")
.select_related("category")
.prefetch_related("tags")
)
هرگز بدون prefetch، related manager را داخل حلقه قالب صدا نزنید. همین عادت
بیشتر تأخیرهای «عجیب» را حذف میکند.
۳. داده کمتر بگیرید
وقتی کارت یا serializer فقط چند ستون میخواهد از only() / defer()
استفاده کنید. اگر فقط وجود مهم است، exists() را به count() ترجیح دهید.
نوشتنها را با bulk_create / bulk_update دستهای کنید، نه save در حلقه.
۴. روی چیزی که فیلتر و مرتب میکنید ایندکس بگذارید
اگر هر لیست روی status فیلتر و با -published_at مرتب میشود، آن مسیر
ترکیبی جای ایندکس است. قبل و بعد روی Postgres از
EXPLAIN (ANALYZE, BUFFERS) استفاده کنید. ایندکس جزئی (مثلاً فقط
status='published') مسیرهای داغ را کوچک نگه میدارد.
از ایندکسکردن همهچیز «برای احتیاط» بپرهیزید—هزینه نوشتن واقعی است.
۵. بعد از تمیز شدن query plan، کش کنید
کش کردن یک queryset بد فقط اتلاف را منجمد میکند. وقتی کوئریها جمعوجور شدند:
- فرگمنتها یا کل صفحه را با cache framework جنگو کش کنید.
- برای کش مشترک بین workerها Redis بگذارید.
- invalidation را عمدی طراحی کنید (سیگنال روی save، TTL کوتاه، یا کمکیار
آگاه به queryset).cache.clear()کور استراتژی نیست.
Connection pooling (PgBouncer یا گزینههای جدیدتر جنگو) وقتی زیر بار
اتصالهای کوتاه زیاد باز میکنید مهم میشود—بعد از اینکه دیگر پنجاه کوئری
در هر درخواست نمیزنید.
چکلیست واقعبینانه
| گام | اقدام | تمام وقتی که |
|---|---|---|
| پروفایل | Debug toolbar / لاگ کوئری کند | endpointهای داغ مشخص شوند |
| ORM | select_related / prefetch_related |
N+1 در لیست/جزئیات برود |
| شکل داده | only / صفحهبندی |
payloadها کوچک شوند |
| ایندکس | منطبق با فیلتر و ordering | EXPLAIN سالم بهنظر برسد |
| کش | Redis + کلیدهای هدفمند | ترافیک تکراری ارزان شود |
| زیرساخت | pooling / scale-out | فقط اگر هنوز لازم بود |
کارایی جنگو در ۲۰۲۶ معمولاً مرموز نیست. کوئریهای اضافی را حذف کنید،
مسیرهایی که همین حالا میپرسید را ایندکس کنید، باقیمانده گران را کش کنید،
و فقط بعد جعبه بزرگتر بخرید. این ترتیب هنوز از microservice زودهنگام بهتر
است.