Пошук уроків, статей та іншого контенту
Розберете, як кеш зменшує затримку й навантаження, а також які компроміси виникають через застарілі дані.
Кеш — це швидке тимчасове сховище для даних, які можуть знадобитися повторно.
Замість того щоб щоразу отримувати дані з повільнішого джерела, система:
спочатку перевіряє кеш;
якщо дані є, повертає їх із кешу;
якщо даних немає, звертається до основного джерела;
зберігає отриманий результат у кеші для наступних запитів.
Основне джерело може бути:
базою даних;
зовнішнім API;
файловою системою;
складним обчисленням.
Кеш зазвичай розташований ближче до програми або користувача, тому доступ до нього швидший.
Затримка — це час відправлення запиту до отримання відповіді.
Наприклад, отримання даних із бази може займати 100 мс. Отримання тих самих даних із кешу в пам’яті може займати менше 1 мс.
Без кешу:
Запит → застосунок → база даних → застосунок → відповідьІз кешем, якщо дані вже збережені:
Запит → застосунок → кеш → відповідьЧим менша кількість повільних операцій, тим швидше користувач отримує відповідь.
Кеш особливо корисний для даних, які:
часто читають;
рідко змінюють;
однакові для багатьох користувачів;
потребують дорогого обчислення або запиту.
Наприклад, список категорій магазину можна зберігати в кеші, тому що він читається часто, але змінюється нечасто.
Без кешу кожен запит може звертатися до бази даних:
1000 запитів → 1000 запитів до бази данихЯкщо після першого запиту результат збережено в кеші:
1000 запитів → 1 запит до бази даних + 999 відповідей із кешуЦе зменшує навантаження на:
базу даних;
зовнішні сервіси;
мережу;
процесор, якщо дані потребують складного обчислення.
Завдяки цьому система може обробляти більше запитів, не збільшуючи ресурси основного джерела.
Один із найпростіших підходів називається cache-aside.
У цьому підході застосунок сам керує читанням і записом у кеш:
перевіряє кеш;
якщо значення знайдено, використовує його;
якщо значення не знайдено, читає дані з основного джерела;
записує отримані дані в кеш;
повертає результат.
Запит
│
├─ Дані є в кеші? ─ Так ─→ Повернути дані
│
└─ Ні
│
├─ Отримати дані з бази
├─ Зберегти дані в кеш
└─ Повернути даніНижче наведено runnable-приклад для Node.js. Він імітує повільне отримання даних із бази та зберігає результат у кеші на 3 секунди.
const cache = new Map();
const CACHE_TTL = 3000;
function wait(milliseconds) {
return new Promise((resolve) => {
setTimeout(resolve, milliseconds);
});
}
async function getProductFromDatabase(id) {
console.log("Запит до бази даних...");
await wait(500);
return {
id,
name: "Навушники",
price: 2500
};
}
async function getProduct(id) {
const cached = cache.get(id);
const now = Date.now();
if (cached && cached.expiresAt > now) {
console.log("Відповідь із кешу");
return cached.value;
}
const product = await getProductFromDatabase(id);
cache.set(id, {
value: product,
expiresAt: now + CACHE_TTL
});
return product;
}
async function main() {
console.time("Перший запит");
console.log(await getProduct(1));
console.timeEnd("Перший запит");
console.time("Другий запит");
console.log(await getProduct(1));
console.timeEnd("Другий запит");
await wait(3500);
console.time("Запит після завершення TTL");
console.log(await getProduct(1));
console.timeEnd("Запит після завершення TTL");
}
main();У цьому прикладі:
перший запит не знаходить дані в кеші й звертається до бази;
другий запит отримує дані з кешу;
після завершення TTL кешоване значення вважається застарілим;
наступний запит знову звертається до бази.
TTL — це Time To Live, тобто час життя запису в кеші.
Кеш може містити значення, яке вже не відповідає поточному стану основного джерела.
Наприклад:
товар коштує 1000 гривень;
застосунок зберігає цю ціну в кеші;
ціну в базі змінюють на 900 гривень;
кеш ще повертає стару ціну 1000 гривень.
Такі дані називають застарілими.
Це головний компроміс кешування:
Чим довше зберігати значення в кеші, тим менше навантаження, але тим вища ймовірність отримати застарілі дані.
Найпростіший підхід — встановити TTL.
Після завершення TTL запис видаляють або вважають недійсним. Наступний запит отримає свіжі дані з основного джерела.
Короткий TTL:
дані швидше оновлюються;
кеш рідше використовується;
навантаження на основне джерело вище.
Довгий TTL:
кеш ефективніший;
навантаження нижче;
дані можуть довше залишатися застарілими.
Коли застосунок змінює дані в основному джерелі, він може одразу видалити відповідний запис із кешу.
Наприклад:
Оновити товар у базі
↓
Видалити товар із кешуНаступний запит не знайде запис у кеші, отримає актуальне значення з бази й збереже його знову.
Цей підхід зменшує час, протягом якого кеш містить старе значення. Проте потрібно не забути оновити або видалити всі пов’язані записи.
Кеш не завжди потрібен.
Він може створити більше складності, якщо:
дані майже ніколи не читають повторно;
дані змінюються майже після кожного читання;
застаріле значення неприпустиме;
обсяг даних занадто великий для доступної пам’яті;
витрати на підтримку кешу перевищують вигоду.
Наприклад, для балансу банківського рахунку важливо отримувати актуальне значення. Простий кеш із довгим TTL може повернути неправильний баланс.
Кеш краще застосовувати після того, як зрозуміло:
які операції є повільними;
які дані часто читають;
як довго дані можуть бути неактуальними;
скільки пам’яті доступно для кешу.
Швидкість кешу часто досягається тим, що дані зберігаються в оперативній пам’яті. Але пам’ять обмежена.
Якщо кеш заповнюється, система має вирішити, які записи видаляти. Поширений підхід — LRU, тобто видалення запису, яким користувалися найдовше тому.
Незалежно від конкретного алгоритму, кеш зазвичай вважається тимчасовим сховищем. Тому застосунок не повинен покладатися на кеш як на єдине місце зберігання важливих даних.
Основні дані мають залишатися в надійному постійному сховищі, наприклад у базі даних.
Під час роботи кешу часто використовують два терміни:
cache hit — потрібні дані знайдено в кеші;
cache miss — потрібних даних у кеші немає.
Висока частка cache hit означає, що кеш добре обслуговує запити.
Наприклад, якщо зі 100 запитів:
80 отримали відповідь із кешу;
20 звернулися до бази;
то частка влучань у кеш становить 80%.
Висока частка влучань зазвичай означає менше навантаження на основне джерело, але сама по собі не гарантує коректність даних. Потрібно також контролювати застарілість і правильне оновлення кешу.
Якщо записи ніколи не видаляються й не оновлюються, у кеші можуть залишитися старі дані.
Для кожного типу даних потрібно визначити:
чи потрібен TTL;
яким він має бути;
як поводитися після його завершення.
Кеш може бути очищено через перезапуск програми, нестачу пам’яті або іншу помилку.
Кеш не повинен бути єдиним джерелом правди для важливих даних.
Ціна товару, список країн і результат короткочасного обчислення можуть мати різні вимоги до актуальності.
TTL потрібно вибирати відповідно до конкретних даних, а не встановлювати однакове значення всюди.
Якщо дані змінили в базі, але старий запис залишили в кеші, користувачі можуть бачити неправильний результат.
Для даних, які змінюються часто, потрібно продумати видалення або оновлення кешу.
Кеш має приносити користь. Якщо дані майже не повторюються або читаються рідко, кеш може лише ускладнити систему та споживати пам’ять.
Кеш зберігає часто потрібні дані у швидкому тимчасовому сховищі.
Він зменшує затримку відповіді та навантаження на базу даних або інше основне джерело.
У підході cache-aside застосунок спочатку перевіряє кеш, а після промаху отримує дані з основного джерела.
Кеш може повертати застарілі дані.
TTL обмежує час життя запису в кеші.
Після зміни даних запис у кеші іноді потрібно оновити або видалити.
Кеш не замінює постійне сховище даних.
Основний компроміс кешування — швидкість і менше навантаження проти складності та можливої неактуальності даних.