MySQL проти Redis: Redis не ваша база даних — але може знизити навантаження MySQL на 80%

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

MySQL перевантажений, ваші CPU завантажені на максимум, і команда продукту щойно «запустила маленьку фічу», яка якимось чином подвоїла трафік. Ви додаєте репліки. Потім додаєте ще реплік. Потім виявляєте, що вузьке місце не в записах — ваші читання виконують дурну роботу мільйони разів на годину.

Redis не врятує поламану схему або погані запити. Але якщо ваше навантаження переважно повторювані читання, обчислені об’єкти, пошуки сесій, перевірки ліміту або «чи має цей користувач дозвіл X?», Redis може значно скоротити трафік до MySQL. Зроблено правильно — 80% не казка. Це звичайний вівторок.

Міф: «Redis vs MySQL» — невірний бій

Коли люди кажуть «MySQL vs Redis», вони зазвичай мають на увазі «я втомився, що MySQL повільний, чи можу я замінити його на щось швидше?» Це як замінити бухгалтерію касовим апаратом, бо апарат швидший. Касовий апарат чудовий. Він також не є бухгалтерією.

MySQL — це надійна реляційна база з транзакціями, обмеженнями, вторинними індексами, JOIN і десятиліттями операційного досвіду. Redis — сервер структур даних у пам’яті: надшвидкий, передбачуваний з низькою затримкою та відмінний для тимчасових або похідних даних, які можна відновити.

Отже, правильне питання не в тому, хто «перемагає». Питання:

  • Які дані мають бути правильними, стійкими та доступними для складних запитів? Помістіть їх у MySQL.
  • Які дані «гарячі», повторювані, похідні, тимчасові або використовуються для координації? Помістіть їх у Redis.
  • Звідки походить навантаження: вартість виконання запитів на CPU, дисковий I/O, блокування, мережеві обміни чи поведінка застосунку?

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

За що насправді корисні кожна система

MySQL: джерело істини з гострими краями

MySQL створено для коректності та довготривалого зберігання. Він зберігає дані на диск, логуює зміни і надає транзакційну семантику. Також він дозволяє ставити складні питання — саме тому люди випадково ставлять складні запити 50 000 разів на хвилину.

Використовуйте MySQL для:

  • Даних, які не можна втратити: замовлення, баланси, дозволи, стан акаунта, події білінгу.
  • Інваріантів на декілька рядків: «тільки одна активна підписка на користувача», зовнішні ключі, унікальність.
  • Аудитної історії змін (часто через таблиці з додаванням або пайплайни на базі binlog).
  • Запитів, що вимагають JOIN або сканування діапазонів по вторинних індексах.

MySQL дає збої знайомими шляхами: конкуренція за блокування, погані індекси, великі скани, промахи буфер-пулу та повільне сховище. Він також дає збої соціально: «ще один стовпець», «ще один JOIN» і «потім закешуємо», що насправді означає «ми покличемо SRE пізніше».

Redis: прискорювач гарячого шляху та шар координації

Redis швидкий, бо простий у ключових місцях: зберігає дані у пам’яті, робить операції дешевими і має простий протокол. Це чудове місце для:

  • Кешування: обчислені об’єкти, відрендерені фрагменти, рішення з авторизації, відповіді API.
  • Сесій і токенів: швидкі пошуки з TTL.
  • Лімітування: атомарні лічильники на ключ з експірацією.
  • Розподіленої координації: блокування (обережно), рейтинги, черги/стріми, ключі для дедуплікації.
  • Фіч-флагів і знімків конфігурацій: малі читання багато разів.

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

Одна цитата, яку варто витатуювати для кожного, хто оперує обома: «Усе ламається, весь час.» —Werner Vogels

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

