Модуль 17

Background tasks

Ось урок на тему Background Tasks (Фонові завдання) у стилі CS50.


🎓 CS50: Background Tasks (Фонові завдання)

Привіт, друзі! Це CS50, і сьогодні ми поговоримо про час. Точніше, про те, як ми його економимо.

1. 🔥 Вступ: Чому ніхто не любить чекати?

Уявіть, що ви зайшли в Starbucks (або вашу улюблену кав'ярню). Ви підходите до каси, замовляєте складний лате з мигдалевим молоком і сиропом.

Що відбувається далі? Варіант А (Поганий дизайн): Касир приймає замовлення, каже вам «чекайте», сам йде до кавомашини, меле зерна, збиває молоко, наливає каву, віддає її вам — і тільки після цього повертається до каси, щоб прийняти замовлення наступної людини в черзі. Результат: Черга тягнеться аж на вулицю. Люди злі. Бізнес втрачає гроші.

Варіант Б (Як це працює насправді): Касир приймає замовлення, пише ваше ім’я на стаканчику, ставить його в чергу на стійку і миттєво повертається до наступного клієнта. А десь там збоку бариста (інша людина!) бачить стаканчик, готує каву і вигукує ваше ім'я, коли все готово.

Питання до вас: Як ви думаєте, якби Instagram працював за Варіантом А, скільки б ви чекали після натискання кнопки «Опублікувати фото», поки сервер його стисне, накладе фільтри й розішле повідомлення всім друзям? 5 секунд? 10 секунд? У вебі 10 секунд — це вічність. Користувач подумає, що сайт зламався, і закриє вкладку.

Ось тут нам і потрібні Background Tasks (Фонові завдання). Це наш «бариста», який робить важку роботу, поки «касир» (веб-сервер) посміхається клієнтам.


2. 🧠 Теорія: Що відбувається «під капотом»?

Давайте розберемо це на схемі веб-додатку.

Коли ви пишете звичайний код (наприклад, на Python чи PHP), він виконується синхронно. Тобто рядок за рядком. 1. Отримати запит. 2. Зробити дію. 3. Повернути відповідь.

Якщо дія важка (надсилання email, обробка відео, генерація PDF-звіту), користувач бачить білий екран і кружечок завантаження.

Щоб це виправити, нам потрібна архітектура з трьох компонентів:

  1. Web Server (Продюсер/Касир): Приймає запит від користувача. Він каже: "Окей, я прийняв задачу, ось тобі номер замовлення (ID), результат буде пізніше".
  2. Message Broker (Черга/Стійка зі стаканчиками): Це спеціальне місце (наприклад, Redis або RabbitMQ), де зберігаються списки завдань. Це просто список: "Зробити звіт для User 1", "Надіслати лист для User 5".
  3. Worker (Споживач/Бариста): Це окремий процес (програма), що працює на фоні. Він постійно питає у Брокера: "Є робота?". Якщо є — бере, виконує і записує результат у базу даних.

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

  • Веб-запит має бути швидким (мілісекунди).
  • Усе, що триває довше 1-2 секунд — у фон.
  • Broker — це посередник, щоб сервер і воркер не залежали один від одного. Якщо воркер впаде, задачі залишаться в черзі (брокері) і будуть виконані пізніше.

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

Давайте подивимося на код. Уявіть, що ми пишемо функцію реєстрації користувача.

Приклад 1: Блокуючий код (Так робити не треба) ❌

def register_user(request):
    # 1. Зберігаємо користувача в БД (швидко - 0.05 с)
    user = db.save(request.form)

    # 2. Надсилаємо вітальний email (ПОВІЛЬНО - 3.0 с)
    # Весь сервер "завис", чекаючи відповіді від поштового сервісу
    email_service.send(user.email, "Welcome!") 

    # 3. Віддаємо відповідь
    return "Ви успішно зареєстровані!"

Результат: Користувач натиснув кнопку і чекає 3+ секунди. Відчуття — сайт гальмує.

Приклад 2: Асинхронний підхід (Background Task) ✅

Тут ми використовуємо умовну бібліотеку (наприклад, Celery у Python).

# Це наш Worker (Бариста)
# Він живе в окремому файлі і нічого не знає про HTTP-запити
@task
def send_email_task(user_id):
    user = db.get(user_id)
    email_service.send(user.email, "Welcome!")
    print(f"Email sent to {user.email}")

# Це наш Web Server (Касир)
def register_user(request):
    # 1. Зберігаємо в БД
    user = db.save(request.form)

    # 2. Кидаємо задачу в чергу (миттєво!)
    # .delay() каже: "Зроби це потім", і не чекає завершення
    send_email_task.delay(user.id)

    # 3. Миттєво віддаємо відповідь
    return "Ви успішно зареєстровані! Лист скоро прийде."

Що очікує студент? Ви можете подумати: "Але ж лист ще не відправлено, коли ми повернули відповідь?" Реальність: Саме так! Користувач бачить "Успіх" миттєво. Лист прийде через 2 секунди, але користувач вже може користуватися сайтом. Це — UX (User Experience).

Приклад 3: Реальна задача — Обробка зображень 🖼️

Уявіть, що користувач завантажує аватарку у 4K (10 МБ). Нам треба зробити з неї маленьку іконку 100x100.

  1. User завантажує фото.
  2. Server зберігає оригінал на диск і каже: "Отримано! Обробляємо...".
  3. Broker отримує повідомлення: {"task": "resize", "image_path": "/tmp/photo.jpg"}.
  4. Worker прокидається, бере картинку, стискає її процесором (це важко!), зберігає мініатюру.
  5. (Опціонально): Воркер посилає сигнал через WebSocket, і у користувача на екрані оновлюється аватарка без перезавантаження.

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

Час розім'яти мозок! Ось вам кілька ситуацій. Як би ви вчинили?

Завдання 1. Детектив лагів У вас є інтернет-магазин. Клієнт натискає «Купити», і сайт думає 10 секунд. Ви дивитесь у код і бачите це: 1. Перевірка наявності товару. 2. Зписання коштів з карти. 3. Генерація PDF-чеку. 4. Відправка чеку на email. 5. Відправка SMS адміністратору про продаж.

Питання: Які з цих пунктів треба залишити в основному потоці (щоб клієнт бачив результат одразу), а які — винести у Background Tasks? Чому?

Завдання 2. "А що, якщо..." Ви відправили задачу воркеру: «Зняти гроші з картки». Але під час виконання воркер «впав» (вимкнули світло в дата-центрі). Питання: Гроші не знялися. Але задача зникла з черги, бо воркер її забрав. Як запобігти втраті задачі? (Підказка: згадайте, як працює підтвердження повідомлень або retry).

Завдання 3. Міні-кейс: Експорт даних Користувач хоче завантажити історію своїх замовлень за 5 років у форматі Excel. Це займає 2 хвилини генерації. Як ви побудуєте взаємодію з користувачем? 1. Нехай чекає 2 хвилини з відкритим вікном? 2. Або...? Опишіть алгоритм.

Завдання 4. Знайди помилку в логіці Розробник-початківець написав: send_email_task.delay(user_object) — передав у чергу цілий об'єкт користувача з усіма даними. Поки задача чекала в черзі (5 хвилин), користувач змінив свій email у налаштуваннях. Воркер дістав задачу зі старим об'єктом і відправив лист на старий email. Питання: Що треба було передати в чергу замість цілого об'єкта user_object, щоб уникнути цього?


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

Як відрізнити новачка від профі в цій темі?

1. Передача даних: * Новачок: Пхає в чергу важкі файли або цілі об'єкти бази даних. * Профі: Передає тільки ID (ідентифікатори). task.delay(user_id=55). Воркер сам піде в базу і дістане найсвіжіші дані по ID.

2. Ідемпотентність (Страшне слово, проста суть): Іноді стається збій, і брокер може віддати одну й ту ж задачу двічі. * Питання: Що буде, якщо ми двічі надішлемо клієнту email? (Ну, трохи спам, не страшно). * Питання: А що буде, якщо ми двічі спишемо гроші з картки? (Катастрофа!). * Порада: Пишіть код так, щоб повторне виконання задачі не ламало систему. Перевіряйте: «Чи я вже робив це?» перед виконанням.

3. Моніторинг: Воркери працюють тихо. Якщо вони впадуть, ви можете дізнатися про це тільки тоді, коли клієнти почнуть кричати: "Де мій лист?!". Досвідчені розробники ставлять системи моніторингу (наприклад, Flower для Celery), щоб бачити: ага, черга росте, воркери не справляються, треба додати ще одного "баристу".


6. 🧩 Підсумок

Отже, що ми сьогодні зробили? Ми навчилися делегувати. Ми зрозуміли, що веб-сервер — це лише обличчя закладу, а справжня "кухня" часто працює у фоні.

Тепер ви вмієте: ✅ Розрізняти синхронні (блокуючі) та асинхронні задачі. ✅ Проєктувати швидкі інтерфейси, навіть якщо під ними лежить важка логіка. ✅ Розуміти роль черг (Queues) та воркерів (Workers).

Що далі? Уявіть, що ви замовили генерацію звіту. Воркер його зробив. Але як сервер дізнається, що звіт готовий, щоб показати його вам? Треба постійно питати "Вже готово? А зараз?" (Polling) чи є кращий спосіб? На наступному уроці ми поговоримо про WebSockets і те, як сервер може сам "подзвонити" клієнту.

А поки що — це був CS50. Щасти вам з кодом!