Модуль 27

Cache aside, write-through, write-behind

Ось урок, створений спеціально для тебе у стилі David Malan із курсу CS50.


🎓 Тема уроку: Стратегії кешування (Cache-Aside, Write-Through, Write-Behind)

Привіт, друзі! 👋

Сьогодні ми поговоримо про швидкість. Про те, як змусити ваш додаток літати, а не повзти, як черепаха. Ви, напевно, чули слово "кеш". Але просто "увімкнути кеш" — це як сказати "я просто буду бігти швидше". Питання в тому — як саме ми біжимо?

Ми розберемо три фундаментальні стратегії: Cache-Aside, Write-Through та Write-Behind.


1. 🔥 Вступ: Чому це болить?

Уявіть, що ви — бібліотекар у величезній стародавній бібліотеці (це наша База Даних). Книги знаходяться у підвалі, де темно, пильно і далеко йти.

Кожен раз, коли студент (наш Користувач) просить книгу "Гаррі Поттер", ви: 1. Йдете в підвал (довго). 2. Шукаєте книгу (довго). 3. Несете її на стійку видачі.

Це займає 5 хвилин. Студент чекає. Черга росте. А що, якщо 100 студентів поспіль просять "Гаррі Поттера"? Ви будете 100 разів бігати в підвал? Це ж безглуздо, правда?

Рішення: Ви кладете одну копію "Гаррі Поттера" прямо собі на стіл (це наш Кеш). Тепер, коли запитують книгу, ви просто простягаєте руку. Час видачі: 1 секунда.

Але тут виникають питання: * А якщо в підвалі книгу переписали (оновили дані), а у вас на столі стара версія? * А якщо ви хочете дописати сторінку в книгу — ви пишете на копії на столі чи біжите в підвал?

Саме ці питання вирішують патерни кешування. Без них ваша система або впаде від навантаження, або покаже користувачеві застарілу ціну на біткоїн. І те, і інше — катастрофа.


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

Давайте розкладемо це на прості механізми. У нас є три гравці: 1. Application (Додаток) — ваш код. 2. Cache (Кеш) — швидка пам'ять (наприклад, Redis), дані живуть недовго. 3. Database (БД) — повільна пам'ять (PostgreSQL, MySQL), дані живуть вічно.

1️⃣ Cache-Aside (Ліниве завантаження)

Це найпопулярніший підхід. * Логіка: Додаток тут головний. Він сам керує і кешем, і базою. * Як це працює: 1. Додаток стукає в Кеш: "Є дані?" 2. Якщо ТАК (Hit) → повертаємо дані. 3. Якщо НІ (Miss) → Додаток йде в БД, бере дані, кладе їх у Кеш і віддає користувачу.

Запам’ятати: Кеш оновлюється тільки тоді, коли хтось запитує дані. Тому це "лінивий" підхід.

2️⃣ Write-Through (Наскрізний запис)

Тут Кеш стає "посередником" для запису. * Логіка: Додаток ніколи не пише в БД напряму. Він пише тільки в Кеш. * Як це працює: 1. Додаток каже Кешу: "Збережи ці дані". 2. Кеш записує дані у себе І синхронно йде в БД, щоб записати їх там. 3. Тільки коли БД підтвердила запис, Кеш каже Додатку: "Готово".

Інтуїтивно: Це як писати під копірку. Ви пишете на верхньому аркуші (Кеш), а воно одночасно пропечатується на нижньому (БД).

3️⃣ Write-Behind (Асинхронний запис)

Це для сміливих. Схоже на Write-Through, але з затримкою. * Логіка: Швидкість понад усе. * Як це працює: 1. Додаток пише в Кеш. 2. Кеш миттєво каже Додатку: "Ок, я прийняв". 3. Кеш (або окремий воркер) фоново, через 5-10 секунд, записує ці дані в БД.

Аналогія: Ви кидаєте лист у поштову скриньку. Ви вже зробили справу, але лист дійде до адресата (БД) трохи згодом.


3. 🧪 Приклади (Код і Реальність)

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

Приклад 1: Cache-Aside (Читання профілю користувача)

Ви очікуєте, що при першому запиті буде повільно, а при другому — миттєво.