Цікаві факти та трохи історії

  • Redis з’явився через реальну потребу продукту: його створив Сальваторе Санфіліппо, щоб вирішити проблеми продуктивності й масштабування в контексті веб-аналітики, а не як академічне дослідження.
  • InnoDB змінив операційну гру MySQL: коли InnoDB став за замовчуванням, відновлення після аварій і транзакційна поведінка стали нормою, а не опційним «серйозним режимом».
  • Популярність Redis зросла із зберігання сесій: раннє масове прийняття часто починалося з «перенесіть сесії з MySQL», бо це низький ризик і одразу зменшує писання.
  • Розкол Memcached і Redis: Memcached просував просту історію тільки кешу; Redis дав багатші типи даних і атомарні операції, що зробило його корисним поза межами простого кешування.
  • Персистентність у Redis з’явилась пізніше за духом: Redis — насамперед у пам’яті; RDB-снімки та AOF-логи існують, але надійність — це налаштовуваний компроміс, а не гарантія за замовчуванням.
  • Реплікація MySQL сформувала архітектуру: здатність розділяти читання на репліки (а пізніше semi-sync і GTID) вплинула на сценарій «масштабування читання» задовго до того, як Redis став підставою.
  • Інвалідизація кешу досі нерозв’язана як людська проблема: найскладніше — не алгоритм, а організаційна дисципліна щодо того, де знаходиться істина і хто відповідає за правила інвалідизації.
  • Redis ввів Lua-скрипти для атомарності: це потужно, але також спосіб створити «приховану бізнес-логіку», яку ніхто безпечно не розгортає.

Як Redis знижує навантаження MySQL без обману

Більшість навантаження MySQL — самозавдані повтори

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

MySQL може обробляти чимало читань, особливо з кешу (буфер-пул) і з правильними індексами. Але він все одно виконує парсинг, планування, керування блокуваннями та виконання логіки. Помножте це на багато інстансів аплікації — і отримаєте смерть від тисяч люб’язних SELECT-ів.

Redis допомагає, коли ваше читальне навантаження:

  • Гаряче: ті самі ключі запитуються знову й знову.
  • Похідне: ви можете реконструювати кеш з MySQL або терпіти іноді переобчислення.
  • Коарсно-Granularне: один хіт кешу замінює кілька запитів або JOIN-важкий запит.
  • Чутливе до затримки: скорочення на 10–30 мс важливе, бо множиться через підлеглі виклики.

Але Redis не допоможе, коли вам потрібні:

  • Ad hoc аналітика з гнучкими предикатами запитів.
  • Складні JOIN-и, що змінюються кожен спринт.
  • Сильна консистентність між багатьма сутностями без ретельного дизайну.
  • Довге зберігання з дешевим зберіганням на ГБ.

Патерн 80% зниження: кешуйте дорогий кордон

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

Це дає два ефекти:

  1. Заміщує кілька викликів MySQL одним Redis GET.
  2. Робить ключ кешу стабільним і простим для розуміння (для інвалідизації та TTL).

Патерни запису: оберіть один, не імпровізуйте

  • Cache-aside (ліниве завантаження): аплікація читає Redis, при промаху читає MySQL, потім наповнює Redis. Найпоширеніший; найпростіший для впровадження; найважчий для підтримки консистентності без дисципліни.
  • Write-through: аплікація записує в кеш і БД у рамках одного запиту (часто спочатку в БД, потім у кеш). Корисно для передбачуваних читань, але треба обробляти часткові відмови.
  • Write-behind: аплікація записує в Redis і асинхронно зливає в БД. Швидко, але ризиковано; ви фактично навмисно будуєте систему бази даних.

Для більшості команд: почніть із cache-aside, додайте явну інвалідизацію при записах і застосуйте TTL, щоб обмежити площу ураження помилок.

Патерни кешування та координації, що працюють у продакшн

1) Cache-aside з явною інвалідизацією

При читаннях:

  1. GET ключ з Redis
  2. якщо хіт: повернути
  3. якщо промах: зробити запит до MySQL, серіалізувати, SETEX в Redis, повернути

При записах:

  • Коміт у MySQL спочатку.
  • Потім видаліть або оновіть кеш-ключі, які залежать від змінених рядків.
  • Всюди використовуйте TTL. TTL — не консистентність, це контроль збитків.

