Организация кэша объектов сайта: методы и реализация в папке wp-content
Введение в объектный кэш WordPress
Кэширование объектов (Object Cache) позволяет сохранять результаты ресурсоёмких операций - запросов к базе данных, вызовов API, повторяющихся вычислений - для последующего использования на протяжении одного или нескольких запросов. По умолчанию WordPress использует непостоянный (non-persistent) кэш, который живёт только в пределах текущего HTTP-запроса. Для ускорения работы сайта можно организовать постоянное хранение кэша в файловой системе, в частности в каталоге wp-content.
Как организовать постоянный объектный кэш в файлах внутри wp-content без установки дополнительного программного обеспечения?
Наиболее эффективное решение - написать собственный файл object-cache.php и поместить его в wp-content. WordPress автоматически использует этот файл как drop-in для постоянного кэширования объектов. Файл реализует методы класса WP_Object_Cache и сохраняет данные в сериализованном виде в подкаталог cache/object/ внутри wp-content.
<?php
// wp-content/object-cache.php
class WP_Object_Cache {
private $cache = [];
private $cache_dir;
private $group_prefix = 'wp_oc_';
public function __construct() {
$this->cache_dir = WP_CONTENT_DIR . '/cache/object/';
if (!file_exists($this->cache_dir)) {
wp_mkdir_p($this->cache_dir);
}
}
public function add($key, $data, $group = 'default', $expire = 0) {
if (isset($this->cache[$group][$key])) return false;
return $this->set($key, $data, $group, $expire);
}
public function set($key, $data, $group = 'default', $expire = 0) {
$this->cache[$group][$key] = $data;
$filename = $this->get_filename($key, $group);
$data_to_store = [
'data' => $data,
'expire' => $expire ? time() + $expire : 0,
];
file_put_contents($filename, serialize($data_to_store), LOCK_EX);
return true;
}
public function get($key, $group = 'default', $force = false, &$found = null) {
if (isset($this->cache[$group][$key])) {
$found = true;
return $this->cache[$group][$key];
}
$filename = $this->get_filename($key, $group);
if (file_exists($filename)) {
$data = unserialize(file_get_contents($filename));
if ($data['expire'] && time() > $data['expire']) {
unlink($filename);
$found = false;
return false;
}
$this->cache[$group][$key] = $data['data'];
$found = true;
return $data['data'];
}
$found = false;
return false;
}
public function delete($key, $group = 'default') {
unset($this->cache[$group][$key]);
$filename = $this->get_filename($key, $group);
if (file_exists($filename)) {
unlink($filename);
}
return true;
}
public function flush($group = null) {
// Очистка кэша по группе или всего
if ($group) {
unset($this->cache[$group]);
array_map('unlink', glob($this->cache_dir . $this->group_prefix . md5($group) . '_*'));
} else {
$this->cache = [];
array_map('unlink', glob($this->cache_dir . $this->group_prefix . '*'));
}
return true;
}
private function get_filename($key, $group) {
return $this->cache_dir . $this->group_prefix . md5($group) . '_' . md5($key) . '.cache';
}
}
function wp_cache_init() {
global $wp_object_cache;
$wp_object_cache = new WP_Object_Cache();
}
function wp_cache_add($key, $data, $group = '', $expire = 0) {
global $wp_object_cache;
return $wp_object_cache->add($key, $data, $group, $expire);
}
function wp_cache_set($key, $data, $group = '', $expire = 0) {
global $wp_object_cache;
return $wp_object_cache->set($key, $data, $group, $expire);
}
function wp_cache_get($key, $group = '', $force = false, &$found = null) {
global $wp_object_cache;
return $wp_object_cache->get($key, $group, $force, $found);
}
function wp_cache_delete($key, $group = '') {
global $wp_object_cache;
return $wp_object_cache->delete($key, $group);
}
function wp_cache_flush($group = null) {
global $wp_object_cache;
return $wp_object_cache->flush($group);
}Object cache php в каталоге wp content (кэш объектов wordpress в wp-content)
Возникающие проблемы и способы их решения:
- Права доступа. Папка wp-content/cache/object должна быть доступна для записи веб-серверу. Если файлы кэша не создаются, проверьте права (обычно 755 для папок и 644 для файлов) или создайте папку вручную.
- Производительность при большом количестве файлов. Файловая система может замедлиться при тысячах мелких файлов. Регулярно очищайте устаревшие записи через wp_cache_flush() или добавьте механизм сборки мусора.
- Конфликт с другими плагинами. Если другой плагин уже использует object-cache.php (например, Redis Object Cache), ваш файл будет проигнорирован. Убедитесь, что drop-in только один.
- Сложность отладки. Используйте WP_DEBUG и логирование, чтобы отслеживать, какие данные кэшируются и когда происходит чтение/запись.
Как быстро подключить файловый объектный кэш с помощью плагина W3 Total Cache?
W3 Total Cache позволяет включить объектный кэш с файловым хранилищем через административный интерфейс. Этот вариант подходит для пользователей, которые не хотят писать код.
- Установите и активируйте плагин W3 Total Cache.
- Перейдите в раздел Performance → General Settings.
- В секции Object Cache включите опцию Enable.
- Выберите метод хранения Disk: Enchanced или Disk: Basic (файлы будут созданы в wp-content/cache/object).
- Сохраните настройки и сбросьте кэш.
Типичные ошибки:
- Плагин может конфликтовать с кэшированием других типов (страницы, база данных) - отключайте их поочерёдно для тестирования.
- При смене хранилища (например, с Redis на файловое) старые файлы могут остаться, что приводит к неактуальным данным. Выполняйте полную очистку кэша.
- Файловый кэш может быть медленнее, чем Memcached, при высоких нагрузках. Для больших проектов рассмотрите более быстрые решения.
Как сохранять данные Transient API в файлы, расположенные в wp-content?
По умолчанию Transient API использует базу данных (через опции) или постоянный объектный кэш. Вы можете переопределить его через фильтр pre_set_transient_{$transient} и сохранять данные в файлы. Это не является заменой полноценного object cache, но позволяет управлять кэшированием отдельных данных без изменения глобального механизма.
// Функция для сохранения transient в файл
function save_transient_to_file($transient, $value, $expiration) {
$file = WP_CONTENT_DIR . '/cache/transients/' . md5($transient) . '.trans';
$data = [
'value' => $value,
'expire' => $expiration ? time() + $expiration : 0,
];
wp_mkdir_p(dirname($file));
file_put_contents($file, serialize($data), LOCK_EX);
return $value;
}
add_filter('pre_set_transient_my_custom_key', 'save_transient_to_file', 10, 3);
// Функция для получения transient из файла
function get_transient_from_file($pre, $transient) {
$file = WP_CONTENT_DIR . '/cache/transients/' . md5($transient) . '.trans';
if (file_exists($file)) {
$data = unserialize(file_get_contents($file));
if ($data['expire'] && time() > $data['expire']) {
unlink($file);
return false;
}
return $data['value'];
}
return false;
}
add_filter('pre_transient_my_custom_key', 'get_transient_from_file', 10, 2);Этот подход полезен для кэширования результатов отдельных запросов или вычислений, например, списка последних записей, данных из внешнего API. Однако он требует явного указания ключа transient в коде и не интегрируется с ядром WordPress автоматически.
Проблемы:
- Необходимо вручную управлять очисткой файлов (удалять при обновлении данных).
- Фильтры работают только для конкретных транзиентов. Для полного переопределения Transient API потребуется написать собственный класс и загрузить его раньше ядра.
- Если используется объектный кэш, транзиенты автоматически сохраняются в нём, и файловое хранение может дублироваться.
Какие альтернативные способы хранения объектного кэша существуют (не в wp-content)?
Для сравнения: Memcached и Redis обеспечивают более высокую производительность, чем файловое хранение. Они используют оперативную память и не создают нагрузки на файловую систему. Если на сервере доступны эти сервисы, рассмотрите плагины Redis Object Cache или LiteSpeed Cache с поддержкой Memcached. Однако они не хранят данные в wp-content, что делает их менее подходящими для данной статьи, но полезными для сравнения.
Выбор метода зависит от ресурсов сервера и требований к скорости. Файловый кэш в wp-content - хороший компромисс для shared-хостинга без доступа к расширениям памяти.
Расширенные примеры кода и сценариев
Ниже приведены детальные примеры, демонстрирующие возможности файлового объектного кэша в wp-content.
1. Полный drop-in object-cache.php с поддержкой TTL, группировки и автоматической очистки
Пример выше показывает базовую реализацию. Расширенная версия включает:
- Поддержку сжатия данных (gzip) для экономии места.
- Периодическую очистку устаревших файлов (сборка мусора) при операции записи.
- Возможность задавать глобальное время жизни через константу OBJECT_CACHE_DEFAULT_TTL.
<?php
// wp-content/object-cache.php (расширенная версия)
define('OBJECT_CACHE_DEFAULT_TTL', 3600); // 1 час
define('OBJECT_CACHE_GC_PROBABILITY', 10); // 10% шанс запуска очистки
class WP_Object_Cache {
private $cache = [];
private $cache_dir;
private $gc_flag = false;
public function __construct() {
$this->cache_dir = WP_CONTENT_DIR . '/cache/object/';
if (!file_exists($this->cache_dir)) {
wp_mkdir_p($this->cache_dir);
}
// Возможность инициализации сборщика мусора
if (rand(1, 100) <= OBJECT_CACHE_GC_PROBABILITY) {
$this->gc();
}
}
public function set($key, $data, $group = 'default', $expire = 0) {
$expire = $expire ?: OBJECT_CACHE_DEFAULT_TTL;
$this->cache[$group][$key] = $data;
$filename = $this->get_filename($key, $group);
$store = [
'data' => gzcompress(serialize($data), 6),
'expire' => $expire ? time() + $expire : 0,
];
file_put_contents($filename, serialize($store), LOCK_EX);
return true;
}
public function get($key, $group = 'default', $force = false, &$found = null) {
if (!$force && isset($this->cache[$group][$key])) {
$found = true;
return $this->cache[$group][$key];
}
$filename = $this->get_filename($key, $group);
if (file_exists($filename)) {
$store = unserialize(file_get_contents($filename));
if ($store['expire'] && time() > $store['expire']) {
unlink($filename);
$found = false;
return false;
}
$this->cache[$group][$key] = unserialize(gzuncompress($store['data']));
$found = true;
return $this->cache[$group][$key];
}
$found = false;
return false;
}
// Остальные методы (add, delete, flush) аналогичны базовой версии
// ...
private function gc() {
$files = glob($this->cache_dir . '*');
foreach ($files as $file) {
$store = @unserialize(@file_get_contents($file));
if ($store && $store['expire'] && time() > $store['expire']) {
@unlink($file);
}
}
}
private function get_filename($key, $group) {
return $this->cache_dir . md5($group) . '_' . md5($key) . '.oc';
}
}2. Результат работы кэша (создание файлов в wp-content/cache/object)
После вызова wp_cache_set('latest_posts', $posts, 'posts', 7200) в каталоге wp-content/cache/object/ появится файл с именем вроде:
wp-content/cache/object/
a1b2c3d4e5f6..._f6e5d4c3b2a1... .oc
Содержимое файла (десериализованный массив):
a:2:{s:4:"data";s:...:...;s:6:"expire";i:177777777;}Где data - сжатые gzip данные, expire - timestamp истечения.
3. Использование кэша в теме или плагине
// Пример: кэширование результата сложного WP_Query
function get_featured_posts_cached() {
$key = 'featured_posts_home';
$group = 'custom_queries';
$cached = wp_cache_get($key, $group);
if (false !== $cached) {
return $cached;
}
$query = new WP_Query([
'category_name' => 'featured',
'posts_per_page' => 5,
'no_found_rows' => true,
]);
$posts = $query->posts;
wp_cache_set($key, $posts, $group, 1800); // 30 минут
return $posts;
}
$featured = get_featured_posts_cached();Результат: повторные вызовы функции в течение 30 минут возвращают данные из файла без обращения к базе данных.
4. Очистка кэша определённой группы
// Очистить все кэшированные запросы custom_queries
wp_cache_flush('custom_queries');
// Полная очистка всего файлового кэша
wp_cache_flush();После выполнения файлы с префиксом md5('custom_queries') будут удалены.
5. Интеграция с WordPress Cron для автоматической очистки
// Запланировать ежедневную очистку устаревших файлов
if (!wp_next_scheduled('wp_object_cache_gc')) {
wp_schedule_event(time(), 'daily', 'wp_object_cache_gc');
}
add_action('wp_object_cache_gc', function() {
global $wp_object_cache;
$wp_object_cache->gc(); // вызов внутреннего метода
});