Модуль 21

Celery з Redis як broker

Ось урок, створений спеціально для тебе в стилі CS50. Уяви, що ми стоїмо в аудиторії Сандерс-театру, я закочую рукави, і ми починаємо розбиратися з тим, що робить сучасні веб-додатки швидкими.


🎓 Урок: Celery та Redis (Асинхронність на практиці)

Привіт, друзі! 👋

Сьогодні ми поговоримо про швидкість і чуйність. Про те, як зробити так, щоб ваш користувач ніколи не дивився на екран завантаження довше, ніж кліпає оком.

1. 🔥 Вступ: Проблема «завислого вікна»

Уявіть ситуацію. Ви пишете інтернет-магазин. Користувач натискає кнопку «Купити». Ваш код має зробити наступне: 1. Зняти гроші з картки. 2. Сформувати PDF-рахунок. 3. Відправити підтвердження на Email. 4. Показати сторінку «Дякуємо за покупку».

Здавалося б, усе просто. Але ось проблема: відправка email через SMTP-сервер може зайняти 2, 3, а іноді й 5 секунд. Генерація PDF — ще 2 секунди.

Питання до вас: Чи повинен користувач, натиснувши кнопку, чекати 7 секунд, дивлячись на колесо завантаження, поки ваш сервер займається "бюрократією"?

Звісно, ні! Користувач подумає, що сайт зламався, і натисне кнопку ще раз (і ви знімете гроші двічі 😱) або просто піде.

Аналогія з життя: ☕️ Ви в кав'ярні. Ви замовляєте лате. * Варіант А (Поганий): Касир приймає замовлення, сам йде молоти каву, гріти молоко, малювати сердечко, віддає вам чашку і ТІЛЬКИ ПОТІМ приймає замовлення у наступної людини. Черга стоїть. * Варіант Б (Правильний): Касир бере гроші, кричить баристі «Один лате!», дає вам чек і миттєво обслуговує наступного клієнта. Бариста готує каву паралельно.

У програмуванні Celery — це той самий бариста. А Redis — це блокнот, куди касир записує замовлення.


2. 🧠 Теоретична база: Як це працює «під капотом»

Ми звикли, що код виконується рядок за рядком (синхронно). Celery дозволяє виконувати код асинхронно — тобто у фоновому режимі.

Для цього нам потрібні три компоненти:

  1. Producer (Виробник): Це ваш основний веб-додаток (Django/Flask/FastAPI script). Він каже: "Треба зробити важку роботу, але я не маю часу. Зробіть це пізніше!".
  2. Broker (Посередник): Це Redis. Це наша поштова скринька або черга повідомлень. Додаток кидає туди задачу і забуває про неї. Redis тримає її у пам'яті.
  3. Worker (Робітник): Це процес Celery. Він постійно слідкує за Redis. Тільки-но там з'являється задача, він хапає її і починає виконувати в окремому процесі.

Що треба запам'ятати залізно:

  • Ваш сайт і Celery — це різні процеси. Вони не ділять спільну пам'ять (змінні). Вони спілкуються ТІЛЬКИ через Redis.
  • Redis тут виступає як Message Broker (брокер повідомлень).

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

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

Приклад 1: "Hello World" у світі асинхронності

Створимо файл tasks.py. Це буде наш "бариста".

# tasks.py
from celery import Celery
import time

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

@app.task
def slow_task(name):
    print(f"⏳ Починаю обробку для {name}...")
    time.sleep(5)  # Імітуємо важку роботу (5 секунд)
    print(f"✅ Готово для {name}!")
    return f"Привіт, {name}, твоя задача виконана!"

Тепер найцікавіше. Нам потрібно запустити Worker (нашого баристу). Відкрийте термінал і напишіть:

celery -A tasks worker --loglevel=info

Ви побачите, як Celery стартує і підключається до Redis.

Приклад 2: Виклик задачі (Producer)

Тепер відкрийте інший термінал (або створіть файл main.py). Це ваш "клієнт".