Якщо інвалідизація здається «занадто складною», це не причина уникати кешування. Це причина визначити відповідальність: який сервіс володіє простором імен ключів кешу, і які записи спричиняють інвалідизацію.

2) Запобігання штурму: single-flight і soft TTL

Cache stampede — це коли гарячий ключ спливає і 5 000 запитів одночасно рвуться до MySQL. База не стає «правильнішою» під навантаженням; вона просто сповільнюється, а потім падає.

Виправити це можна:

  • Згортання запитів (single-flight): один запит відновлює ключ, поки інші чекають.
  • Soft TTL: віддавати трохи застарілі дані протягом короткого вікна, поки фонова оновлювальна задача оновлює кеш.
  • Джиттер: рандомізувати TTL, щоб уникнути синхронного закінчення терміну дії.

3) Негативне кешування (кешуйте «не знайдено»)

Якщо боти або зламані клієнти постійно питають ID, яких не існує, MySQL буде робити ці пошуки вічно. Кешуйте 404 з коротким TTL (секунди до хвилин). Це дешева страховка.

4) Використовуйте Redis для «рішень», а не для «істини»

Хороші ключі Redis представляють рішення: «користувач X обмежений», «сесія Y дійсна до T», «набір фіч-флагів F для когорти C». Вони часозв’язані і похідні.

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

5) Лічильники і лімітування: атомарно і нудно

Лімітування — це рідна ніша Redis, бо INCR і EXPIRE дешеві й атомарні. Використовуйте фіксоване вікно, якщо треба, або скользяче вікно, якщо хочете, але тримайте реалізацію простою, щоб хтось міг дебагнути її о 3:00 ранку.

6) Черги і стріми: знайте, що купуєте

Списки Redis, pub/sub і стріми можуть реалізувати черги задач. Вони гарні для низьколатентного fanout і легких пайплайнів. Але якщо вам потрібно «exactly-once» оброблення, стійке зберігання, ребалансування споживачів і гарантії між дата-центрами, ви вже не обираєте Redis — ви обираєте систему логів.

Стійкість Redis: що робить, а що ні

Персистентність Redis реальна, але її контракт відрізняється від реляційної бази. Спочатку потрібно вирішити модель відмов перш ніж налаштовувати персистентність.

RDB-снімки

RDB записує знімки стану на диск у певні точки часу. Це компактно і швидко для перезапусків, але ви можете втратити дані з моменту останнього знімка. Це нормально для кешів; сумнівно для черг; лячно для бухгалтерських реєстрів.

AOF (Append Only File)

AOF логує кожне записування. За політиками fsync можна зменшити вікно втрат даних ціною накладних витрат на запис. AOF rewrite періодично стискає лог. Це ближче до довговічності, але все одно не замінює реляційну БД з обмеженнями та транзакційними гарантіями по рядках.

Реплікація і фейловер

Реплікація в Redis асинхронна у більшості поширених налаштувань. Фейловер може призвести до втрати останніх записів. Якщо ви поклали «істину» в Redis, ви маєте прийняти, що ця істина може бути відкотена під час фейловера. Якщо від цього у вас крутиться шлунок — добре, тримайте істину в MySQL.

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

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

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

Перше: MySQL перевантажений або просто чекає?

  • Перевірте з’єднання та виконувані потоки MySQL: якщо threads_running високе й стабільне, MySQL зайнятий; якщо низьке, але запити повільні — можливо, чекаєте на I/O або блокування.
  • Перевірте основні підписи запитів: один поганий шаблон запиту може домінувати по CPU, навіть якщо кожен виклик «лише 30 мс».
  • Перевірте затримку диска: якщо сховище повільне, кешування не виправить записів або промахів буфер-пулу.

Друге: Redis справді допомагає чи ховає проблему?

  • Хітрейт Redis: низький хітрейт означає, що ви платите за мережу і серіалізацію без користі.
  • Стрибки затримки Redis: великі ключі, повільні команди або fsync персистентності можуть спричиняти хвостову затримку, яка повертається до MySQL (через повтори й тайм-аути).
  • Витіснення: витіснення означає, що політика кешу приймає рішення за вас — зазвичай не на користь.

