Ускоряем генерацию страниц в три раза, а сам WordPress на 30%

28 комментариев

Глянул я тут на время генерации страницы WP и обомлел — ~1 секунда на генерацию главной. Не позволительно много и долго.

Путем нехитрых манипуляций удалось снизить это время до ~0.3 секунд. Почему в заголовке написано на 30%? Потому что помимо всего прочего на дополнительные 30% удалось ускроить сам WordPress, слазив в основной код и добавив использование memcached.

Но об этом всем по порядку под катом.

Серверные оптимизации

Как говорил ранее, я выкинул apache нафиг. Блог у меня крутится на nginx + php-fpm.

Перенес таблицы с MyISAM на InnoDB. Да, да все говорят мол MyISAM производительнее для выборок типа SELECT, но нет. Смотрим тесты http://vbtechsupport.com/657/ — на выборку результат практически одинаковый, а вот в смешанном режиме InnoDB лидирует. Подкрутил немного конфиги, увеличил буферы для mysql. Теперь:

Поставил php APC. Подправил конфиг выделил чуть больше памяти под кеш, имеем практически 100%-ое использование кеша:

4

Ну и в заключении поставил memcached.

Первичные оптимизации

Выкинул из вордпресса ненужные плагины. У меня их скопилось достаточно много, нужный функционал из некоторых объеденил в один свой плагин (это как например для какой нибудь малюсенькой функции требуется ставить здоровенный монструозный плагин).

Поставил W3 Total Cache. Включил Page cache Как оказалось в связке с nginx он просто бесподобен. По факту он генерирует html страницы, которые уже отдает nginx пользователям. В итоге wordpress и php не используется совсем. Время загрузки таких закешированных страниц — ~0.08 секунды. Гуд? Гуд.

Включил object cache (тут как раз и пригодился memcached).

В общем после всех этих манипуляций WP стал генерировать главную страницу за ~0.4 в среднем. Многовато.

Лезем в WordPress

Запускаем профайлер и смотрим что у нас работает дольше всего:

3

Как видно 30% времени сжирает загрузка переводов из mo файлов. Вообще не пойму почему для этого в wordpress-е не используются стандартные средства. Но факт остается фактом — файлы парсятся средствами php кода, и это адово долго.

Открываем файл /wp-includes/pomo/mo.php строки 23-28 функция import_from_file:

Меняем ее на такую:

Ищем функцию import_from_reader, строки 145-225:

И модифицируем ее:

objMemcache() — моя простая надстройка над базовым классом memcache — скачать можно тут class.Memcache

Запускаем. Загружаем страницу — супер — ~0.2-0.3 секунды на генерацию страницы.

Да понятно что изменения в самом ядре WordPress-а не айс. И понятно, что они пропадут после следующего обновления вордперса. Но с другой стороны — почему бы и не попробовать то?

Еще немного оптимизаций

Помимо всего прочего меня в WordPress бесит хренова куча стилий и JS, так как каждый плагин норовит подключить отдельные JS скрипты и стили. Каждый отдельный файл — это отдельный запрос на сервер => дополнительное время к загрузке страницы.

Решается это дело просто — ставим плагин BWP Minify. Он умеет грамотно сжимать скрипты и стили и объединять их в один файл. Тоже очень удобно, и дает огромный плюс — избавляемся от десятка ненужных запросов клиента к серверу.

Итог

Первичная генерация страницы у меня занимает ~0.2-0.3 секунды. А если страница уже была закеширована, то минуя php она загружается за ~0.08 секунды. Вполне неплохо на мой взгляд :)

