Web Analytics Made Easy - StatCounter
Главная Блог 100 мс -> 40 мс -> 1 мс: максимальное кеширование ответов в Laravel

100 мс -> 40 мс -> 1 мс: максимальное кеширование ответов в Laravel

100 мс -> 40 мс -> 1 мс: максимальное кеширование ответов в Laravel

Введение

Как получить (некоторое) время отклика от 100 мс до 40 мс до 1 мс. Все приведенные ниже тактики основаны на моем сайте - Lean-admin.dev, развернутом на Laravel Vapor. На сайте есть целевая страница, страница с ценами, страницы документации (на Laravel) и пользовательская панель управления. Как всегда, YMMV (Your Mileage May Vary).

Кеширование чего?

Мы будем кэшировать HTTP-ответы. Это означает фактический вывод вашего веб-сервера, который отправляется в браузер. Не то, что происходит на сервере, например запросы к базе данных.

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

Кеши

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

Браузеры

Браузер может кэшировать ответы, заставляя их увеличиваться от ~ 100 мс до ~ 1 мс, поскольку они обслуживаются из памяти браузера и никаких сетевых запросов не требуется.

Кеширование браузера, в отличие от CloudFlare, можно использовать и для личной информации. Однако, когда кеш браузера промахивается, ответ будет медленнее, чем от CF. Важная информация: браузер кэширует ответы на основе инструкций, содержащихся в заголовке Cache-Control.

CloudFlare

CloudFlare служит общим кешем для ваших клиентов. Это не так быстро, как кеширование в браузере, но время отклика может составлять от ~ 100 мс (оптимизированный веб-сервер) до ~ 50 мс, потому что он обслуживает ваш контент из распределенной сети. Это означает, что пользователи, приезжающие из Европы, будут получать контент с близкого к ним сервера, как и пользователи, приезжающие из Южной Америки или любой другой части мира.

CF не может использоваться для кэширования частной информации (например, страницы профиля для вошедшего в систему пользователя).

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

Отдельные переменные имеют примерно такой приоритет:

  1. Custom page rules — настройки, которые вы можете применить к определенным маршрутам на панели управления CF.
  2. CF security defaults — если в ответе есть cookie, он не будет кеширован
  3. Заголовки ответов - Cache-Control.
  4. CF performance defaults — например, оптимизация изображений и файлов .js

Итак, исходя из вышесказанного, возникает очевидная проблема. Laravel устанавливает файлы cookie. Даже когда мы не используем auth. Раньше был способ удалить их на CF бесплатно, теперь нет. Поэтому нам понадобится специальное промежуточное ПО для удаления файлов cookie.

Прежде чем сделать это, убедитесь, что они вам действительно не нужны. Также: такие инструменты, как Google Analytics, по-прежнему будут работать. У CF есть проблема только с заголовками Set-Cookie на сервере, но сценарии JS могут без проблем устанавливать файлы cookie.

Код для удаления файлов cookie, а также все остальные функции, которые нам понадобятся, находятся в конце статьи.

Два сценария

  • Для каждого запроса есть два сценария.
  • Либо это будет аутентифицированный пользователь, либо это будет гостевой запрос.
  • Для каждого сценария требуется своя стратегия кэширования.

Авторизованные пользователи

Первое, что вы должны сделать, когда обнаружите, что пользователь аутентифицирован, - это установить для заголовка Cache-Control значение private. Это означает, что контент может быть кэшируемым (в зависимости от других ключей конфигурации), но определенно не может храниться в кешах, таких как CF или корпоративные прокси.

Теперь несколько соображений.

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

Это не идеально, но давайте посмотрим, какие есть варианты.

  1. Кэшировать на долгое время, пофиг.
  2. Кэшируйте его на долгое время, но требуйте проверки при каждом запросе (браузер будет спрашивать сервер, есть ли у него последняя версия).
  3. Не кешировать вообще.
  4. Кэшировать на короткий период времени.

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

Route::get('/cache-test', fn () => ''); 

И посетите в браузере /cache-test. Обратите внимание на время отклика. Теперь посетите страницу, которая действительно передает HTML. Обратите внимание на время отклика.

Скорее всего, время отклика будет в основном идентичным.

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

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

Третье решение лучше. Никакого кеширования. Поскольку предыдущие два примера не могут принести особой пользы, зачем беспокоиться о сложности кода. Просто не будем ничего кэшировать. Это второй лучший вариант из перечисленных здесь. Самый лучший - последний: кэшировать на короткий промежуток времени.

Это лучшее из обоих миров с минимальным компромиссом. Все кэшируется, но клиенты не увидят данных за месяц. Установите TTL кеша на минуту или пять. Страница загружается быстро, а то, что клиентам покажется «ошибками», останется незамеченным или замеченным только на минуту.

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

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

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

Публичные страницы:

Cache-Control: public, max-age=60 

Панель пользователя:

Cache-Control: no-store, private, max-age=0 

Параметр no-store означает, что он не может храниться ни в браузере, ни на прокси-сервере.

Пользователи-гости

