Ось урок на тему "Result backend та зберігання результатів", написаний у стилі CS50: енергійно, з аналогіями та фокусом на розуміння суті.
🎓 CS50: Result Backend — Де ми загубили нашу відповідь?
Привіт, друзі! Радий бачити вас знову.
Сьогодні ми торкнемося теми, яка є критично важливою, коли ваші програми починають робити більше однієї справи одночасно. Ми вже знаємо про черги задач (Task Queues) і про те, як відправити важку роботу "на фон" (background), щоб не змушувати користувача чекати.
Але тут виникає одне дуже цікаве питання.
1. 🔥 Вступ: проблема та мотивація
Уявіть ситуацію. Ви — клієнт у хімчистці. Ви приносите свій улюблений костюм (це наша Задача або Task) і віддаєте його працівнику (це наш Брокер повідомлень). Працівник кидає костюм у величезний кошик, звідки його колись заберуть майстри прання (Воркери).
Ви йдете додому. Воркери працюють: перуть, сушать, прасують. Костюм готовий! Він чистий і свіжий.
Але стривайте... Де зараз костюм? Він у майстра в руках? Він лежить на підлозі? Чи майстер має бігати містом і шукати вас, щоб віддати його?
Якщо ми не домовилися про місце видачі, робота зроблена, але результат (чистий костюм) втрачено або він недоступний вам.
У програмуванні це виглядає так:
1. Ви відправили важку задачу: calculate_report.delay().
2. Воркер її виконав. Отримав число 42.
3. Воркер каже: "Я закінчив!" і... число 42 зникає в нікуди, бо воркер просто переходить до наступної задачі.
Питання до вас: Як нам отримати це число 42 назад у нашу основну програму, якщо воркер і основна програма — це абсолютно різні процеси, які можуть бути навіть на різних серверах?
Нам потрібне "місце видачі". Нам потрібен Result Backend.
2. 🧠 Теоретична база (Що там під капотом?)
Давайте розкладемо це на атоми.
Що таке Result Backend?
Це спеціальне сховище (база даних, кеш, файл), яке слугує "дошкою оголошень" або "полицею видачі".
- Broker (Брокер): Відповідає за доставку задачі від вас до воркера.
- Worker (Воркер): Виконує роботу.
- Result Backend: Місце, куди воркер кладе результат (або помилку, якщо щось пішло не так).
Як це працює (Step-by-step):
- Клієнт: Відправляє задачу. Система видає унікальний Task ID (наприклад,
uuid-1234). Це як номерок у гардеробі. - Воркер: Бере задачу, пітніє, працює. Отримує результат.
- Збереження: Воркер бере цей результат і записує його в Backend під ключем
uuid-1234. uuid-1234: "Ready: Result is 42"- Клієнт: Коли йому цікаво, він стукає в Backend: "Ей, що там із
uuid-1234?" - Backend: "Ще робиться" АБО "Ось твій результат".
💡 Що треба запам’ятати залізно:
- Result Backend — це не про чергу. Це про зберігання статусу та відповіді.
- AsyncResult. Коли ви відправляєте задачу, ви миттєво отримуєте об’єкт
AsyncResult. Це не сам результат, це "обіцянка" (promise) результату, за допомогою якої ви можете опитувати Backend.
Що використовують як Backend?
- Redis / Memcached: Найпопулярніші. Чому? Бо вони працюють у пам'яті (RAM). Це дуже швидко. Результати часто потрібні ненадовго, тому зберігати їх у вічній базі даних не завжди є сенс.
- Бази даних (PostgreSQL, MySQL): Можна, але повільніше. Підходить, якщо історія результатів важлива надовго.
3. 🧪 Приклади (від простого до реального)
Уявімо, що ми пишемо на Python (використовуючи Celery), бо це стандарт індустрії для таких задач. Але логіка однакова для будь-якої мови.
Приклад 1: Проста арифметика
Ви хочете додати два числа на іншому сервері (звучить дивно, але для прикладу — ідеально).
# tasks.py (Це код воркера)
@app.task
def add(x, y):
return x + y # Воркер повертає це значення
# main.py (Це ви, клієнт)
result = add.delay(4, 4)
# В цей момент задача пішла в чергу.
# Змінна result — це НЕ 8. Це "квитанція" (AsyncResult).
Питання: Що буде, якщо я одразу напишу print(result)?
Відповідь: Ви побачите щось типу <AsyncResult: a1b2c3d4...>, але не число 8.
Щоб отримати 8, нам треба звернутися до бекенду:
print(result.status) # Питаємо бекенд: "PENDING" (ще чекаємо)
# ... проходить секунда ...
print(result.get()) # Питаємо бекенд: 8! (Дістали з Redis)
Приклад 2: Генерація звіту (Реальний кейс)
У вас є кнопка "Завантажити звіт за рік". Це займає 30 секунд.
- Фронтенд робить запит:
POST /generate-report. - Бекенд запускає задачу і повертає фронтенду Task ID.
- Фронтенд показує спіннер ("Крутилку") і кожні 2 секунди питає сервер:
- "Задача
id-123готова?" -> "Ні, статус PROCESSING" - "Задача
id-123готова?" -> "Ні, статус PROCESSING" - "Задача
id-123готова?" -> "Так! Статус SUCCESS. Ось посилання на файл: /media/report.pdf"
Ось тут Result Backend зберігав цей статус і фінальне посилання.
4. 🛠 Практична частина
Час забруднити руки кодом (або псевдокодом).
Завдання 1: Налаштування
Уявіть, що ви конфігуруєте Celery. Ви вказали Брокер (RabbitMQ). Що станеться, якщо ви не вкажете Result Backend, але спробуєте викликати result.get()?
(Спробуйте здогадатися. Правильно: буде помилка або результат ніколи не прийде, бо воркеру нікуди його класти).
Завдання 2: "Асинхронний чекун"
Напишіть функцію на псевдокоді, яка:
1. Відправляє задачу heavy_computation.
2. Не блокує програму (не використовує wait).
3. Кожну секунду перевіряє статус.
4. Якщо статус SUCCESS — друкує результат. Якщо FAILURE — друкує помилку.
Завдання 3: TTL (Time To Live) Ви використовуєте Redis як Result Backend. У вас мільйон задач на день. Проблема: Пам'ять Redis переповнилася через тиждень. Чому? Рішення: Що треба налаштувати, щоб результати зникали самі через 1 годину після виконання? (Шукаємо налаштування result expires).
Завдання 4: Міні-кейс "Прогрес-бар" Ми хочемо бачити смужку завантаження (0% -> 100%). Як Result Backend може допомогти тут? Підказка: Чи можемо ми оновлювати стан задачі в процесі виконання, записуючи туди не тільки фінал, а й проміжні дані (meta-data)?
5. 💡 Мислення як у розробника
Ось де новачки часто помиляються, а сеньйори хитають головою.
🚫 Помилка 1: Передача великих даних через Backend
Ви згенерували PDF на 100 МБ.
Погано: Повернути бінарний код файлу через return (і записати 100 МБ у Redis).
Добре: Зберегти файл на диск (S3, локальне сховище), а в Result Backend повернути лише шлях (string): "https://s3.../file.pdf".
Чому? Result Backend має бути швидким і легким. Не забивайте його сміттям.
🚫 Помилка 2: "Забування" результатів
Якщо ви не забираєте результати (не робите .get() або .forget()) і не налаштували автовидалення (expiry), ваша база даних розпухне від старих результатів 5-річної давнини. Завжди налаштовуйте expiry time.
🚫 Помилка 3: Використання Result Backend як основної БД
Не треба зберігати там бізнес-дані. Це тимчасове сховище для результатів задач. Як тільки ви отримали результат і записали його в свою основну базу — забувайте про запис у Result Backend.
6. 🧩 Підсумок
Отже, що ми маємо сьогодні в сухому залишку?
- Broker передає задачі, але не зберігає відповіді.
- Result Backend — це "камера схову", де воркер залишає результат для клієнта.
- Ми отримуємо Task ID, за яким, як за номерком, забираємо результат пізніше.
- Ми не кладемо туди "слонів" (великі файли), ми кладемо туди "адреси слонів".
Тепер ви вмієте: не просто відправляти задачі в космос, а й отримувати зворотний зв'язок. Ви замкнули коло обміну даними!
Що далі? Добре, ми отримали результат. А що, якщо задача впала з помилкою? А якщо світло вимкнули, поки воркер працював? На наступному уроці ми поговоримо про Retry Policies (політики повторів) та Idempotency (ідемпотентність). Буде гаряче!
Це був CS50. Побачимось! 👋