MariaDB vs Redis: патерни кешування, що пришвидшують сайти без втрати даних

Було корисно?

Деякі сайти не “масштабуються”. Вони просто повільно накопичують затримки, доки перший помітний сплеск трафіку не перетворить вашу базу даних на димлячу кратеру. Ви додаєте індекси. Ви додаєте репліки. Ви додаєте “кеш”. Графіки виглядають краще — доки цього не стається, і тепер ви дебагуєте відсутні корзини, застарілі ціни та випадкові виходи користувачів із сесій.

Неприємна правда: кешування легко прикрутити, складно зробити правильним і дуже просто зробити швидким у неправильному напрямку. MariaDB і Redis обидва можуть використовуватись для кешування, проте вони дають різні відмови. Якщо ви розумієте ці режими помилок, ви зможете будувати кеші, що пришвидшують сайт без втрати даних.

Що ви насправді вирішуєте

«MariaDB vs Redis» рідко буває бінарним вибором. Насправді ви вирішуєте, як розподілити відповідальності між:

  • Системою запису: базою даних, яка має залишатися коректною й відновлюваною після збоїв (зазвичай MariaDB).
  • Обчисленнями та координацією: лічильники, блокування, rate limit-и, лідерборди, ключі дедуплікації (в цьому Redis переважає).
  • Похідними даними: кешовані результати запитів, відрендерені сторінки, попередньо обчислені агрегати (обидва можуть це робити, але не однаково).
  • Операційним ризиком: чи можете ви терпіти втрату вмісту кешу? Якщо ні — йдеться про стійкість, бекапи, реплікацію та відпрацювання відновлення, а не просто «додати Redis».

Якщо ви хочете швидкість без втрати даних, треба чесно відповісти, що означає «дані». Втратити кешовану сторінку товару — нормально. Втратити статус підтвердженої оплати — ні. Redis може бути досить стійким. MariaDB може використовуватись як кешоподібна система. Але жодну з них не варто примусово виставляти як іншу без плану.

Парафраз ідеї від Werner Vogels: «Все ламається постійно», тож проектуєте систему, припускаючи, що компоненти будуть падати, і робите відновлення рутинним.

MariaDB як кеш: коли це розумно, коли це пастка

Використання MariaDB як кешу зазвичай означає одне з наступного:

  • Схожі на матеріалізовані таблиці: попередньо обчислені агрегати, збережені в таблицях і оновлені періодично.
  • Денормалізовані моделі читання: «таблиця індексу пошуку», «product_summary», «user_profile_compact».
  • Кешування результатів додатком: збереження серіалізованих бінарних даних у таблиці з полями, схожими на TTL.
  • Репліки для читання: це не кеш у прямому розумінні, але знімає навантаження з основного вузла і створює відчуття кешування.

Чому MariaDB може бути хорошим кешем

Тому що вона нудна. Нудність — це перевага, коли на першому місці коректність.

  • Рідна стійкість: InnoDB redo-логи, doublewrite buffer, відновлення після краху. Вона спроєктована так, щоб зберігати біти.
  • SQL дає інструменти: ви можете інкрементально оновлювати кеш-таблицю, робити JOINи, фільтрувати, робити backfill.
  • Операційна команда вже її знає: бекапи, реплікація, моніторинг, доступи — часто вже налаштовані.

Чому MariaDB як кеш може нашкодити

Кешування має знижувати дорогі операції. Якщо ви робите «кеш» таким самим дорогим, як оригінальна робота — ви не кешуєте, ви дублюєте навантаження.

  • «Гарячі» рядки стають вузьким місцем: лічильники, rate limit-и, «останнє бачення», «залишок товару» можуть спричинити бурі оновлень і контенцію блокувань.
  • Треш у buffer pool: кешування великої кількості епhemeral-блобів може виштовхнути реально важливі сторінки.
  • Очищення за TTL шкодить: видалення прострочених рядків може спричиняти піки І/О, відставання purge та затримки реплікації.

Правило великого пальця: використовуйте MariaDB для кешування структурованих, запитуваних похідних даних, які ви готові управляти як справжній набір даних. Не використовуйте її для «мільйонів крихітних TTL-ключів», якщо вам не подобається пояснювати фінансам, чому ваш primary має 95% CPU у вівторок вранці.

Redis як кеш: швидко, але з гострими краями

Redis — це сервер структури даних в пам’яті. Це речення несе багато ваги. Цінність полягає не тільки в швидкості; а й у примітивних операціях: атомарні інкременти, множини, відсортовані множини, стріми, pub/sub, Lua-скрипти та швидке встановлення терміну життя.

У чому Redis відмінний

  • Низьколатентні звернення: cache-aside для отримання об’єктів, HTML-фрагментів, прав доступу, feature-флагів.
  • Швидко мінливий епhemeral-стан: сесії, CSRF-токени, одноразові посилання, idempotency-ключі.
  • Координація: розподілені блокування (обережно), rate limit-и, черги (з застереженнями), множини для дедуплікації.
  • Боротьба зі stampede: за допомогою TTL, джиттера і патернів «single flight».