С гостевыми пользователями вы можете кэшировать гораздо более свободно. На самом деле можно все кэшировать. Так сделай это. Кешируйте все как в CloudFlare, так и в браузере.

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

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

Cache-Control: public, max-age=60 

Собираем все вместе

We'll need to make a tiny change to our application and configure CloudFlare properly. Let's start with the code changes.

Код

Создайте промежуточное ПО с именем, например, `` CacheControl``. Зарегистрируйте его в своем `` App \ Http \ Kernel '' следующим образом:

protected $middlewareGroups = [ 'web' => [ CacheControl::class, // Last middleware out 

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

Внутри метода handle () промежуточного программного обеспечения добавьте такой код:

public function handle(Request $request, Closure $next) { /** @var Response $response */ $response = $next($request); if (auth()->check() || $this->hasForms($response)) { // Don't cache anything $response->setCache(['private' => true, 'max_age' => 0, 's_maxage' => 0, 'no_store' => true]); } else { // Cache all responses for 1 minute $response->setCache(['public' => true, 'max_age' => 60, 's_maxage' => 60]); // Remove all cookies foreach ($response->headers->getCookies() as $cookie) { $response->headers->removeCookie($cookie->getName()); } } return $response; } /** @param Response|ResponseFactory $response */ protected function hasForms($response): bool { $content = strtolower($response->getContent()); return Str::of($content)->contains('<input type="hidden" name="_token"'); } 

Посмотрим, что здесь происходит.

  1. Если есть пользователь, мы говорим, что данные являются private что они не должны кэшироваться более 0 мс ни в браузере, ни в CF, и что они не должны вообще нигде храниться. Некоторые из этих частей не нужны, когда другие уже установлены, но они четко определены и упрощают настройку в будущем.
  2. Если есть гость, мы кешируем абсолютно все на 60 секунд. Это public кеш, поэтому он хранится как на CF, так и в браузере.
  3. Если на странице есть форма POST, мы не кэшируем ее. Токены CSRF - печально известная проблема со статическим кешированием. Здесь мы полностью избегаем этого.

CloudFlare

Откройте свой сайт в CloudFlare (или зарегистрируйтесь - это бесплатно) и перейдите на вкладку Page Rules:

Создайте правило страницы (вам нужно только одно), которое устанавливает для параметра Cache Level значение Cache Everything.

Вы можете спросить, зачем Cache Everything, если мы хотим кэшировать только определенные вещи. CloudFlare немного сбивает с толку, но в основном, чтобы иметь возможность кэшировать HTML-ответы, вам необходимо установить эту конфигурацию. Таким образом, CloudFlare по умолчанию будет кэшировать всё на время, указанное в заголовках ответа Cache-Control.

А как насчет вещей, которые не следует кэшировать? Если вы посмотрите на код промежуточного программного обеспечения, вы увидите, что он всегда добавляет к ответу заголовок кеша. И параметр Cache Everything не использует кеш, если в ответе указано значение private, max-age = 0 или no-store. Мы устанавливаем все эти значения для некоторых ответов.

Таким образом, все будет работать так, как говорит наш сервер, за исключением того, что CF не будет на самом деле слушать, независимо от типа содержимого наших ответов.

По умолчанию TTL CloudFlare составляет несколько часов, вы можете немного снизить его ниже значения по умолчанию или даже больше, если у вас есть подписка (я считаю, что это 1 час для моего плана в 20 долларов в месяц).

Это означает, что первый посетитель попадет в ваше приложение, время отклика примерно 150 мс. Следующий посетитель получит CF, время отклика около 50 мс. И все остальные посетители тоже. Когда один и тот же посетитель посещает одну и ту же страницу дважды в течение одной минуты (например, переключается между целевой страницей и страницей с ценами), время ответа будет мгновенным - 1 мс.

Бонус: кеширование определенных страниц для аутентифицированных пользователей

Как я уже упоминал выше, мы все еще можем захотеть кешировать, например docs, даже если есть аутентифицированный пользователь.

Для этого создайте правила для определенных путей (/docs / *,/, /pricing, ...) со следующими настройками:

  • Cache Level: Cache Everything
  • Browser Cache TTL: 30 minutes (или ваш самый низкий вариант)
  • Origin Cache Control: off

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

При такой конфигурации эти страницы будут кэшироваться даже для пользователей, прошедших проверку подлинности, таким образом, чтобы кэш был закрыт для пользователя. Мы устанавливаем только новый заголовок Cache-Control для браузера Browser Cache TTL, CloudFlare по-прежнему получает инструкции от сервера.

Однако у этого решения есть и недостатки. Мы установили время жизни кеша браузера для /docs / * на 30 минут, и это будет так для всех, включая гостевых пользователей. Несмотря на нашу 1-минутную настройку в промежуточном программном обеспечении.

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

Итак, вы должны решить, какое решение имеет для вас смысл. Кэширование - это не сложная вещь, но она требует рассмотрения.

Принимайте решения и наслаждайтесь новым временем отклика :)

Источник

100ms -> 40ms -> 1ms: Maximizing response caching in Laravel, автор Samuel Štancl