WordPress – Optymalizacja szybkości funkcjonowania

Jeśli poniższy tekst jest zbyt skomplikowany i nie pomógł Tobie poprawić wydajności WordPressa, to możesz skontaktować się ze mną (lub z firmą Weblabs). Zobaczymy co da się zrobić.

Podstawową cechą WordPressa jest możliwość zainstalowania dodatkowych wtyczek, które zamieniają bloga w portal społecznościowy, dają ludziom możliwość dodawania ogłoszeń albo tworzenie galerii lub podpressów. Wszystko działa w miarę sprawnie, o ile wtyczki są napisane w odpowiedni sposób. Większa liczba wtyczek także może sprawiać problemy. Co wtedy można zrobić ? Najprościej jest wgrać którąś z wtyczek od cache. Niech ona przejmie na siebie wszystkie zadania i odciąży serwer (głównie MySql). Niestety nie zawsze jest to tak proste. Cache wtyczkowe może wchodzić w kolizję z innymi wtyczkami. Nie pomoże także w momencie odświeżania wcześniej wygenerowanych danych (w końcu cache ma działać chwilowo, a nie w nieskończoność, więc po upływie ważności serwer zasypywany jest zapytaniami odnawiającymi zawartość cache). Przed włączeniem wtyczki od cache dobrze jest skupić się na znalezieniu wtyczek/plików szablonu generujących niepotrzebny ruch. Jak już zostaną poprawione, to dopiero wtedy odpalamy wtyczkę od cache i możemy cieszyć się szybkim serwisem działającym bez zgrzytów przez cały czas.

Z tego co zaobserwowałem, to WordPress jest nastawiony na minimalizację ilości pochłanianego ramu. Dlatego też dane meta do postów pobierane są w momencie odwołania się do tych danych z poziomu danego posta (np. w głównej pętli odpalamy get_post_meta) i zapamiętywane na później. Jeśli do wszystkich postów będziemy brali określone meta, to nie ma sensu odpalać zapytań po jednym (chyba, że na stronie mamy 10 dokładnie tych samych postów w kilku różnych konfiguracjach). Jeżeli są potrzebne dane meta i wiemy, które będą nam potrzebne, to rozsądniej jest pobrać posty, z nich id i w jednym zapytaniu wyciągnąć do tablicy odpowiednie dane. Kolejną rzeczą, która może bardzo męczyć WordPressa są wtyczki napisane w dość nietypowy sposób. Nietypowy, tj. w taki, który spowoduje wygenerowanie 800+ zapytań do bazy (w końcu czemu by nie ? serwer wytrzyma 😉 ). Nie wszystko da się poprawić, ale przy odpowiedniej dozie samozaparcia możemy znaleźć wtyczki do wymiany (lub naprawienia, jeśli nie ma innej).

Od czego zacząć ?
Na początek wp-config.php i linijka

define('SAVEQUERIES', true);

Dzięki niej zapytania wędrujące do bazy będą zapamiętywane na liście, którą będzie można później wyświetlić np. w footer.php (plik szablonu):

print_r($wpdb->queries);

Po zapisaniu tych linijek w odpowiednich plikach i przeładowaniu zobaczymy na dole całkowicie bezużyteczne informacje. Będą tam linijki zawierające zapytania do bazy, ich czas wykonania i ścieżkę uruchomienia. Brakuje jednak najważniejszej informacji – pliku, w którym nastąpiło wywołanie. Jest na to rada. Edytujemy plik wp-includes/wp-db.php. Znajdujemy w nim linijkę zawierającą „function get_caller” i podmieniamy linijkę zawierającą $caller[] na:

	$caller[] = $function.' ('.$call['file'].':'.$call['line'].')';

Ta prosta modyfikacja spowoduje, że w stopce strony dostaniemy także informacje o tym, w którym pliku (i linijce) wywołana została funkcja generująca zapytanie do bazy. Pozwoli to szybko i bez problemów znaleźć wszystkich potencjalnych „mącicieli”.

Przykład na bazie szablonu Gazette
Szablon jest niezbyt skomplikowany. U góry mamy wyróżnione (boks z jQuery), poniżej małe boksy z postami, po prawej okienko z możliwością przełączenia wyświetlanych linków: popularnych postów/ostatnich postów/najnowszych komentarzy/tagów i rss. Każdy z tych elementów generuje zapytania, które razem dają około 50-60 zapytań do bazy. A gdyby tak zmniejszyć ich liczbę np. o połowę ?

Na liście zapytań w stopce będą się znajdować przydatne informacje (o ile dopisane zostało zapamiętywanie pliku i numeru linii). Jedną z takich linii będzie przykładowo coś w rodzaju:

require (/index.php:23), require_once (/wp-blog-header.php:16), 
include (/wp-includes/template-loader.php:27), include (/wp-content/themes/gazette/home.php:15), 
include (/wp-content/themes/gazette/layouts/default.php:37), 
query_posts (/wp-content/themes/gazette/includes/video.php:31), 
WP_Query->query (/wp-includes/query.php:62), WP_Query->get_posts (/wp-includes/query.php:2523), 
W3_Db->query (/wp-includes/wp-db.php:933)

