Модуль 35

Celery та Redis

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


🎓 Урок: Celery та Redis. Асинхронність у реальному житті

Привіт, друзі! 👋 Радий бачити вас на сьогоднішньому занятті.

Сьогодні ми розберемо тему, яка перетворює "гальмівні" веб-сайти на швидкі та чуйні додатки. Ми поговоримо про Celery та Redis.


1. 🔥 Вступ: Проблема "Втомленого Офіціанта"

Уявіть собі ситуацію. Ви заходите на сайт інтернет-магазину, натискаєте кнопку «Купити», і... сайт завмирає. Крутиться коліщатко завантаження. 3 секунди, 5 секунд, 10 секунд... Ви вже думаєте, що інтернет зник. Аж раптом — бац! — "Ваше замовлення прийнято, лист надіслано".

Що сталося? Чому ви чекали?

Справа в тому, що сервер у цей момент робив усе послідовно: 1. Зберіг замовлення в базу. 2. Почав формувати PDF-чек. 3. З'єднувався з поштовим сервером (який, можливо, тупив). 4. Відправляв email. 5. І тільки тоді відповів вам: "Все ок".

Риторичне питання: Чи хотіли б ви чекати в ресторані, поки офіціант піде на кухню, сам приготує страву, помиє посуд, і тільки потім повернеться до вас, щоб прийняти замовлення у наступного клієнта?

Звісно, ні! Це неефективно.

Як це має працювати: Офіціант (ваш веб-сервер) приймає замовлення, записує його на папірець, вішає на спеціальну дошку на кухні і миттєво повертається до вас. А кухарі (фонові процеси) вже готують страву, коли мають час.

У світі Python: * Офіціант — це ваш веб-фреймворк (Django, Flask, FastAPI). * Дошка із замовленнями — це Redis. * Кухар — це Celery.

Без цієї парочки будь-яка важка операція (обробка відео, надсилання розсилок, складні звіти) "повісить" ваш сайт.


2. 🧠 Теоретична база: Що відбувається "під капотом"?

Давайте розберемося з визначеннями, але без зайвої академічності.

