Модуль 32

Redis у FastAPI та Django

Ось твій урок, створений у стилі CS50. Пристебни паски, ми прискорюємось! 🚀


🎓 УРОК: Redis у FastAPI та Django — Як змусити твій код літати

Привіт, друзі! Мене звати [Твоє Ім'я в ролі Malan], і сьогодні ми поговоримо про швидкість. Ні, не про те, як швидко ти друкуєш, а про те, як швидко твій сервер віддає дані користувачеві.

1. 🔥 Вступ: Чому твій додаток "гальмує"?

Уявіть ситуацію. Ти написав чудовий інтернет-магазин на Django або швидкий API на FastAPI. Все працює ідеально на твоєму ноутбуці. Але ось настає "Чорна п'ятниця". Тисячі користувачів одночасно заходять подивитися ціну на новий iPhone.

Твій код робить запит до бази даних (PostgreSQL/MySQL): 1. Знаходить товар. 2. Перевіряє знижки. 3. Рахує податки. 4. Формує відповідь.

Це займає, скажімо, 300 мілісекунд. Непогано? А якщо таких запитів 10 000? База даних починає "диміти", черга запитів росте, і 300 мс перетворюються на 5 секунд. Користувачі йдуть до конкурентів.

Питання до тебе: Навіщо кожного разу питати у бази даних ціну iPhone, якщо вона не змінювалася останні 5 хвилин?

Це як у бібліотеці. * База даних — це величезне сховище в підвалі. Щоб знайти книгу, треба спуститися, знайти полицю, взяти книгу, піднятися. Це довго. * Redis — це стіл бібліотекаря прямо біля входу. Якщо 100 людей поспіль питають "Гаррі Поттера", бібліотекар не бігає в підвал. Він кладе одну книгу собі на стіл і віддає її миттєво.

Сьогодні ми навчимося ставити такий "стіл" у твоїх FastAPI та Django проєктах.


2. 🧠 Теоретична база: Що таке Redis "під капотом"?

Redis (Remote Dictionary Server) — це, по суті, велетенський Python-словник (dict), який живе в оперативній пам'яті (RAM).

Чому це важливо?

  • HDD/SSD (База даних) — надійно, але повільно.
  • RAM (Redis) — неймовірно швидко (читання/запис за частки мілісекунди), але дані зникають, якщо вимкнути світло (хоча Redis вміє їх зберігати на диск, але про це пізніше).

Ключова концепція: Key-Value (Ключ-Значення)

У звичайній базі даних у нас є таблиці, рядки, стовпчики, зв'язки (JOIN). У Redis все тупо і просто:

# Логіка Redis виглядає так:
data = {
    "user:1:balance": "100",
    "product:42:price": "999.99"
}

Ти даєш ключ — отримуєш значення. Миттєво.

Що треба запам'ятати (обов'язково!):

  1. Кеш (Cache) — це тимчасове сховище. Якщо даних там немає, ми йдемо в основну БД.
  2. TTL (Time To Live) — час життя. Ми не хочемо зберігати ціну вічно. Ми кажемо Redis: "Тримай це число 5 хвилин, а потім видали". Це запобігає показу застарілих даних.

3. 🧪 Приклади: Від консолі до Django та FastAPI

Крок 1: Мінімальний приклад (Уяви, що це Python-оболонка)

Перш ніж писати веб-додаток, подивимось, як це виглядає в коді. Уяви, що ти вже підключився до Redis.

(Я очікую, що ти зараз подумаєш: "Це ж просто як dict[key] = value!")

# Псевдокод
redis.set("name", "David", ex=10) # ex=10 означає "видалити через 10 секунд"
print(redis.get("name"))
# Вивід: David

# ... чекаємо 11 секунд ...
print(redis.get("name"))
# Вивід: None

Бачиш? Просто. Але як це виглядає в реальному фреймворку?


Крок 2: Redis у Django (Синхронний світ)

У Django є вбудована система кешування. Тобі навіть не завжди треба писати "сирий" код Redis.

Сценарій: У нас є важка в'юшка, яка рахує статистику продажів.

from django.core.cache import cache
from django.shortcuts import render
import time

def heavy_statistics_view(request):
    # 1. Спробуємо дістати дані з кешу
    stats = cache.get('sales_stats')

    # А що, якщо в кеші пусто? (Cache Miss)
    if not stats:
        print("Рахую дані з нуля... (це довго)")
        time.sleep(2)  # Імітуємо важку роботу БД
        stats = {"total": 10000, "items": 500}

        # 2. Зберігаємо результат у кеш на 60 секунд (Cache Set)
        cache.set('sales_stats', stats, timeout=60)
    else:
        print("Взяв дані з Redis! (миттєво)")

    return render(request, 'stats.html', {'stats': stats})

