Модуль 26

Обробка великих та довгих задач

Ось готовий урок, створений спеціально за твоїм запитом, у стилі CS50: енергійно, з аналогіями та акцентом на розуміння суті.


🎓 УРОК: Обробка великих та довгих задач (Background Tasks & Queues)

Привіт, друзі! Радий бачити вас знову.

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

Готові? Тоді поїхали! 🚀


1. 🔥 Вступ: Чому ваш сайт "висить"?

Уявіть собі ситуацію. Ви приходите в популярну кав’ярню в центрі міста. Ви підходите до касира і замовляєте складний лате з кокосовим молоком і сиропом.

А тепер уявіть, що касир, прийнявши замовлення, сам іде до кавомашини, починає молоти каву, збивати молоко, малювати сердечко пінкою... А вся черга позаду вас — чекає. Касир не приймає нових замовлень, поки не віддасть вам каву.

Звучить абсурдно, правда?

Але саме так працює більшість програм, які пишуть новачки! Коли користувач натискає кнопку "Згенерувати звіт за рік" або "Обробити відео", ваш сервер (касир) кидає все і починає це робити. Сайт "крутиться", браузер зависає, а інші користувачі не можуть навіть зайти на головну сторінку.

Риторичне питання: Чи буде користувач чекати 30 секунд, дивлячись на білий екран? Ні, він закриє вкладку і піде до конкурентів.

Сьогодні ми навчимося: 1. Розділяти "прийом замовлення" і "виконання замовлення". 2. Використовувати Черги (Queues) та Воркери (Workers). 3. Робити так, щоб ваша система літала, навіть коли вона робить важку роботу.


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

Давайте повернемося до кав’ярні. У правильній кав’ярні процес виглядає так:

  1. Касир (Web Server): Приймає замовлення → Дає вам чек з номером → Кричить "Вільна каса!". Це займає секунди.
  2. Чек (Message/Task): Це інформація про те, що треба зробити (зварити лате). Він потрапляє на спеціальну планку на кухні.
  3. Планка (Queue/Черга): Місце, де накопичуються замовлення. Це наш буфер.
  4. Бариста (Worker): Людина, яка стоїть спиною до каси і просто бере чеки по черзі і робить каву. Він не спілкується з клієнтами, він просто працює.