У чому Redis поганий (і як ви можете зробити ще гірше)

  • Претендувати на роль бази даних без плану на стійкість: якщо ви зберігаєте основні бізнес-дані в Redis без стратегії persistence — ви ставите компанію на RAM та налаштування за замовчуванням.
  • Незв’язаний ріст пам’яті: без maxmemory-політик і прибирання ключів він буде приймати дані, поки не прийде OOM killer ядра, ніби небажаний аудитор.
  • Великі ключі й великі значення: одиночні ключі з гігантськими payload-ами спричиняють спайки латентності через блокуючі операції й накладні витрати на евікцію.

Жарт №1: Redis — як еспресо: приємно, коли вимірюєш, катастрофа, якщо продовжуєш підливати, бо «все ще поміщається».

Цікаві факти та невелика історія, що має значення

  1. Redis почався (2009) як спосіб обробляти статистику в реальному часі, не навантажуючи реляційну базу — його ДНК: «швидкі лічильники та множини», а не ідеальна стійкість.
  2. MariaDB створили (2009–2010) як форк MySQL після придбання Sun компанією Oracle; багато команд перейшли на неї, щоб зберегти відкритий шлях розвитку.
  3. Історичний Query Cache в MySQL (релевантний для спадщини MariaDB) був відомий за mutex-контенцію під навантаженням записів; у підсумку його прибрали в MySQL 8.0, бо він більше шкодив, ніж допомагав в масштабі.
  4. Висновок ключів у Redis — лінивий + активний: ключі видаляються при доступі і додатково через фонове вибіркове сканування. Це впливає на планування пам’яті й на відладку питання «чому застаріле ще є?».
  5. У Redis є два основні режими персистентності: RDB-дампи та AOF-журнал; обидва мають компроміси щодо write amplification та часу відновлення.
  6. Буферний пул InnoDB — це кеш: MariaDB вже кешує сторінки даних в пам’яті. Іноді ваш «рівень кешу» дублює те, що движок уже робить добре.
  7. Відставання репліки — стара проблема: використання реплік для читання вводить проблеми консистентності, які можуть виглядати як втрачені записи чи «випадкові баги».
  8. Однопотокове виконання команд у Redis (для головного event loop) — це фіча для атомарності, але це означає, що повільні команди і великі payload-и шкодять усім.
  9. Інувалідизація кешу — відома складна проблема в розподілених системах десятиліттями — не тому, що інженери погані, а тому що час, конкуренція та часткові відмови дуже дошкульні.

Патерни кешування, що не “з’їдають” ваші дані

1) Cache-aside (ліниве завантаження): стандартний вибір

Потік: читання з кешу → miss → читання з MariaDB → встановлення кешу з TTL → повернення.

Чому це працює: MariaDB залишається джерелом істини. Кеш можна викинути будь-коли. Відновлення — це «почекати, поки прогріється».

Режими відмов:

  • Stampede: багато запитів промахуються і всі одночасно накривають MariaDB.
  • Застарілі дані: кеш не інвалідований після записів або TTL занадто довгий.
  • Гарячий ключ: один популярний ключ викликає churn кешу і контенцію в додатку.

Робіть так:

  • Додайте TTL з джиттером (випадкова додаткова кількість секунд), щоб уникнути синхронного протермінування.
  • Використовуйте «single flight» в додатку (одне перерахування на ключ) або Redis-блокування з коротким TTL.
  • Кешуйте негативні результати коротко (наприклад, «користувач не знайдений» на 30 с), щоб запобігти bruteforce-промахам.

2) Read-through cache: віддати обробку пропусків кешу стороні

Деякі клієнтські бібліотеки або sidecar-и можуть на пропуску підвантажувати з БД. Операційно привабливо. Зазвичай погана ідея, якщо тільки це не перевірено бойово у вашому середовищі, бо приховує навантаження від додатка і ускладнює розуміння трафіку до БД.

Випадок використання: внутрішні платформи зі стандартизованими патернами доступу і гарною спостережуваністю.

3) Write-through: правильність перш за все, затримка потім

Потік: запис у кеш і БД в одному потоці; читання звертаються до кешу.

Перевага: кеш залишається свіжим; немає складнощів з інвалідизацією для багатьох випадків.

Вартість: вища латентність запису, більше складних елементів у write-path. Якщо Redis впаде, записи можуть не пройти, якщо ви явно не дозволите деградацію до тільки-БД.

Робіть так: трактуйте оновлення кешу як best-effort, якщо кешоване значення не потрібне для коректності. Для більшості веб-сайтів воно не є критичним.

4) Write-behind (aka write-back): найшвидший шлях до винаходу втрати даних

Потік: запис у кеш, повернення успіху користувачу, асинхронний запис у БД пізніше.

Коли припустимо: рідко. Можливо для аналітичних лічильників, де допустима втрата декількох оновлень і є ідемпотентність та можливість рейплею.

Коли це катастрофа: замовлення, баланси, права доступу, квоти, інвентар, контент, створений користувачем.

Write-behind — це спосіб перетворити збій кешу на постмортем «чому 2% корзин випарувалися». Це також привертає увагу compliance до ваших вихідних планів на вихідні.

5) TTL-only інвалідизація: дешеве, веселе, і іноді неправильне

