Ось урок, створений спеціально для тебе в стилі CS50. Уяви, що ми стоїмо в аудиторії Сандерс-театру, я закочую рукави, і ми починаємо розбиратися з тим, що робить сучасні веб-додатки швидкими.
🎓 Урок: Celery та Redis (Асинхронність на практиці)
Привіт, друзі! 👋
Сьогодні ми поговоримо про швидкість і чуйність. Про те, як зробити так, щоб ваш користувач ніколи не дивився на екран завантаження довше, ніж кліпає оком.
1. 🔥 Вступ: Проблема «завислого вікна»
Уявіть ситуацію. Ви пишете інтернет-магазин. Користувач натискає кнопку «Купити». Ваш код має зробити наступне: 1. Зняти гроші з картки. 2. Сформувати PDF-рахунок. 3. Відправити підтвердження на Email. 4. Показати сторінку «Дякуємо за покупку».
Здавалося б, усе просто. Але ось проблема: відправка email через SMTP-сервер може зайняти 2, 3, а іноді й 5 секунд. Генерація PDF — ще 2 секунди.
Питання до вас: Чи повинен користувач, натиснувши кнопку, чекати 7 секунд, дивлячись на колесо завантаження, поки ваш сервер займається "бюрократією"?
Звісно, ні! Користувач подумає, що сайт зламався, і натисне кнопку ще раз (і ви знімете гроші двічі 😱) або просто піде.
Аналогія з життя: ☕️ Ви в кав'ярні. Ви замовляєте лате. * Варіант А (Поганий): Касир приймає замовлення, сам йде молоти каву, гріти молоко, малювати сердечко, віддає вам чашку і ТІЛЬКИ ПОТІМ приймає замовлення у наступної людини. Черга стоїть. * Варіант Б (Правильний): Касир бере гроші, кричить баристі «Один лате!», дає вам чек і миттєво обслуговує наступного клієнта. Бариста готує каву паралельно.
У програмуванні Celery — це той самий бариста. А Redis — це блокнот, куди касир записує замовлення.
2. 🧠 Теоретична база: Як це працює «під капотом»
Ми звикли, що код виконується рядок за рядком (синхронно). Celery дозволяє виконувати код асинхронно — тобто у фоновому режимі.
Для цього нам потрібні три компоненти:
- Producer (Виробник): Це ваш основний веб-додаток (Django/Flask/FastAPI script). Він каже: "Треба зробити важку роботу, але я не маю часу. Зробіть це пізніше!".
- Broker (Посередник): Це Redis. Це наша поштова скринька або черга повідомлень. Додаток кидає туди задачу і забуває про неї. Redis тримає її у пам'яті.
- 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! 🏛️