Модуль 5

Перша асинхронна задача в Celery

Ось готовий урок, створений за твоїм майстер-промптом. Поїхали! 🚀


🎓 CS50 Style: Перша асинхронна задача в Celery

Привіт, друзі! Мене звати [Твоє Ім'я], і сьогодні ми поговоримо про магію, яка відбувається, коли користувач натискає кнопку, а сторінка не зависає.

1. 🔥 Вступ: Чому ми взагалі тут?

Уявіть ситуацію. Ви заходите на сайт, реєструєтесь, натискаєте кнопку "Створити акаунт"... і чекаєте. Крутиться спіннер. Проходить секунда, дві, п'ять... Ви починаєте нервувати. Чи натиснути ще раз? Чи закрити вкладку?

Що відбувається "під капотом"? У цей момент сервер, можливо, генерує важкий PDF-звіт, надсилає вітальний email або обробляє ваше фото для аватарки. І поки він це робить, він ігнорує вас.

Риторичне запитання: Чи хотіли б ви користуватися таким сайтом? Звісно, ні.

Аналогія з життя: Уявіть, що ви в Starbucks. Ви замовляєте лате. Касир (це ваш веб-сервер) приймає замовлення, бере гроші... а потім сам йде молоти каву, збивати молоко, малювати сердечко пінкою. Черга стоїть. Всі чекають, поки касир повернеться. Це — синхронний підхід.

А як це працює насправді? Касир бере замовлення, пише ваше ім'я на стаканчику, передає його баристі (це Celery) і одразу кричить: "Наступний!". Ви відходите, а робота робиться у фоні. Це — асинхронність.

Сьогодні ми навчимо нашого "касира" (Python-код) делегувати важку роботу "баристі" (Celery).


2. 🧠 Теоретична база (Як це працює?)

Отже, щоб реалізувати цю схему "Касир — Бариста", нам потрібні три компоненти. Давайте розберемо їх без складної термінології.

  1. Producer (Продюсер/Клієнт): Це ваш основний код (наприклад, Django або Flask). Він каже: "Треба надіслати цей лист". Він не надсилає лист, він лише створює задачу.
  2. Broker (Брокер): Це дошка із замовленнями або "поштова скринька". Сюди потрапляють задачі. Найпопулярніші брокери — Redis або RabbitMQ. Брокер тримає задачі в черзі, поки хтось їх не забере.
  3. Worker (Воркер/Робітник): Це окремий процес (наш бариста). Він постійно дивиться на Брокера: "Є щось для мене?". Як тільки з'являється задача, він її хапає і виконує.

⚙️ Що відбувається "під капотом"?

Коли ви викликаєте функцію через Celery, ваш Python не виконує її код. Він робить наступне: 1. Бере назву функції та аргументи (наприклад, send_email(user_id=5)). 2. Пакує це в повідомлення (серіалізує, зазвичай у JSON). 3. Кидає це повідомлення в Redis (Брокер). 4. Повертає вам контроль миттєво!

А десь там, в паралельному всесвіті (в іншому процесі), Celery Worker розпаковує повідомлення і запускає функцію.

❗️ Запам'ятайте обов'язково: Основна програма і Celery Worker — це різні процеси. Вони не мають спільної пам'яті. Вони спілкуються тільки через повідомлення (Брокер).


3. 🧪 Приклади: Від простого до реального

Для прикладів уявімо, що у нас вже встановлений Redis і бібліотека Celery (pip install celery redis).

Приклад 1: "Hello World" у світі задач

Створимо файл tasks.py.

from celery import Celery
import time

# Налаштовуємо Celery на роботу з Redis
app = Celery('my_tasks', broker='redis://localhost:6379/0')

@app.task
def add(x, y):
    print(f"🧮 Починаю додавати {x} + {y}...")
    time.sleep(5)  # Імітуємо важку роботу (наприклад, складні обчислення)
    result = x + y
    print(f"✅ Готово! Результат: {result}")
    return result

Питання до вас: Якщо я викличу add(4, 4) у звичайному скрипті, скільки часу я чекатиму? Правильно, 5 секунд.

А тепер, давайте викличемо це через Celery в іншому файлі run.py:

from tasks import add

# Використовуємо .delay() — це магічна команда запуску в фоні
result = add.delay(4, 4)

print("🚀 Я не чекав 5 секунд! Я пішов далі!")

Чому результат такий? Тому що add.delay(4, 4) лише кинув задачу в Redis і втік. Якщо у вас не запущений Worker, задача буде просто лежати в Redis і чекати.

Щоб магія спрацювала, у терміналі треба запустити воркера: celery -A tasks worker --loglevel=info


Приклад 2: Реальний кейс (Надсилання Email)

У реальності ми не додаємо числа, ми робимо I/O операції (мережа, диск).

@app.task
def send_welcome_email(email):
    print(f"📧 Підключаюсь до SMTP сервера для {email}...")
    time.sleep(3) # Імітація затримки мережі
    print(f"📨 Лист відправлено на {email}!")
    return True

Уявіть, що це частина реєстрації користувача. Без Celery користувач 3 секунди дивиться на білий екран. З Celery — він миттєво бачить сторінку "Дякуємо за реєстрацію!".


4. 🛠 Практична частина

Час забруднити руки кодом! Виконайте ці завдання:

  1. 🔹 Hello Celery: Запустіть Redis (можна через Docker), створіть файл tasks.py з прикладу вище і запустіть воркера. Переконайтеся, що ви бачите логи у вікні воркера.
  2. 🔹 Ефект "delay": Створіть скрипт, який запускає задачу add.delay(10, 20), одразу після цього друкує "Задачу відправлено", а потім ще 5 разів друкує крапку . з паузою в 1 секунду. Спостерігайте, як у воркері виконується задача паралельно з вашими крапками.
  3. 🔹 Виправ помилку: Спробуйте передати в задачу аргумент, який неможливо серіалізувати в JSON (наприклад, відкритий файловий об'єкт або складний клас без методів серіалізації). Що напише Celery у логах?
  4. 🔹 Міні-кейс "Кухня": Напишіть задачу cook_dish(dish_name, duration), яка приймає назву страви і час приготування. Запустіть цикл, який "замовляє" 3 різні страви підряд. Подивіться, як вони готуються (послідовно чи паралельно? Підказка: за замовчуванням Celery запускає кілька процесів-воркерів, тому може бути паралельно).
  5. 🔹 А що, якщо...: Зупиніть воркера (Ctrl+C). Запустіть задачу через Python. Потім знову увімкніть воркера. Що сталося із задачею? (Вона мала виконатися, бо Redis зберіг її, поки воркер спав).

5. 💡 Мислення як у розробника

Ось тут новачки часто ламають зуби. Послухайте досвідченого "старшого брата":

❌ Помилка №1: Передача об'єктів бази даних

Ніколи, чуєте, НІКОЛИ не передавайте цілий об'єкт користувача в задачу.

# ⛔️ ПОГАНО:
send_email.delay(user_object)

# ✅ ДОБРЕ:
send_email.delay(user_object.id)

Чому? Поки задача дійде до виконання (може пройти хвилина), дані користувача в базі можуть змінитися. А у воркера буде старий, "протухлий" об'єкт. Завжди передавайте ID і діставайте свіжі дані всередині задачі.

❌ Помилка №2: Очікування результату одразу

Якщо ви робите result = task.delay(...) і одразу викликаєте result.get() — ви перетворюєте асинхронний код назад у синхронний! Ви знову заблокували "касира", поки "бариста" не зробить каву.

🧠 Як думає профі?

Профі думає про ідемпотентність. Це страшне слово означає просту річ: чи безпечно виконати цю задачу двічі? Іноді мережа глючить, і Celery може спробувати виконати задачу ще раз. Якщо ваша задача — "списати гроші", і вона виконається двічі... у вас проблеми. Профі пише код так, щоб перевіряти: "А чи не робили ми це вже?".


6. 🧩 Підсумок

Ну що, вітаю! 🎉 Ви щойно розблокували суперсилу фонових задач.

Сьогодні ми: 1. Зрозуміли, що не треба змушувати користувача чекати. 2. Розділили відповідальність: Продюсер (створює) — Брокер (зберігає) — Воркер (робить). 3. Навчилися запускати задачі через .delay().

Тепер ви вмієте: Будувати веб-додатки, які "літають", навіть якщо під капотом вони рахують числа Пі або конвертують відео.

🤔 Що далі? Зараз ми запускали задачі "руками". А що, якщо ми хочемо, щоб задача виконувалася сама щоранку о 9:00 (наприклад, надсилала дайджест новин)? На наступному уроці ми познайомимося з Celery Beat — будильником для ваших задач.

А поки — спробуйте не спалити воркера! Успіхів! 👋