Якщо ви встановили TTL і ніколи не інвалідовуєте явно — ви покладаєтесь на час, щоб виправити коректність. Це підходить для контенту, який може бути трохи застарілим (головні сторінки, трендові списки), і неприйнятно для всього транзакційного.

Краще: поєднати TTL з event-driven інвалідизацією для критичних ключів.

6) Event-driven інвалідизація: складніше, але масштабує коректність

Потік: запис йде в MariaDB → публікується подія «сутність змінилася» → споживачі інвалідовують або оновлюють ключі в Redis.

Перевага: мала застарілість, узгоджена поведінка під навантаженням.

Ризик: доставка повідомлень, відставання споживачів і впорядкування. Потрібна ідемпотентність і припущення, що події можуть дублюватись.

7) Версіоновані ключі: антипатерн для застарілості, що реально працює

Замість видалення ключів ви змінюєте простір імен ключів, підвищуючи номер версії.

  • Ключ: product:123:v17
  • Інший ключ зберігає поточну версію: product:123:ver

При оновленні інкрементуйте версію. Нові читання йдуть до нового ключа. Старі ключі природньо протерміновуються. Це проста і надійна стратегія для високонавантажених сторінок, де видалення багатьох ключів може розгрузити Redis.

8) Патерни захисту від cache stampede

  • Ймовірнісне раннє перерахування: оновлюйте до закінчення TTL з ймовірністю, що базується на залишковому TTL і вартістю обчислення.
  • Сервіруйте застаріле, поки перераховується: тримайте soft TTL (дозволяє застаріле) і hard TTL (потрібне перерахування). Застаріле дає час, коли БД навантажена.
  • Single-flight: тільки один робітник перераховує; інші чекають або віддають застаріле.

9) Redis для сесій: швидко і зазвичай коректно

Сесії — хороший кейс для Redis, бо вони епhemeral і природно мають TTL. Але і тут потрібно вирішити, що означає «без втрати даних». Втратити сесії — дратує користувачів; зазвичай це не ламає гроші.

Не зберігайте довготривалий стан авторизації в сесіях, якщо не можете коректно відкликати доступ. Кешувати права можна; зберігати «цей користувач назавжди адмін» — ні.

10) MariaDB summary tables: «кеш», що лишається запитуваним

Якщо дорогою є багатоджойновий агрегат, Redis може кешувати результат, але ви втрачаєте можливість запитувати по частинах. Summary tables у MariaDB дозволяють індексувати та фільтрувати похідні дані. Ви платите складністю оновлення і зберігання, але отримуєте пояснюваний SQL і стійкість.

Жарт №2: інвалідизація кешу — доросла версія «ви пробували вимкнути і ввімкнути», тільки кеш пам’ятає, що ви вже пробували.

Стійкість: «без втрати даних» у реальному світі

Коли хтось каже «без втрати даних», запитайте: яких саме даних і який допустимий вік втрат?

  • Кешовані похідні дані: їхня втрата прийнятна; їх можна перерахувати з MariaDB.
  • Епhemeral-стан користувача: втрата терпима, але має бути рідкою; сесії можна повторно авторизувати.
  • Транзакційні факти: мають пережити крах процесу, відмову вузла та помилки оператора. Зберігайте їх у MariaDB (або іншій системі запису), а не тільки в Redis.

Чекліст стійкості MariaDB (базово)

  • InnoDB з налаштуванням flush, відповідним до вашого ризикового апетиту.
  • Увімкнені бінарні логи, якщо потрібне відновлення до точки в часі.
  • Бекапи протестовані відновленням, а не надією.
  • Реплікація під моніторингом на предмет відставання і помилок.

Опції стійкості Redis (якщо ви наполягаєте на збереженні важливого стану)

Redis може персистувати дані, але це не магія. Це набір компромісів, які ви повинні свідомо обрати.

  • RDB-дампи: періодичні знімки. Швидші записи, але ви можете втратити дані між дампами.
  • AOF (append-only file): лог кожного запису. Краще надійність, більше I/O для записів і поведінка переписування файлу для контролю розміру.
  • Реплікація: покращує доступність, але не гарантує нульову втрату, якщо ви не проєктуєте відповідно і не приймаєте затримки.

Якщо ви дійсно не можете втратити дані Redis, то ви вже не «використовуєте кеш». Ви запускаєте іншу базу даних. Трактуйте її як таку: персистентність, реплікація, бекапи, відпрацювання відновлення і планування потужностей.

Практичні завдання: команди, виводи та ваше рішення

Ось типи перевірок, які ви робите під час інциденту або рев’ю продуктивності. Кожне завдання включає: команду, приклад виводу, що це означає і рішення, яке ви приймете.

Завдання 1: Підтвердити, що MariaDB — вузьке місце (топ-запити)

cr0x@server:~$ sudo mariadb -e "SHOW FULL PROCESSLIST\G" | sed -n '1,60p'
*************************** 1. row ***************************
     Id: 8421
   User: app
   Host: 10.0.3.24:41372
     db: prod
Command: Query
   Time: 12
  State: Sending data
   Info: SELECT ... FROM orders JOIN order_items ...
*************************** 2. row ***************************
     Id: 8422
   User: app
   Host: 10.0.3.25:41810
     db: prod