🔑 Ключові поняття (запам'ятайте їх):

  • Synchronous (Синхронно): Ви чекаєте на результат одразу (як телефонна розмова). Якщо задача довга — це блокує процес.
  • Asynchronous (Асинхронно): Ви відправляєте запит і можете займатися іншими справами. Результат буде пізніше (як SMS або імейл).
  • Message Queue (Черга повідомлень): Посередник. Це як поштова скринька або та сама планка для чеків. Найпопулярніші інструменти тут — Redis, RabbitMQ.
  • Producer (Виробник): Тот, хто створює задачу (ваш веб-сайт).
  • Consumer/Worker (Споживач/Робочий): Окремий процес, який "розгрібає" чергу.

Інтуїтивно: Головний сайт — це "обличчя", він має бути швидким і усміхненим. Воркер — це "м'язи", він може бути повільним, брудним і пітним, але його ніхто не бачить.


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

Давайте подивимось на код (псевдокод), щоб зрозуміти логіку.

Приклад 1: "Наївний" підхід (Блокуючий)

Уявіть, що нам треба надіслати вітальні листи 10 000 користувачам після реєстрації події.

# Ця функція запускається, коли адмін натискає кнопку "Розсилка"
def send_newsletter(request):
    users = get_all_users() # 10 000 людей

    # 🛑 УВАГА: Тут починається проблема
    for user in users:
        email_service.send(user.email, "Привіт!") # Це займає 1 секунду

    # Якщо 10 000 користувачів * 1 сек = 10 000 секунд (майже 3 години!)
    # Браузер адміна відвалиться по таймауту через 30-60 секунд.
    return "Розсилка завершена!" 

Що тут не так? Адмін натиснув кнопку і... все зависло. Сервер зайнятий. Ніхто інший не може користуватися сайтом.

Приклад 2: Правильний підхід (З чергою)

Ми розділяємо це на дві частини: Web і Worker.

Частина А: Веб-сервер (Producer)

# Тепер це працює миттєво
def send_newsletter(request):
    # Ми не шлемо листи, ми просто створюємо ЗАДАЧУ
    queue.enqueue("task_send_emails", payload={ "subject": "Привіт!" })

    # Відповідаємо адміну одразу!
    return "Задачу прийнято! Ми надішлемо листи у фоновому режимі."

Частина Б: Воркер (Consumer) Це окремий скрипт, який працює вічно десь на фоні.

while True:
    # Чекає задачу з черги
    task = queue.pop() 

    if task:
        # А ось тут уже важка робота
        users = get_all_users()
        for user in users:
             email_service.send(user.email, task.payload["subject"])
             print(f"Відправлено для {user.email}")

Питання до вас: Що побачить адмін у другому випадку? Він побачить повідомлення "Задачу прийнято" миттєво і зможе піти пити каву, поки сервер працює.


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

Час розім'яти мозок! 🧠

Завдання 1: Ідентифікація проблеми У вас є функція завантаження аватарки користувача. Зараз вона: 1. Отримує картинку. 2. Зменшує її розмір (resize). 3. Накладає водяний знак. 4. Зберігає на диск. 5. Відповідає "ОК". Питання: Які кроки тут "швидкі", а які "важкі"? Що варто винести у фонову задачу (worker)?

Завдання 2: "А що, якщо..." Уявіть, що ваш Воркер (той, що шле імейли) раптово вимкнувся (світло зникло) посеред обробки списку з 10 000 людей. Він встиг надіслати 500 листів. Питання: Коли світло увімкнуть і Воркер перезапуститься, що станеться? Він почне спочатку (і перші 500 людей отримають дублікати) чи продовжить? Як би ви це вирішили?

Завдання 3: Міні-кейс (Реальне життя) Ви робите сайт для конвертації YouTube відео в MP3. Користувач вставляє посилання -> Натискає "Конвертувати". Опишіть покроково, що відбувається на сервері, використовуючи слова: Черга, ID задачі, Статус "В обробці", Статус "Готово".

Завдання 4: Зміна умов Що робити, якщо задач стає занадто багато? Наприклад, "Чорна п'ятниця", і черга виросла до 1 мільйона завдань. Один воркер не справляється. Яке найпростіше рішення ви можете запропонувати, не переписуючи код? (Підказка: згадайте аналогію з касирами та баристами).


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

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

  1. Помилка новачка: Намагатися зробити все в одному HTTP-запиті. "Ну, це ж лише 2 секунди, користувач почекає".

    • Реальність: 2 секунди перетворяться на 10, коли сервер буде під навантаженням.
  2. Помилка новачка №2 (Fire and Forget): Запустити фонову задачу і забути про неї.

    • Досвідчений розробник: Завжди думає: "А як користувач дізнається, що все готово?". Він додасть механізм сповіщення (імейл, пуш-повідомлення або сторінку зі статусом прогресу).
  3. Ідемпотентність (Idempotency): Це страшне слово означає просту річ. Якщо ваша задача випадково виконається двічі — це зруйнує систему?

    • Погано: Списати гроші з картки двічі.
    • Нормально: Згенерувати мініатюру картинки двічі (просто перезапишеться файл).
    • Порада: Пишіть код так, щоб повторний запуск був безпечним.

6. 🧩 Підсумок

Отже, що ми сьогодні розібрали?

  • Ми зрозуміли, що веб-сервер — це адміністратор, а не вантажник. Його робота — швидко прийняти запит і делегувати важку роботу.
  • Ми дізналися про Черги (Queues) — буферну зону, яка рятує нас від заторів.
  • Ми навчилися мислити асинхронно: результат не обов'язково має бути миттєвим, головне — щоб система була чуйною (responsive).

Тепер ви вмієте: Проєктувати архітектуру для "важких" сайтів. Ви більше не боїтеся обробки відео, генерації PDF-звітів чи масових розсилок. Ви знаєте, куди це заховати!

👇 Тизер наступного уроку: Ми відправили задачу воркеру. Але як сторінка в браузері дізнається, що задача вже виконана, не оновлюючи сторінку кожні 5 секунд? Наступного разу поговоримо про магію WebSockets та реальний час!

Це був CS50. Побачимось! 👋