Третє: чи аплікація — справжній підозрюваний?

  • Паралелізм: збільшення кількості воркерів може подвоїти навантаження на БД, навіть якщо трафік такий самий.
  • N+1 запити: один ендпоінт може тихо виконувати сотні запитів на запит.
  • Шторм повторів: неправильно налаштовані тайм-аути можуть множити навантаження, коли щось вже повільне.

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

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

Завдання 1: Визначте топ-запити MySQL за сумарним часом

cr0x@server:~$ mysql -e "SELECT DIGEST_TEXT, COUNT_STAR, ROUND(SUM_TIMER_WAIT/1e12,2) AS total_s FROM performance_schema.events_statements_summary_by_digest ORDER BY SUM_TIMER_WAIT DESC LIMIT 5\G"
...output...

Що означає вивід: DIGEST_TEXT показує нормалізований шаблон запиту; COUNT_STAR — кількість виконань; total_s — сумарний час.

Рішення: Якщо один digest домінує за сумарним часом, націльтеся на нього для кешування або індексації перед додаванням заліза. Якщо багато digest пов’язані, шукайте системні проблеми (тайм-аути, N+1).

Завдання 2: Підтвердіть, чи MySQL обмежений CPU або чекає на I/O

cr0x@server:~$ mysql -e "SHOW GLOBAL STATUS LIKE 'Threads_running'; SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_reads'; SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_read_requests';"
...output...

Що означає вивід: Threads_running високе означає активне навантаження. Buffer_pool_reads (фізичні читання) vs read_requests (логічні читання) дає інтуїцію щодо хітів кешу.

Рішення: Якщо фізичні читання зростають і затримка сховища висока, виправте I/O або збільшіть buffer pool перед тим, як робити ставку на Redis.

Завдання 3: Знайдіть контенцію блокувань у MySQL

cr0x@server:~$ mysql -e "SELECT * FROM sys.innodb_lock_waits ORDER BY wait_age_secs DESC LIMIT 10\G"
...output...

Що означає вивід: Показує блокуючі й очікувальні транзакції та скільки часу вони заблоковані.

Рішення: Якщо очікування блокувань поширені, кешування читань може допомогти, але не розв’яже проблему записної контенції. Виправте область транзакцій, індекси або ізоляційні налаштування.

Завдання 4: Швидко перегляньте slow query log MySQL

cr0x@server:~$ sudo pt-query-digest /var/log/mysql/mysql-slow.log --limit 10
...output...

Що означає вивід: Топ-класи запитів за сумарним часом, середнім часом, оглянутими рядками та варіацією.

Рішення: Якщо rows examined величезні для простих пошуків, потрібні індекси або виправлення запитів. Якщо запити передбачувані і повторювані — вони ідеальні для кешування.

Завдання 5: Перевірте використання індексів MySQL для «гарячого» запиту

cr0x@server:~$ mysql -e "EXPLAIN SELECT * FROM orders WHERE user_id=123 AND status='open'\G"
...output...

Що означає вивід: Дивіться type (ALL — погано), ключ, який використовується, оцінку rows і extra (Using filesort / temporary може шкодити).

Рішення: Якщо план — скан або використовується неправильний індекс, виправте схему/запит спочатку. Redis не повинен бути пластирем на уникненні сканів.

Завдання 6: Перевірте хітрейт Redis і поведінку простору ключів

cr0x@server:~$ redis-cli INFO stats | egrep 'keyspace_hits|keyspace_misses|instantaneous_ops_per_sec'
...output...

Що означає вивід: Хіти й промахи дозволяють обчислити коефіцієнт хітів. ops/sec показує обсяг трафіку.

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

Завдання 7: Виявлення витіснень Redis (безшумний податок на коректність)

cr0x@server:~$ redis-cli INFO stats | egrep 'evicted_keys|expired_keys'
...output...

Що означає вивід: evicted_keys > 0 означає, що Redis видаляє ключі під тиском пам’яті. expired_keys — нормальна поведінка при використанні TTL.

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