Command: Query
   Time: 11
  State: Sending data
   Info: SELECT ... FROM orders JOIN order_items ...

Значення: довгі запити на читання домінують, і багато з них ідентичні. Це ідеальна площина для кешування.

Рішення: реалізувати cache-aside для дорогого шляху читання або створити summary table, якщо запит складний і потребує фільтрації.

Завдання 2: Перевірити стан buffer pool MariaDB

cr0x@server:~$ sudo mariadb -e "SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_read%';"
+---------------------------------------+-----------+
| Variable_name                         | Value     |
+---------------------------------------+-----------+
| Innodb_buffer_pool_read_requests      | 984332111 |
| Innodb_buffer_pool_reads              | 12099122  |
+---------------------------------------+-----------+

Значення: фізичні читання проти логічних читань. Високе співвідношення reads до read_requests вказує на холодний або замалий buffer pool.

Рішення: якщо miss rate у buffer pool високий, налаштування MariaDB може дати кращий ефект, ніж додавання Redis для деяких робочих навантажень.

Завдання 3: Виявити повільні запити, які ви збираєтесь «перекрити кешем»

cr0x@server:~$ sudo mariadb -e "SHOW VARIABLES LIKE 'slow_query_log%'; SHOW VARIABLES LIKE 'long_query_time';"
+---------------------+-------+
| Variable_name       | Value |
+---------------------+-------+
| slow_query_log      | ON    |
| slow_query_log_file | /var/lib/mysql/slow.log |
+---------------------+-------+
+-----------------+-------+
| Variable_name   | Value |
+-----------------+-------+
| long_query_time | 1.000 |
+-----------------+-------+

Значення: у вас увімкнено логування повільних запитів з порогом 1 с.

Рішення: перед кешуванням виправте очевидні відсутні індекси та патерни N+1. Кеш має зменшувати навантаження, а не приховувати неякісні підходи до доступу.

Завдання 4: Виміряти відставання реплікації (коли репліки — ваш «кеш»)

cr0x@server:~$ sudo mariadb -e "SHOW SLAVE STATUS\G" | egrep "Seconds_Behind_Master|Slave_IO_Running|Slave_SQL_Running"
Slave_IO_Running: Yes
Slave_SQL_Running: Yes
Seconds_Behind_Master: 37

Значення: репліка відстає на 37 секунд. Читання з репліки буде застарілим.

Рішення: не використовуйте репліку для read-after-write шляхів (логін, checkout). Використовуйте primary або побудуйте стратегію консистентності (sticky reads, GTID-based reads або явну інвалідизацію кешу).

Завдання 5: Перевірити пам’ять Redis і ризик евікції

cr0x@server:~$ redis-cli INFO memory | egrep "used_memory_human|maxmemory_human|mem_fragmentation_ratio"
used_memory_human:7.83G
maxmemory_human:8.00G
mem_fragmentation_ratio:1.62

Значення: ви близькі до maxmemory, фрагментація висока, ймовірно йде тиск евікції.

Рішення: або збільшіть пам’ять, або зменшіть розміри значень, або шардуйте, або змініть політику евікції. Також виправте фрагментацію, переглянувши поведінку аллокатора і churn ключів.

Завдання 6: Перевірити політику евікції Redis (можливо, ви евіктуєте не те)

cr0x@server:~$ redis-cli CONFIG GET maxmemory-policy
1) "maxmemory-policy"
2) "noeviction"

Значення: Redis відмовиться приймати записи, коли пам’ять заповнена. Додатки часто інтерпретують це як «випадкові помилки».

Рішення: для кешу віддавайте перевагу allkeys-lfu або volatile-lfu залежно від того, чи кожен ключ має TTL. Для критичного стану будьте явними: можливо, вам потрібен noeviction, щоб провал був голосним.

Завдання 7: Шукати великі ключі (спайки латентності та марнотратство пам’яті)

cr0x@server:~$ redis-cli --bigkeys
# Scanning the entire keyspace to find biggest keys as well as biggest overall keys per data type
Biggest string found 'pagecache:/product/123' has 5242880 bytes
Biggest hash found 'session:hash' has 183201 fields
0 keys sampled

Значення: у вас багатомегабайтні значення і величезні хеші; обидва можуть створювати паузи і нерівномірну евікцію.

Рішення: розділяйте великі об’єкти, зжимайте обережно (компроміс із CPU), і уникайте «одного гігантського хешу для всього». Віддавайте перевагу per-session ключам з TTL.

Завдання 8: Перевірити налаштування персистентності Redis (вікно втрати даних)

cr0x@server:~$ redis-cli CONFIG GET save appendonly appendfsync
1) "save"
2) "900 1 300 10 60 10000"
3) "appendonly"
4) "no"
5) "appendfsync"
6) "everysec"

Значення: RDB-дампи увімкнені; AOF вимкнено. У гіршому випадку ви можете втратити дані між дампами.

Рішення: якщо в Redis зберігається щось важливіше за тимчасовий кеш — увімкніть AOF і виберіть політику fsync; інакше прийміть втрату кешу й спроєктуйте систему для цього.

Завдання 9: Виявити заблокованих Redis-клієнтів (повільні команди)

