Ось готовий урок, створений спеціально для тебе в стилі David Malan. Вмикай уяву, ми починаємо! 🚀
🎓 Урок CS50: Реальні кейси використання Celery
Привіт, друзі! Радий бачити вас.
Сьогодні ми поговоримо про те, що відрізняє "студентський" проєкт від серйозного, масштабованого веб-додатку. Ми розберемо інструмент, який дозволяє вашому серверу робити тисячу справ одночасно і не "задихатися". Ім'я цьому герою — Celery.
1. 🔥 Вступ: Чому ваш сайт "гальмує"?
Уявіть ситуацію. Ви створили чудовий інтернет-магазин. Користувач натискає кнопку "Купити". Що відбувається далі?
- Ви записуєте замовлення в базу даних.
- Списуєте кошти з картки.
- Генеруєте PDF-квитанцію.
- Відправляєте красивий Email-підтвердження.
- Відправляєте SMS-сповіщення менеджеру.
Якщо робити це все "в лоб" (синхронно), користувач натиснув кнопку і... чекає. Крутиться колесо завантаження. 3 секунди... 5 секунд... 10 секунд... Питання: Що зробить користувач через 10 секунд очікування? Правильно, він закриє вкладку і піде до конкурентів.
Аналогія з кав'ярнею ☕️ Уявіть Starbucks. Ви підходите до каси (це ваш веб-сервер) і замовляєте складний лате. * Сценарій без Celery: Касир приймає замовлення, сам йде молоти каву, збивати молоко, малювати серденько на пінці, віддає вам каву і тільки потім повертається до каси, щоб прийняти замовлення наступної людини в черзі. Черга буде кілометрова! * Сценарій з Celery: Касир бере гроші, пише ваше ім'я на стаканчику (створює задачу) і ставить його в чергу на стійку. Касир миттєво готовий обслуговувати наступного. А бариста (Worker) спокійно робить каву у фоновому режимі.
Ось навіщо нам Celery. Щоб "касир" (Django/Flask/FastAPI) завжди був вільний, а важку роботу робили "баристи" (Celery Workers).
2. 🧠 Теоретична база: Як це працює "під капотом"
Давайте розберемо механіку, але без нудних схем.
Celery — це асинхронна черга задач.
У цій системі є три головні дійові особи:
- Producer (Продюсер/Клієнт): Це ваш веб-сайт. Він каже: "Треба відправити імейл!".
- Broker (Брокер/Посередник): Це "дошка оголошень" або "скринька". Сюди складаються всі задачі. Найчастіше це Redis або RabbitMQ. Веб-сайт кидає туди задачу і забуває про неї.
- Worker (Робітник): Це окремий процес (програма), яка постійно дивиться на Брокера. "О, нова задача! Беру!". Він виконує її і (опціонально) записує результат.
🔑 Що треба зрозуміти залізно:
- Веб-запит має бути швидким. 200 мілісекунд — це добре. 10 секунд — це катастрофа. Все, що довго — у Celery.
- Fire and Forget (Вистрілив і забув). Веб-сервер не чекає, поки імейл відправиться. Він просто кидає задачу брокеру і каже користувачу: "Все буде зроблено!".
Що відбувається "під капотом"?
Коли ви викликаєте задачу в коді, Celery бере вашу функцію та аргументи, "пакує" їх (серіалізує) в текстове повідомлення (наприклад, JSON) і відправляє в Redis. Worker бачить це повідомлення, "розпаковує" його і запускає код.
3. 🧪 Приклади: Від Hello World до реальності
Рівень 1: Мінімальний приклад
Уявіть функцію додавання чисел.
Без Celery:
def add(x, y):
return x + y
# Виклик:
result = add(4, 4) # Програма чекає тут
print(result)
З Celery:
from celery import Celery
app = Celery('tasks', broker='redis://localhost:6379/0')
@app.task
def add(x, y):
return x + y
Як ми це викликаємо?
# Виклик:
result = add.delay(4, 4)
# Програма НЕ чекає! Вона йде далі миттєво.
Запитання до вас: Що ми отримаємо в змінній result одразу після виклику? Число 8?
Відповідь: Ні! Ми отримаємо об'єкт AsyncResult (щось типу квитанції з номером замовлення), за яким пізніше можна спитати статус виконання.
Рівень 2: Класика жанру — Email 📧
Це найпопулярніший кейс. SMTP сервери (Gmail, SendGrid) можуть тупити.
# tasks.py
import time
from celery import shared_task
@shared_task
def send_welcome_email(user_email):
# Імітація довгої роботи
time.sleep(5)
print(f"📧 Лист відправлено на {user_email}")
return "Done"
У вашому контролері (View):
def register_user(request):
user = create_user(request.POST)
# Ми не чекаємо 5 секунд!
send_welcome_email.delay(user.email)
return HttpResponse("Реєстрація успішна! Перевірте пошту.")
Рівень 3: Обробка зображень 🖼️
Користувач завантажує аватарку у 4K роздільній здатності (10 МБ). Нам треба зробити з неї мініатюру 100x100. Це "спалить" процесор, якщо робити прямо в запиті.
@shared_task
def process_avatar(image_path):
img = Image.open(image_path)
# Це важка операція для CPU
img.thumbnail((100, 100))
img.save(f"thumbs/{image_path}")
print("Avatar resized!")
Логіка: Користувач завантажив -> Зберегли оригінал -> Відповіли "ОК" -> Celery у фоні робить ресайз.
4. 🛠 Практична частина
Час забруднити руки кодом! (Умовно).
Завдання 1: Налаштування Уявіть, що у вас є встановлений Redis. Напишіть конфігурацію Celery (всього 2 рядки), щоб підключитися до нього.
Завдання 2: "Важка" математика
Напишіть таску calculate_factorial(n), яка рахує факторіал великого числа. Викличте її через .delay() для числа 1000.
Завдання 3: Виправлення помилки Подивіться на цей код:
def view(request):
task = heavy_task.delay()
result = task.get() # <--- У чому тут проблема?
return JsonResponse({'status': result})
Підказка: Згадайте аналогію з кав'ярнею. Що робить task.get() з нашою чергою?
Рішення: Це робить запит знову синхронним! Ми чекаємо результату. Виправте так, щоб ми повертали task_id клієнту, а клієнт потім окремим запитом (polling) перевіряв статус.
Завдання 4: Міні-кейс Ви робите систему для генерації місячних звітів у PDF. Звіт формується 2 хвилини. Опишіть алгоритм: 1. Що робить фронтенд, коли юзер тисне "Згенерувати"? 2. Що робить бекенд? 3. Як юзер дізнається, що PDF готовий? (Запропонуйте варіант: пошта, веб-сокет або просте оновлення сторінки).
Завдання 5: А що, якщо... Що станеться із задачами в черзі, якщо вимкнути світло (зупинити Redis), а Workers продовжать працювати? Чи зможуть вони отримати задачі?
5. 💡 Мислення як у розробника
Як відрізнити новачка від профі, дивлячись на код Celery?
❌ Типова помилка новачка: Передача об'єктів бази даних
# ТАК РОБИТИ НЕ МОЖНА!
process_order.delay(order_object)
Чому? Поки задача дійде до воркера (через 100 мс або через годину), дані в базі можуть змінитися. А ви передали стару версію об'єкта. До того ж, серіалізація складних об'єктів часто ламається.
✅ Як думає профі: Передача ID
# ТАК ТРЕБА!
process_order.delay(order_id)
Усередині таски ви робите Order.objects.get(id=order_id). Ви завжди берете найсвіжіші дані. Це State of Truth.
💡 Порада з практики:
Завжди пам'ятайте про ідемпотентність. Це страшне слово означає просту річ: якщо виконати задачу двічі, нічого поганого не станеться. Уявіть, що Celery подумав, що задача впала, і запустив її знову. Якщо ваша задача — "Списати $10", то користувач втратить $20. Це погано. Профі перевіряє всередині таски: "Чи списані вже кошти для цього замовлення?", і якщо так — просто виходить.
6. 🧩 Підсумок
Отже, що ми сьогодні забрали з собою?
- Celery — це спосіб винести довгі задачі за межі запиту користувача.
- Архітектура: Клієнт -> Брокер (Redis) -> Воркер.
- Ми використовуємо
task.delay()замістьtask(). - Ми передаємо в задачі прості дані (ID, числа, рядки), а не складні об'єкти.
Що ви тепер вмієте? Ви знаєте, як зробити так, щоб ваш сайт літав, навіть якщо йому треба перекодувати відео, розіслати тисячу листів чи порахувати складний звіт. Ви розблокували "Баристу"!
🔜 У наступній серії: Ми навчилися запускати задачі "зараз". А як щодо того, щоб запускати задачі за розкладом? Наприклад, щоранку о 9:00 надсилати дайджест новин? Готуйтеся, наступна тема — Celery Beat.
Це був CS50 (українська версія). Кодимо далі! 💻