def get_user_profile(user_id):
    # 1. Спробуємо знайти в кеші
    user = cache.get(user_id)

    if user is not None:
        print("🚀 Взяли з кешу!")
        return user

    # 2. Якщо не знайшли (Cache Miss), йдемо в базу
    print("🐢 Йдемо в базу...")
    user = db.query("SELECT * FROM users WHERE id = ?", user_id)

    # 3. Обов'язково зберігаємо в кеш на майбутнє!
    cache.set(user_id, user, time_to_live=300) # житиме 5 хвилин

    return user

Де це використовують? Майже всюди, де читають більше, ніж пишуть. Новини, каталоги товарів, профілі.

Приклад 2: Write-Behind (Лічильник лайків)

Уявіть YouTube. Якщо мільйон людей натиснуть лайк, ми покладемо базу, якщо будемо писати кожен лайк одразу.

def add_like(video_id):
    # 1. Оновлюємо тільки кеш (Redis працює блискавично)
    current_likes = cache.increment(video_id)

    # 2. Відразу відповідаємо користувачу "Лайк зараховано!"
    # Ми НЕ чекаємо запису в базу даних.
    return current_likes

# Окремий фоновий процес (раз на хвилину)
def sync_likes_to_db():
    for video_id in cache.get_updated_keys():
        likes = cache.get(video_id)
        db.execute("UPDATE videos SET likes = ? WHERE id = ?", likes, video_id)

Чому так? Якщо сервер вимкнеться до синхронізації — ми втратимо пару лайків. Неприємно, але не смертельно для бізнесу. Зате швидкість шалена.


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

Час закачати рукави! Уявіть, що ви архітектор системи.

Завдання 1: Класика Подивіться на код Cache-Aside вище. Що станеться, якщо ми змінимо ім'я користувача в БД вручну (через адмінку), але не очистимо кеш? Що побачить користувач?

Завдання 2: Фінансова пастка Ви розробляєте банківський додаток. Функція transfer_money. Яку стратегію ви оберете: Write-Through чи Write-Behind? Аргументуйте. (Підказка: чи готові ви сказати клієнту "Гроші списались", якщо сервер впав і запис у БД не відбувся?)

Завдання 3: Міні-кейс У вас є інтернет-магазин. * Товари змінюються рідко (ціна, опис). * Кошик користувача змінюється дуже часто і має бути супершвидким. Запропонуйте стратегії кешування для обох сутностей.

Завдання 4: Debugging У стратегії Cache-Aside ви помітили, що кеш забився даними, які ніхто не запитує вже місяць. Пам'ять сервера закінчується. Який параметр ми забули додати при cache.set()?


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

Як думає досвідчений інженер (Senior), коли обирає кешування?

  1. "Чи можу я дозволити собі втратити дані?"

    • Так (лайки, перегляди) -> Write-Behind.
    • Ні (гроші, замовлення) -> Write-Through або транзакції в БД без кешу на запис.
  2. "Проблема застарілих даних (Cache Invalidation)"

    • Новачки думають: "Кеш — це просто прискорення".
    • Профі знають: "В кеші лежить брехня". Питання лише в тому, наскільки ця брехня стара.
    • Порада: Завжди ставте TTL (Time To Live) — час життя кешу. Нехай дані самі зникають через 10 хвилин або годину. Краще сходити в базу ще раз, ніж показувати ціну минулого року.
  3. Thundering Herd (Ефект натовпу)

    • Уявіть, що кеш популярного товару зник (закінчився TTL). І в цю ж секунду 1000 користувачів зайшли на сторінку.
    • Усі 1000 запитів отримають "Cache Miss" і одночасно вдарять по Базі Даних. База впаде.
    • Рішення: Це тема для окремого уроку, але пам'ятайте про такий ризик!

6. 🧩 Підсумок

Отже, друзі, що ми маємо в сухому залишку:

  1. Cache-Aside: Ви самі перевіряєте кеш. Немає — йдете в базу. Найбезпечніший старт.
  2. Write-Through: Пишемо в кеш і базу одночасно. Дані завжди узгоджені, але запис повільніший.
  3. Write-Behind: Пишемо в кеш, база оновлюється потім. Дуже швидко, але є ризик втрати даних.

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

У наступній серії: Ми згадували, що кеш може "забитися". А як вирішити, кого виганяти з пам'яті, коли місця немає? Найстаріших? Тих, кого рідко дивляться? Поговоримо про Eviction Policies (LRU, LFU). Це буде цікаво!

А поки що — спробуйте реалізувати Cache-Aside у своєму пет-проєкті. Успіхів! 🚀