Завдання 8: Перевірка фрагментації пам’яті Redis і поведінки аллокатора

cr0x@server:~$ redis-cli INFO memory | egrep 'used_memory_human|used_memory_rss_human|mem_fragmentation_ratio|maxmemory_human'
...output...

Що означає вивід: RSS значно більше за used_memory означає фрагментацію або накладні витрати аллокатора; maxmemory показує верхню межу.

Рішення: Висока фрагментація може вимагати налаштування (поведінка jemalloc, активна дефрагментація) або зміни навантаження (уникати великого циклу ключів однакового розміру).

Завдання 9: Знайдіть повільні команди Redis

cr0x@server:~$ redis-cli SLOWLOG GET 10
...output...

Що означає вивід: Перелік повільних виконань команд із тривалістю і аргументами (часто обрізаними).

Рішення: Якщо бачите KEYS, великий HGETALL, великі діапазонні запити або Lua-скрипти, що займають мілісекунди, ви створили гранату затримки. Замініть на SCAN-патерни, менші значення або попередньо обчислені ключі.

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

cr0x@server:~$ redis-cli CONFIG GET appendonly save appendfsync
...output...

Що означає вивід: appendonly вкл/викл, розклади save для RDB, політика appendfsync (always/everysec/no).

Рішення: Для чистого кешу можна відключити персистентність, щоб зменшити накладні витрати. Для даних координації (локи, ліміти) персистентність може бути опційною, але розумійте поведінку при рестарті. Для «істини» перегляньте весь дизайн.

Завдання 11: Виміряйте розподіл затримок Redis під навантаженням

cr0x@server:~$ redis-cli --latency-history -i 1
...output...

Що означає вивід: Повідомляє min/avg/max затримки з часом. Сплески корелюють з fork для RDB, fsync AOF або великими командами.

Рішення: Якщо максимальні сплески затримки пов’язані з подіями персистентності, налаштуйте персистентність, перейдіть на швидше сховище або ізолюйте Redis на виділених вузлах.

Завдання 12: Перевірте сигнали тиску Linux, що впливають на MySQL і Redis

cr0x@server:~$ vmstat 1 5
...output...

Що означає вивід: si/so вказують на свопінг (погано); wa показує I/O wait; r — runnable-потоки; free/buff/cache показує позицію пам’яті.

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

Завдання 13: Підтвердіть затримку диска для томів MySQL

cr0x@server:~$ iostat -x 1 3
...output...

Що означає вивід: r_await/w_await показують затримку чит/запису; %util показує насичення.

Рішення: Якщо затримка висока і util близький до 100%, ви I/O-bound. Кешування читань може допомогти, але якщо проблема в записах — потрібне сховище, батчинг або зміни схеми.

Завдання 14: Слідкуйте за крутінням з’єднань MySQL (часто прихована плата)

cr0x@server:~$ mysql -e "SHOW GLOBAL STATUS LIKE 'Connections'; SHOW GLOBAL STATUS LIKE 'Aborted_connects'; SHOW GLOBAL STATUS LIKE 'Threads_connected';"
...output...

Що означає вивід: Швидке зростання Connections свідчить про відсутність пулінгу; Aborted_connects говорить про проблеми автентифікації/мережі.

Рішення: Виправте пулінг і тайм-аути до додавання Redis. Інакше ви просто побудуєте швидший шлях перевантажити MySQL.

Завдання 15: Перевірте розподіл ключів Redis і гарячі ключі

cr0x@server:~$ redis-cli --hotkeys
...output...

Що означає вивід: Оцінює найчастіше доступні ключі (найкраща евристика на вибірці).

Рішення: Якщо один ключ надзвичайно гарячий, шардируйте його (ключі на користувача), додайте локальний кеш або перерахуйте дизайн, щоб уникнути глобальних лічильників, що серіалізують трафік.

Три корпоративні міні-історії (анонімізовано, достатньо реальні)

Міні-історія 1: Інцидент через неправильне припущення

