Кэширование данных следует рассматривать для данных, которые часто считываются, но редко записываются, особенно для таблиц, чьи запросы RDB чаще всего встречаются в топ-5, при этом обновления выполняются нечасто.
При кэшировании данных, чувствительных к актуальности, необходимо проявлять осторожность. Локальное кэширование обеспечивает высокую скорость, но требует внимания к управлению экземплярами.
Я применил локальное кэширование для данных, связанных с оплатой, и установил TTL равным 5 секундам для повышения производительности. В дальнейшем планируется проверить эффективность данного решения с помощью тестов производительности.
Здравствуйте! Это Джейон.
Сегодня я хочу объяснить критерии настройки кэша. Поскольку это пост, написанный на основе моего личного опыта работы, я надеюсь, вы воспримете его как справочную информацию ㅎㅎ
Что такое кэш?
Кэш — это предварительное сохранение результатов, которые могут быть запрошены в будущем, для их быстрого предоставления. Иными словами, это техника, при которой результаты предварительно сохраняются, а при последующих запросах вместо обращения к базе данных (DB) или API происходит обращение к кэшу для обработки запроса. Причина появления кэша заключается в законе Парето.
Закон Парето гласит, что 80% результатов обусловлены 20% причин. Для лучшего понимания вы можете обратиться к изображению ниже!
То есть, нет необходимости кэшировать все результаты. Кэширование только 20% наиболее часто используемых данных при обслуживании позволяет повысить общую эффективность.
Какие данные следует кэшировать?
В соответствии с законом Парето, не следует кэшировать любые данные, а только те, которые действительно необходимы. Итак, какие же данные следует кэшировать?
Данные, которые часто считываются, но редко записываются
Теоретически часто говорят, что «следует кэшировать данные, которые часто считываются, но редко записываются», но критерии «частоты считывания» и «редкости записи» были довольно расплывчатыми.
Поэтому я использую следующую последовательность шагов для исследования данных, которые необходимо кэшировать.
С помощью APM, такого как DataDog, проверяем ТОП-5 запросов к RDB.
Из них находим запросы на выборку и проверяем, из какой таблицы они извлекают данные.
Проверяем, как часто вызываются запросы на обновление для этой таблицы.
Этот процесс позволяет определить, часто ли выполняются запросы на выборку и редко ли выполняются запросы на обновление. В таблицах, которые я проверил на практике, запросы на выборку выполнялись 1,74 миллиона раз в день, а запросы на обновление — не более 500 раз. По таким критериям, очевидно, что данные подходят для кэширования ㅎㅎ
Данные, чувствительные к обновлению
Данные, чувствительные к обновлению, подразумевают, что расхождение между RDB и кэшем должно быть минимальным. Например, информация, связанная с платежами, очень чувствительна к обновлению, поэтому, даже если она соответствует вышеуказанным условиям кэширования, необходимо тщательно взвесить целесообразность ее применения.
Мне нужно было кэшировать таблицы, связанные с платежами, которые соответствовали этим двум характеристикам. Поэтому я не применял кэширование ко всей логике, использующей эти таблицы, а решил использовать частичное кэширование для относительно безопасной логики, где фактически не происходят платежи.
Локальное кэширование против глобального кэширования
Теперь мы определились с данными и областью кэширования. Следующий шаг — решить, «где» хранить кэшированные данные. Обычно данные можно хранить в локальной памяти или на отдельном сервере, таком как Redis.
Локальное кэширование
Локальное кэширование — это способ хранения кэшированных данных в памяти сервера приложения. Обычно для этого используются Guava cache или Caffeine cache.
Преимущества
При выполнении логики приложения кэш извлекается непосредственно из памяти того же сервера, что обеспечивает высокую скорость.
Простая реализация.
Недостатки
При использовании нескольких экземпляров возникают различные проблемы.
Изменения кэша на одном экземпляре не могут быть распространены на другие экземпляры. Однако существуют библиотеки локального кэширования, которые позволяют распространять изменения на другие экземпляры.
Кэш хранится на каждом экземпляре, поэтому при запуске нового экземпляра необходимо повторно загружать кэш. Это может привести к большому количеству промахов кэша, из-за чего приложение может не выдерживать нагрузки и завершаться.
Глобальное кэширование
Глобальное кэширование — это использование отдельного сервера для хранения кэшированных данных, такого как Redis.
Преимущества
Кэш совместно используется между экземплярами, поэтому, даже если кэш изменяется на одном экземпляре, все экземпляры получают одинаковое значение кэша.
При запуске нового экземпляра он просто обращается к уже существующему хранилищу кэша, поэтому нет необходимости загружать кэш заново.
Недостатки
Поскольку требуется сетевой трафик, скорость ниже, чем при локальном кэшировании.
Необходимо использовать отдельный сервер кэша, что влечет за собой расходы на управление инфраструктурой.
Расходы на управление инфраструктурой? → плата за сервер, время, затраченное на настройку и обслуживание инфраструктуры, разработка мер реагирования на сбои и т. д.
Какой вариант выбрал я?
В настоящее время сервер приложений нашей компании использует несколько экземпляров, но я выбрал локальное кэширование.
Основные причины — три.
Объем кэшируемых данных, хранящихся в RDB, составляет чуть менее 40 000 записей, и загрузка их всех в память занимает менее 4 МБ.
Необходимо было повысить производительность запросов к данным, связанным с платежами.
Хотя Redis уже используется, хранение нового кэша в Redis приведет к дополнительным расходам на инфраструктуру.
Как обновлять кэш?
Если у нас несколько серверов приложений и используется локальное кэширование, значения кэша, хранящиеся на каждом сервере приложений, могут отличаться. Например, значение кэша, хранящееся на сервере A, может быть равно «1», а значение кэша, хранящееся на сервере B, после изменения на сервере B может быть равно «2». В такой ситуации, если пользователь отправляет запрос через балансировщик нагрузки, он может получить разные значения от сервера A и сервера B.
Поэтому необходимо настроить автоматическое удаление кэша на каждом экземпляре, чтобы приложение запрашивало данные из RDB. В этом случае обычно используется TTL.
Какое значение TTL следует задать?
TTL (Time To Live) — это настройка, позволяющая удалять кэш по истечении определенного времени. Например, если задать TTL 5 секунд, кэшированные данные будут автоматически удалены через 5 секунд. После этого, при возникновении промаха кэша, данные будут извлечены из RDB и сохранены.
Какое же значение TTL следует задать?
Чтение/запись происходят на одном сервере кэша
Если чтение/запись происходят на одном сервере глобального кэша, таком как Redis, или на одном сервере приложения с локальным кэшированием, значение TTL можно увеличивать до единиц времени. В любом случае при записи будет изменено существующее содержимое кэша, и сервер, извлекающий данные из этого кэша, всегда будет получать обновленные данные.
В этом случае можно не устанавливать TTL, а использовать алгоритм LRU для постепенного освобождения кэша по мере его заполнения.
Чтение/запись происходят на нескольких серверах кэша
Если чтение/запись происходят на нескольких серверах глобального кэша или на нескольких серверах приложений с локальным кэшированием, лучше использовать TTL в диапазоне от секунд до минут. Это связано с тем, что существует вероятность чтения устаревших данных с сервера кэша, где еще не отражены изменения.
В этом случае значение TTL определяется в зависимости от различных контекстов. Чем важнее обновление и выше вероятность изменения значения, тем меньше должно быть значение TTL. И наоборот, чем менее важно обновление и ниже вероятность изменения значения, тем больше значение TTL.
Какое значение TTL я установил?
Кэшируемые мной данные связаны с платежами, и хотя я не применяю кэширование к строгой логике обработки платежей, обновление данных все равно важно из-за специфики платежей. Однако, учитывая низкую вероятность обновления, я установил TTL равным 5 секундам для обеспечения безопасности.
Заключение
Подводя итог, выбранный мной способ кэширования выглядит следующим образом.
Данные, связанные с платежами
Очень частые запросы на выборку, но редкие обновления.
Кэширование применяется только к логике, где происходят запросы на выборку, но фактически не происходят платежи.
Используется локальное кэширование, TTL установлен равным 5 секундам.
В дальнейшем планируется провести тестирование производительности выбранного способа кэширования. Конкретный план тестирования еще находится в стадии разработки, и я опубликую его в следующем посте!