Ось готовий урок, згенерований за твоїм майстер-промптом.
🚀 Тема уроку: Оптимізація продуктивності FastAPI
Привіт, друзі! Це CS50 (умовно 😉), і сьогодні ми говоримо про швидкість.
1. 🔥 Вступ: Коли "Fast" у назві FastAPI перестає працювати
Уявіть ситуацію. Ви створили стартап — новий, крутий сервіс доставки піци. Ви обрали FastAPI, бо, ну знаєте, він же називається FastAPI. У документації написано, що він найшвидший.
Ви запускаєте сервіс. Приходить 10 клієнтів — все літає. Приходить 100 — все окей. Але раптом маркетинг спрацював на відмінно, і до вас стукає 10 000 користувачів одночасно. І тут... ваш сервер "лягає". Запити висять по 10 секунд. Клієнти йдуть до конкурентів.
Питання: Чому так сталося? Хіба фреймворк не обіцяв нам магічну швидкість?
Відповідь: FastAPI — це як Ferrari. У нього потужний двигун. Але якщо ви, як водій, тиснете на гальма (пишете блокуючий код) або заливаєте погане паливо (неефективна робота з базою даних), навіть Ferrari не поїде швидко.
Сьогодні ми розберемося, як не бути тим водієм, що гальмує суперкар.
2. 🧠 Теоретична база: Що відбувається під капотом?
Щоб оптимізувати, треба розуміти, як воно працює.
Головний герой: Event Loop (Цикл подій)
Уявіть собі одного офіціанта в дуже популярному ресторані. Це ваш FastAPI процес. * Синхронний код (Sync): Офіціант приймає замовлення, йде на кухню і чекає, поки кухар приготує страву. Він просто стоїть і дивиться на пательню. В цей час інші клієнти чекають. Це погано. * Асинхронний код (Async): Офіціант приймає замовлення, передає його на кухню і одразу йде обслуговувати наступного клієнта. Коли страва готова, дзвоник дзеленчить, і він повертається забрати її.
🔑 Що треба запам'ятати залізно:
FastAPI працює на одному потоці (здебільшого). Якщо ви зробите щось "важке" (наприклад, складні математичні обчислення або синхронний запит до стороннього API) прямо в цьому потоці — ви заблокуєте "офіціанта". Весь сервер зупиниться для всіх користувачів.
🧩 Інтуїтивне розуміння:
- I/O Bound (Введення/Виведення): Робота з базою, мережею, файлами. Тут ідеально працює
async/await. Ми "чекаємо", поки хтось інший зробить роботу. - CPU Bound (Процесор): Обробка зображень, складні формули, ML-моделі.
asyncтут не допоможе, бо процесор реально зайнятий. Тут потрібні інші трюки (про них згодом).
3. 🧪 Приклади: Від гальм до швидкості
Приклад 1: Класична помилка (Вбивця продуктивності)
Багато хто пише так, думаючи, що async def — це магічне заклинання.
import time
from fastapi import FastAPI
app = FastAPI()
@app.get("/slow")
async def slow_endpoint():
# Симулюємо довгу роботу (наприклад, важкий запит)
time.sleep(5)
return {"message": "Я прокинувся!"}
Питання до вас: Якщо я відправлю 3 запити одночасно на цей ендпоінт, скільки часу сумарно чекатиме третій користувач? 5 секунд чи 15?
... ...
Відповідь: Він чекатиме 15 секунд (плюс-мінус).
Чому? time.sleep(5) блокує весь Event Loop. Поки сервер "спить" для першого клієнта, він не може навіть прийняти з'єднання від другого.
Приклад 2: Виправляємо ситуацію (Asyncio)
import asyncio
from fastapi import FastAPI
app = FastAPI()
@app.get("/fast")
async def fast_endpoint():
# Віддаємо управління назад в Event Loop
await asyncio.sleep(5)
return {"message": "Я прокинувся, але не блокував інших!"}
Тепер, якщо ми кинемо 100 запитів одночасно, вони всі завершаться приблизно за 5.1 секунди. Офіціант (FastAPI) прийняв замовлення і пішов далі, поки таймер тікав.
Приклад 3: Турбо-режим з ORJSON
Стандартний JSON-серіалізатор у Python повільний. Якщо ваш API повертає великі списки даних (наприклад, 10 000 товарів), серіалізація може займати більше часу, ніж запит до БД.
Використовуємо ORJSONResponse (він написаний на Rust і дуже швидкий).
from fastapi.responses import ORJSONResponse
@app.get("/items", response_class=ORJSONResponse)
async def read_items():
# Уявіть тут величезний список словників
large_data = [{"id": i, "name": f"Item {i}"} for i in range(10000)]
return large_data
Результат: Просто змінивши клас відповіді, ми можемо прискорити віддачу даних у 5-10 разів.
4. 🛠 Практична частина
Час закотити рукави. Виконайте ці завдання, щоб відчути різницю.
- 🔹 Повтори та відчуй: Запусти код із Прикладу 1 ("поганий"). Відкрий дві вкладки браузера і спробуй завантажити сторінку одночасно. Переконайся, що друга вкладка "висить", поки перша не закінчить.
- 🔹 Магічне виправлення: Заміни код на Приклад 2. Зроби те саме. Відчуваєш різницю?
- 🔹 Реальна задача (БД): Уяви, що в тебе є функція
get_users_from_db(), яка не є асинхронною (використовує стару бібліотеку).- Завдання: Як змусити її працювати у FastAPI, не блокуючи цикл?
- Підказка: Визнач ендпоінт просто як
def(безasync), або використайrun_in_executor.
- 🔹 Міні-кейс: Тобі треба зробити ендпоінт, який приймає зображення, зменшує його розмір (це CPU-навантаження) і повертає назад.
- Питання: Чи допоможе тут
await asyncio.sleep()? Чому ні? - Рішення: Напиши псевдокод, як би ти це реалізував (варто подивитися в бік
BackgroundTasksабо окремого воркера типу Celery).
- Питання: Чи допоможе тут
- 🔹 А що, якщо... Що буде, якщо ми запустимо FastAPI не в один процес, а в 4 (через
gunicorn -w 4 ...)? Як це вплине на Приклад 1? (Спойлер: це частково вирішить проблему, але тільки для 4 користувачів).
5. 💡 Мислення як у розробника
Як відрізнити новачка від сеньйора в оптимізації?
❌ Помилка новачка:
Він намагається оптимізувати все підряд. Переписує цикли, змінює назви змінних. Це "економія на сірниках". Або він пхає async усюди, навіть там, де бібліотека це не підтримує (наприклад, стандартний модуль requests у Python є синхронним — ніколи не використовуйте його всередині async def у FastAPI! Беріть httpx).
✅ Думки профі:
Досвідчений розробник спочатку вимірює. Він ставить питання: 1. Де пляшкове горлечко? Це База Даних? Це серіалізація JSON? Чи це мій Python-код? 2. I/O чи CPU? Якщо це очікування (I/O) — робимо асинхронним. Якщо це обчислення (CPU) — виносимо в окремий процес або фонову задачу. 3. Кешування. Навіщо генерувати відповідь, якщо вона не змінилася? (Redis — ваш найкращий друг).
Порада: Використовуйте профайлери (наприклад, PyInstrument) або middleware для заміру часу виконання запиту. Не гадайте на кавовій гущі.
6. 🧩 Підсумок
Отже, що ми сьогодні поклали у свою "валізу знань"?
- FastAPI швидкий, тільки якщо ми не блокуємо Event Loop.
time.sleep()— ворог,asyncio.sleep()— друг.- Синхронні операції (старі драйвери БД, важкі обчислення) вбивають продуктивність асинхронного сервера.
- Використання
ORJSONResponse— це "безкоштовне" прискорення для великих JSON-ів.
Тепер ви вмієте не просто писати код, який працює, а код, який літає і витримує навантаження.
👉 Наступного разу: Ми поговоримо про те, де зберігати дані, щоб це було так само швидко. Готуйтеся зануритися у світ Redis та Кешування.
А поки що — щасти вам у коді!