Такі збої часто впізнають ще до розмови про новий сервер. Трафік звичайний. Redis доступний. У гарячого ключа завершується TTL, або викладка навмисно прибирає групу ключів. На короткий час багато запитів проходять повз кеш. Кожен такий промах запускає дорогу дію: важкий SQL-запит, побудову звіту, рендер із кількома зовнішніми викликами. Черга в базі росте, застосунок гріє процесор, а команда робить висновок, що бракує потужності.
Потім той самий сюжет повторюється вже на більшій машині, бо корінь проблеми був не в розмірі інстансу. Це навала на кеш, або thundering herd. Один промах майже безпечний, якщо нове значення обчислює один запит, а решта або чекає, або ще трохи отримує попередню версію. Коли ж одночасно приходить п’ятдесят промахів, ви оплачуєте п’ятдесят однакових перерахунків. Додаткова пам’ять у Redis не зливає їх докупи. Додатковий CPU лише дає кожному трохи більше місця, але не прибирає дублювання.
Це не та історія, де один процес повільно росте весь день, а потім сервер його зупиняє. Такий сценарій інший і розібраний окремо в матеріалі що перевірити, перш ніж купувати пам’ять для процесу, який росте. Навала на кеш може виглядати як хвилинна аварія пам’яті або CPU, а потім зникати до наступного завершення TTL. Якщо дивитися лише на піковий графік, легко купити сервер під таймер, а не під реальне навантаження.
1. Назвіть ключ, час життя і що саме запускає промах
Починайте не з абстрактного слова «кеш», а з конкретного шаблону ключа. На сторінку, на акаунт, на звіт, на користувача. Запишіть TTL у секундах і вкажіть, хто його виставляє. Якщо багато ключів закінчуються в ту саму хвилину, ви самі створили навалу за розкладом. Якщо викладка стирає їх разом, ефект той самий. Ключ без TTL означає іншу проблему: Redis росте, бо значення не зникають. Це вже не навала, а утримання даних без межі.
Далі відкрийте код, який спрацьовує на промаху, і назвіть одним реченням, що саме він робить. Який SQL-запит виконує. Який зовнішній сервіс викликає. Яку важку побудову запускає. Якщо цього короткого опису немає, збільшувати сервер зарано, бо ви ще не знаєте, яку саме роботу машина повторить десятки разів. Якщо всередині є повільний SQL, спершу читайте його як запит. Для цього є окремий матеріал: розбір повільного журналу до рефакторингу.
2. Дивіться на одне закінчення, а не на середній графік
Середні значення тут майже нічого не пояснюють. Більшість хвилин проходить на влучаннях у кеш, тому p50 може виглядати здоровим. Справжній стрибок живе в короткому вікні, коли ключ щойно зник. Візьміть один гарячий ключ на копії з реальним обсягом даних. Дайте йому завершитися або видаліть його один раз. Зафіксуйте три числа: скільки запитів промахнулося, скільки перерахунків реально стартувало і скільки тривав один перерахунок.
Якщо в одному такому вікні був лише один перерахунок, навали ще немає. Тоді проблема в повільному оновленні, і далі треба дивитися на запит або зовнішній виклик усередині. Якщо ж в одному вікні стартує багато однакових перерахунків того самого ключа, причина вже названа. Купувати сервер до цього виміру означає платити за дубльовану роботу. Порожня база тут обманює: на ній перерахунок здається дешевим, тому й сама проблема виглядає меншою. На бойовому сайті вчитися теж погана ідея, бо в цей момент користувачі чекають разом із вами.
Окремо варто записати, чи зникло старе значення до того, як нове встигли покласти назад. Саме цей розрив створює небезпечне вікно. Якщо система може ще трохи віддавати попередню версію, поки нова обчислюється, кількість справжніх промахів різко падає. Багато стеків трактують кожне завершення TTL як жорсткий промах, але це не закон Redis, а вибір реалізації.
3. Нехай перерахунок виграє один, а замок живе недовго
Найкорисніший ремонт тут простий: нове значення має рахувати один виконавець. Перший запит, який побачив промах, бере замок і запускає перерахунок. Інші не повинні одночасно виконувати той самий важкий шлях. Вони можуть коротко почекати, отримати попереднє значення або повернути чесне повідомлення на кшталт «спробуйте ще раз», якщо для цієї сторінки очікування гірше. Коли нове значення готове, його записують у ключ і відпускають замок. Другий перерахунок того самого ключа в цьому ж вікні — це вже помилка, а не захист.
У замка має бути власний TTL. Якщо воркер впаде, тримаючи замок без строку, навала перетвориться на простій: ніхто не перерахує значення, і ніхто інший не зможе спробувати. Тривалість замка має бути більшою за нормальний час перерахунку і меншою за той проміжок, протягом якого ви готові віддавати застарілі дані або повертати помилку. Якщо одна й та сама задача приїхала двічі, результат усе одно має записатися один раз. Це та сама ідемпотентність, яка потрібна і в чергах. Якщо важка робота вже не поміщається в HTTP-запит, її можна винести в чергу, як описано в матеріалі коли роботу варто винести в чергу. Але сама черга не лікує навалу: п’ятдесят задач на один ключ — це все ще той самий натовп.
Поруч із замком майже завжди потрібен розкид TTL. Якщо сто ключів мають однаковий строк, вони завершаться разом навіть за ідеального блокування. Додайте випадковий діапазон до часу життя, щоб завершення розійшлися в часі. Друга корисна техніка — раннє оновлення, коли нове значення готується ще до зникнення старого. Читачі продовжують отримувати попередню версію, а запис виконує один процес. Для цього не потрібен новий продукт кешування. Потрібні ключ, замок і чітке рішення, чи дозволяєте ви трохи застарілі дані.
4. Коли більший сервер справді доречний
Пам’ять або процесор варто купувати після того, як один ключ уже перераховується лише один раз, а залишкова вартість походить від реального трафіку. Якщо один перерахунок дешевий, але база все одно впирається в межу, бо одночасно холодними стають багато різних ключів, це вже може бути чесне навантаження. Міряйте його як кількість різних ключів, а не як повтори одного й того самого. Якщо Redis викидає гарячі ключі через справжній ліміт пам’яті, а в цих ключів є TTL і зрозуміле призначення, додаткова пам’ять може бути правильною покупкою.
Але навіть тоді спершу запишіть причину витіснення і шаблон ключа. Фраза «Redis забитий» без шаблону часто закінчується тим, що безмежний набір ключів просто переїжджає на більшу машину. Не купуйте сервер, щоб пережити масове стирання ключів. Викладка, яка зносить гарячий набір, створить навалу на будь-якому розмірі сервера, доки ви не приберете це стирання або не зведете перерахунок до одного виконавця. І не купуйте сервер замість читання запиту, який запускається на промаху. П’ятдесят швидких запитів — це все одно п’ятдесят запитів, а один повільний, помножений на п’ятдесят, лише голосніше повторює ту саму проблему.
Коли варто читати шлях промаху разом
Якщо ви вже працюєте з Redis, назвати ключ і один раз видалити його на копії можна хоч цього тижня. Допомога потрібна тоді, коли кожен промах досі запускає запит, який ніхто не міряв; коли стирання кешу під час викладки вважають нормальною практикою; або коли замок і черга не узгоджені між собою, і повтор знову обчислює те саме значення. Завдання тут просте: зробити одне завершення ключа дешевим. Не починати з нового кеш-продукту. Не починати з більшого сервера.
Послуги покривають саме такий розбір: ключ, промах і запит або задача за ним. Обрані роботи показують час поруч із конкретною операцією, а не як гасло про масштаб. Якщо пишете, надішліть шаблон ключа, TTL і результат одного примусового завершення на копії. Паролі від бойового доступу в перше повідомлення не додавайте.