Чому це круто? Перший користувач чекатиме 2 секунди. Наступні 1000 користувачів отримають сторінку за 0.001 секунди.


Крок 3: Redis у FastAPI (Асинхронний світ ⚡️)

FastAPI любить async. Тому тут ми використовуємо бібліотеку redis-py (яка тепер підтримує async) або aioredis.

Сценарій: Отримати профіль користувача.

from fastapi import FastAPI
import redis.asyncio as redis
import json

app = FastAPI()

# Підключення до Redis
r = redis.Redis(host='localhost', port=6379, db=0, decode_responses=True)

@app.get("/user/{user_id}")
async def get_user_profile(user_id: int):
    # Ключ має бути унікальним!
    cache_key = f"user:{user_id}:profile"

    # 1. Перевіряємо кеш
    cached_data = await r.get(cache_key)

    if cached_data:
        # Redis повертає рядок, тому декодуємо назад у JSON
        return {"source": "cache", "data": json.loads(cached_data)}

    # 2. Якщо немає в кеші — йдемо в "БД" (імітація)
    user_data = {"id": user_id, "name": "Neo", "role": "The One"}

    # 3. Записуємо в кеш (важливо: серіалізуємо в JSON рядок)
    await r.set(cache_key, json.dumps(user_data), ex=30)

    return {"source": "db", "data": user_data}

Зверни увагу: У Redis ми кладемо рядки (або байти). Тому словники Python треба перетворити в JSON (json.dumps), а коли дістаємо — назад (json.loads).


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

Час бруднити руки кодом! Виконай ці завдання.

🔹 Завдання 1: "Ручне управління" Встанови Redis (через Docker: docker run -d -p 6379:6379 redis) або локально. Запусти Python консоль, підключись і збережи своє ім'я з TTL 10 секунд. Перевір, чи зникло воно.

🔹 Завдання 2: "Детектив" У прикладі FastAPI вище зміни код так, щоб кеш жив 1 годину. Але додай можливість примусово оновити кеш, якщо передати параметр ?refresh=true в URL. (Підказка: якщо refresh=true, пропусти читання з кешу, зроби запит в БД і перезапиши кеш).

🔹 Завдання 3: "Rate Limiter" (Міні-кейс) Уяви, що хакери атакують твій логін-ендпоінт. Реалізуй логіку: * Коли юзер пробує залогінитись, збільшуй лічильник в Redis (ключ: login_attempts:{ip}). * Якщо лічильник > 5, видавай помилку "Too many requests". * Лічильник має "згорати" через 1 хвилину. * Команда Redis для підглядання: INCR (increment).

🔹 Завдання 4: Виправ баг Студент написав код: redis.set("count", 10). Потім він робить val = redis.get("count") і намагається зробити val + 1. Отримує помилку. Чому? (Підказка: Який тип даних повертає Redis?)


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

Як думає досвідчений інженер (Senior), коли працює з Redis?

  1. "Invalidation is Hard" (Інвалідація — це біль)

    • Новачок: Кешує на 24 години і радіє.
    • Профі: Думає: "А що, якщо ціна зміниться через хвилину?".
    • Порада: Завжди став розумний TTL. Краще кеш протухне трохи раніше, ніж юзер побачить неправильну ціну. Або видаляй ключ вручну при оновленні даних в БД.
  2. "Не все те золото, що блищить"

    • Не пхай у Redis все підряд. Там мало пам'яті. Redis — це для гарячих даних. Великі архіви зберігай на диску.
  3. Ключі — це папки

    • Ніколи не називай ключі просто user або data. Використовуй простір імен через двокрапку:
      • id
      • app:v1:users:42:settings
    • Так ти завжди зрозумієш, чиї це дані, коли будеш дебажити.

6. 🧩 Підсумок

Отже, що ми сьогодні зробили? 1. Зрозуміли, що Redis — це "оперативна пам'ять" для вашого веб-додатку. 2. Навчилися кешувати повільні запити у Django та FastAPI. 3. Дізналися про TTL, щоб дані не "протухали".

Тепер ти вмієш: Робити так, щоб твій API відповідав за 5 мс, навіть якщо база даних вмирає від навантаження. Ти офіційно прокачав свій бекенд!

🔜 У наступній серії: Ми кешували дані, щоб їх швидко читати. А що робити, якщо треба виконати задачу, яка займає 10 хвилин (наприклад, обробити відео або надіслати 1000 емейлів), і не змушувати юзера чекати? Тут на сцену виходять Фонові задачі та Черги (Celery + Redis). Не перемикайтесь!

Це був CS50... тобто, урок про Redis. 🖐🎤 (Mic drop)