ГлавнаяРазноеПроизводительность опций в WordPress

Производительность опций в WordPress

Таблица с опциями в WordPress содержит все настройки вашего сайта, тем и плагинов. В ней могут храниться как простые флажки (да/нет), так и данные более крупных размеров, иногда даже логи. API для работы с опциями в WordPress очень простой, его можно резюмировать в две строки:

update_option( 'foo', 'bar' ); // сохранить опцию с названием foo
$foo = get_option( 'foo' ); // получить опцию foo

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

Fatal error: Allowed memory size of 134217728 bytes exhausted

Конечно можно обратиться к хостинг-провайдеру, попросить поднять лимит памяти до 256 мегабайт, затем еще больше, еще и еще. Но таким образом вы временно скрываете проблему, а не решаете ее.

Как работают опции в WordPress

В WordPress есть таблица wp_options, ее структура достаточно простая:

+-----------+-------------+-------------------+----------+
| option_id | option_name | option_value      | autoload |
+-----------+-------------+-------------------+----------+
|         1 | siteurl     | http://develop.lo | yes      |
+-----------+-------------+-------------------+----------+

При обращении к функции get_option() в WordPress, мы получаем значение опции по ключу из базы данных, но здесь стоит помнить одну очень важную вещь: опции с флажком autoload подгружаются в память автоматически при загрузке ядра WordPress, за это отвечает внутренняя функция wp_load_alloptions().

Это значит, что при обращении к опции siteurl в нашей теме или в плагине WordPress, результат будет выдан сразу из кэша объектов, а не из базы данных MySQL.

Такой подход является безусловным плюсом для тех опций, которые мы (ядро, наша тема, наши плагины) намерены использовать для отображения текущей страницы в WordPress. Это предотвращает лишние походы в базу данных MySQL за каждой требуемой опцией.

Но что насчет тех опций, которые мы загрузили в память, но так ими и не воспользовались? Они ведь просто потребляют наши ресурсы.

Что с этим делать?

Вряд ли найдется такая страница, для отображения которой потребовались бы абсолютно все опции с флагом autoload, и наверняка большинство страниц на вашем WordPress сайте используют намного меньше половины всех авто-подгружаемых опций, и это нормальная практика.

Проблемы с производительностью возникают далеко не из-за их количества. Вернемся к нашему API для работы с опциями WordPress, возьмем простой плагин, который решил записать в опцию foo значение bar:

update_option( 'foo', 'bar' );

Многие разработчики даже не задумываются над тем, станет ли такая опция подгружаться автоматически. Требуется ли эта опция для отображения большинства страниц на сайте, и сколько «лишней» памяти будет потреблять эта опция.

Запомните, при использовании функций update_option() или add_option(), флаг autoload ставится по умолчанию для новых опций. А при деактивации или удалении темы или плагина, опция (как правило) никуда не исчезает. Если вам потребовалось создать опцию без autoload, то воспользуйтесь четвертым аргументом функции add_option().

Конечно для опции foo-bar это всего порядка 6 байт памяти (три байта для ключа и три для значения), и даже тысяча таких опций (~ 6 килобайт) практически никак не скажутся на скорость работы сайта и потребление памяти, но давайте рассмотрим более интересный пример.

Пример

Не так давно мы помогали разобраться с производительностью одного небольшого проекта, обнаружили следующий код, написанный на заказ небольшой студией. Им поставили простую задачу — записывать IP адреса всех, кто посещает страницу оплаты в их интернет-магазине:

add_action( 'wp_footer', function() {
    if ( ! is_page( 'payment' ) )
        return;

    $log = get_option( 'payment-ip-log' );
    $log .= $_SERVER['REMOTE_ADDR'] . PHP_EOL;
    update_option( 'payment-ip-log', $log );
} );

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

К сожалению их хостинг-провайдер перевел их на более дорогой тарифный план, затем еще дороже и еще, ссылаясь на то, что WordPress «прожорлив к серверным ресурсам». Они деактивировали все плагины, поставили тему по умолчанию, но это не помогло.

Потребление памяти в WordPress

Потребление памяти в WordPress

Проблема здесь в том, что опция payment-ip-log была установлена с флагом autoload по умолчанию. Таким образом, при загрузке любой страницы сайта, WordPress загружал весь этот лог IP-адресов в память:

mysql> SELECT option_name, LENGTH(option_value), autoload FROM wp_options WHERE option_name = 'payment-ip-log';
+----------------+----------------------+----------+
| option_name    | LENGTH(option_value) | autoload |
+----------------+----------------------+----------+
| payment-ip-log |            131329015 | yes      |
+----------------+----------------------+----------+

Если бы разработчик здесь поставил no для флага autoload, это решило бы проблему для всех страниц, кроме страницы оплаты, так как на ней мы все равно пытались бы загрузить большой объем в память.

Удалив эту опцию целиком из базы данных, и удалив плагин, который ее генерировал, нам удалось решить проблему с потреблением памяти, и вернуть лимит до стандартных 32 мегабайт. Если же говорить о самой задаче, то намного логичнее было бы решить ее с помощью произвольной таблицы в MySQL, или с помощью записи значений в файл на диске.

