1 of 27

Кэширование

Проектирование и разработка распределенных программных систем, лекция 7

2 of 27

3 of 27

Кэширование

Запоминаем результаты обработки запросов, сохраняем их в быстрое хранилище, а затем отдаем уже вычисленные результаты в ответ на повторные запросы.

4 of 27

Кэширование

Несколько запросов к БД → 1 запрос к кэшу

5 of 27

Системы кэширования

  • memcached
  • Redis

6 of 27

Системы кэширования

И memcached, и Redis представляют собой хэш-таблицу, хранящую данные в оперативной памяти.

7 of 27

Алгоритм работы с кэшем

def get_data(query):

key = get_cache_key(query)

data = cache.get(key)

if not data:

data = get_data_from_db(query)

cache.set(key, data, ttl=5*60)

return data

8 of 27

Ключ кэширования

Ключ, по которому данные размещаются в хэш-таблице.

Ключ зависит от параметров запроса и должен вычисляться:

  • Одинаково, когда параметры одинаковые
  • По-разному, когда параметры разные

9 of 27

10 of 27

Ключ кэширования

region-74-type-hairdryer-power-1000-speed-1

11 of 27

Проблемы

  1. Размер кэша
  2. Оверхед
  3. Устаревание данных в кэше

12 of 27

Размер кэша

Разных вариантов фильтров может быть много, а размер кэша ограничен доступной оперативной памятью.

13 of 27

Что делать, когда кончается память

memcached: LRU eviction

Redis:

  • Сбрасывать редко используемые ключи на диск
  • Несколько стратегий для eviction

14 of 27

Стратегии eviction в Redis

  • volatile-lru: remove the key with an expire set using an LRU algorithm
  • allkeys-lru: remove any key accordingly to the LRU algorithm
  • volatile-random: remove a random key with an expire set
  • allkeys->random: remove a random key, any key
  • volatile-ttl: remove the key with the nearest expire time (minor TTL)
  • noeviction: don't expire at all, just return an error on write operations

15 of 27

Hit и Miss

Cache hit — из кэша удалось прочитать данные по ключу

Cache miss — данных по ключу в кэше не нашлось

Hit ratio — доля cache hit среди всех запросов к кэшу

16 of 27

Эффективность кэша

Пусть запрос в кэш занимает 20 мс, в базу — 100 мс.

Запрос с попаданием в кэш: 20 мс, с промахом: 120 мс.

Если количество промахов составляет:

  • 10% — кэш ускоряет в 3,3 раза
  • 50% — кэш ускоряет в 1,4 раза
  • 80% — кэш не приносит пользы
  • 90% — кэш замедляет работу на 10%

17 of 27

Устаревание данных в кэше

Данные в БД обновились. Как обновить данные в кэше?

  • Инвалидация по времени
  • Ручная инвалидация

18 of 27

Инвалидация по времени

  • При записи значения в кэш устанавливаем TTL
  • Кэш сам удаляет ключ, когда истекает TTL
  • Если данные обновляются раньше истечения TTL, продолжаем брать значение из кэша и показывать пользователям старые данные

19 of 27

Ручная инвалидация

  • При изменении данных определяем ключи, которые были затронуты изменением
  • Проходим по ним и удаляем их

Сложно

20 of 27

Инвалидация с помощью тегов

  • Заводим теги, для каждого тега храним его версию (например, время последнего изменения)
  • Добавляем версию тега в ключ кэша (например: type-hairdryer-20201110183000)
  • При изменении информации по тегу обновляем его версию
  • Ключи со старой версией и устаревшими данными перестают использоваться, появляются новые ключи с актуальными данными

21 of 27

Dogpile-эффект

В сервисе с большим трафиком после инвалидации ключа могут прийти сразу много запросов за данными по этому ключу.

Все эти запросы будут обслуживаться через БД, резко вырастет нагрузка.

В самом плохом случае запросы к БД будут таймаутить, и данные никогда не попадут в кэш.

22 of 27

Алгоритм работы с кэшем

def get_data(query):

key = get_cache_key(query)

data = cache.get(key)

if not data:

data = get_data_from_db(query)

cache.set(key, data, ttl=5*60)

return data

23 of 27

Решение проблемы dogpile

  • Не удалять ключи с истекшим TTL, а возвращать их с пометкой, что они устарели.
  • При получении устаревшего ответа отдавать его пользователю, но запускать фоновое обновление данных в кэше (с помощью очереди задач)

24 of 27

Прогрев кэша

При старте приложения может быть нужен прогрев кэша, чтобы он наполнился раньше, чем придут соответствующие запросы от пользователей.

25 of 27

Заключение

Кэш дает большой выигрыш в производительности, но очень легко сделать что-то не так и получить внезапную деградацию производительности или отображение устаревших данных (либо потратить много сил разработчиков на то, чтобы этого не произошло).

Поэтому применять кэширование нужно точечно и только там, где оно действительно нужно.

26 of 27

Ссылки

27 of 27

Ссылки

  1. Web, кэширование и memcached http://highload.guide/blog/web-caching-memcached.html
  2. Кэширование данных в web приложениях. Использование memcached http://highload.guide/blog/caching-data-in-web-applications.html
  3. Использование memcached и Redis в высоконагруженных проектах http://highload.guide/blog/using-memcached-and-redis.html