Кэширование
Кеширование — механизм, с помощью которого можно повысить скорость работы приложения за счёт переноса часто используемых данных в очень быстрое хранилище.
Кэширование очень активно используют во множестве систем. Например:
- Внутренний кэш баз данных.
- DNS кэш внутри нашего компьютера или браузера.
- Кэш статического контента в браузере.
- CDN также является своего рода кэшем.
В любом приложении есть операции, которые долго выполнялись, но результат которых можно сохранить на какое-то время. Это позволит меньше выполнять таких операций и отдавать заранее сохранённые данные.
Что можно кэшировать?
- Запросы в базу данных.
- Пользовательские сессии.
- Медленные операции внутри приложения (расчеты, какие-то итоговые данные и т. д.)
- Запросы к внешним системам.
Алгоритм работы
Приложение в первую очередь идёт за данными в кэш, если они там есть, то запроса в базу данных не происходит, что позволяет сэкономить время и ресурсы, такой случай называется попаданием кэша. Если данных нету, то приложение читает данные из базы данных после чего кладёт их в кэш для дальнейшего использования, такая ситуация называется промахом кэша. На основании этих двух показателей можно вычислить эффективность системы кэширования.
Стратегии кэширования
Cache Aside
Самый простой способ кэширования, зачастую множество фреймворков уже имеют встроенную реализацию.
В такой системе данные лениво загружаются в кэш. Пользователь делает запрос к нашей системе, после этого приложение сначала идет в кэш, если данные в нем есть, то возвращает их клиенту иначе идет в базу данных, обновляет кэш и отдает пользователю.
Плюсы:
- Отлично подходит для тяжелых операций чтения.
- Обычно система устойчива к отказу кэша. Если он упадет всегда можно пойти напрямую в базу данных.
- Возможно использовать разные структуры данных для кэша и базы данных. Особенно полезно когда нам нужно закешировать результат какой-то сложной выборки
Минусы:
- Из-за того что запись идет напрямую в базу данных, данные в кэше могут стать не консистентными. Для этого нужно использовать TTL (время жизни данных в кэше) или инвалидировать кэш, когда нужно гарантированно отдавать актуальные данные.
Read Through
Очень похожий на ленивый кэш, но в таком случае данные всегда остаются консистентными. Это достигается за счет того что приложение читает данные только из кэша, а уже он сам идет в базу данных в случае промаха.
Плюсы:
- Отлично работает с тяжелыми операциями чтения.
- Данные всегда в консистентном виде.
Минусы:
- Модель данных в кэше и в базе данных не могут отличатся.
- Первый запрос в кэш всегда будет приводить к промаху и последующему запросу в БД. Эту проблему можно — решить с помощью “прогревания” кэша.
Write Through
Эта стратегия подразумевает приложение сначала запишет данные в кэш, после чего сохранит их в базу данных.
Лучше всего использовать эту стратегию вместе с read-through подходом. В таком случае мы получаем плюсы двух стратегий.
Плюсы:
- Гарантирует полною консистентность данных.
- Отлично работает с тяжелыми операциями записи.
Минусы:
- Добавляет дополнительную задержку для операций записи.
Write Back
Самый сложный способ кэширования, в нем все операции записи происходят только в кэше, откуда они с некоторой задержкой попадают в основное хранилище.
Плюсы:
- Позволяет сильно оптимизировать сложные операции записи.
- Данные всегда в консистентном виде.
Минусы:
- Сложность реализации.
- Есть шанс потерять данные если произойдет сбой кэша до того как данные запишутся в основное хранилище.
Алгоритмы вытеснения
Если в кэше нет свободного места для новых данных, то в работу вступают разнообразные механизмы вытеснения (очистки) кэша.
- LRU (Least recently used) — алгоритм, который находит и вытесняет данные, которые не использовались дольше всех.
- MRU (Most Recently Used) — вытесняются последние используемые данные.
- LFU (Least Frequently Used) — вытесняются редко запрашиваемые данные.
Выбор правильного алгоритма зависит от того как данные используются в системе. LRU подходит в тех случая когда данные гарантированно будут повторно используемые. MRU подходит для данных, которые запрашивались совсем недавно, но данные не будут повторно использованы в ближайшее время.
Проблемы с кэшированием
В процессе создания и работы кэширующей системы может возникнуть набор разные проблем и вопросы, такие как:
- Деление данных между кэширующими серверами.
- Параллельные запросы на обновление данных.
- “Холодный” старт приложения.
Деление данных между кэширующими серверами
Для повышения надежности и производительности системы кеширования приходится разделять ее на несколько серверов. Поэтому возникает вопрос как делить данные между серверами.
Вариант 1. Использовать хеширование ключей. В таком случае данные разделяются по серверам в зависимости от ключа кэширования. Для этого используется хеш функция, которая на вход принимает ключ и возвращает номер сервера.
Проблема такого подхода заключается в том, что в случае падения сервера мы теряем почти 80% данных из-за изменения месторасположения ключей.
Вариант 2. Логическое разделение данных на основе какого-то признака. Например мы можем разделять данные на основании пользователя. Так данные одного пользователя будут лежать рядом на одном сервере.
Примеры проблем и их решения отлично описаны в статье “Проблемы при работе с кэшем и способы их решения”
Параллельные запросы на обновление данных
Время от времени данные в кеше протухают и удаляются. В таком случае их необходимо обновить. Обычно это происходит, когда запрос обращается в кэш, видит, что данных нет, идет за ними в БД после чего обновляет кэш. Может возникнуть ситуация, когда таких запросов нескольколько, тогда все они пойдут в базу данных и нагрузят систему.
Решение 1. Блокировка перед обновлением кеша. Первый запрос получает уникальную блокировку на загрузку данных в кэш, все остальные ожидают блокировку. Это решает проблему с обновлением, но порождает новые: как обрабатывать таймауты, как правильно подобрать время блокировки и т. д.
Решение 2. Фоновое обновление кэша. Приложение всегда читает данные из кеша и никогда не ходит в базу данных. Данные в кеше обновляет специальный скрипт или программа, которая работает в фоне. В таком случае никогда не наступит параллельного обновления кеша. Но возникает проблема с временем реакции на изменение данных в БД.
Решение 3. Вероятностное обновление данных. Обновляем не только данные, которых нет в кэше, но и с какой-то вероятностью те что уже есть. Это позволит обновить данные, до того как они протухнут и будут удалены.
Решение 4. Дублирование ключей. В системе есть два ключа, где второй имеет время жизни немного больше чем первый. В таком случае после того как исчезнет первый ключ мы обновляем его данные, но остальные запросы на чтение могут взять данные из второго ключа.
«Холодный» старт приложения
В момент запуска приложения данных в кэше может не быть. Как только пойдут запросы от пользователей все они пойдут массово в базу данных и начнуть одновременно обновлять кэш. Такая ситуация может привести к резкому падению производительности всей системы.
Решение. Можно прибегнуть к технике «прогревания» кеша. В таком случае при старте приложения мы также наполняем кэш данными, которые активно используются.
Итого
Кэширование это очень мощный и простой в использовании механизм, который позволяет значительно увеличить производительность системы. Если вы планируете использовать кэш, то стоит внимательно посмотреть на то какие данные вы хотите кэшировать и как они используются. От этого зависит эффективность решения и какую стратегию выбрать.
Неплохая статейка, спс
Всегда рад :)