Помимо всего этого, рекомендую включить протокол HTTP/2 на своем сервере. Это даст огромный плюс в скорости загрузки.

    1. Дмитрий Амиров Автор

      Использую XDebug. Для просмотра логов использую Webgrind. В хроме стоит расширение Xdebug helper — очень удобно включать отладку/профилирование когда нужно, а не вручную собирать HTTP запрос в сторонних тулзах.

  1. Сергей

    Дмитрий я совсем новичок, но ваша статья бесподобна!

    Возник такой вопрос куда и как использовать вашу надстройку: class.Memcache

    Я новичок , но немного шарю, просто объясните что куда и как , если не затруднит

    1. Дмитрий Амиров Автор

      Благодарю.

      Этот класс нужно приинклудить в любом месте WP. А дальше в статье описаны файлы которые нужно менять. Но я бы на вашем месте не заморачивался бы. Все эти изменения слетят после первого же обновления WP.

  2. Сергей

    Дмитрий, скажите не могли бы вы мне помочь :

    Есть выделенный сервер Intel i3 3.3 ГГц, 4 Гб DDR3, 2×1000 Гб SATA
    Чистый на debian7-x64
    Ispmanager 5 lite

    Необходимо:
    1. Настроить сервер для работы с сайтами только на wordpress 10-20 штук ( 50-200 запросов в день на каждом)
    2. Php-fpm+ nginx или Apache+nginx
    3. Mysql
    4 memcache
    5. Php apc
    ===========
    Цена вопроса ?

    1. Дмитрий Амиров Автор

      Честно сказать, я не занимаюсь настройкой серверов на заказ)

      Конфигурация, описанная выше, подходит для одного сайта (nginx + php-fpm). Но не совсем подходит для 10-20 сайтов. В основном главная причина — отсутствие поддержки аналога .htaccess у nginx. Т.е. при добавлении/удалении сайта нужно каждый раз править конфиг nginx.conf добавляя правила реврайта. А это очень неудобно. Поэтому я думаю что в вашем случае подойдет только nginx+apache.

      А в остальном в принципе все просто, ставите на сервер memcahe, apc. Их как правило настраивать не надо. Тюните mysql по рекомендациям этого скрипта. На каждый вордпресс ставите плагин W3 Total Cache. Включаете Page Cache. Включаете Оbject cache и Database cache (кешировать в memcache).

  3. Сергей

    Мне очень понравилась ваша статья, скорость ответа сайта поражает.

  4. Евгений

    Спасибо за статейку. Спасибо комментатору. У меня с ним схожая ситуация и как раз пригодились ответы. Тоже была мысль обратиться за настройкой vps
    Спасибо Дмитрий.

  5. Slam

    Здравствуйте,

    Скажите, пожалуйста, а как боретесь с кешированием админки в APC. Зачастую, когда отметил чекбокс в найтроках плагина/WP, то следующая загрузка дает страницу из кеша со снятым чекбоксом.
    Также, перейти на php7 не пробовали?

    1. Slam

      Также, вместо BWP Minify можно Autoptimize поставить. Автор плагин поддерживает и минификации в кеше хранятся, а не каждый раз генерируются. Удобно в общем.

      1. Дмитрий Амиров Автор

        BWP тоже минификации хранит в кеше. Так что особой разницы нет)

        1. Slam

          Оно мало того, что минимизирует и хранит в кеше, но и в конце документа подгружает скрипты.
          Также, позволяет CSS загружает в конце документа, а в шапке css critical path выводит.

          Сейчас буду пробовать это модулем pagee speed для nginx сделать, но Autoptimize на порядок BWP мощнее. Попробуйте, понравится.

          1. Дмитрий Амиров Автор

            Хм, но опять же в защиту BWP скажу. Он также умеет переносить CSS и JS в конец документа. Только critical path сам строить не умеет. Но сомневаюсь, что Autoptimize тоже умеет это делать.
            На самом деле я не гонюсь за комбайнами. Если плагин в целом проще, и он выполняет возложенную на него функцию, то он лучше. По крайней мере в моем случае :)

    2. Дмитрий Амиров Автор

      Насчет кеширования админки — замечал такую странность. Но сильно не мешало. В крайнем случае отключал кеширование, менял настройки и включал кеширование обратно.

      На php7 — не пробовал, у меня debian wheezy стоит. На нем вроде нет стандартного пакета с php7, подключать сторонний репозиторий не хотелось, а собирать вручную — лень)

      1. Slam

        Бывает так, что оно другому пользователю отдает Вашу сессию. Бывает, что после обновления поста, отдает его старую версию.
        Если трафика много, в очередь может несколько вариаций одной и той же страницы забивать и по очереди отдавать и если это не котролировать, то работа над лонгридом, к примеру, может превратиться в кошмар.
        Вот как раз в процессе поиска решения, как это бороть :( Теоретически, нужно на хуки insert_post, update_post и т.д. вешать очистку кеша.

        1. Дмитрий Амиров Автор

          Отдает чужую сессию? Это что то совсем странное.

          Вообще тот же w3tc ставит сам хуки на изменение поста, и обнуляет кеш при изменении страницы.

  6. Илья

    Здравствуйте!
    Подскажите, пожалуйста, такой вопрос.

    1. В чем смысл использования плагина Total Cache, если можно внутри htaccess прописать следующие строчки:

    FileETag MTime Size

    ExpiresActive on
    ExpiresDefault «access plus 1 month»

    # Compress HTML, CSS, JavaScript, Text, XML and fonts
    AddOutputFilterByType DEFLATE application/javascript
    AddOutputFilterByType DEFLATE application/rss+xml
    AddOutputFilterByType DEFLATE application/vnd.ms-fontobject
    AddOutputFilterByType DEFLATE application/x-font
    AddOutputFilterByType DEFLATE application/x-font-opentype
    AddOutputFilterByType DEFLATE application/x-font-otf
    AddOutputFilterByType DEFLATE application/x-font-truetype
    AddOutputFilterByType DEFLATE application/x-font-ttf
    AddOutputFilterByType DEFLATE application/x-javascript
    AddOutputFilterByType DEFLATE application/xhtml+xml
    AddOutputFilterByType DEFLATE application/xml
    AddOutputFilterByType DEFLATE font/opentype
    AddOutputFilterByType DEFLATE font/otf
    AddOutputFilterByType DEFLATE font/ttf
    AddOutputFilterByType DEFLATE image/svg+xml
    AddOutputFilterByType DEFLATE image/x-icon
    AddOutputFilterByType DEFLATE text/css
    AddOutputFilterByType DEFLATE text/html
    AddOutputFilterByType DEFLATE text/javascript
    AddOutputFilterByType DEFLATE text/plain
    AddOutputFilterByType DEFLATE text/xml

    2. Нужно ли ставить отдельный плагин BWP Minify, если в последних версиях Total Cache уже есть функция minify?

    1. Дмитрий Амиров Автор

      Суть плагина Total cache в том что он кеширует страницу на сервере в готовую html-ку. Т.е. без плагина у вас происходит каждый раз следующее:
      запрос -> сервер -> php-интерпретатор -> wordpress -> html-код -> ответ

      С плагином же, интерпретатор php не вызывается каждый раз, для каждого пользователя и их запросов, и все сокращается до:
      запрос -> сервер -> html-код -> ответ
      т.е. убирается самая ресурсоемкая часть всего процесса.

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

      Насчет минификаци — тут уж смотрите сами. Чем меньше разных плагинов — тем лучше конечно. Мне просто встроенный чем то не понравился, уже не помню чем.

      1. Илья

        Т.е. вот эта часть настроек — плохая, по вашему мнению. Она как раз отвечает за кэширование:
        FileETag MTime Size

        ExpiresActive on
        ExpiresDefault «access plus 1 month»

        А вот эта часть — нормальная. Она отвечает за gzip-сжатие. Верно?
        # Compress HTML, CSS, JavaScript, Text, XML and fonts
        AddOutputFilterByType DEFLATE application/javascript
        AddOutputFilterByType DEFLATE application/rss+xml
        AddOutputFilterByType DEFLATE application/vnd.ms-fontobject
        AddOutputFilterByType DEFLATE application/x-font
        ……………………………………………………….

        И вместо прописывания кэшировочной части — можно поставить Total Cache и аккуратно его настроить?

        1. Дмитрий Амиров Автор

          Да, я бы на вашем месте поставил бы плагин кеширования и не заморачивался.

  7. vinaction

    Насколько выбор php APC предпочтительнее XCache?
    В википедии пишут, что APC активно поддерживает только php5.4 и с 2012 года не поддерживается, не разрабатывается. В отличие от XCache у которого полноценная поддержка php5.6 и который поддерживается.
    Не было ли у вас тестов с подобной конфигурацией, где вместо php APC работал бы XCache?

    1. Дмитрий Амиров Автор

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

      Но в целом я не думаю что есть смысл вообще заморачиваться на эту тему, с php5.5 в нем по дефолту используется OPcache. Так что необходимость ставить сторонние расширения для кеширования опкодов отпадает.

      Моя статья в этом плане немного устарела :)

      1. vinaction

        Спасибо за ответ!
        Но тогда возникает следующий вопрос — не планируете ли вы сделать обновленную версию этой статьи с текущей конфигурацией хостинга для ваших WP сайтов?

        1. Дмитрий Амиров Автор

          Хороший вопрос. На самом деле в последнее время я стал слишком ленивой жопой и перестал писать новые статьи( Так что, боюсь, если подойти объективно к вашему вопросу, в ближайшее время — точно нет. Мои извинения(

  8. Андрей

    Здравствуйте, спасибо за статью, вдохновляет) Возник вопрос, может у вас есть мысли по этому поводу:
    Столкнулся с проблемой, когда в системе много таксономий (у меня их более 1000) , скорость сайта упала на ~3 секунды (взялись они от woocommerce, т.е. в ней глобальные атрибуты создаются отдельными таксономиями pa_attr1, pa_attr_2, … pa_attrN, а регистрация таксономий происходит практически на каждой странице бек- и фронтенда, т.е. woo делает register_taxonomy() в цикле с каждым атрибутом на кайдой странице сайта ) Вот пример загрузки главной страницы: https://yadi.sk/i/9Z5inojCbhR8gA

    Собственно, появилась мысль закэшировать результат register_taxonomy для каждой из них в memcache, но функция возращает $taxonomy_object, его я так понимаю не получится так закэшировать? Или есть другие способы обойти это? Спасибо.

Добавить комментарий

Прочли запись? Понравилась? Не стесняйтесь, оставьте, пожалуйста, свой комментарий. Мне очень интересно, что вы думаете об этом. Кстати в комментарии вы можете задать мне любой вопрос. Я обязательно отвечу.

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