Хто є хто?

  1. Celery (Селера 🌿) — це Task Queue (черга задач). Це бібліотека, яка дозволяє запускати код окремо від основного потоку програми. Це ваші "робітники" (workers), які постійно перевіряють, чи є для них робота.
  2. Redis — це Message Broker (посередник повідомлень). Celery не може спілкуватися з вашим сайтом напряму телепатично. Їм потрібне місце, куди сайт покладе задачу, а Celery її звідти забере. Redis — це надшвидка база даних (в оперативній пам'яті), яка ідеально підходить для ролі цієї "поштової скриньки".

Як це працює (Логіка процесу):

Уявіть цей процес покроково:

  1. Producer (Ваш сайт): Користувач натискає кнопку. Сайт каже: "Треба надіслати email". Але замість того, щоб слати його самому, він формує повідомлення ("Надішли лист юзеру ID 5") і кидає його в Redis. Сайт одразу відповідає юзеру: "Готово!". Час: 0.01 с.
  2. Broker (Redis): Тримає це повідомлення у списку (черзі).
  3. Consumer (Celery Worker): Це окремий процес, який запущено на сервері. Він постійно "смикає" Redis: "Є щось нове? Є щось нове?". Як тільки бачить задачу — хапає її, виконує (справді шле email) і записує результат.

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

  • Веб-сервер НЕ виконує задачу, він лише планує її (створює таск).
  • Celery і ваш сайт — це різні процеси. Якщо сайт впаде, Celery може продовжувати працювати (і навпаки).

3. 🧪 Приклади (Coding time!)

Для початку переконайтеся, що у вас встановлені Redis (сервер) та бібліотеки: pip install celery redis

Приклад 1: Мінімальний (Hello World)

Створимо файл tasks.py. Це наша кухня.

from celery import Celery
import time

# Налаштовуємо Celery
# Перший аргумент - ім'я модуля
# broker - адреса нашого "посередника" (Redis)
app = Celery('hello', broker='redis://localhost:6379/0')

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

Тепер уявімо, що ми — веб-сайт (відкрийте термінал Python або окремий файл main.py).

Питання до студента: Як ви думаєте, якщо я зараз запущу функцію say_hello("Alex"), вона виконається миттєво чи через 5 секунд?

Відповідь: Якщо викликати як звичайну функцію — через 5 секунд. Ми заблокуємо програму. Але магія Celery — у методі .delay().

from tasks import say_hello

# Це відпрацює миттєво!
result = say_hello.delay("Alex") 

print(f"Задачу відправлено! ID задачі: {result.id}")

Але стривайте! Ви не побачите повідомлення "Привіт, Alex" у терміналі, де запустили скрипт. Чому? Бо "кухар" ще не вийшов на зміну! Задача лежить у Redis і чекає.

Потрібно запустити Worker (у терміналі):

celery -A tasks worker --loglevel=INFO

Ось тепер ви побачите у логах воркера, як він підхопив задачу і виконав її.

Приклад 2: Реальна ситуація (Email)

У реальному житті ми не просто друкуємо текст.

@app.task
def send_confirmation_email(user_email):
    # Тут могла б бути логіка SMTP
    import time
    print(f"📨 Підготовка листа для {user_email}...")
    time.sleep(3) # Імітація з'єднання з сервером
    print(f"📤 Лист надіслано на {user_email}")
    return True

У вашому веб-контролері (наприклад, у Flask) це виглядало б так:

@app.route('/register', methods=['POST'])
def register():
    email = request.form['email']
    save_user_to_db(email)

    # Асинхронний виклик! Користувач не чекає 3 секунди.
    send_confirmation_email.delay(email)

    return "Реєстрація успішна! Перевірте пошту."

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

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

  1. 🔹 Запуск бази: Встановіть та запустіть Redis (або через Docker: docker run -d -p 6379:6379 redis).
  2. 🔹 Перший запуск: Створіть файл tasks.py з прикладу вище. Запустіть воркер в одному терміналі, а в іншому викличте задачу через python -c "from tasks import say_hello; say_hello.delay('Student')"
  3. 🔹 Змініть умови: Додайте функцію add(x, y), яка додає два числа, але з затримкою в 10 секунд. Запустіть 5 таких задач підряд у циклі. Подивіться, як воркер їх обробляє (послідовно чи паралельно? Підказка: Celery за замовчуванням запускає стільки процесів, скільки ядер у вашого процесора).
  4. 🔹 Error Handling: Зробіть задачу, яка спеціально ділить на нуль. Подивіться, що напише воркер у логах.
  5. 🔹 Міні-кейс: Уявіть, що ви робите Instagram. Напишіть задачу process_image(image_path), яка "імітує" накладання фільтру (просто sleep і print). Викличте її.

Питання "А що, якщо...": Що станеться, якщо ви вимкнете воркер (Ctrl+C), відправите 10 задач через .delay(), а потім знову увімкнете воркер? (Спробуйте! Спойлер: Redis збереже їх, і воркер виконає їх, як тільки прокинеться. Нічого не загубиться!)


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

Ви тепер знаєте синтаксис, але як не наробити помилок? Ось як думають сеньйори:

  1. Не передавайте об'єкти бази даних у Celery!

    • Помилка: send_email.delay(user_object)
    • Чому: Поки задача дійде до воркера, дані користувача в базі могли змінитися. Або серіалізація (перетворення об'єкта в текст для Redis) може зламатися.
    • Правильно: send_email.delay(user_id). Воркер отримає ID, сам піде в базу і дістане свіжі дані.
  2. Задачі мають бути ідемпотентними. Це складне слово означає просту річ: якщо виконати задачу двічі, нічого поганого не має статися.

    • Приклад: Якщо задача "Списати гроші" виконається двічі через збій мережі — це біда. Потрібно перевіряти всередині задачі: "Чи вже списано гроші за цією транзакцією?".
  3. Моніторинг. Коли задач тисячі, дивитися в консоль неможливо. Професіонали використовують Flower — це веб-інтерфейс для Celery. Він показує графіки, помилки та швидкість виконання.


6. 🧩 Підсумок

Отже, що ми сьогодні зробили? Ми навчилися делегувати. Ми розділили наш додаток на дві частини: 1. Швидкий фронт-офіс (веб-сайт), який миттєво відповідає клієнтам. 2. Потужний бек-офіс (Celery + Redis), який розгрібає важку роботу у фоні.

Тепер ви вмієте: * Налаштовувати зв'язку Celery + Redis. * Створювати фонові задачі. * Розуміти, як не змушувати користувача чекати.

👉 Тизер наступного уроку: А що, якщо задачу треба виконати не просто "у фоні", а чітко щопонеділка о 9:00? Або кожні 5 хвилин? На наступному уроці ми познайомимося з Celery Beat — планувальником, який перетворить ваш код на швейцарський годинник.

До зустрічі! 🚀