Рубріки: HighloadТеория

Кэширование данных

Игорь Грегорченко

Кэширование — это один из способов оптимизации Web приложений. В любом приложении встречаются медленные операции (SQL запросы или запросы к внешним API), результаты которых можно сохранить на некоторое время. Это позволит выполнять меньше таких операций, а большинству пользователей показывать заранее сохраненные данные.

Наиболее популярная технология кеширования для Web приложений — Memcache.

Когда нужно кэшировать

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

  • Используйте классы или функции, для работы с данными. Не используйте повторяющихся SQL выборок в основном приложении.
  • Используйте обертки для работы с внешними API.

Что кэшировать?

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

  • Результаты запросов к внешним сервисам (RSS, SOAP, REST и т.п.).
  • Результаты медленных выборок из базы данных.
  • Сгенерированные html блоки либо целые страницы.

Кэширование выборок из баз данных

Запросы к базе данных — наиболее распространенный пример. На основе Мemcache реализуется очень просто:

$q = mysql_query($sql);
while ($row = mysql_fetch_assoc($q)) $list[] = $row;
memcache_set('online_users', $list, 60*60);
}

return $list;
}
$list = get_online_users();
...

Запрос на получение пользователей кэшируется на 1 час

Обновление данных

Если Вы кэшируете данные, которые могут обновляться, необходимо очищать кэш после каждого обновления:

memcache_connect('localhost', 11211);
function get_user($id)
{

if ( !$data = memcache_get('user' . $id) )
{

$sql = 'SELECT * FROM users WHERE id= ' . intval($id);
$q = mysql_query($sql);
$data = mysql_fetch_assoc($q);
memcache_set('user' . $id, $data, 60*60);

}
return $data;
}

function save_user($id, $data)
{
mysql_query('UPDATE users SET ... WHERE id = ' . intval($id));
memcache_delete('user' . $id);
}

Кэширование списков

Допустим, Вы кэшируете данные каждого пользователя, как в примере, а также их списки (например, список online пользователей). При обновлении данных пользователя, Вы удаляете данные из кэша только для указанного пользователя. Но его данные могут также присутствовать в списке online пользователей, которые тоже лежат в кэше. Сбрасывать списки при каждом обновлении данных любого пользователя не эффективно. Поэтому обычно используют такой подход:

  1. Кэшируют списки, которые состоят только из ID пользователей.
  2. Для вывода списка отправляют отдельный запрос для получения данных каждого пользователя.

Реализация выглядит так:

$q = mysql_query($sql);
while ($row = mysql_fetch_assoc($q)) $list[] = $row['id'];
memcache_set('online_users', $list, 60*60);
}

return $list;

}
$list = get_online_users();
foreach ( $list as $id )
{
$user = get_user($id);
...
}

Получим список ID пользователей и для каждого из них получим актуальные данные

Для получения данных сразу нескольких объектов можно использовать Multiget.

Повторные запросы

Некоторые данные могут запрашиваться несколько раз в рамках одной страницы, например:

...
Email:
...
Моя страница
...

Каждый вызов get_user() будет получать данные из кэша. Если Memcache стоит на отдельном сервере, это вызовет большой сетевой трафик и задержки.Чтобы этого избежать, можно использовать дополнительный кэш внутри самого приложения:

memcache_connect('localhost', 11211);
function get_user($id)

{
global $app_cache;
if ( $app_cache['user' . $id] ) return $app_cache['user' . $id];
if ( !$data = memcache_get('user' . $id) )

{
$sql = 'SELECT * FROM users WHERE id= ' . intval($id);
$q = mysql_query($sql);
$data = mysql_fetch_assoc($q);
memcache_set('user' . $id, $data, 60*60);
$app_cache['user' . $id] = $data;

}
return $data;
}

function save_user($id, $data)
{
global $app_cache;
mysql_query('UPDATE users SET ... WHERE id = ' . intval($id));
memcache_delete('user' . $id);
unset($app_cache['user' . $id]);
}

В реальных приложениях, имеет смысл иметь обертку для Memcache с дополнительным кэшом:

$data = memcache_get( $this->resource, $key );
$this->inner_cache[$key] = $data;
return $data['value'];

}
public static function set( $key, $value, $ttl )
{
memcache_set($key, $value, $ttl);
$this->inner_cache[$key] = $value;
}