Компанія мала монолітний MySQL-бекенд і додала Redis «для сесій». Це спрацювало. Тому вони розширили Redis для зберігання прав користувачів — за що користувач заплатив. Міркування були прості: права часто читаються, рідко пишуться, Redis швидкий, і вони увімкнули AOF, отже «досить збережено».

Потім стався фейловер під час події «галасливого сусіда» на хості віртуалізації. Redis промотнув репліку, що була трохи позаду. Невелике вікно змін прав — апгрейди й даунгрейди — зникло. Деякі користувачі втратили доступ; деякі отримали доступ, якого не повинні були мати. Почалися звернення в підтримку. Фінанси почали ставити питання в тоні, який люди використовують, коли намагаються залишатися ввічливими.

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

Проблема в корені — припущення «AOF означає база даних». Вони виправили це, перемістивши права назад у MySQL як джерело істини, потім кешували обчислений бандл прав у Redis з короткими TTL і явною інвалідизацією при записах прав. Також додали шлях перевірки здоров’я: якщо Redis каже, що користувач має право, а MySQL — ні, MySQL перемагає і Redis виправляється.

Після цього фейловери знову стали нудними. Це і є мета.

Міні-історія 2: Оптимізація, що повернулася бумерангом

Інша команда пишалася своїм хітрейтом кешу. У них був популярний ендпоінт: «отримати дашборд користувача». Вони кешували всю JSON-відповідь у Redis на 30 хвилин. Навантаження MySQL різко впало. Графіки виглядали як слайд успіху.

Через два місяці продукт випустив віджет «живих сповіщень» у дашборді. Він мав оновлюватися відразу, коли повідомлення прочитане. Натомість користувачі бачили старі повідомлення до 30 хвилин. Команда намагалася інвалідизувати кеш при читанні повідомлень, але кеш дашборду залежав від кількох таблиць і типів подій. Інвалідизація стала клубком «якщо X змінюється, видалити ключі A, B, C, крім коли…». Пішли баги. Потім інцидент: деплой випадково припинив інвалідизацію для одного з джерел даних, і застарілі дашборди розповсюдилися як чутка.

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

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

Навантаження MySQL трохи зросло від «героїчного» базового рівня. Інцидентів стало значно менше. Це кращий обмін.

Міні-історія 3: Сумлінна, але правильна практика, що врятувала день

Стабільна платформа ставилася до Redis як до залежності з власним SLO, дашбордами і плануванням ємності. Не захопливо. Ефективно. Вони робили рутинні навантажувальні тести, включно зі сценаріями відмов Redis: холодний кеш, частковий кеш і перезапуски Redis під навантаженням.

Одного дня оновлення ядра спричинило перезавантажувальний цикл на частині вузлів кластера Redis. Доступність Redis погіршилася. Аплікація не впала. Затримки збільшилися, але залишалися в межах користувацьких SLO для більшості ендпоінтів.

Чому? Два нудні рішення. По-перше, аплікація мала жорсткі тайм-аути для Redis (однозначні мілісекунди) і відкот на MySQL при промахах або помилках лише для обмеженої кількості ендпоінтів. По-друге, вони впровадили circuit breaker: якщо помилок Redis забагато, аплікація перестає звертатися до Redis на короткий час, щоб уникнути шторму повторів.

MySQL відчув збільшення читального трафіку, але у них був запас, бо стратегія кешу цілеспрямовано покривала найвищі за змінністю читання і вони вже виправили найгірші запити. Режим «кеш недоступний» був деградованим, а не катастрофічним.

Команда виправила кластер без публічного інциденту. Жодних героїчних вчинків. Просто системи, що поводяться як дорослі.

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

1) Симптом: навантаження MySQL не впало після додавання Redis

Корінна причина: низький хітрейт, кешування невірного гранулювання або відсутність cache-aside логіки під конкурентністю.

Виправлення: Виміряйте keyspace_hits/misses, визначте, що кешується, і кешуйте на рівні граничного об’єкта сервісу. Додайте згортання запитів, щоб зупинити штурми.

