Четыре уровня кэширования в сети: клиентский, сетевой, серверный и уровень приложения

В этой статье мы рассмотрим один из секретов высокой масштабируемости и производительности сайтов. Из блога об архитектуре Flickr, на серверах которого размещается более 5 000 000 фотографий, мы узнали, что кэширование и оперативная память играют ключевую роль в масштабируемости и производительности сайта.

Сайт может хранить данные для ускорения обработки последующих запросов на четырёх уровнях:

  • клиентский;
  • сетевой;
  • серверный;
  • уровень приложения.

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

Кэш на клиентском уровне

Заголовки HTTP отвечают за определение возможности кэширования ответа и за определение срока хранения данных. Следующий пример заголовка Cache-control указывает, что ответ может находиться в кэше в течение 7 дней. Браузер отправит повторный запрос на хранение данных, если срок хранения истечёт или пользователь целенаправленно обновит страницу.

Запрос и ответ, которые могут быть кэшированы в течение 604800 секунд.

Ответ также может включать заголовок Last-Modified или Etag. Эти заголовки нужны для проверки возможности повторного использования данных. Статус ответа 304 указывает, что содержимое не изменилось и повторная загрузка не требуется. Обратите внимание на парные заголовки Last-Modified и If-Modified-Since, а также на даты ниже:

Ответ с заголовком «Last-Modified» и последующим запросом с его использованием.

Заголовок Etag используется с If-None-Match аналогичным образом для обмена кодами ответа при определении изменений в контенте, если они имеются.

Сайт с продуманными HTTP-заголовками обретёт больший успех у пользователей. Кроме того, браузер сэкономит время и пропускную способность.

Кэш на сетевом уровне

Согласно Википедии, Сеть Доставки Контента (CDN) — географически распределённая сетевая инфраструктура, позволяющая оптимизировать доставку и дистрибуцию контента конечным пользователям в сети Интернет. Иначе говоря, CDN — это распределённое хранение и использование кэша.

Директива HTTP-заголовка Cache-control: public позволяет различным частям сети кэшировать ответ. С помощью заголовка Cache-Control: public, max-age=31536000 находят ресурсы, которые хранятся в течение одного года.

Возможно, вы уже знакомы с другими директивами заголовков. Существует также ещё один мощный заголовок, для обработки аутентифицированных и других видов динамических ответов.

Кэш на серверном уровне

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

Первый подход к более быстрым ответам и экономии ресурсов — настройка кэш-сервера между приложением и клиентом.

Клиенты, запрашивающие одно и то же содержимое на прокси-сервере.

Такие инструменты, как Varnish, Squid и nginx кэшируют изображения, скрипты и прочее содержимое, которое требуется пользователям. Следующая настройка nginx собирает кэш, опираясь только на HTTP-заголовки в приложении.

proxy_cache_path /path/to/cache keys_zone=my_cache:10m;

server {
  location / {
    proxy_cache my_cache;
    proxy_pass http://aplication;
  }
}

Существует ещё одна директива, которая называется proxy_cache_lock, которая позволяет прокси-серверу делегировать только первый из похожих клиентских запросов за один раз для приложения. Если директива установлена, клиенты будут получать ответ при возврате первого запроса.

Множество клиентов, запрашивающих одно и то же содержимое одновременно.

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

Идея последнего, но не менее важного подхода заключается в том, что прокси-сервер может улучшить отказоустойчивость приложения. Существуют флаги директивы proxy_cache_use_stale для доставки контента с истёкшим сроком актуальности, когда приложение возвращает статус ошибки или когда связь между прокси-сервером и приложением не работает должным образом.

В руководстве по кэшированию с NGINX и NGINX Plus содержится более подробная информация и параметры конфигурации.

Кэш на уровне приложения

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

Мемоизация

def price
  @price ||= Price.new(unit_price, category)
end

В приведённом выше коде на Ruby используется простой метод мемоизации, который сохраняет цену продукта, чтобы избежать дополнительных вычислений. Эта функция сохранит данные в экземпляре объекта и сэкономит ресурсы во время обработки запроса.

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

Интеллектуальное кэширование в памяти

cache.fetch("category-tax-#{category_id}", expires_in: 1.minute) do
  CategoryTax.new(category_id)
end

В приведённом выше коде используется API кэширования Rails для хранения и повторного использования метки категории в течение одной минуты во время обработки запросов. Ключом кэша для идентификации данных является category_id. Этот метод используется для экономии ресурсов, времени и уменьшения объёма запросов к внешней службе меток категорий.

Многие библиотеки предоставляют этот шаблон, но память приложения — не бесконечный ресурс. Например, менеджер кэша для Node не управляет объёмом потребляемой памяти. Также это может стать проблемой, если ваше приложение кэширует данные в больших объёмах, потребляя всю доступную память.

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

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

Умение обращаться с растущим количеством пользователей и запросов — важный объект веб-разработки. Один из способов масштабирования приложения — добавление экземпляров приложения (горизонтальное масштабирование). Как вы, наверно, догадались, простой кэш в памяти не может использоваться несколькими экземплярами.

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

Хранилище со значениями ключей, такое как Memcached или Redis, может использоваться для совместного распределения данных кэша между экземплярами приложения. Эти инструменты имеют разные алгоритмы для сокращения количества кэшированных данных. Хранилища кэша также могут быть устойчивы к ошибкам с репликацией и хранением данных. Алгоритмы настолько сильно различаются, что Netflix создала свой собственный инструмент.

Ещё один важный аспект при использовании хранилищ кэша — это состояние гонки, которое происходит, когда разные экземпляры приложения обращаются к некэшированным данным одновременно. API кэширования запросов Rails содержит свойство race_condition_ttl для минимизации этого эффекта.

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

Заключение

Надеемся, что эта статья поможет вам понять и выбрать лучшую стратегию для вашего приложения. HTTP-заголовки — это самое простое, что вы можете и должны настроить для оптимизации кэширования вашего приложения. Используйте также и другие стратегии, когда у вас появятся определённые проблемы в производительности, но помните, что преждевременная оптимизация — корень всех бед.

Перевод статьи «Client, Network, Server and Application Caching on the Web»