public static function del( $key )

{
memcache_delete($key);
unset($this->inner_cache[$key]);
}

}

$inner_cache хранит дополнительный кэш

Внимание. Использование этого подхода может приводить к утечкам памяти в случаях, когда идет работа с большим количеством данных в кэше. Например, в cron-задачах (допустим, мы перебираем всех пользователей для отправки рассылки). Тогда лучше добавить отключение внутреннего кэша:

$data = memcache_get( $this->resource, $key );
$this->inner_cache[$key] = $data;
return $data['value'];

}
public static function set( $key, $value, $ttl )
{

memcache_set($key, $value, $ttl);
if ( self::$inner_cache_enabled ) $this->inner_cache[$key] = $value;

}
public static function del( $key )
{

memcache_delete($key);
unset($this->inner_cache[$key]);
}
}

...
mem_cache::$inner_cache_enabled = false;

Отключаем внутренний кэш

Подогревание

При обновлении особенно тяжелых данных следует использовать не сброс кэша, а прямое обновление данных в нем:

memcache_connect('localhost', 11211);
function get_rss($id)
{

if ( !$data = memcache_get('rss') )
{

$data = file_get_contents('http://rss.com/rss');
memcache_set('rss', $data, 60*60);
}

return $data;
}
function update_rss_feed($id, $data)
{

# операции по обновлению внешних ресурсов
$data = file_get_contents('http://rss.com/rss');
memcache_set('rss', $data, 60*60);
}

Это позволит избежать дополнительной нагрузки при выполнении тяжелых выборок, когда ключ удаляется. Такую методику обычно используют в cron задачах, чтобы периодически обновлять результаты очень тяжелых выборок.Время жизни (ttl)

ttl (время жизни) — это время, после которого, данные будут удалены из кэша. В Memcache устанавливается в секундах:


memcache_set('rss', $data, 60*60);

Установка ttl на 1 час

Чаще всего ttl ставят от нескольких минут до нескольких дней. Не используйте значение 0 (бесконечное хранение), это может засорить память.LRU

Любой кэш работает по принципу вытеснения если ему не хватает памяти. Т.е. если Memcache может использовать максимум 1G памяти, а Вы пытаетесь сохранить ключей на 2G, то половину из этих данных Memcache удалит. Для определения, какие именно ключи удалять, используется алгоритм LRU (Least Recently Used):Memcache постарается удалить прежде всего те данные, которые запрашивались очень давно (т.е. менее популярные удалит, а более популярные оставит).

Кэширование очень медленных запросов

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

Чтобы этого избежать, необходимо использовать специальную методику дублирования.Атомарные операции

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

---$count = memcache_get('count');
$count++;
memcache_set('count', $count);---

Memcache поддерживает две атомарные операции увеличения и уменьшения чисел:

memcache_increment('count');

Увеличит счетчик на 1, функция memcache_decrement() уменьшает счетчик

Самое важное

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

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

Останні статті

Обучение Power BI – какие онлайн курсы аналитики выбрать

Сегодня мы поговорим о том, как выбрать лучшие курсы Power BI в Украине, особенно для…

13.01.2024

Work.ua назвал самые конкурентные вакансии в IТ за 2023 год

В 2023 году во всех крупнейших регионах конкуренция за вакансию выросла на 5–12%. Не исключением…

08.12.2023

Украинская IT-рекрутерка создала бесплатный трекер поиска работы

Unicorn Hunter/Talent Manager Лина Калиш создала бесплатный трекер поиска работы в Notion, систематизирующий все этапы…

07.12.2023

Mate academy отправит работников в 10-дневный оплачиваемый отпуск

Edtech-стартап Mate academy принял решение отправить своих работников в десятидневный отпуск – с 25 декабря…

07.12.2023

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

Служба безопасности Украины задержала в Киеве 46-летнего программиста, который за деньги устанавливал шпионские программы и…

07.12.2023

Как вырасти до сеньйора? Девелопер создал популярную подборку на Github

IT-специалист Джордан Катлер создал и выложил на Github подборку разнообразных ресурсов, которые помогут достичь уровня…

07.12.2023