Ось твій урок у стилі CS50. Вмикай уяву, ми починаємо!
🎓 CS50: Redis Transactions — Атомарність у світі хаосу
Привіт, друзі! Радий бачити вас. Сьогодні ми зануримося в тему, яка відділяє "код, що просто працює" від "надійного коду, якому можна довірити гроші".
1. 🔥 Вступ: Коли все йде шкереберть
Уявіть собі ситуацію. Ви розробляєте банківський додаток. Користувач Аліса хоче переказати 100 гривень користувачу Бобу.
Логіка здається простою, правда? 1. Перевіряємо баланс Аліси. 2. Віднімаємо 100 грн у Аліси. 3. Додаємо 100 грн Бобу.
А тепер скажіть мені: що станеться, якщо сервер "впаде" (світло вимкнули, помилка мережі, процес вбито) рівно між кроком 2 і кроком 3?
(Пауза для роздумів)
Саме так! Гроші в Аліси зникли, а до Боба не дійшли. Вони розчинилися у цифровому просторі. Аліса лютує, Боб засмучений, а ваш бос дзвонить вам у суботу ввечері.
Або інша ситуація: ви продаєте квитки на концерт. Залишився останній квиток. Двоє людей натискають "Купити" одночасно. Ваш код перевіряє: "Квиток є?" — "Так". І продає його обом. У вас овербукінг і скандал.
Нам потрібен механізм, який гарантує: або виконується ВСЕ, або не виконується НІЧОГО. І ніхто інший не може втрутитися в процес посередині.
У Redis це називається Транзакції. І сьогодні ми розберемо тріо команд: MULTI, EXEC та WATCH.
2. 🧠 Теоретична база: Як це працює "під капотом"
Давайте спростимо. Redis — це однопотоковий сервер. Він обробляє команди по одній, дуже швидко. Але між вашими двома командами (зняти гроші / зарахувати гроші) може вклинитися команда іншого клієнта.
Транзакція в Redis — це спосіб сказати серверу:
"Слухай, Redis! Я зараз дам тобі пачку команд. Не виконуй їх одразу. Просто записуй у чергу. А коли я скажу 'ПОЇХАЛИ', виконай їх усі підряд, нікого не пропускаючи без черги".
Ключові команди:
MULTI— Початок транзакції.- Аналогія: Ви берете кошик у супермаркеті. Поки що ви нічого не купили, ви просто почали збирати товари.
- Команди всередині (
SET,INCRтощо) — Вони не виконуються! Redis відповідаєQUEUED.- Аналогія: Ви кладете хліб і молоко в кошик. Вони ще не ваші, вони просто в кошику.
EXEC— Виконати всі команди з черги.- Аналогія: Ви підійшли до каси й оплатили все разом. Тільки в цей момент змінюється стан бази даних.
DISCARD— Скасувати транзакцію.- Аналогія: Ви кинули кошик посеред залу і вийшли з магазину. Нічого не змінилося.
WATCH— Оптимістичне блокування (про це трохи згодом, це найцікавіше!).
⚠️ Що треба запам'ятати (Важливо!):
Redis-транзакції — це не те саме, що транзакції в SQL (MySQL/PostgreSQL).
* В SQL: Якщо одна команда в транзакції падає з помилкою, відкочується все.
* В Redis: Якщо одна команда падає (наприклад, ви намагаєтесь зробити математичну операцію над рядком), інші команди все одно виконаються! Redis не підтримує Rollback у класичному розумінні.
3. 🧪 Приклади: Від простого до реального
Приклад 1: База (Атомарний інкремент)
Давайте спробуємо збільшити два лічильники одночасно.
Як це виглядає в консолі (redis-cli):
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379(TX)> INCR likes:post:1
QUEUED
127.0.0.1:6379(TX)> INCR total:likes
QUEUED
127.0.0.1:6379(TX)> EXEC
1) (integer) 1
2) (integer) 1
Що відбулося?
Ви помітили, що після INCR Redis не повернув число? Він сказав QUEUED. Команди реально виконалися тільки після EXEC. Інші клієнти в цей час не могли змінити ці ключі між першим і другим INCR.
Приклад 2: Реальна задача (Переказ коштів)
У нас є user:alice (1000 грн) і user:bob (0 грн). Переказуємо 100.
# Підготовка даних
SET user:alice 1000
SET user:bob 0
# Транзакція
MULTI
DECRBY user:alice 100
INCRBY user:bob 100
EXEC
Результат:
1) (integer) 900
2) (integer) 100
Все чітко. Ніякої магії, але й ніякого ризику втрати грошей посередині.
Приклад 3: Проблема перегонів (Race Condition) та WATCH
А тепер уявіть, що ми — інтернет-магазин. У нас є товар iPhone15, кількість: 1.
Ми хочемо купити його, тільки якщо він є в наявності.
Звичайний підхід:
1. GET stock:iphone (Бачимо 1).
2. Думаємо (в коді)... Ок, купуємо!
3. MULTI -> DECR -> EXEC.
Проблема: Поки ми "думали" (крок 2), хтось інший міг вже купити телефон! Ми виконаємо DECR, отримаємо -1, і склад піде в мінус.
Тут на сцену виходить WATCH. Це "сигналізація". Ми кажемо Redis: "Стеж за цим ключем. Якщо хтось змінить його до того, як я зроблю EXEC, скасуй мою транзакцію".
SET stock:iphone 1
# Клієнт 1 (Ми):
WATCH stock:iphone
GET stock:iphone
# > "1"
# ...У цей момент Клієнт 2 купує телефон і робить stock:iphone = 0...
MULTI
DECR stock:iphone
EXEC
# > (nil) <-- ТРАНЗАКЦІЯ НЕ ПРОЙШЛА!
Redis повертає nil (пустоту), бо ключ stock:iphone був змінений кимось іншим. Ми не продали повітря. Ура!
4. 🛠 Практична частина
А тепер ваша черга. Відкривайте термінал!
Завдання 1: Розігрів
Створіть ключ my_score зі значенням 10. За допомогою MULTI/EXEC збільшіть його на 5 і відразу подвойте (помножте, але пам'ятайте, в Redis немає MULTIPLY, як викрутитесь? Підказка: немає прямої команди, тому просто зробіть INCRBY ще раз на те саме значення, або уявіть, що це просто два додавання).
Завдання 2: "Ой, передумав"
Почніть транзакцію (MULTI). Додайте команду видалення важливого ключа (DEL user:data). А тепер згадайте, що це продакшн. Скасуйте транзакцію безпечно. Яку команду використаєте?
Завдання 3: Симуляція помилки
Це важливо, щоб зрозуміти відмінність від SQL.
1. MULTI
2. SET key1 "hello"
3. INCR key1 (Спроба додати +1 до слова "hello" — це викличе помилку).
4. SET key2 "world"
5. EXEC
Питання: Чи створиться key2? Перевірте через GET key2. Чому так сталося?
Завдання 4: Реалізація WATCH
Вам знадобиться два вікна терміналу (або два вкладки).
* Вікно 1: SET price 100, потім WATCH price.
* Вікно 2: Змініть ціну: SET price 200.
* Вікно 1: Спробуйте змінити ціну в транзакції: MULTI, SET price 150, EXEC.
* Що повернув Redis?
Міні-кейс (Challenge):
Придумайте, як за допомогою WATCH реалізувати логіку: "Збільшити баланс користувача на 50, тільки якщо він зараз не заблокований (ключ user:is_banned не існує або дорівнює 0)".
5. 💡 Мислення як у розробника
Як досвідчений інженер, я маю попередити вас про підводні камені.
-
Не блокуйте Redis надовго! Redis — однопотоковий. Поки виконується ваш
EXECз 10 000 команд, ніхто в світі не може нічого записати чи прочитати з цього сервера. Всі чекають.- Порада: Транзакції мають бути короткими та швидкими.
-
Помилка новачка з
DISCARDІноді думають, щоDISCARDможна викликати після помилки вEXEC, щоб все повернути назад. Ні!DISCARDпрацює тільки доEXEC. Як тільки ви сказалиEXEC— поїзд пішов. -
Pro Tip: Lua-скрипти Ви запитаєте: "А якщо мені треба прочитати значення, додати до нього 5, і якщо результат більше 100, то записати, а якщо ні — то ні?" В транзакції ви не можете "читати і приймати рішення" всередині. Для складної логіки розробники зараз частіше використовують Lua-скрипти (команда
EVAL). Вони теж атомарні, але дозволяють писати логіку (if/else) прямо всередині Redis. Ми вивчимо це пізніше, але знайте — це "транзакції на стероїдах".
6. 🧩 Підсумок
Отже, що ми сьогодні поклали в свій ментальний багаж?
MULTI/EXEC— це як "зібрати замовлення і відправити одним пакетом". Це гарантує, що ніхто не вклиниться між командами.- Частковий успіх — Redis виконає валідні команди, навіть якщо одна в середині впаде. Це не SQL!
WATCH— це наш запобіжник від race conditions (стану перегонів). Це перетворює транзакцію на умовну: "роби, тільки якщо нічого не змінилося".
Тепер ви можете писати код, який не губить гроші користувачів і не продає один квиток двом людям. Це вже рівень Middle-розробника!
Спойлер наступного уроку: Ви помітили, що ключі зникають, якщо сервер перезавантажити? У наступній серії ми поговоримо про Redis Persistence (RDB та AOF) — як зробити так, щоб дані пережили ядерну війну (або хоча б перезавантаження сервера).
Це був CS50. Побачимось! 👋