Jak widać, mamy pełną listę plików powiązanych z danym zapytaniem. Na pliki WordPressa (wp-db.php, query.php itd.) nie ma co zwracać uwagi. Interesujące są te, zawierające pliki z szablonów i wtyczek. W tym przypadku: /wp-content/themes/gazette/home.php, /wp-content/themes/gazette/layouts/default.php oraz /wp-content/themes/gazette/includes/video.php. Gdy już wiadomo czym się zająć można przystąpić do zmian.

Przyjrzyjmy się plikowi /wp-content/themes/gazette/includes/comments.php. Znajduje się w nim linijka:

$sql = "SELECT DISTINCT ID, user_id, post_title, post_name, post_password, comment_ID,
comment_post_ID, comment_author, comment_author_email, comment_date_gmt, comment_approved,
comment_type,comment_author_url,
SUBSTRING(comment_content,1,50) AS com_excerpt
FROM $wpdb->comments
LEFT OUTER JOIN $wpdb->posts ON ($wpdb->comments.comment_post_ID =
$wpdb->posts.ID)
WHERE comment_approved = '1' AND comment_type = '' AND
post_password = ''
ORDER BY comment_date_gmt DESC LIMIT ".$comment_posts;

Potem następuje pętla z get_avatar i get_permalink. Jedno i drugie wrzuca do bazy zapytania o dane użytkownika, dane meta posta i ew. dane meta użytkownika. Jeżeli wiemy skąd idą avatary (i jakie mają oznaczenia), ustawiliśmy odpowiedni permalink i nie przewidujemy zmian w przyszłości, to możemy lekko zmienić zawartość tego pliku:

foreach( $comments as $comment ) {
  $tab_id[] = $comment->user_id ;
}
$tab_id = implode( "','", $tab_id ) ;
$user_meta = $wpdb->get_results( "select user_id, meta_value from wp_usermeta where user_id in ( '$tab_id' ) and meta_key = 'mngl_avatar'" ) ;
$dane_meta = array() ;
foreach( $user_meta as $meta ) {
  $dane_meta[$meta->user_id] = str_replace( '.jpg', '-35x35.jpg', $meta->meta_value );
} 

(o ile używamy Mingle).
Podobnie robimy z permalinkiem. Najprościej jeśli jest taki sam jak post_name (kolumna w tabeli wp_posts). Jeżeli przyjazne linki zawierają daty, to trzeba będzie lekko pokombinować. Tak czy inaczej odpadnie kolejne zapytanie.

Cache stron
Jak już wcześniej wspominałem niektóre wtyczki mogą kolidować z wtyczkami od cache (np. z w3 total cache). W teorii powinno działać, ale czasami nie działa i nie da się z tym wiele zrobić. Nie ma jednak co się załamywać. W3TC świetnie nada się do cache zapytań dla wszystkich oraz do cache stron dla niezalogowanych (nie muszą oni widzieć wszystkiego na bieżąco). Zrobienie cache może być czasochłonne, ale pozwoli uzyskać odpowiednie efekty. Przede wszystkim możemy samodzielnie ustawić czas przechowywania wygenerowanej treści. Chmurka tagów może się zmieniać raz na dwa dni, najpopularniejsze posty raz na tydzień, a ostatnie komentarze raz na 10 minut. W odpowiednich miejscach wstawiamy kod:

if( filemtime( 'wp-content/cache/NAZWA_PLIKU.txt' ) > time() - CZAS ) {
 echo '<!-- cache --> '.file_get_contents( 'wp-content/cache/NAZWA_PLIKU.txt' ) ;
}
else {
  ob_start() ;  
  /* ... oryginalny kod pliku ... */
  $js_data = ob_get_contents();
  ob_end_flush();

  $file = fopen( 'wp-content/cache/NAZWA_PLIKU.txt', 'w' ) ;
  fwrite( $file, $js_data ) ;
  fclose( $file ) ;
}

Katalog wp-content/cache ustawiamy na pełne prawa (666 lub 777). W przypadku szablonu Gazette w taki sposób można potraktować pliki, które są wstawiane do prawej kolumny. Podstrony, a zwłaszcza strony z postami, mogą wymagać większych zabiegów. Wszystko zależy od tego jakie wtyczki są odpalane i czy ich treść może być zapamiętana na dłuższą chwilę. Stronę główną i archiwum postów także można wrzucić do cache, ale tutaj trzeba zastosować dodatkowy zabieg. Podzielone są one na podstrony po kilkanaście wpisów. Dlatego też robimy sobie zmienną:

$numer_strony = (get_query_var('paged')) ? get_query_var('paged') : 1;
...
'wp-content/cache/NAZWA_PLIKU'.$numer_strony.'.txt'
...

Jest to żmudna robota, ale przy odrobinie zacięcia można sprawić, że serwis postawiony na WordPressie nie będzie sypać komunikatami typu „503 Service Unavailable”.

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *

*