2) Симптом: пам’ять Redis росте до початку витіснень

Корінна причина: відсутні TTL, занадто багато унікальних ключів (висока кардинальність) або значення більше за очікуване (JSON-блоки, незжаті масиви).

Виправлення: Додавайте TTL за замовчуванням, обмежуйте розмір корисного навантаження, використовуйте хеші для пов’язаних полів і визначайте maxmemory + політику витіснення, що відповідає «кешу», а не «зберіганню назавжди».

3) Симптом: P99 затримка погіршилася після «кешування»

Корінна причина: Redis додає хвостову затримку через fsync персистентності, повільні команди, великі ключі або мережеві переходи; плюс аплікаційні повтори підсилюють проблему.

Виправлення: Перевірте redis-cli –latency-history і SLOWLOG. Зменшіть операції з великими ключами, налаштуйте персистентність і встановіть агресивні тайм-аути з circuit breaker-ами.

4) Симптом: випадкові застарілі дані, важко відтворити

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

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

5) Симптом: MySQL у порядку, але CPU Redis високий

Корінна причина: забагато операцій на запит, болтлива модель доступу або важкі Lua-скрипти.

Виправлення: Батчуйте читання (MGET/пайпелайн), кешуйте багатші об’єкти, щоб зменшити кількість викликів, і тримайте Lua-скрипти маленькими та ретельно протестованими.

6) Симптом: після рестарту Redis аплікація розвалюється і MySQL слідує

Корінна причина: холодний кеш і штурм відновлення, відсутність обмеження конкуруючих відновлень і відсутність circuit breaker-ів.

Виправлення: Використовуйте soft TTL, single-flight блокування на ключ, фонове прогрівання критичних ключів і обмежуйте паралельність відновлення.

7) Симптом: звинувачення у втраті даних після фейловеру Redis

Корінна причина: сприйняття Redis як авторитетного сховища для бізнес-критичного стану з асинхронною реплікацією.

Виправлення: Тримайте істину в MySQL (або іншому довговічному сховищі), кешуйте похідні подання в Redis і документуйте вікно втрат, яке ви готові терпіти для ключів координації.

8) Симптом: кластер Redis стабільний, але клієнти бачать тайм-аути

Корінна причина: виснаження пулу з’єднань, зміни DNS/ендпоінтів, тиск NAT-портів або неправильно налаштовані тайм-аути клієнта.

Виправлення: Інструментуйте клієнтські пули, тримайте тайм-аути Redis жорсткими, але реалістичними, повторно використовуйте з’єднання і уникайте шаблону «підключення/відключення на запит».

Контрольні списки / покроковий план

Покроково: впровадження Redis для зниження навантаження MySQL

  1. Виберіть ціль: один ендпоінт або один digest запиту, що домінує за загальним часом MySQL, а не «всю базу».
  2. Визначте кешований об’єкт: що сервіс справді потребує (наприклад, «user_context:v3:{user_id}»).
  3. Вирішіть модель консистентності: лише TTL, інвалідизація при записі або версіоновані ключі.
  4. Встановіть TTL з джиттером: почніть консервативно (хвилини), додайте ±10–20% джиттер, щоб уникнути синхронного спливу.
  5. Реалізуйте cache-aside: GET, при промаху SELECT, SETEX, повернути.
  6. Додайте захист від штурму: single-flight на ключ або згортання запитів у процесі.
  7. Інструментуйте: хітрейт кешу, зниження QPS MySQL, P50/P95/P99 затримки, рівень помилок, кількість витіснень.
  8. Поведінка при відмові: визначте, що відбувається коли Redis недоступний. Швидкий відкат з fallback для критичних читань; circuit breaker для запобігання штормам повторів.
  9. План ємності: оцініть кількість ключів, середній розмір значення, churn TTL і наклад пам’яті. Встановіть maxmemory явно.
  10. Розгортання поступово: feature flag, включення за відсотком і простий відкат.