cr0x@server:~$ redis-cli INFO clients | egrep "blocked_clients|connected_clients"
connected_clients:812
blocked_clients:17

Значення: клієнти заблоковані; щось повільне (великі ключі, Lua-скрипти, повільний диск для AOF або мережеві затримки).

Рішення: перевірте slowlog, визначте команду і виправте робоче навантаження. Redis в пам’яті не означає, що він імунний до I/O та CPU.

Завдання 10: Інспектувати slowlog Redis, щоб спіймати самостворену біль

cr0x@server:~$ redis-cli SLOWLOG GET 3
1) 1) (integer) 19042
   2) (integer) 1735250401
   3) (integer) 15423
   4) 1) "KEYS"
      2) "*"
   5) "10.0.2.9:51244"
   6) ""
2) 1) (integer) 19041
   2) (integer) 1735250399
   3) (integer) 8120
   4) 1) "HGETALL"
      2) "session:hash"
   5) "10.0.2.10:42118"
   6) ""

Значення: хтось виконав KEYS * (блокує Redis на великих наборах даних) і ви використовуєте HGETALL на величезному хеші.

Рішення: забороніть KEYS у production (використовуйте SCAN), переробіть зберігання сесій, щоб уникнути великих хешів, і додайте інструментальні guardrails.

Завдання 11: Підтвердити індексацію MariaDB на гарячому шляху

cr0x@server:~$ sudo mariadb -e "EXPLAIN SELECT * FROM orders WHERE user_id=123 ORDER BY created_at DESC LIMIT 20\G"
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: orders
         type: ref
possible_keys: idx_user_created
          key: idx_user_created
      key_len: 4
          ref: const
         rows: 20
        Extra: Using where

Значення: запит використовує очікуваний композитний індекс і сканує лише невелику кількість рядків.

Рішення: кешування цього запиту може все ще допомогти, але ви вже не маскуєте відсутній індекс. Добре. Тепер ви можете встановити TTL кешу відповідно до бізнес-вимог щодо свіжості.

Завдання 12: Перевірити контенцію блокувань у MariaDB (лічильники пішли не так)

cr0x@server:~$ sudo mariadb -e "SHOW ENGINE INNODB STATUS\G" | sed -n '/LATEST DETECTED DEADLOCK/,+40p'
------------------------
LATEST DETECTED DEADLOCK
------------------------
*** (1) TRANSACTION:
TRANSACTION 928331, ACTIVE 0 sec updating or deleting
mysql tables in use 1, locked 1
LOCK WAIT 3 lock struct(s), heap size 1128, 2 row lock(s)
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 221 page no 9 n bits 80 index PRIMARY of table `prod`.`rate_limits`

Значення: дедлоки в таблиці rate_limits. Класичний випадок «база використовується як сервіс лічильників/блокувань».

Рішення: перемістіть rate limiting і лічильники в Redis, де атомарні опції дешеві, а MariaDB залишайте для стійких записів.

Завдання 13: Валідувати гігієну TTL ключів Redis (чи не тече пам’ять?)

cr0x@server:~$ redis-cli INFO keyspace
db0:keys=1823492,expires=24112,avg_ttl=0

Значення: майже немає ключів з терміном життя; avg_ttl=0 свідчить про необмежені ключі.

Рішення: для кешу більшість ключів мають мати TTL. Якщо цього немає — ви будуєте другу базу даних без плану міграції.

Завдання 14: Виявити stampede кешу на рівні додатка (hit rate Redis)

cr0x@server:~$ redis-cli INFO stats | egrep "keyspace_hits|keyspace_misses"
keyspace_hits:12099331
keyspace_misses:8429932

Значення: miss rate високий; Redis не виконує ефективно роль кешу, або TTLи занадто короткі, або імена ключів неконсистентні.

Рішення: стандартизувати побудову ключів, підвищити TTL там, де безпечно, додати джиттер і реалізувати single-flight, щоб уникнути thundering herd.

Завдання 15: Виміряти тиск ОС на вузол Redis (свопінг — це смерть)

cr0x@server:~$ free -h
               total        used        free      shared  buff/cache   available
Mem:            16Gi        15Gi       120Mi       1.1Gi       900Mi       300Mi
Swap:           2Gi        1.8Gi       200Mi

Значення: Redis-сервер свопиться. Латентність стане нелінійною, потім інцидент перетвориться на «містичний».

Рішення: зупиніть свопінг (налаштуйте, додайте RAM, зменшіть датасет). Якщо Redis має бути надійним — вимкніть своп або встановіть суворі рамки пам’яті й алерти.

Завдання 16: Підтвердити, на що MariaDB витрачає час (CPU проти I/O)

cr0x@server:~$ sudo iostat -x 1 3
Linux 6.1.0 (db1)  12/30/2025  _x86_64_  (8 CPU)

avg-cpu:  %user   %nice %system %iowait  %steal   %idle
          22.10    0.00    5.33   41.20    0.00   31.37

Device            r/s     w/s   rKB/s   wKB/s  await  svctm  %util
nvme0n1         820.0   310.0 52160.0 20120.0  18.2   0.9   92.5

Значення: високий iowait і високе завантаження диска; БД зв’язана з I/O.

