کارایی جنگو در عمل: اول N+1 را بکشید، بعد کش کنید

بیشتر اپ‌های کند جنگو به‌خاطر پایگاه‌داده کندند. اول کوئری‌های N+1 را درست کنید، ایندکس مناسب بگذارید، بعد Redis—سخت‌افزار را آخر مقیاس دهید.

رک‌های سرور نماد زیرساخت پایگاه‌داده و کش

کارایی جنگو در عمل: اول 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 زودهنگام بهتر
است.

اشتراک‌ گذاری

این مطلب را در تلگرام، واتساپ به اشتراک بگذارید یا لینک را کپی کنید.

تلگرام واتساپ

ادامه مطالعه

مطالب مرتبط

آموزش گام‌به‌گام اتصال الستیک‌سرچ به لاراول: جستجوی فوق‌سریع در دیتابیس‌های بزرگ

اگر تا به حال در پروژه‌ای با حجم دیتای بالا کار کرده باشید، قطعاً متوجه شده‌اید که کوئری‌های سنتی نظیر `LIKE %keyword%` در MySQL یا PostgreSQL وقتی رکوردها به صدها هزار یا میلیون‌ها ردیف می‌رسند، فشار سنگینی به سرور تحمیل کرده و زمان پاسخ‌گویی را به شدت کند می‌کنند.

ادامه مطلب

نوشته‌های بیشتر

کاوش در کل وبلاگ

مقالات بیشتری درباره سیستم‌های بک‌اند، API و ساخت محصولات قابل اعتماد بخوانید.

همه مطالب