- Что такое кэширование?
- Почему кэширование важно
- Вызовы
- Проблема когерентности
- Выбор данных для кэширования
- Работа с промахами кэша
- Типы кэширования
- Кэширование в памяти
- Кэширование баз данных
- Веб-кэширование
- Кэширование веб-клиентов
- Кэширование веб-сервера
- Преимущества кэша
- Недостатки кэша
- устаревшие данные
- Что такое аннулирование кэша?
- Стратегии кэширования
- Запись через кэш
- Кэш в стороне (ленивая загрузка)
- Связанные ссылки
Что такое кэширование?
Кэширование данных — это процесс, который сохраняет несколько копий данных или файлов во временном хранилище — или кэше — для более быстрого доступа к ним. Он сохраняет данные для программных приложений, серверов и веб-браузеров, что позволяет системе не выполнять этот процесс при каждом обращении к веб-сайту или приложению для ускорения загрузки сайта.
Почему кэширование важно
Кэширование чрезвычайно важно, потому что оно позволяет разработчикам добиться повышения производительности, иногда значительного. Как упоминалось ранее, это жизненно важно.
В частности, ни пользователи, ни разработчики не хотят, чтобы приложениям требовалось много времени на обработку запросов. Как разработчики, мы хотели бы развернуть наиболее производительную версию наших приложений. А как пользователи, мы готовы ждать всего несколько секунд, а иногда даже миллисекунд. Правда в том, что никому не нравится тратить время на просмотр загружающихся сообщений.
Кроме того, важность обеспечения высокой производительности настолько велика, что кэширование быстро стало неизбежным понятием в компьютерных технологиях. Это означает, что все больше и больше сервисов используют его, что делает его практически вездесущим. Как следствие, если мы хотим конкурировать с множеством приложений на рынке, мы обязаны правильно реализовать системы кэширования.
Вызовы
Кэширование отнюдь не является простой практикой, и здесь неизбежно возникают проблемы. Давайте рассмотрим самые коварные из них.
Проблема когерентности
Поскольку при кэшировании данных создается копия, теперь существует две копии одних и тех же данных. Это означает, что со временем они могут расходиться. В нескольких словах, это проблема когерентности, которая представляет собой наиболее важный и сложный вопрос, связанный с кэшированием. Не существует конкретного решения, которое было бы предпочтительнее другого, и наилучший подход зависит от требований. Определение лучшего механизма обновления или аннулирования кэша — одна из самых больших проблем, связанных с кэшированием, и, возможно, одна из самых трудных проблем в информатике.
Выбор данных для кэширования
Практически любой тип данных может быть помещен в кэш. Это означает, что выбор того, что должно находиться в нашем кэше, а что исключить, открыт для бесконечных возможностей. Таким образом, это может стать очень сложным решением. При решении этой проблемы необходимо учитывать некоторые аспекты. Во-первых, если мы ожидаем, что данные будут часто меняться, мы не должны хотеть кэшировать их слишком долго. В противном случае мы можем предложить пользователям неточные данные. С другой стороны, это также зависит от того, сколько времени мы можем терпеть несвежие данные. Во-вторых, наш кэш должен быть всегда готов к хранению часто требуемых данных, на создание или извлечение которых уходит много времени.
Работа с промахами кэша
Пропуски кэша представляют собой временные издержки, связанные с наличием кэша. Фактически, пропуски кэша вносят задержки, которых не было бы в системе, не использующей кэширование. Поэтому, чтобы получить выгоду от увеличения скорости, обусловленного наличием кэша, промахи кэша должны быть относительно низкими. В частности, они должны быть низкими по сравнению с попаданиями в кэш. Достичь такого результата нелегко, и если этого не сделать, то система кэширования может превратиться не более чем в накладные расходы.
Типы кэширования
Хотя кэширование является общей концепцией, есть несколько типов, которые выделяются на фоне остальных. Они представляют собой ключевые понятия для всех разработчиков, заинтересованных в понимании наиболее распространенных подходов к кэшированию, и их нельзя не упомянуть. Давайте рассмотрим их все.
Кэширование в памяти
При таком подходе кэшируемые данные хранятся непосредственно в оперативной памяти, которая, как предполагается, быстрее, чем обычная система хранения, где находятся исходные данные. Наиболее распространенная реализация этого типа кэширования основана на базах данных «ключ-значение». Их можно представить как наборы пар ключ-значение. Ключ представлен уникальным значением, а значение — кэшированными данными.
Кэширование баз данных
Каждая база данных обычно поставляется с некоторым уровнем кэширования. В частности, внутренний кэш обычно используется для того, чтобы избежать чрезмерных запросов к базе данных. Кэшируя результат последних выполненных запросов, база данных может сразу же предоставить ранее кэшированные данные. Таким образом, в течение периода времени, когда нужные кэшированные данные действительны, база данных может избежать выполнения запросов. Хотя каждая база данных может реализовать это по-разному, наиболее популярный подход основан на использовании хэш-таблицы, хранящей пары ключ-значение. Как и раньше, ключ используется для поиска значения. Обратите внимание, что такой тип кэширования обычно предоставляется по умолчанию технологиями ORM (Object Relational Mapping).
Веб-кэширование
Его можно разделить на две дополнительные подкатегории:
Кэширование веб-клиентов
Этот тип кэша знаком большинству пользователей Интернета, и он хранится на клиентах. Поскольку он обычно является частью браузеров, его также называют Web Browser Caching. Он работает очень интуитивно понятным образом. Когда браузер в первый раз загружает веб-страницу, он сохраняет ресурсы страницы, такие как текст, изображения, таблицы стилей, скрипты и медиафайлы. При следующей загрузке той же страницы браузер может найти в кэше ресурсы, которые были ранее закэшированы, и получить их с компьютера пользователя. Как правило, это намного быстрее, чем скачивать их из сети.
Кэширование веб-сервера
Это механизм, направленный на хранение ресурсов на стороне сервера для повторного использования. В частности, такой подход полезен при работе с динамически создаваемым контентом, на создание которого требуется время. И наоборот, в случае со статическим контентом он не полезен. Кэширование веб-сервера позволяет избежать перегрузки серверов, сократить объем работы, которую необходимо выполнить, и повысить скорость доставки страниц.
Преимущества кэша
- скорость
- меньше используемых ресурсов
- повторное использование
- быть умным
Недостатки кэша
- неактуальные данные
- накладные расходы
- сложность
устаревшие данные
Это означает, что при использовании кэшированного контента/данных вы рискуете представить старые данные, которые уже не соответствуют новой ситуации. Если вы кэшировали запрос продуктов, но за это время менеджер по продуктам удалил четыре продукта, пользователи получат листинги продуктов, которых не существует. Выяснить, как с этим бороться, очень сложно, но в основном это касается создания хэшей/идентификаторов для кэшей, которые что-то значат для состояния данных в кэше, или бизнес-логики, которая сбрасывает кэш (или обновляет, или добавляет) с новыми битами данных. Это сложная область, и она очень сильно зависит от ваших требований. Затем накладные расходы — вся бизнес-логика, которую вы используете, чтобы убедиться, что ваши данные находятся где-то между быстрыми и несвежими, что ведет к сложности, а сложность ведет к большему количеству кода, который вам нужно поддерживать и понимать. Вы легко потеряете контроль над тем, где данные находятся в комплексе кэширования, на каком уровне, и как исправить несвежие данные, если вы их получили. Это может легко выйти из-под контроля, и вместо кэширования сложной логики вы вернетесь к простым временным меткам и просто скажете, что запрос кэшируется на минуту или около того, и будете надеяться на лучшее (что, по общему признанию, может быть довольно эффективным и не слишком безумным). Можно задать время жизни кэша (скажем, он будет жить X минут в кэше), доступ (он будет жить 10 запросов), таймер (он будет жить до 10 вечера) и другие варианты. Чем больше вариаций, тем больше сложности, конечно.
Что такое аннулирование кэша?
По определению, кэш не является источником истины ваших данных (например, база данных). Аннулирование кэша описывает процесс активного аннулирования устаревших записей в кэше, когда данные в источнике истины изменяются. Если аннулирование кэша выполняется неправильно, оно может на неопределенное время оставить в кэше противоречивые значения, которые отличаются от тех, что находятся в источнике истины. Аннулирование кэша подразумевает действие, которое должно быть выполнено чем-то другим, кроме самого кэша. Что-то (например, клиент или система pub/sub) должно сообщить кэшу, что произошла мутация. Кэш, который зависит только от времени жизни (TTL) для поддержания свежести, не содержит аннулирования кэша и, таким образом, выходит за рамки данного обсуждения. В остальной части этой заметки мы будем исходить из предположения о наличии аннулирования кэша.
Стратегии кэширования
Существует две распространенные стратегии кэширования: Write-Through и Cache Aside. Я объясню, как они работают и как ключевым аспектом признания кэша недействительным является определение границ.
Запись через кэш
Стратегия кэширования Write-Through предполагает запись в кэш сразу же после изменения состояния основной базы данных.
Например, клиент делает HTTP-запрос к вашей службе App Service, которая является HTTP API. Наше приложение обращается к нашей основной базе данных и производит некоторое изменение состояния. В реляционной базе данных это может быть оператор UPDATE/INSERT/DELETE, а в базе данных документов это может быть добавление элемента или обновление элемента из коллекции.
Сразу же после этого в рамках того же процесса мы обновляем наш кэш последней версией, которая отражает только что сделанное изменение состояния. Опять же, все это делается в рамках того же процесса первоначального HTTP-запроса к нашей службе App Service.
Преимуществом этой стратегии является то, что вы всегда поддерживаете кэш в актуальном состоянии, как только вносите какие-либо изменения в вашу основную базу данных. Недостатком является то, что поскольку ваш кэш постоянно обновляется, вы кэшируете данные, которые со временем могут быть прочитаны/доступны не очень часто.
Подводным камнем этой стратегии (и других) является то, что вы должны запускать все изменения состояния через ваше приложение или службу. Это потому, что именно оно занимается обновлением вашего кэша. Вы не можете обойти приложение/сервис и вручную обновить данные непосредственно в базе данных, иначе ваш кэш будет не синхронизирован и не обновлен.
Это означает, что вы не можете позволить другому приложению или сервису вносить изменения в состояние вашей базы данных, не пройдя через ваш API. Я думаю, что большинство разработчиков привыкли использовать клиентский инструмент для ручного подключения к базе данных и внесения изменений в данные. Опять же, этого не может произойти, поскольку вы не будете обновлять кэш.
Кэш в стороне (ленивая загрузка)
Другая стратегия кэширования называется Cache Aside или Lazy Loading. Принцип ее работы заключается в том, что вы запрашиваете в кэше нужные вам данные. Если происходит промах в кэше, то есть данные отсутствуют в кэше, вы запрашиваете первичную базу данных. После получения данных из первичной базы данных вы записываете их в кэш. Часто вместе со значением кэша указывается срок действия или время жизни.
Чтобы проиллюстрировать это, у нас есть запрос от нашего клиента к приложению/сервису. Наше приложение/сервис делает вызов к нашему кэшу.
Если данные находятся в кэше, мы используем их. Если нет, мы обращаемся к нашей основной базе данных, чтобы получить нужные нам данные.
Теперь, когда у нас есть данные, мы записываем их в наш кэш, чтобы последующий запрос получил кэшированное значение и не пришлось обращаться к основной базе данных.
Как уже упоминалось, мы можем установить срок действия кэша, чтобы кэшировать его только в течение определенного периода времени. После истечения срока действия и автоматической очистки кэша следующий клиент, запрашивающий эти данные, снова пройдет через этот цикл.
Теперь проблема с аннулированием кэша заключается в том, что мы должны обновить или удалить элемент из кэша при любой записи или изменении состояния нашей основной базы данных. Для этого мы можем использовать архитектуру, управляемую событиями, чтобы публиковать событие, когда происходит изменение состояния. Затем мы можем подписаться (потреблять) на это событие, чтобы асинхронно выполнить аннулирование.
Чтобы проиллюстрировать это, когда клиент делает вызов нашего приложения или сервиса, и мы делаем изменение состояния нашей базы данных. В рамках того же процесса запроса мы также публикуем событие в брокере сообщений.
Теперь запрос завершен клиентом, асинхронно в другом потоке или в другом процессе, мы можем потреблять это сообщение из брокера.
Когда мы потребляем это сообщение, мы можем вызвать наш кэш, чтобы удалить данные. Или мы также можем обратиться к основной базе данных и обновить кэш.
Поскольку мы используем метод «в сторону кэша», можно просто удалить данные из кэша, а следующий вызов, который попытается получить их с пропуском кэша, получит их из базы данных и запишет в кэш.
Как и в случае с записью через кэш, вы не можете обойти ваше приложение/сервис, поскольку именно оно публикует сообщение, которое приведет к аннулированию. В зависимости от ваших требований, если вы определили короткий срок действия для элемента кэша, это может быть приемлемо, но это полностью зависит от вашего контекста.
Связанные ссылки
кэширование в cdn
кэширование базы данных
Кэширование — концепция проектирования системы для начинающих