Рішення: кешування гарячих читань у Redis може допомогти, але також розгляньте налаштування buffer pool, оптимізацію запитів/індексів і покращення сховища. Не намагайтесь вирішити повільні диски лише кешем, якщо робочий набір має вміщатися в пам’ять.

Швидкий план діагностики

У вас немає часу на філософію, коли графік p95 latency виглядає як гірськолижний трамплін. Ось порядок перевірок, що швидко знаходить вузькі місця з мінімальною самообманом.

По-перше: це база, кеш чи додаток?

  1. Перевірте hit/miss і латентність Redis: якщо misses високі, Redis може бути неважливим; якщо латентність сплесками — Redis може бути перевантажений або свопиться.
  2. Перевірте активні запити MariaDB і lock waits: довгі «Sending data» вказують на дорогі читання; lock waits/deadlocks — на контенцію записів.
  3. Перевірте помилки/таймаути додатка: помилки кешу можуть каскадувати в навантаження на БД (класика). Навантаження на БД може викликати stampede кешу (також класика).

По-друге: шукайте динаміку stampede

  1. Чи прострочився популярний ключ одночасно по всьому флоту (без джиттера)?
  2. Чи був деплой, що змінив найменування ключів або дефолтні TTL?
  3. Чи змінився трафік (кампанія, сканер, бот)?

По-третє: підтвердіть тиск на ресурси

  1. Пам’ять Redis: майже maxmemory? thrash евікції? фрагментація? свопінг?
  2. I/O MariaDB: високий iowait? пропуски buffer pool? вибух logs повільних запитів?
  3. Мережа: збільшений RTT між додатком та Redis/DB може маскуватися як «кеш повільний».

По-четверте: оберіть найменш ризиковане пом’якшення

  • Сервіруйте застаріле під час перерахування для безпечного контенту.
  • Тимчасово збільшіть TTL і додайте джиттер.
  • Обмежте перерахунки (single-flight), щоб захистити MariaDB.
  • Для перевантаження Redis: зменшіть розмір значень, відключіть витратні команди, масштабуйтесь, або fail open (тільки БД), якщо це безпечно.

Типові помилки: симптоми → причина → виправлення

1) «Додали кеш, але навантаження на БД не впало»

Симптоми: CPU Redis низький, misses високі, QPS MariaDB не змінився.

Причина: кешування неправильного шару (наприклад, після персоналізації), неконсистентні ключі, занадто короткі TTL або більшість запитів унікальні.

Виправлення: кешуйте на більш спільному рівні (фрагменти), стандартизуйте побудову ключів і вимірюйте hit rate по endpoint. Розгляньте summary tables, якщо запит по суті некешований.

2) «Redis був швидким, поки раптово перестав бути»

Симптоми: переривчасті сплески, заблоковані клієнти, таймаути.

Причина: великі ключі, повільні команди, свопінг, fsync AOF, або однопотокове гаряче місце.

Виправлення: запустіть --bigkeys, перегляньте slowlog, приберіть блокуючі команди, тримайте запас пам’яті і уникайте гігантських значень.

3) «Користувачі бачать застарілі дані після оновлень»

Симптоми: оновлення профілю не відображається, ціни повертаються, адмін-зміни запізнюються.

Причина: тільки TTL інвалідизація або відсутні події інвалідизації; читання з відстаючих реплік; ключі не версіоновані.

Виправлення: event-driven інвалідизація для критичних сутностей, версіоновані ключі для гарячих об’єктів і read-after-write консистентність (sticky primary reads, коли потрібно).

4) «Ми втратили дані після перезапуску Redis»

Симптоми: порожні лідерборди, відсутні сесії, зниклі лічильники; додаток панікує.

Причина: Redis використовували як систему запису; персистентність вимкнена або недостатня; відсутній план відновлення.

Виправлення: перенесіть стійкі факти в MariaDB; увімкніть AOF, якщо Redis має персистувати; протестуйте поведінку при рестарті й час відновлення.

5) «MariaDB стала повільнішою після додавання ‘cache tables’»

Симптоми: падає hit rate buffer pool, зростає I/O, purge lag, затримки реплікації.

Причина: ефемерні кеш-дані заповнюють InnoDB, видалення за TTL спричиняє churn.

Виправлення: перемістіть TTL-важкі ключі в Redis; якщо залишаєте кеш-таблиці — партицинуйте їх, видаляйте батчами і розділяйте навантаження по інстансам.

6) «Випадкові 500 під час сплесків трафіку»

Симптоми: помилки корелюють з навантаженням; Redis «OOM command not allowed» або таймаути.

Причина: досягли maxmemory з noeviction, або політика евікції не відповідає навантаженню; stampede кешу змушує перерахунок.

Виправлення: встановіть maxmemory і політику евікції, придатну для кешів; додайте джиттер і single-flight; захистіть MariaDB з допомогою circuit breakers.

7) «Кеш погіршив коректність більше, ніж його відсутність»

Симптоми: неконсистентні читання, неможливі стани, важкорепродюсовані баги.

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

Виправлення: кешуйте іммутабельні знімки; версіонуйте ключі; уникайте ілюзій мульти-ключових транзакцій; якщо потрібне надійне повідомлення — використовуйте відповідні черги/стріми з гарантіями доставки.

