Skip to content

Latest commit

 

History

History
100 lines (61 loc) · 8.8 KB

File metadata and controls

100 lines (61 loc) · 8.8 KB

Cache

Sep 14, 2020 · 4 min read

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

Хотелось бы иметь возможность минимизировать эти два фактора, особенно в приложениях с драконовскими требованиями к времени ответа.

На помощь приходит кеширование.

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

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

Давайте рассмотрим эти варианты, плюсы и минусы.

CDN #

Самое близкое куда можно положить кеш — на компьютер пользователя, т.е. на клиенте. Это можно делать в случае со статичным контентом.

На помощь приходят CDN, на которых правильно настроены заголовки кеширования. Браузеры, запросив ресурс один раз, в следующий раз возьмут его из кеша.

Отличный пример — библиотеки типа jQuery или React, которыми пользуется половина интернета. Имеет смысл подключать их с CDN Гугла или Яндекса, повышая вероятность того, что в браузере уже окажется закешированной та же версия, потому что ранее пользователь ходил на другой сайт (где библиотека подключалась так же).

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

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

Application Layer #

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

Действительно, мы идём в базу за данными по сети. Можно складывать ответы в какой-нибудь хэш-мап, чтобы в следующий раз уже не ходить по сети, верно?

key = "user.%s" % user_id
value = cache.get(key)

if value is None:
    user = mysql.query(
        "SELECT * FROM users WHERE user_id=\"%s\"",
        user_id
    )
    if user:
        cache.set(key, json.dumps(user))
    return user
else:
    return json.loads(value)

При таком подходе, однако, кеш не может масштабироваться отдельно от инстансов приложений. Тогда всё это дело уносят в отдельный слой, получается глобальный кеш. Например, Redis часто используется в качестве глобального кеша (но не только).

Database Layer #

Иногда бывает удобнее настроить кеш на уровне базы данных, т.к. с точки зрения других приложения «база просто быстро работает», без написания кода в приложении.

Cache Policy #

Как говорит Фаулер, в программировании есть две сложные проблемы: инвалидация кеша и именование переменных 😊

Какие есть подходы к инвалидации?

Write-through #

Данные пишутся в кеш и базу одновременно. Минус в том, что кеш заполняется сразу всеми данными, которые могут быть и не нужны клиентам (не находиться на высокочастотных запросах), а память не бесконечная.

Read-through #

Пишем только в базу, а кеш заполняем что называется on-demand. Минус в том, что данные не окажутся в кеше пока их не явно не попросят — увеличивается время ответа.

Write-back #

Пишем только в кеш и сразу открываем доступ на чтение. В базу пишем в фоне или по расписанию. Минус в том, что легко получить рассинхронизацию или потерять какие-то данные вовсе.

Что выбрать? Зависит от конкретного случая, что важнее. Играем доступной памятью и временем ответа.

Какие есть стратегии по удалению ненужных (временно) записей? Нельзя же держать бесконечный кеш. Условное правило такое — база бесконечная, а в кеше хранится небольшое подмножество, только самое нужное и часто запрашиваемое.

  • FIFO — простая очередь. Снимаем с начала очереди, т.е. элемент, который попал туда раньше всех.
  • LIFO — просто стек. Снимаем последний элемент сверху.
  • LRU — очередь с приоритетом. Учитываем количество обращений к элементу, удаляем наиболее редко запрашиваемые.
  • MRU — по аналогии с LIFO vs FIFO, удаляем наиболее часто запрашиваемые.
  • рандомно убирать элементы из кеша когда заканчивается место

Что выбирать, соответственно, сильно зависит от профиля нагрузки, учитывая, что некоторые политики противорят друг другу.

Материалы #

PS. Обсудить можно в телеграм-чате любознательных программистов. Welcome! 🤗

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