![](https://habrastorage.org/files/28e/531/50c/28e53150cafa4d139ee1fadc11ff641a.jpg)
Автор данной статьи не встречал структурированной обзорной информации о важных этапах кэширования, поэтому ему хотелось бы поделиться наработанным опытом в этой области, соединить воедино всю основную информацию по данному вопросу, а также рассмотреть плюсы и минусы каждого вида кэширования.
В первую очередь, хотелось бы пояснить, что кэширование – это одна из наиболее важных составляющих любого проекта. В частности, это единственный способ сделать больше и быстрее при использовании ограниченных ресурсов. А, как известно, ресурсы всегда ограничены: как серверные, так и пользовательские.
Основной проблематикой кэширования является быстрота реакции на запросы к основным системам хранения и обработки входящей и исходящей структурированной информации.
Представьте, что необходимо осуществить быструю передачу информации, однако скорость доступа к данным крайне низкая. Или другая ситуация: скорость хорошая, но мало доступной памяти или ширина канала недостаточная, или процессорные и дисковые факторы мешают осуществить задачу. В этом случае кэширование – это единственный выход из ситуации.
Виды кэширования
Кэширование (или кэш) – это некий промежуточный буфер, в котором хранятся данные. Благодаря кэшированию страница сайта не воссоздается заново для каждого пользователя. Кэширование позволяет осуществлять работу с большим количеством данных в максимально сжатые сроки и при ограниченных ресурсах (серверных и пользовательских).
Необходимо понимать, что работу с данными можно производить как на стороне клиента, так и на сервере. Притом, серверная обработка данных централизована и имеет ряд несомненных преимуществ (особенно для службы поддержки).
![](https://habrastorage.org/files/6cc/e77/735/6cce777357c546ecb8e88f2af296f365.jpg)
Существует несколько видов кэширования, предлагаем рассмотреть каждый вид, его особенности и рекомендации по применению:
1. Браузерное кэширование или клиентское кэширование
Представляет собой составление для браузера команды использовать имеющуюся кэшированную копию. Работа такого кэширования основана на том, что при повторном посещении, браузеру отдаётся заголовок 304 Not Modified, а сама страница или картинка загружаются из локального пользовательского кэша. Получается, что вы экономите на трафике между браузером посетителя и хостингом сайта. Соответственно, страница вашего сайта начинает загружаться быстрее.
1.1 Кэширование файлов и картинок
Браузерное кэширование как нельзя лучше подходит для сайтов, содержащих большое количество изображений: картинка не скачивается каждый раз при открытии сайта, а просто загружается через кэш браузера.
![](https://habrastorage.org/files/3c8/027/272/3c8027272e024330b01edcddd7d3b7f4.jpg)
Это первый уровень кэширования, который состоит в отдаче заголовка «expired» и заголовка «304 Not Modified». Наиболее эффективным считается кэширование на 2 недели.
Однако в данном случае есть важный нюанс: если изображение на сайте меняется, то браузер узнает об этом не сразу, а только если выждать expiry или сбросить кэш в самом браузере. Это не очень эффективно, если файл постоянно изменяется и необходимо постоянно отдавать его актуальную версию.
1.2 Кэширование https
Специальные заголовки вида strict-security. Позволяет браузеру всегда обращаться по https к выбранному домену. Сохраняет это состояние довольно жёстко и, в случае отмены этого вида кэша, браузер ещё довольно долго будет пытаться загрузить страницу по https, при этом игнорируя текущие заголовки.
1.3 Кэширование центра сертификации
Так называемый, stamp центра сертификации.
Данный вид кэширования считается обязательным для применения, если вы не хотите, чтобы пользователи вашего сайта ждали, когда центр сертификации (а это некий сервер, который отвечает за достоверность вашего сертификата) обработает запрос от браузера пользователя и подтвердит, что ваш сайт действительно подтверждён им.
1.4 Кэширование страниц
Когда страница уже сгенерирована, нужно постоянно отслеживать ее актуальность. Для этого вы должны использовать серверный кэш с отслеживанием времени изменения отдельных частей страницы (если страница строится из множества динамически генерируемых блоков). При таком подходе в каждом ответе от сервера установлены специальные заголовки, обозначающие время изменения страницы, которые затем отправляются браузером пользователя при повторном обращении к странице сайта. Сервер при получении таких заголовков можем проанализировать текущее состояние страницы (возможно, даже отрисовать её), но вместо содержимого страницы отдать заголовок «304 Not Modified», что для пользовательского браузера будет означать, что можно показать страницу из своего (браузера пользователя) кэша.
Конечно, можно отправлять соответствующие заголовки без использования серверного отслеживания кэша, но в таком случае большинство пользователей получат обновление контента страницы довольно поздно. При таком подходе браузер иногда опрашивает сервер для получения обновлений, но периодичность и правила для каждого браузера настраиваются его разработчиком, поэтому надеяться на то, что ваши пользователи получат обновления вовремя, не приходится.
Как правило, кэш подразделяется по типу пользователей:
— для авторизованных;
— для неавторизованных.
Данное разделение обусловлено уникальностью контента, для каждого авторизованного пользователя и общностью контента для гостевых пользователей. В большинстве сайтов не авторизованный пользователь не может изменять содержимое сайта, а значит и влиять на его содержимое.
Браузерный кэш позволяет экономить трафик и время, затрачиваемое на загрузку страниц. Но для достижения эффекта экономии, пользователь должен хотя бы один раз посетить нашу страницу, а это означает, что нагрузка на серверные ресурсы уменьшится, но не значительно.
2. Серверное кэширование
Под серверным кэшированием понимаются все виды кэширования, при котором данные хранятся на серверной стороне. Эти данные не доступны клиентским браузерам. Кэш создаётся и хранится по принципу «один ко многим» (многие, в данном случае, — это клиентские устройства).
![](https://habrastorage.org/files/e47/494/05f/e4749405f29e4f0daaf4a6b32bf337fc.jpg)
2.1 Кэширование страницы целиком
Наиболее эффективный кэш. Чем он интересен? Самое большое его достоинство в том, что отдача страницы происходит практически в момент обращения, как следствие – это возможность обработки миллионов запросов даже на самом слабом сервере со скоростью работы памяти и с незначительным задействованием процессора.
Пожалуй, любой когда-либо мечтал о сайте, работающем со скоростью «ping» или быстрее.
Но и у этого типа кэша есть свои минусы: например, невозможность кэшировать страницы для авторизованного пользователя, либо пользователя, содержимое страницы которого зависит от текущих переменных пользователя.
Используйте этот кэш, если серверу известны все статичные состояния внешних данных, такие как: uri, get (без дополнительных параметров), пользователь не авторизован — то есть, фактически, это идеальное состояние страницы для гостевых пользователей. Учитывайте тот факт, что при таком кэшировании архитектура сайта или приложения всегда должна однотипно обрабатывать входящие запросы и отдавать однотипные ответы. Такое состояние есть в любом приложении или сайте, его нужно лишь отследить и применить к нему кэш.
Кэширование страниц целиком, чаще всего, применяют в каких-то экстренных случаях, при этом кэш страниц сохраняется на заранее указанное время (от 2 минут), в течение которого ответы от сервера однотипны (не позволяйте браузеру кэшировать это).
2.2 Кэширование результатов компиляции php-файлов
Различают как чистую компиляцию кода, так и его оптимизацию во время компилирования (подмена скриптов). Наиболее яркие примеры:
— APC;
— XCache;
— Компиляция с подменой скриптов HipHopVirtualMachine.
И тот и другой вид кэширования могут использоваться в проекте, но у каждого есть собственные нюансы, которые необходимо учитывать при написании кода.
![](https://habrastorage.org/files/5d5/aee/7db/5d5aee7dbcf147c1b31704e4839ab97d.png)
2.3 Кэширование отдельных блоков страницы
Это, пожалуй, самый интересный, но и сложный вид кэширования. Тем не менее, он тоже может быть эффективным, и на его примере легче всего объяснить принципы кэширования в целом.
Необходимо отслеживать: состояние таблиц, состояние сессии пользователя, выключать ли кэширование при POST или GET запросах (http query), зависимость от текущего адреса, постоянство кэширования (при изменении предыдущих условий) или его динамическую подстройку.
Кэширование отдельных блоков страниц лучше других типов кэширования подойдёт, если вам нужно, например, уменьшить количество запросов к базе данных от реальных (авторизованных) пользователей. Кстати, при правильно заданных зависимостях, он будет работать даже эффективнее, чем все последующие виды кэширования.
Почему этот вид кэширования настолько важен? Всё дело в том, что расширение пула серверов баз данных намного более сложная задача, чем расширение пула серверов php-части сайта. Более того, php конфликты состояния кэширования решаются гораздо легче, чем конфликты при работе с множеством баз данных.
![](https://habrastorage.org/files/dba/255/fdb/dba255fdb9464e6a8577f6f84a78ff35.jpg)
2.4 Кэширование php на основе неразделяемых ресурсов
Лучше всего подходит при стандартизации запросов, получении данных из общих ресурсов, наличии внутренних переменных, к которым php-ресурсы обращаются несколько раз при генерации страницы.
2.5 Кэширование php на основе общих ресурсов
Такое кэширование применяйте для хранения сериализированных данных. Например: конфигурационного файла, состояния таблиц, списков файловой системы.
2.6 Кэширование mysql на основе query cache
Это довольно известная и наиболее освещённая тема. Тем не менее, хотелось бы рассмотреть специфику работы с timestamp и то, как можно избежать постоянного сброса query cache.
Наверняка, вы регулярно сталкивались с ситуацией, когда необходимо отдать новые материалы, дата публикации которых уже разрешена текущим timestamp? Проще говоря,
WHERE show_ts<=UNIX_TIMESTAMP()
Если использовать постоянно меняющийся timestamp в таких запросах, то sql кэш будет не только бесполезен, но даже вреден, так как будет копиться количество кэшированных запросов, данные которых устарели в момент создания кэша.
Мы предлагаем следующий выход из ситуации:
Как правило, любой материал публикуется в определенные моменты времени. К примеру, 00:00. Всё что нужно сделать — создать запрос, который будет оценивать таблицу по максимальной дате, при этом, меньшей текущей.
Что-то вроде:
SELECT SQL_NO_CACHE MAX(show_ts) … WHERE show_ts<=UNIX_TIMESTAMP();
Да, этот запрос кэшироваться не будет, но будут кэшироваться все запросы к этой таблице, если их количество больше одного. Эта простая операция существенно улучшит жизнь sql-кэширования.
Кэшировать эти запросы имеет смысл, если чтений из таблицы немного больше чем записи.
2.7 Кэширование mysql результатов работы, агрегирующие таблицы
Существует правило: обновлений данных должно быть значительно меньше, чем чтения для их отдачи.
То есть не имеет смысл агрегировать то, что изменится в тот же момент, при этом важна актуальность агрегированных данных.
Что выбирать для агрегирования? Обычно это какая-то статистическая информация о числе записей, дате последнего обновления, авторе последнего обновления и тому подобное.
Заключение
Учитывая постоянную сетевую нагрузку, без кэширования вы не сможете создать ни один проект. Кэширование дает возможность доставить данные большому кругу клиентов, задействовав при этом минимальные ресурсы. В этой статье мы рассмотрели множество видов кэширования, среди которых, уверены, найдется подходящее решение и для вашего проекта.
![](https://habrastorage.org/files/d79/224/15b/d7922415be7e4acc90fb0323d0d2c8d4.jpg)
Комментарии (12)
xmaster83
28.11.2016 20:47Лучше бы про Django, RoR, Sping, а то это жуткий баян.
Lure_of_Chaos
29.11.2016 01:59Поддержу: почему написано только про PHP и MySQL?
Пост был бы интереснее, если бы обзор был бы шире.
jbubsk
28.11.2016 21:02+2Еще про кеширование на уровне service worker можно упомянуть. Весьма полезная вещь!
M-A-XG
28.11.2016 21:10+3Кэширование страницы целиком
Возможный минус такого кеширования в определенный случаях — при изменении какого-то блока, который есть на всех страницах, нужно сбрасывать весь кеш.
— APC;
Уже актуален OpCache
2.4, 2.5
Не понял :)
2.6 Кэширование mysql на основе query cache
Я отключаю.
Минус — в его тупости. Изменение хоть одной строки таблицы удаляет из кеша все записи, где есть эта таблица.miksoft
29.11.2016 10:10+2Минус — в его тупости. Изменение хоть одной строки таблицы удаляет из кеша все записи, где есть эта таблица.
Это не минус. Это практически единственный возможный вариант работы кэша запросов. Особенно, когда в кэше лежат результаты тысяч или десятков тысяч довольно сложных запросов.
А вот настоящий минус, имхо, в том, что он это удаление делает весьма долго. Из-за этого нет смысла делать его большого размера, т.к. накладные расходы на его содержание начинают превышать выгоды. По моим наблюдениям верхний порог находится где-то в районе 128-256 Мбайт.
POPSuL
30.11.2016 15:28нужно сбрасывать весь кеш
Ну, есть же теги. Инвалидируем по тегу некую группу данных и всё. Абсолютно весь кэш дропать не нужно.
M-A-XG
30.11.2016 15:55блок на всех страницах -> тег на всех страницах -> сбрасываем кеш тега -> сбрасываем весь кеш
:)POPSuL
01.12.2016 02:34А… Ну да, не подумал об этом.
Из-за похожей проблемы (можно закэшировать всю страницу, но баннеры кэшировать нельзя) мне пришлось у себя сделать небольшой костыль:
- генерирую целиком страницу, но вместо блоков я вывожу плейсхолдер и кэширую ее (или беру из кэша если уже есть)
- перед выводом страницы генерирую блоки для каждого плейсхолдера и заменяю их.
Вообще, такую задачу вроде как можно с помощью varnish решить, но мне было лень разбираться :)
miksoft
29.11.2016 13:16Проще говоря,
Не будет.
WHERE show_ts<=UNIX_TIMESTAMP()
Если использовать постоянно меняющийся timestamp в таких запросах, то sql кэш будет не только бесполезен, но даже вреден, так как будет копиться количество кэшированных запросов, данные которых устарели в момент создания кэша.
How the Query Cache Operates
A query cannot be cached if it contains any of the functions shown in the following table.
…
UNIX_TIMESTAMP() with no parameter
renskiy
29.11.2016 13:26Плюс за разъяснение необходимости кэширования. Но хотелось бы увидеть в статье больше пояснений и ссылок на спецификации.
1.2 Кэширование https
Например, этот пункт для меня остался непонятным.
браузер иногда опрашивает сервер для получения обновлений, но периодичность и правила для каждого браузера настраиваются его разработчиком
А для этого случая есть формализованные рекомендации в RFC7234, упоминание которого, кстати, в контексте статьи было бы очень уместным.
miksoft
В контексте MySQL еще остались неупомянутыми:
Это просто уменьшит вымывание кэша и, соответвенно, увеличит шансы на попадание в кэш результатов других запросов, независимо от того, обращаются они к этой же таблице или к какой-то другой.