خیلیا فکر میکنن همین که پشت کد های FastAPI کلمه async رو بنویسن، دکمه ی نیترو رو زدن و همهچیز قراره پرواز کنه. اما واقعیت اینه که اگر رانندگی با این موتور رو بلد نباشی، توی اولین پیچ، چرخ هات قفل میکنه و همه ی درخواست ها رو توی صف معطل میکنی.
قلب تپنده: اِونت لوپ (Event Loop) چطور کار میکند؟
قبل از اینکه سراغ تله ها بریم، باید بفهمی زیر این باک خوش رنگ چه خبره. FastAPI روی چیزی به اسم Event Loop میچرخه. تصور کن یک ایستگاه کنترل سایبری داری که فقط یک اپراتور (Single Thread) داره. این اپراتور فوق العاده هوشمند و سریعه. کارش اینه:
-
درخواست اول رو میگیره.
-
اگر درخواست نیاز به زمان داشت (مثلاً خوندن از دیتابیس)، اپراتور اون رو میسپره به سیستمِ "انتظار" و بلافاصله میره سراغ درخواست دوم.
-
به محض اینکه جواب دیتابیس اومد، اپراتور برمیگرده و کارِ اول رو تموم میکنه.
فاجعه زمانی رخ میده که این اپراتورِ تنها رو مجبور کنی یک کارِ وقتگیر انجام بده که "قابلیت سپردن به سیستم انتظار" رو نداره. در این لحظه، اپراتور قفل میشه و کل ایستگاه کنترل (سرور شما) از کار می افته.
تله اول: ترمز دستی وسط پیست (time.sleep)
این رایج ترین تله ای هست که توسعهدهنده هایی که از دنیای جنگو یا فلاسک میان، توش میافتن. توی پایتونِ سنتی، ما عادت داریم برای وقفه انداختن از time.sleep() استفاده کنیم. اما توی FastAPI، این کار مثل اینه که وسط مسابقهی فرمول یک، ترمز دستی رو بکشی!
-
وقتی مینویسی
time.sleep(5): یعنی به اون اپراتور تنها میگی: «بشین و ۵ ثانیه به ساعت دیواری زل بزن و حق نداری به هیچکس دیگهای جواب بدی!» -
نتیجه: اگر ۱۰۰ نفر همزمان به سایت شما بیان، نفر صدم باید ۵۰۰ ثانیه منتظر بمونه!
راه حل حرفهای: همیشه و همیشه از asyncio.sleep() استفاده کن. این دستور به اپراتور میگه: «من ۵ ثانیه کار دارم، تو برو به بقیه برس، ۵ ثانیه دیگه صدام کن.»
تله دوم: بار تریلی روی دوش موتور (CPU-Bound Tasks)
گاهی اوقات شما کارِ "انتظار" ندارید (مثل خوندن از دیتابیس)، بلکه کار "محاسباتی" سنگین دارید. مثلاً میخواید یک فایل اکسل ۱۰۰ هزار ردیفی رو پردازش کنید، یا یک عکس رو فشرده کنید.
اینجا یک تله ی ذهنی وجود داره: «خب من که کلمه async رو گذاشتم، پس حتماً سریع انجام میشه!» اشتباهه! کار های محاسباتی از CPU کار میکشن. وقتی CPU درگیر حساب و کتاب سنگین یک تابع async بشه، دیگه نمیتونه به Event Loop اجازه بده که بین درخواست ها جا به جا بشه.
چطور این مشکل رو حل کنیم؟ FastAPI یک ترفند فوق العاده داره. اگر کاری داری که محاسباتش سنگینه، کلمهی async رو از پشت defحذف کن!
وقتی تابع رو به صورت یک def معمولی مینویسی، FastAPI میفهمه که این یک کارِ "بلاککننده" (Blocking) هست. پس اون رو از مسیر اصلی مسابقه برمیداره و میفرسته به یک Thread Pool (یک اتاق خلوت با اپراتورهای کمکی). اینطوری مسیر اصلی برای بقیه کاربرا باز میمونه.
تله سوم: استفاده از ابزارهای عتیقه (مثل کتابخانه requests)
کتابخانهی requests توی پایتون مثل یک رفیقِ قدیمی و مطمئنه، اما این رفیقِ ما "ناهمگام" (Async) نیست. وقتی توی یک تابعِ Async از requests.get استفاده میکنی، تمام اون ساختارِ سرعتی که FastAPI ساخته بود رو نابود میکنی. چون این کتابخانه کلِ ترد (Thread) رو تا زمان گرفتن جواب، گروگان میگیره.
جایگزین مدرن: باید از کتابخانههایی استفاده کنی که با زبانِ FastAPI حرف میزنن. مثلاً HTTPX.
import httpx
@app.get("/get-data")
async def get_data():
async with httpx.AsyncClient() as client:
# اینجا اپراتور منتظر نمیمونه و میره سراغ بقیه
resp client.get("https://api.test.com")
return response.json()
چطور بفهمیم کدمان دارد "بلاک" میشود؟
یکی از سختترین کارها در FastAPI، پیدا کردنِ کدهاییه که دارن سرعت رو مخفیانه پایین میارن. یک ابزار عالی برای این کار وجود داره به اسم aiodebug یا حتی استفاده از لاگهای خودِ asyncio. اگر دیدی زمان پاسخدهی (Response Time) سایتت با بالا رفتنِ تعداد کاربرا به صورت وحشتناکی زیاد میشه، مطمئن باش یک جا داری از یک کتابخانه یا متدِ "بلاککننده" استفاده میکنی.
نتیجهگیری
راندنِ FastAPI لذتبخش هست، اما به شرطی که یادت نره این موتور چطور کار میکنه:
-
Event Loop ناموسِ پروژه شماست؛ هرگز، تحت هیچ شرایطی، اون رو با کدهای سنگین یا متوقفکننده قفل نکن.
-
اگر کتابخانهای نسخه
asyncنداره، اون تابع رو به صورتdefمعمولی بنویس تا FastAPI خودش اون رو به تردِ جداگانه بفرسته. -
همیشه یادت باشه:
asyncیعنی هنرِ منتظر موندنِ هوشمندانه، نه جادوگری برای سریع تر کردنِ محاسباتِ ریاضی.
اگر این قواعد رو رعایت کنی، اپلیکیشن تو میتونه با کمترین منابع، به هزاران کاربر همزمان سرویس بده؛ چیزی که توی فریمورک های قدیمی تر مثل یک رویا بود.
نظرات کاربران (0)