В первой части статьи я рассказал теоретические основы работы постоянных ссылок в WordPress (permalink-ов) и предложил ответить читателям, как можно оптимизировать, на мой взгляд, не очень оптимальное использование постоянных ссылок. К сожалению ни одного варианта я не дождался, а времени прошло уже немало, поэтому решил опубликовать свой вариант, то как это придумал я.
Суть метода
Так как нам нужно избавиться от постоянных генераций ссылок, то логично сгенерировать ссылку и сохранить её где-нибудь, а потом просто её от туда "брать". Сохранять я решил в Базе Данных (где же еще), в которой, как оказалось, есть даже специально поле для этого, т.е. изначально разработчики WordPress так и хотели поступить с постоянными ссылками, но, видимо, в силу не гибкости такого подхода решили, что постоянно генерировать ссылки - это лучше.
Удобство постоянной генерации заключается в том, что если у нас по каким-то причинам измениться постоянная ссылка на статью, то генерирующиеся ссылки всегда будут оставаться рабочими (актуальными), тогда как ссылки которые записываются, при изменении, могут стать нерабочими. Однако, на деле ссылки практически никогда не меняются, а если уж мы сменили домен сайта или сменили структуру ЧПУ у статей, то перезаписать (создать новые) ссылки в БД очень просто (я сделал мини плагин).
Поле куда мы будем записывать "готовые" постоянные ссылки находится в таблице wp_posts (в ней хранятся все записи) и называется guid и это поле нигде не используется, когда-то оно было создано разработчиками и теперь висит в БД "мертвым грузом", по крайней мере я ни разу не замечал, чтобы оно где-либо использовалось, и рассчитано оно как раз для того, чтобы туда записывать готовые permalink-и (я даже проверял на рабочих сайтах - туда пишутся иногда готовые пермалинки, а иногда ссылки вида http://site.ru/?p=133, причем происходит это в каком-то хаотичном порядке, зависит, вроде, от того публикуется материал до или после записи первого черновика. Да, это и не важно).
Таким образом, чтобы постоянно не генерировать ссылки нам нужно:
- При публикации/обновлении записи в поле guid таблицы wp_posts записывать постоянную ссылку на статью
- При использовании ссылки в шаблоне WordPress не использовать функцию get_permalink(), а брать уже готовую ссылку: $post->guid
Реализация
Чтобы в БД правильно записывались постоянные ссылки в поле guid, я сделал такой хак, для файла темы functions.php:
/* Хак на перезапись параметра guid при публикации или обновлении поста в админке (записывается пермалинк в текущей структуре)
--------------------------------------------------------------------------------------------------------------------------------- */
function guid_write($id){
global $wpdb;
if($id)
$wpdb->query("UPDATE $wpdb->posts SET guid='". get_permalink($id) ."' WHERE ID=$id LIMIT 1");
}
add_action ('save_post', 'guid_write', 100);
Этот хак работает когда пост/страница публикуется, обновляется. Хак так же срабатывает при удаленной публикации через xml-rpc.
Таким образом, на вновь создаваемый сайт нужно вставить этот хак в файл темы functions.php и затем в шаблоне, в циклах вывода постов использовать не
<?php echo get_permalink() ?>
а
<?php echo $post->guid ?>
В этом случае, мы будем просто брать готовую ссылку и выводить её, вообще без каких либо обращений к данным, генерации или php вычислений. Единственная проблема заключается в том, что придется залезть в шаблон и поменять все:
echo get_permalinks(); echo get_permalinks($post->ID); the_permalink(); the_permalink($post->ID);
на
echo $post->guid
Важно понимать, что, например, конструкции вида the_permalink(25); или the_permalink($post_id); , где $post_id устанавливается заранее, как ID какого-то поста, менять на echo $post->guid нельзя, потому что такие конструкции получают ссылки на определенный пост, а echo $post->guid выводит ссылку на пост, который в текущий момент находится в глобальной переменной $post.
Кстати, в моих функциях вывода: вывод последних записей, предыдущие из категории также можно и даже нужно заменить get_permalink($pst->ID) на $pst->guid. Я в них специально добавлял в выборку поле guid. Однажды даже забыл поменять на get_permalink() и в комментариях мне припомнили, что функция не чувствительная к пермалинкам 
Для уже рабочих сайтов, где поля guid уже определены "неправильно"
Если сайт уже рабочий и в нем уже немало записей, постоянные ссылки которых, записаны "неправильно" в поле guid, то для таких случаев я сделал плагин на перезапись (ремонт) поля guid:
Скачать плагин
Устанавливается плагин как обычно: копируем файл из архива fix_guid.php в каталог плагинов и активируем его в админке.
После того как поля guid "отремонтированы", плагин можно удалить. Плагин так же умеет грамотно удалять ревизии записей (удаляется все включая авто-сохранения).
Рекомендации
Если у вас структура ЧПУ не содержит тегов %category%, %tag%, %author%, то нет острой необходимости использовать этот прием оптимизации. Однако, если эти теги используются, то этот прием даст отличный эффект.
Тесты
В комментариях меня "спровоцировали" провести тесты, чтобы посмотреть как в действительности влияет на оптимизацию такой способ, вот что получилось:
С ЧПУ вида /%category%/%postname% функция the_permalink() (она же get_permalink(), только без фильтров) затрачивает 0,12 секунды на генерацию 10 ссылок. Тогда как при ЧПУ вида, например, /%year%/%monthnum%/%day%/%postname% уходит всего 0,02 секунды.
Замеры я проводил на своем компе, где, для сравнения, главная страница WordPress 3.0.1 с дефолтной темой генерируется за 1,3 секунды.
Таким образом, если у нас в ЧПУ присутствует %category% и на странице выводиться, скажем 50 уникальных (не повторяющихся) ссылок, то на генерацию этой страницы уйдет плюс 0,6 секунды (0,12*5=0,6), почти пол секунды.
Нужно отметить (я этого раньше не знал), что один раз сгенерировав ссылку WordPress, её кэширует и при повторном вызове the_permalink() для того же поста, ссылка "генерируется" в доли секунды. Имею ввиду это:
//получаем ссылку для поста с ID 25 the_permalink(25); //время 0,012 //получаем ссылку еще раз the_permalink(25); //время, примерно, 0,0001
Вроде, все сказал, что хотел, все остальное можно без труда прояснить в комментариях 
- Предыдущие записи
- Как лучше удалить слово category из постоянной ссылки (УРЛа) в WordPress ← 14 Ноябрь 2010 // 43
- Удаление виджетов из Консоли WordPress ← 4 Ноябрь 2010 // 20
- Функция вывода постов по количеству комментариев (самый комментируемые записи в WordPress) ← 25 Ноябрь 2010 // 23

Насколько быстрее сайт стал работать? По-моему прирост в производительности совсем мал. Генерация ссылки на лету занимает очень мало времени.
Можете дать технические замеры "до и после"?
Причем тут есть наверное один тонкий момент, это переадресация. Если человек стал использовать ЧПУ на сайте, то по старым ссылкам вида "?p=id" будет происходить редирект и как бы все хорошо. Если же перебить напрямую, то теряется гибкость определенная.
С одной стороны это логично, но что-то мне говорит, что это повлияет только на время генерации страницы и очень незначительно, что овчинка выделки не будет стоить, но все равно интересно взглянуть на технические замеры
Редирект разве от этого зависит? То что я предлагаю, это всего лишь не использовать
get_permalink(), который выводит ссылку на самой странице. А по какой ссылке посетитель пришел на сайт и как движок редиректит при этом - это другое. Хотя, может я не понял что имелось ввиду?В общем так и есть, если в ЧПУ тега %category% нет. А с ним чувствуется разница, причем, чем больше ссылок на странице выводиться и чем глубже структура категорий, тем значительнее.
В любом случае, минусов в этом подходе я не вижу абсолютно, единственно, если человек не понимает как это работает, тогда могут возникнуть проблемы...
Конкретных чисел к сожалению нет
, потерялись, не могу найти, помню записывал, давненько дело было.
Вот, нашел, но не совсем то - это с выключенным кэшем, на странице около 120-140 вызовов ссылок, ЧПУ
/%category%/%year%-%month%/%post_name%, версия ВП 2.9.1, на локалке:до: SQL: 1280 за 8.256 сек., 15.39 MB
после: SQL: 493 за 3.517 сек., 15.24 MB
С включенным кэшем разница конечно не такая, но тоже весьма заметна (точнее не помню). При этом, если кэш пишется в какие-то временные файлы, то нагрузка идет на файловую систему, которая на времени загрузки страницы мало отражается, а выражается в чем-то другом, насколько я понимаю.
Ну на SQL смотреть тут явно не нужно. ВП не работает с выключенным кэшем, поэтому ориентироваться в данном случае на этот показатель не стоит. Если выключить кэш, то думаю сайт долго не протянет и рассматривать это как показать не стоит в данном случае.
В штатном режиме будет одинаковый показатель.
Встроенный кэш по умолчанию включен, думаю, что не стоит на это совсем смотреть.
А примерно на вскидку разнуца будет примерно такая:
до: SQL: 25 за 3.617 сек., 15.27 MB
после: SQL: 25 за 3.517 сек., 15.24 MB
Из-за такого прироста что-то делать вручную - просто лень.
Как-нить протестирую, выложу сюда результаты.
Ваше примерно, навскидку кажется неправильное, я когда без кэша проверял с кэшем тоже проверял, просто не записал, и там была разница побольше. Ну, увидим.
А насчет возиться эт, пожалуй, правда... Такой подход я предлагаю, если делается новый шаблон или пишется очередная функция вывода или еще что-нить где участвует генерация ссылок (а она участвует практически везде). Если нет разницы, зачем платить больше?
Спасибо за проявленный интерес к статье
Протестировал на генерацию 10 ссылок уходит 0,12 секунды.
Дополнил пост, подробнее там.
Это опять все зависит от настроек сервера.
У меня на локальном хосте домашнего ПК страница целиком генерится за 0,2 сек. А на локальном хосте ноутбука эта же страница вся генерится за 0,09 сек
Ну да, но сравнительная нагрузка то остается. Само сабой разумеется, чем мощнее сервер и лучше настроен, тем меньше необходимости что-то оптимизировать...
Назрела необходимость исследования производительности WP для выявления "слабых мест".
Kama, не подскажешь, как проводил стресс-тесты?
Можно ли на локалке эмулировать "посетителей" с различными параметрами?
Такое эмулирование я не проводил ни разу. Помочь не могу в этом вопросе.
WordPress не рекомендует использование /%category%/%postname%/ и в первой части статьи вы доступно объяснили, почему. Но там же вы пишете, что при использовании кэша количество запросов к БД составляет 0.
Я небольшой знаток WordPress, поэтому не совсем понял, почему кэширование не является оптимальным (а из ваших слов я понял, что кэширование только частично решает проблему).
И чисто технический вопрос: в каком именно шаблоне находится то, что нужно заменять? Поиском по файлам я нашел 3-4 десятка файлов, в которых находятся выражения, подлежащие замене.
Замену нужно производить в файлах темы.
Вообще, если не до конца понятно как работает этот метод, то, наверное, лучше ничего не менять, потом встречные проблемы могут возникнуть.
Если можно, то лучше просто не используйте /%category%/ в ссылке.
По оптимизации - Kama, у вас на блоге jquery.lightbox.min.js.gzip - грузится 3 раза...
Опять же - много запросов идут от смайлов.
и no_avatar.gif грузится столько раз сколько пользователей без авторизации с граватара (сейчас 3)(причем 1 раз только сервер отдал её с 304 ответом, а 2 картинки грузятся последовательно и без кэша).еще один из no_avatar.gif - грузился 3.5 секунды...
Это относится к оптимизации, но не к этой статье, так что смело удаляйте мое сообщение. Я не обижусь
Это вы как смотрели, где? Не вижу в упор. Да и быть такого не может, чтобы один и тоже скрипт грузился 3 раза! Максимум, браузер заголовками обменивается с сервером на предмет, надо ли еще раз загружать.
no_avatar.gif - не может грузиться несколько раз. Там только редиректит с gavatar на файл no_avatar.gif, на редирект время уходит, а сама картинка 1 раз загружается, но это уже издержки использования gavatar.
я фаербагом смотрю. в фаерфоксе. вкладка - сеть
http://s008.radikal.ru/i304/1103/bf/604e1f8f55eb.jpg
http://s43.radikal.ru/i101/1103/85/44dba2fc488d.jpg
Нет. именно отдает несколько раз.
Спасибо за скрины и проявленный интерес! Но я опять не соглашусь, все происходит так как я и сказал.
Вы с цветами разберитесь - там не загрузка, а ожидание показано (редиректы, обмен заголовками, блокировка). А размер указан для всех элементов без исключения, не важно загружался он или нет.
Спрятанные смайлики кстати не грузятся, блокируются, там и это показано.
Значит все верно - я неправильно трактовал отчёт.