# main.py
from tasks import slow_task
import time

print("🚀 Я основна програма. Відправляю задачу...")

# Ключовий момент: ми використовуємо .delay()!
result = slow_task.delay("Студент CS50")

print("🏃‍♂️ Задача пішла в Redis! Я не чекаю і працюю далі.")
print(f"🆔 ID задачі: {result.id}")

# Просто для демонстрації почекаємо тут результату, 
# хоча в реальності ми б пішли робити щось інше.
while not result.ready():
    print("💤 Чекаю на фоновий процес...")
    time.sleep(1)

print(f"🎉 Результат отримано: {result.get()}")

Що ви очікуєте побачити? У терміналі main.py повідомлення "Задача пішла..." з'явиться миттєво. Програма не зависне на 5 секунд на рядку з .delay(). А в терміналі з celery worker ви побачите, як через 5 секунд задача виконається.


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

Час бруднити руки кодом! 👐

Завдання 1: Запуск Запустіть Redis, створіть tasks.py з прикладу вище, запустіть воркер і викличте задачу через python shell (python -> from tasks import...). Переконайтеся, що все працює.

Завдання 2: Математика у фоні Напишіть задачу add(x, y), яка додає два числа, але з штучною затримкою в 3 секунди. Запустіть 5 таких задач підряд у циклі for. Питання: Скільки часу займе відправка цих 5 задач? (Має бути майже миттєво).

Завдання 3: Обробка помилок Створіть задачу divide(x, y). Викличте її з параметрами (10, 0). Подивіться, що напише у логах Celery Worker. Чи впав сам Worker? (Спойлер: хороший worker не вмирає від однієї помилки, він просто пише ERROR).

Завдання 4: Міні-кейс "Email Reminder" Уявіть, що користувач реєструється, і ми хочемо надіслати йому лист не одразу, а через 10 секунд (щоб не набридати). Використовуйте метод .apply_async(args=[...], countdown=10). Перевірте, чи справді задача виконається із затримкою.


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

Ось де ми переходимо від новачків до профі. Як не вистрелити собі в ногу з Celery?

⚠️ Типова помилка новачка: Передача об'єктів

Уявіть, що у вас є об'єкт User з бази даних. НЕ РОБІТЬ ТАК:

# ❌ ПОГАНО
send_email.delay(user_object)

Чому? Поки задача дійде до Celery (через секунду чи хвилину), дані користувача в базі могли змінитися, або серіалізація об'єкта (перетворення в JSON для Redis) пройде криво.

РОБІТЬ ТАК:

# ✅ ДОБРЕ
send_email.delay(user_id)

Передавайте тільки прості типи даних (ID, рядки, числа). Нехай Celery сам дістане свіжі дані з бази за ID.

🧠 Порада профі: Атомарність та Ідемпотентність

Страшні слова, але суть проста. Що, як світло блимне, і Redis віддасть задачу, але Worker вимкнеться на половині шляху? А потім Redis (або налаштування retry) віддасть цю задачу знову. Ваш код має бути готовим виконатися двічі й не зламати нічого (наприклад, не списати гроші двічі). Перевіряйте статус транзакції перед дією!


6. 🧩 Підсумок

Отже, що ми сьогодні зробили? 1. Ми зрозуміли, що користувач не повинен чекати. 2. Ми дізналися, що Celery — це наш кухонний персонал, а Redis — це стійка із замовленнями. 3. Ми навчилися відправляти задачі у фон за допомогою .delay().

Тепер ви можете будувати додатки, які літають, навіть якщо під капотом вони роблять складні обчислення, конвертують відео або шлють тисячі листів.

🔮 Тизер наступного уроку: А що, якщо нам треба запускати задачу не коли користувач клікнув, а суворо щоранку о 9:00? Ми не будемо сидіти і натискати кнопку. Наступного разу поговоримо про Celery Beat і періодичні задачі. Це як будильник для вашого коду.

А поки що... це був CS50! 🏛️