Три корпоративні міні-історії (анонімізовано)

Історія 1: Інцидент через хибне припущення (репліки — це «кеш»)

Команда тримала великий контентний сайт з MariaDB primary + репліками. Хтось запропонував «безкоштовну продуктивність», перенаправивши всі читання на репліки. У staging це працювало, бо у staging не було значущого обсягу записів. У production — було.

Вони відправляли сторінку облікового запису (недавні покупки, адресна книга, статус підписки) на репліки. Почали надходити звернення до підтримки: «Я змінив адресу, і вона не збереглась». Інженери перевірили primary — дані були правильні. UI все ще показував старі дані, бо репліка відставала під навантаженням.

Хибне припущення було тонким: вони сприймали репліки як кеш, де допустима застарілість. Але сторінка облікового запису — це інтерфейс read-after-write. Люди помічають, коли їхню адресу повертають назад.

Виправлення було нудне й ефективне: sticky reads. Після успішного запису наступні N секунд читання користувача йшли на primary. Також вони інструментували відставання реплік і змусили маршрутизатор відмовлятися від читань з реплік, якщо відставання перевищує поріг.

Урок: репліки можуть зменшити навантаження на читання, але вони вводять поведінку консистентності, яку потрібно проєктувати. Промах кешу просто коштує часу. Застаріле читання коштує довіри.

Історія 2: Оптимізація, що відкотилась назад (write-behind «за швидкість»)

Компанія електронної торгівлі мала повільний шлях «додати до корзини». Хтось помітив, що таблиця корзин у MariaDB була гарячою точкою — багато маленьких оновлень, багато контенції. Вони перемістили корзини в Redis і зробили write-behind у MariaDB у фоновому воркері.

Латентність значно покращилась. Усі аплодували. Потім один з Redis-вузлів перезапустився під час рутинного патчування ядра. Черга бекграунд-воркера відстала, потім почала пушити. Деякі оновлення були застосовані поза порядком. Частина корзин відкотилась або дублювала елементи залежно від патерну ретраїв.

Аварія не була тотальним колапсом; вона була гіршою: часткова корупція. Система checkout мусила додати захисні перевірки, а саппорт обробляв розгніваних клієнтів, які клялись, що «додали товар тричі». Вони були праві.

Вони відкотилися до MariaDB як системи запису для корзин, і використовували Redis як cache-aside для відображення корзин, плюс невелику Redis-структуру для «dirty flags», щоб зменшити перерахунки. Також додали idempotency-токени у write-запити, щоб ретрай не міг дублювати операції.

Урок: write-behind — податок на коректність, який ви сплачуєте потім з відсотками. Якщо ви не можете описати семантику порядку та ретраїв — не робіть цього.

Історія 3: Нудна, але коректна практика, що врятувала день (serve stale + versioned keys)

SaaS-продукт мав дашборд, що бився по MariaDB важким агрегатним запитом. Вони зробили Redis cache-aside. Це допомогло, але при сплеску трафіку масове протермінування кешу іноді накривало базу.

Замість погоні за хитрощами, вони реалізували два нудних патерни: версіоновані ключі і «serve stale while revalidating». Кожна карточка дашборда мала soft TTL (віддавати кеш) і hard TTL (потрібно перерахувати). Механізм single-flight гарантував, що лише один перерахунок на ключ відбувався одночасно.

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

Через кілька тижнів MariaDB мала короткий I/O-випад під час техобслуговування сховища. Дашборд лишався реактивним, віддаючи трохи застаріле для кількох хвилин. Підтримка нічого не помітила. Команда помітила лише через гарний моніторинг.

Урок: найкраща функція кешування — контрольований деград. Коли щось ламається, користувачі не повинні про це знати.

Чеклісти / покроковий план

Покроково: обрати MariaDB, Redis або обидва

  1. Класифікуйте дані:
    • Транзакційні факти → MariaDB.
    • Ефемерний стан → Redis (з TTL).
    • Похідні моделі читання → таблиці MariaDB або Redis залежно від потреб у запитуваності.
  2. Виберіть патерн кешування:
    • За замовчуванням: cache-aside з TTL + джиттер.
    • Для високої коректності та великого читання: event-driven інвалідизація або версіоновані ключі.
    • Уникайте write-behind, якщо не прийнятні втрати і не доведені семантики.
  3. Визначте бюджет застарілості: по кожному endpoint явно. «Кілька секунд» — не план; запишіть конкретно.
  4. Спроектуйте захист від stampede: single-flight, stale-while-revalidate і/або раннє оновлення.
  5. Плануйте потужності Redis: maxmemory, політика евікції, запас пам’яті, покриття TTL, розмір значень.
  6. Тримайте MariaDB здоровою: перегляд індексів, ревізія запитів, розмір buffer pool, моніторинг I/O, перевірки реплікації.
  7. Визначте поведінку при відмовах:
    • Якщо Redis недоступний — відкриваєтесь на БД (fail open) чи ні?
    • Якщо БД повільна — віддаєте застаріле чи повертаєте помилки?
  8. Інструментуйте базові метрики: hit rate кешу, p95 latency для Redis і БД, відставання реплік, пам’ять Redis, повільні запити БД.
  9. Проведіть game day: перезапустіть Redis, симулюйте евікцію, інжектуйте відставання репліки. Переконайтеся, що сайт деградує так, як ви планували.

