Организация кэша объектов сайта: методы и реализация в папке wp-content

Раздел: Управление контентом CMS -> Оптимизация WordPress

Введение в объектный кэш 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 позволяет включить объектный кэш с файловым хранилищем через административный интерфейс. Этот вариант подходит для пользователей, которые не хотят писать код.

  1. Установите и активируйте плагин W3 Total Cache.
  2. Перейдите в раздел Performance → General Settings.
  3. В секции Object Cache включите опцию Enable.
  4. Выберите метод хранения Disk: Enchanced или Disk: Basic (файлы будут созданы в wp-content/cache/object).
  5. Сохраните настройки и сбросьте кэш.

Типичные ошибки:

  • Плагин может конфликтовать с кэшированием других типов (страницы, база данных) - отключайте их поочерёдно для тестирования.
  • При смене хранилища (например, с 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(); // вызов внутреннего метода
});

Кэш объектов WordPress в wp-content - comments

En
Object cache php в каталоге wp content (php)