Как найти подобные опции?

С помощью запроса в базу данных MySQL можно получить список самых крупных по объему опций с флагом autoload:

mysql> SELECT option_name, LENGTH(option_value) AS length FROM wp_options
  WHERE autoload = 'yes' ORDER BY length DESC LIMIT 10;
+--------------------------------+--------+
| option_name                    | length |
+--------------------------------+--------+
| jetpack_file_data              |  23046 |
| rewrite_rules                  |  22430 |
| weaverii_settings              |  17952 |
| _site_transient_update_plugins |  11697 |
| columbus                       |   7836 |
| useradminsimplifier_options    |   6308 |
| theme_mods_semicolon           |   4787 |
| wp_user_roles                  |   4188 |
| _site_transient_update_themes  |   2817 |
| wpseo_titles                   |   2633 |
+--------------------------------+--------+

Все эти опции будут загружаться при каждом запросе на ваш WordPress сайт. В данном примере все вполне прилично, 23кб не такой существенный объем, хотя если вы например не используете плагин Jetpack, то опцию jetpack_file_data можно смело удалить. Если у вас не активирована тема Weaver II, то опцию weaverii_settings можно удалить.

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

Также не следует удалять опции ядра WordPress, а если вы заметили что некоторые из них стали потреблять слишком много памяти, то вероятно проблема в стороннем плагине, теме или в конфигурации сервера.

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

Заключение

Если вы заметили, что ваш сайт на WordPress со временем стал медленным и использует чрезмерное количество памяти, то не спешите бросаться деньгами в вашего хостинг-провайдера. Возможно проблему можно решить более дешевым и надежным способом.

Если у вас возникли вопросы по работе опций в ядре WordPress — оставьте комментарий.

Подписаться на рассылку

Подписаться → Подпишитесь на бесплатную рассылку журнала WP Magazine и получайте новости, события, подборки тем и плагинов, уроки, советы и многое другое в мире WordPress!

  • И именно поэтому не стоит на рабочем (боевом) сайте пробовать все темы и плагины подряд. Большинство тем и плагинов даже из официальной директории не удаляют мусор за собой. Такая же ситуация и с некоторыми плагинами, которые добавляют разный стафф в метаданные к постам.

    • P. S. Кому-то было лень писать функционал для работы с таблицей сохранения ip адесов :)

      Согласен. Мы им потом глаза открыли, сказав что эту информацию можно найти в access.log простым cat-ом и grep-ом, и что она была бы более корректной с учетом кэширования страниц.

  • Evgen

    Мне кажется проблема решается очень просто. Наверно стоит не присваивать в этом 4-ом аргументе «yes», кто в теме сам поправит, а кто не в курсе (а таких большинство) и не столкнуться с этим

    • 4-й аргумент «yes» по умолчанию :) Если бы это было не так, то была бы еще больше катастрофа с MySQL запросами.

      • Андрей

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

  • Спасибо, что поделились таким полезным опытом! В практике точно пригодится

  • campusboy

    Большое спасибо, что поделились информацией, нигде такого совета не встречал, потому что как бы это не очевидно и в глаза сразу не бросается. Сразу все пытаются Базу оптимизировать, запросы смотреть! :-)

  • Дмитрий

    Отличнейшая статья, спасибо!

  • Спасибо, дельный совет, особенно как найти такие параметры.

    В общих чертах знал об `autoload`, иногда использовал, но как-то детально не вникал.

    • Также следует помнить, что во многих ячейках option_value значения хранятся в виде сериализованных строчек, поэтому вручную их нельзя редактировать. Немного об этом было в статье https://wpmag.ru/2015/move-wordpress/.

  • Спасибо за статью. Я правильно понял, что именно это отвечает за сохранение настроек тем и плагинов даже после их удаления? И где именно находится эта таблица?

    • Да, она находится в базе данных WordPress, там же где и записи, пользователи и т.д.

  • Владимир

    Спасибо за статью.

  • Есть ли смысл ставить индекс при громадной таблице?

    ALTER TABLE `wp_options` ADD INDEX (`autoload`);

    • Если речь о 20-30 тыс. и более записей, то может быть и есть, хотя такой объем больше требует очистки, нежели индекса.

      На практике сталкивались с одним сайтом, который при каждом запросе создавал новый транзит, и спустя несколько недель в опциях было около 300,000 записей. Скорость запроса начинает существенно падать на 80-90k записей. Конечно это зависит от конкретной конфигурации серверов, поэтому советуем измерить скорость запроса с помощью SHOW PROFILE до и после создания индекса.

      • Спасибо, Константин.

        WooCommerce с 120к товаров (

        • Интересно, а что именно WC хранит в опциях?

          • Транзитные кеш, метаданные заказов да и еще кучу всего, сессии, корзины юзеров.
            Сессии я переделал на свою таблицу уже по этой причине