Чекліст: безпечна конфігурація Redis для кешування

  • Встановіть maxmemory і обдуману політику maxmemory-policy.
  • Переконайтесь, що більшість ключів кешу має TTL.
  • Алертуйте по used_memory/maxmemory, blocked_clients і evicted_keys.
  • Уникайте блокуючих команд у продакшені (KEYS, великі SORT, величезні HGETALL на гігантських хешах).
  • Тримайте запас пам’яті, щоб уникнути фрагментації/штормів евікції.

Чекліст: безпечне використання MariaDB поряд з Redis

  • Зробіть read-after-write endpoint-и явними; не віддавайте їх з відстаючих реплік.
  • Використовуйте summary tables для важких агрегатів, які потребують фільтрації й індексування.
  • Не реалізовуйте rate limit-и або гарячі лічильники як оновлення рядків на primary.
  • Моніторте deadlocks і lock waits; вони підкажуть, куди переносити координаційні навантаження в Redis.

FAQ

1) Чи може Redis бути системою запису?

Тільки якщо ви готові управляти ним як первинною базою даних: персистентність, стратегія реплікації, бекапи, тестування відновлення і суворе управління потужностями. Для більшості сайтів — ні. Тримайте транзакційну істину в MariaDB.

2) Якщо MariaDB вже кешує в buffer pool, навіщо Redis?

MariaDB кешує сторінки, а не обчислені додатком результати. Redis корисний, коли дорогі операції — це JOINи/агрегації, серіалізація, рендеринг шаблонів, перевірки прав або координаційні примітиви.

3) Який найнадійніший за замовчуванням патерн кешування?

Cache-aside з TTL + джиттер, плюс single-flight або stale-while-revalidate для гарячих ключів. Це тримає MariaDB авторитетною і робить втрату кешу відновлюваною.

4) Як уникнути застарілості без видалення мільйона ключів?

Використовуйте версіоновані ключі. Підвищуйте номер версії при записі, читайте з нового простору і дозвольте старим ключам природньо протермінуватися. Це масштабується краще за шторм видалень.

5) Чи краще write-through, ніж cache-aside?

Write-through може зменшити застарілість, але збільшує складність write-path і залежність від доступності Redis. Використовуйте його, коли вам потрібні свіжі кешовані читання і ви можете безпечно деградувати, якщо Redis недоступний.

6) Чому write-behind такий ризиковий?

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

7) Яку політику евікції обрати для Redis як кешу?

Якщо все — кеш: allkeys-lfu — сильний дефолт. Якщо евікція має відбуватись лише серед ключів з TTL: volatile-lfu. Уникайте noeviction, якщо ви не хочете різких помилок при заповненні.

8) Як зрозуміти, чи допомагає Redis?

Виміряйте hit rate по endpoint і порівняйте QPS/латентність MariaDB до і після. Якщо misses лишаються високими або навантаження на БД не падає — ви кешуєте неправильну річ або неправильно формуєте ключі.

9) Чи можна зберігати сесії в MariaDB замість Redis?

Можна, але часто це створює контенцію записів і шум очищення. Redis з TTL зазвичай кращий варіант. Якщо сесії критичні, явно визначте очікування щодо повторної авторизації та персистентності.

10) Що з кешуванням відрендерених HTML-сторінок?

Добре, коли персоналізація обмежена. Кешуйте цілі сторінки або фрагменти в Redis і інвалідовуйте при зміні контенту через події або версіонування. Не кешуйте сторінки по користувачу, якщо не зробили математику щодо кардинальності ключів.

Наступні кроки, які ви реально можете зробити цього тижня

  1. Виберіть три endpoint-и з найгіршою вартістю для БД. Заміряйте їх шаблони запитів і вирішіть, що кешувати: об’єкти, фрагменти чи summary tables.
  2. Додайте cache-aside з TTL + джиттер для одного endpoint-а й інструментуйте hit rate, p95 latency і вплив на QPS БД.
  3. Реалізуйте захист від stampede (single-flight або stale-while-revalidate) для одного гарячого шляху ключів.
  4. Встановіть maxmemory і політику евікції Redis свідомо, і налаштуйте алерти при зменшенні запасу пам’яті.
  5. Аудит «без втрати даних»: переконайтесь, що транзакційні факти в MariaDB з бекапами і відновленнями; Redis тримає лише те, що ви готові втратити, або те, що ви правильно персистите.
  6. Проведіть drill перезапуску: перезапустіть Redis у робочий час контрольовано, спостерігайте поведінку і виправте те, що ламається до справжнього інциденту.

Якщо ви нічого більше не зробите: перестаньте ставитись до «кешу» як до магічної наклейки продуктивності. Трактуйте його як другу систему з власними режимами відмов — і проєктуйте під відмови перш за все.

← Попередня
Зниження напруги GPU: тихий трюк продуктивності, про який не кажуть
Наступна →
Hyper-Threading розкрито: магічні потоки чи хитрощі планувальника?

Залишити коментар