خیلیا فکر میکنن همین که توی مستندات جنگو خوندن «حالا دیگه جنگو هم Async شده»، یعنی کافیه پشت defهای ویو (View) یک کلمه async بذارن و موتور بوگاتی رو روی بدنه پراید ببندن! اما حقیقت اینه که ORM جنگو مثل یک اسب وحشیه؛ اگر بلد نباشی چطور در حالت ناهمگام (Async) افسارش رو بگیری، نه تنها سرعتت زیاد نمیشه، بلکه کل سرور رو با خطای SynchronousOnlyOperation به در و دیوار میکوبی.

قلب تپنده برنامه‌های مدرن، Event Loop است. یک اپراتور سریع که نباید حتی برای یک میلی ثانیه بیکار بمونه. در جنگو های قدیمی، وقتی شما یک کوئری به دیتابیس می‌زدید، اپراتور (Thread) همون جا مینشست و به مانیتور زل میزد تا دیتابیس جواب بده. در Async، ما میخوایم اپراتور کوئری رو بفرسته و بره به درخواست یک کاربر دیگه برسه.

تله اول: بمب ساعتیِ SynchronousOnlyOperation

تله اول: بمب ساعتیِ SynchronousOnlyOperation

جنگو خیلی روی "امنیتِ تردها" (Thread Safety) حساسه. اگر سعی کنی وسط یک تابع async از متد های معمولی ORM (مثل .get() یا .filter()) استفاده کنی، جنگو بلافاصله ترمز رو میکشه و با یک خطای قرمز به صورتت سیلی میزنه!

چرا؟ چون ORM سنتی جنگو "بلاک‌کننده" (Blocking) است. اگر اجازه بده اون رو توی محیط Async اجرا کنی، کل Event Loop رو قفل میکنی و بقیه کاربرا رو توی صف میذاری.

راه حل مدرن: از متدهای جدید که با حرف a شروع می‌شن استفاده کن:

  • به جای .get() از .aget()

  • به جای .create() از .acreate()

  • به جای .save() از .asave()

فریبِ QuerySet‌های تنبل (Lazy Evaluation)

تله دوم: فریبِ QuerySet‌های تنبل (Lazy Evaluation)

اینجاست که اکثر حرفه ‌ای‌ها هم مچ شون خوابونده میشه! فرض کن این کد رو بنویسی:

# یک تله‌ی شیک و مجلسی
@app.get("/users")
async def get_users(request):
    users = User.objects.filter(is_active=True) # (1) اینجا خطایی نمیده
    for user in users: # (2) فاجعه اینجا رخ میده!
        print(user.username)

چه اتفاقی افتاد؟ در خط (1)، جنگو هیچ کوئری‌ ای به دیتابیس نمیزنه (چون ORM تنبله). اما در خط (2)، وقتی میخوای روی usersپیمایش کنی، تازه جنگو میخواد بره دیتابیس رو بخونه. اونجاست که چون هنوز توی محیط async هستی و داری از یک ابزار غیر async (حلقه for معمولی) استفاده میکنی، برنامه منفجر میشه.

راه حل حرفه‌ای: باید از async for استفاده کنی:

async for user in User.objects.filter(is_active=True):
    print(user.username)

تله سوم: ارتباطاتِ فامیلی (Foreign Keys & Prefetching)

توی دنیای Async، شما نمیتونی به راحتی بنویسی user.profile.address. چرا؟ چون دسترسی به user.profile خودش یک کوئری پنهان به دیتابیسه و این کوئری "ناهمگام" نیست.

اگر در یک ویوی Async هستی، باید تمام فیلد های مرتبط (Related Fields) رو همون اول با استفاده از select_related یا prefetch_related فراخوانی کنی، وگرنه وقتی وسط کد بخوای بهشون دسترسی پیدا کنی، با دیوارِ بلاک شدن برخورد می‌کنی.

تله چهارم: سیگنال‌ها و میان‌افزارها (Signals & Middlewares)

یادت باشه، هنوز همه جای جنگو کاملاً Async نشده. خیلی از سیگنال‌ ها (مثل post_save) هنوز در محیط سنتی اجرا می‌شن. اگر یک ویوی Async سنگین بنویسی که کلی سیگنالِ Sync رو پشت سر هم صدا میکنه، عملاً داری با ترمز دستیِ کشیده، گاز میدی!

چطور این مشکل رو حل کنیم؟ اگر مجبوری از یک کتابخانه یا بخشی از کد استفاده کنی که نسخه Async نداره، از ابزارِ نجات‌بخشِ sync_to_async استفاده کن:

from asgiref.sync import sync_to_async

# تابعی که هنوز قدیمی و بلاک‌کننده است
def my_old_heavy_task():
    # محاسبات سنگین یا کار با کتابخانه‌های قدیمی
    pass

# تبدیل آن به یک تابع که با اپراتورِ ما سازگار است
await sync_to_async(my_old_heavy_task)()

نتیجه‌گیری: کی بریم سراغ Async ORM؟

استفاده از Async در جنگو مثل استفاده از نیترو در مسابقات خیابانیه؛ اگر مسیرت مستقیم و طولانیه (مثلاً وقتی که باید همزمان به ۳ تا API دیگه درخواست بزنی و از دیتابیس هم دیتا بگیری)، Async معجزه میکنه. اما اگر فقط یک کوئری ساده داری، همون متدهای قدیمی جنگو (Sync) با یک وب‌سرورِ خوب مثل Gunicorn، هنوز هم مثل ساعت کار می‌کنن.

فراموش نکن:

  1. Event Loop خط قرمز شماست؛ با کدهای Sync بلاکش نکن.

  2. در ویوهای Async، حتماً از نسخه a متدها (aget, asave, ...) استفاده کن.

  3. اگر تردید داری که کدت بلاک‌کننده هست یا نه، حتماً اون رو به sync_to_async بسپار.