Контрольний список: чи безпечно зберігати ці дані в Redis?

  • Чи можна їх відновити з MySQL або інших довговічних сховищ?
  • Чи можна терпіти застарілі читання до TTL?
  • Чи можна терпіти втрату останніх кількох секунд записів при фейловері?
  • Чи є чіткий власник інвалідизації?
  • Чи обмежена кардинальність ключів або вона може вибухнути від вводу користувача?

Контрольний список: готовність до продакшну Redis перед MySQL

  • Тайм-аути Redis жорсткі і повтори обмежені.
  • Існує circuit breaker для помилок/тайм-аутів Redis.
  • maxmemory і політика витіснення явно налаштовані.
  • SLOWLOG під моніторингом; великі ключі уникнуті; SCAN використовується замість KEYS.
  • Сценарій холодного кешу протестований під піковим навантаженням.
  • MySQL має запас потужності, щоб пережити деградацію кешу.

FAQ

1) Чи може Redis замінити MySQL?

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

2) Який найбезпечніший перший випадок використання Redis?

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

3) Як оцінити, чи можна скоротити навантаження MySQL на 80%?

Подивіться на топ digest-ів запитів за сумарним часом і кількістю. Якщо невеликий набір повторюваних читальних запитів домінує і їхні результати можна кешувати, великі скорочення реалістичні. Якщо домінують записи або читання дуже унікальні — 80% не вдасться.

4) Кешувати окремі рядки чи повні об’єкти?

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

5) Який TTL мені використовувати?

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

6) Яку політику витіснення обрати?

Для типової кеш-роботи підходить варіант LRU/LFU з врахуванням TTL. Головне — встановити maxmemory і планувати поведінку при витісненнях на боці аплікації. Політика «noeviction» може бути нормальною для координаційних даних при правильному розмірі, але вона може перетворити тиск пам’яті на простій.

7) Як уникнути перетворення Redis у single point of failure?

Запускайте Redis з реплікацією і фейловером, але ще важливіше — зробіть аплікацію стійкою: жорсткі тайм-аути, обмежені повтори, circuit breaker-и і протестований режим деградації, що не викликає штурм MySQL.

8) Чи достатня персистентність Redis (AOF/RDB) для критичних даних?

Зазвичай ні. Персистентність зменшує вікна втрат, але не дає реляційних і транзакційних гарантій, які потрібні багатьом критичним наборам даних. Якщо дані мають з’являтися в юридичному аудиті — вони в MySQL (або еквіваленті), а не в «майже довговічній» пам’яті.

9) Чому Redis підвищив навантаження MySQL під час відмови?

Тому що ваш fallback шлях ймовірно викликає штурм: кожен промах кешу перетворюється на DB-запит, а повтори множать трафік. Виправте це single-flight, обмеженням швидкості відновлення і circuit breaker-ами.

10) А як щодо використання Redis для пошуку або аналітики?

Redis може підтримувати вторинні індекси і складні структури, але операційно це не загально призначена аналітична система. Якщо потрібна гнучка фільтрація й агрегація в масштабі — тримайте це в системах, призначених для аналітики.

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

Якщо хочете, щоб Redis драматично скоротив навантаження MySQL — перестаньте думати в лозунгах і почніть думати в контрактах. MySQL — істина. Redis — швидкість. Істина без швидкості повільна; швидкість без істини — майбутній інцидентний звіт.

  1. Витягніть топ-5 digest-ів запитів MySQL за сумарним часом і оберіть одну цільову кінцеву точку.
  2. Спроєктуйте ключ кеша, що представляє об’єкт рівня сервісу, а не рядок таблиці.
  3. Реалізуйте cache-aside з явною інвалідизацією при записах і TTL з джиттером.
  4. Додайте захист від штормів відновлення і circuit breaker перед глобальним включенням.
  5. Виміряйте: хітрейт, витіснення, затримку Redis і QPS/CPU MySQL до і після.

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

← Попередня
Windows ME: як випустити операційну систему, яку люди пам’ятають як покарання
Наступна →
Гібридний план ZFS: HDD для даних + SSD для метаданих + NVMe кеш, зроблено правильно

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