Saat WordPress-sivustostasi entistä nopeamman tallentamalla sivuston osia transienteiksi. Vieläkin nopeammin toimivan sivuston saat objektivälimuistin avulla. Näin se tapahtuu!
Julkaistu
Päivitetty

Jokaisen verkkosivuston tulisi hyödyntää välimuistia tehokkaasti, jotta se latautuisi nopeasti ja sujuvasti. Vaikka suuri osa verkkosivujen sisällöstä on interaktiivista, on niissä silti elementtejä tai osioita, jotka muuttuvat todella harvoin – kuten vaikkapa otsikot tai footerit. Mikäli haluat sivustollesi entistäkin nopeampia latausaikoja, tutustu WordPressin Transienteihin. Ohjeillamme pääset alkuun!

Fragmentoitu välimuisti

WordPressin Transientien avulla voidaan valita vain tietty osa sivustoa ja tallentaa se erikseen välimuistiin. Tallennettava osio tai elementti tulisi olla staattinen, eli sen sisältö ei muutu. Kun elementti tallennetaan välimuistiin, PHP:n ei tarvitse generoida elementin HTML-koodia erikseen jokaisella sivulatauksella.

Tätä esimerkkiä varten valitsemme sivuston header-osion, jossa on seuraavat elementit:

  • Sivuston otsikko (site title)
  • Hakukenttä
  • Widget tekstiä varten

Esimerkkiä varten luotu demosivusto on seuraavanlainen:

Teema – GeneratePress Premium (GeneratePress lapsiteema käytössä) + Prime-sivupohja demosisältöineen GeneratePressin Site Library -moduulista. Demosivuston etusivu näyttää tältä:

Lisäosat – Asensin muutamia yleisiä lisäosia simuloidakseni oikeaa sivustoa, sillä sivusto ilman toiminnallisuuksia ei toimisi kovin hyvänä esimerkkinä. Lisäosat FiboSearch, WooCommerce ja GenerateBlocks asentuivat Prime-sivupohjan mukana, lisäksi muutama yleisesti käytetty lisäosa.

$ wp plugin list
+-----------------------------+----------+--------+---------+
| name                        | status   | update | version |
+-----------------------------+----------+--------+---------+
| advanced-custom-fields      | active   | none   | 5.12.2  |
| contact-form-7              | active   | none   | 5.5.6.1 |
| ajax-search-for-woocommerce | active   | none   | 1.18.1  |
| generateblocks              | active   | none   | 1.4.3   |
| gp-premium                  | active   | none   | 2.1.2   |
| jetpack                     | active   | none   | 10.9.1  |
| log-http-requests           | active   | none   | 1.3.1   |
| polylang                    | active   | none   | 3.2.3   |
| redirection                 | active   | none   | 5.2.3   |
| trustpilot-reviews          | active   | none   | 2.5.901 |
| woocommerce                 | active   | none   | 6.5.1   |
| wordpress-seo               | active   | none   | 18.9    |
| bedrock-autoloader          | must-use | none   | 1.0.3   |
| register-theme-directory    | must-use | none   | 1.0.0   |
| install.php                 | dropin   | none   |         |
+-----------------------------+----------+--------+---------+

Nopeustestit komentoriviltä

Nopeusoptimoinnissa keskeisintä on aina sivuston nopeuden mittaus, muutosten tekeminen, uudet mittaukset – ja tietysti tulosten analysointi. Testataan sivuston nopeus ennen kuin alamme tehdä muutoksia.

Seravon WP-palvelussa käytettävissäsi on wp-speed-test apukomento, joka mittaa kauanko PHP:lla kestää generoida haluttu sivu, oletuksena etusivu. Oletuksena HTTP-välimuisti ohitetaan, jonka voi kiertää --cache -vivulla.

Voit myös käyttää seuraavanlaistafor -silmukkaa:

for x in {1..20}; do curl -s -o /dev/null -w "%{time_total}\n" -H "Pragma: no-cache" $(wp option get home); done | tee /tmp/speed-test.log && echo && echo "Average loading speed, 20 requests, bypassing HTTP cache: " && awk 'BEGIN{s=0;}{s=s+$1;}END{print s/NR;}' /tmp/speed-test.log

Oheinen silmukka toimii seuraavasti:

  • Noudetaan sivustolle asetettu home URL (wp option get home) cURL:illa 20 kertaa, HTTP-välimuisti ohittaen
  • Tulostetaan tulokset terminaaliin ja tallennetaan ne väliaikaiseen tiedostoon
  • Lasketaan ja tulostetaan tulosten keskiarvo terminaaliin

Tässä ovat demosivuston antamat tulokset sekunteina (objektivälimuisti on kytketty pois päältä):

0.339449
0.366431
0.355617
0.305701
0.367452
0.421360
0.297334
0.408036
0.440382
0.374408
0.343140
0.455011
0.347240
0.318424
0.315717
0.383347
1.212737
0.374326
0.433679
0.293023

Average loading speed, 20 requests, bypassing HTTP cache:
0.407641

Ei hullumpaa! Tuloksissa näkyy hyvin tyypillinen keskiarvo kevyille WooCommerce-kaupoille. Jotta voidaan simuloida hieman raskaampaa ja hitaammin toimivaa sivustoa, lisätään lapsiteemaan sleep-funktio header.php-tiedostoon. Tästä lisää tuonnempana.

// Sleep for one second for simulation
sleep(1);

Testataan sivuston nopeutta uudelleen ajamalla sama for-silmukka kuin aiemminkin:

1.432624
1.293855
1.353330
1.384656
1.333774
1.409841
1.331775
1.368506
1.332189
1.359927
1.366213
1.356763
1.430705
1.335494
1.306732
1.305885
1.449740
1.301413
1.408697
1.328469

Average loading speed, 20 requests, bypassing HTTP cache:
1.35953

Sivun osan tallentaminen transientiin

Nyt voimme viimein aloittaa hitaasti toimivan demosivustomme nopeusoptimoinnin! Tässä esimerkissä tulemme keskittymään sivuston header-osioon, sillä sen sisältämät tiedot muuttuvat vain harvoin. Tämän vuoksi PHP:n ei tarvitse generoida headerin HTML-koodia uudelleen ja uudelleen sivulatausten myötä. Tallennetaanpa se WordPressin Transientiksi.

GeneratePress-teemassagenerate_header -toiminto generoi kaiken HTML-koodin <header> -tagien sisällä. Kopioin header.php-tiedoston pääteemasta lapsiteeman hakemistoon ja tein seuraavat lisäykset generate_header-toiminnon ympärille.

Huomaathan, että jokaisen teeman koodi on hieman erilainen, joten seuraava toimii vain esimerkkinä. Nappaa koodi talteen ja muokkaa sitä tarvettasi vastaavaksi.

    // Create a unique caching key for the transient
    $cache_key = 'advanced-caching-header-key_' . hash('crc32b', $_SERVER['REQUEST_URI']);

    // Get the header from a transient
    $output = get_transient( $cache_key );

    // If the variable output is empty, generate the header and save it in a transient
    if ( ! $output ) {

      // Sleep for one second for simulation
      sleep(1);

      // Start PHP output buffer
      ob_start();

      // Tell GeneratePress to generate the header
      do_action( 'generate_header' );

      // Get the generated header from output buffer, save it in a variable and delete current buffer
      $output = ob_get_clean();

      // Save the header in a transient with a one week expiration period
      set_transient( $cache_key, $output, WEEK_IN_SECONDS );
    }

    // Print the header
    echo $output;

Ylläolevassa esimerkissä transientiin tallennettu sisältö näytetään jokaiselle sivustolla vierailevalle, mutta on tärkeää miettiä kelle välimuistitettu sisältö tarjoillaan – sisäänkirjautuneille/sisäänkirjautumattomille, selaimen maakoodin perusteella jne. WordPress Codexista löydät funktioita, toimintokutsuja (hook) ja suotimia, joista on apua rajauksien tekemiseen.

Listataanpa transientit kyseisen koodin lisäämisen jälkeen. Käytän haussa suodinta, sillä aika moni sivustolle lisäämistäni lisäosista käyttää transienteja omien tietojensa tallentamiseen, ja haluamme tutkailla vain äsken lisättyä transientia.

$ wp transient list --search=advanced-caching*
+------+-------+------------+
| name | value | expiration |
+------+-------+------------+
+------+-------+------------+

Lista palautuu tyhjänä, sillä sivu täytyy ladata kertaalleen, jotta aiemmin lisätty koodi ajetaan sivustolla.

$ curl -IL $(wp option get home);

Nyt transienttimme näkyy listassa. Jätin pois transientin arvon, sillä se sisältää aivan kaiken header-tagien sisällä ja tekisi tästä tulosteesta vaikealukuisen. Vipu --human-readable muuttaa vielä välimuistin voimassaoloajan ihmiselle ymmärrettävämpään muotoon.

$ wp transient list --search=advanced-caching* --fields=name,expiration --human-readable
+-------------------------------+------------+
| name | expiration |
+-------------------------------+------------+
| advanced-caching-key_79d3d2d4 | 7 days |
+-------------------------------+------------+

Nyt kun koodi on kertaalleen suoritettu ja header generoitu, se tallennetaan transienttina tietokantaan viikoksi. Nyt voimmekin tutkia, onko sivuston toiminta yhtään nopeutunut. Käytetään edelleen samaa for-silmukkaa:

0.364501
0.395852
0.363992
0.381374
0.348991
0.400017
0.324965
0.433002
0.510654
0.320748
0.374619
0.386063
0.410854
0.384388
0.329187
0.395960
0.303866
0.303973
0.391231
0.318609

Average loading speed, 20 requests, bypassing HTTP cache:
0.372142

Pientä parannusta havaittavissa, ei kuitenkaan toivottu lopputulos. On hyvä pitää mielessä, että kyseessä on vain esimerkki ja demosivusto – oikealla sivustolla hyödyt ja nopeus voivat olla täysin eri luokkaa, sillä toimintoja ja sisältöä on paljon enemmän. Nämä muutamat koodirivit nopeuttivat tätä kevyttä demosivustoa kuitenkin jo n. 9 %.

Huomaa, että yksi sekunti jonka lisäsimme torkkufunktioomme (sleep) on poissa! Funktio on sijoitettu if-lauseen sisälle, joten header-osion sisältö latautuu transientista, jolloin funktiota ei suoriteta joka sivulatauksella.

Vielä enemmän nopeutta objektivälimuistilla

Aktivoidaan objektivälimuisti, jonka ansiosta transientit tallentuu palvelimen RAM-muistiin tietokannan sijaan. Tiedon hakeminen RAM-muistista on monin kerroin nopeampaa kuin levyltä.

Seravon WP-palvelussa objektivälimuisti on oletuksena käytössä jokaisella sivustolla. Mikäli objektivälimuisti syystä tai toisesta onkin pois päältä, sen saa käyttöön seuraavalla apukomennolla:

$ wp-update-object-cache -i

Vaihtoehtoisesti voit kopioida tarvittavan PHP-tiedoston Seravon WordPress-projektipohjasta:

cp /usr/share/seravo/wordpress/htdocs/wp-content/object-cache.php /data/wordpress/htdocs/wp-content

Kun objektivälimuisti on aktivoitu, tulee tyhjentää palvelimen välimuistit:

$ wp-purge-cache

Objektivälimuistin käytön mahdollistaa Redis. Seuraavalla komennolla saadaan tarkistettua sen tila:

$ redis-cli monitor

Komennon vastauksen kuuluisi olla ”OK”, ja kun sivusto ladataan, tulisi terminaaliin tulostua seuraavantyyppistä sisältöä:

1653985085.577596 [0 127.0.0.1:56954] "GET" "wp:transient:wc_term_counts"
1653985085.577662 [0 127.0.0.1:56954] "GET" "wp:term_meta:30"
1653985085.577718 [0 127.0.0.1:56954] "SETEX" "wp:transient:wc_term_counts" "2592000" "a:1:{i:24;s:1:\"5\";}"
1653985085.577783 [0 127.0.0.1:56954] "EXISTS" "wp:product_cat_relationships:47"
1653985085.577813 [0 127.0.0.1:56954] "SET" "wp:product_cat_relationships:47" "a:1:{i:0;i:24;}"
1653985085.577859 [0 127.0.0.1:56954] "GET" "wp:product_tag_relationships:47"
1653985085.577956 [0 127.0.0.1:56954] "GET" "wp:terms:get_terms-7866e70a0e23d81c5ba2c05ac6fabeb1-0.25130100 1653985084"
1653985085.579281 [0 127.0.0.1:56954] "EXISTS" "wp:terms:get_terms-7866e70a0e23d81c5ba2c05ac6fabeb1-0.25130100 1653985084"
1653985085.579322 [0 127.0.0.1:56954] "SETEX" "wp:terms:get_terms-7866e70a0e23d81c5ba2c05ac6fabeb1-0.25130100 1653985084" "86400" "a:0:{}"
1653985085.579386 [0 127.0.0.1:56954] "EXISTS" "wp:product_tag_relationships:47"
1653985085.579416 [0 127.0.0.1:56954] "SET" "wp:product_tag_relationships:47" "a:0:{}"
1653985085.579458 [0 127.0.0.1:56954] "GET" "wp:product_shipping_class_relationships:47"
1653985085.579546 [0 127.0.0.1:56954] "GET" "wp:terms:get_terms-3f69b04eb77efcae00441065ecc3520b-0.25130100 1653985084"
1653985085.581175 [0 127.0.0.1:56954] "EXISTS" "wp:terms:get_terms-3f69b04eb77efcae00441065ecc3520b-0.25130100 1653985084"
1653985085.581227 [0 127.0.0.1:56954] "SETEX" "wp:terms:get_terms-3f69b04eb77efcae00441065ecc3520b-0.25130100 1653985084" "86400" "a:0:{}"
1653985085.581345 [0 127.0.0.1:56954] "EXISTS" "wp:product_shipping_class_relationships:47"
1653985085.581387 [0 127.0.0.1:56954] "SET" "wp:product_shipping_class_relationships:47" "a:0:{}"
1653985085.583058 [0 127.0.0.1:56954] "GET" "wp:posts:120"
1653985085.584185 [0 127.0.0.1:56954] "EXISTS" "wp:posts:120"

Kyllähän se toimii. Testataanpa sitten sivuston nopeutta, jälleen kerran:

0.176452
0.179764
0.182328
0.191773
0.211399
0.183295
0.196298
0.166301
0.192860
0.208346
0.165879
0.178638
0.203015
0.168783
0.197439
0.178079
0.172654
0.174988
0.185884
0.176625

Average loading speed, 20 requests, bypassing HTTP cache:
0.18454

Latausnopeus parantui lähes 200 millisekuntia. Testataan vielä lopuksi sivuston nopeutta ilman no-cache-HTTP-otsaketta (header), jotta näemme kuinka nopeasti sivusto voi teoriassa latautua.

for x in {1..20}; do curl -s -o /dev/null -w "%{time_total}\n" $(wp option get home); done | tee /tmp/speed-test-http-cached.log && echo && echo "Average loading speed, 20 requests, HTTP cache: " && awk 'BEGIN{s=0;}{s=s+$1;}END{print s/NR;}' /tmp/speed-test-http-cached.log
0.019819
0.017404
0.017774
0.019797
0.017840
0.019566
0.025630
0.015618
0.017396
0.017400
0.015159
0.014875
0.016610
0.024926
0.022222
0.016180
0.018447
0.029069
0.016252
0.015288

Average loading speed, 20 requests, HTTP cache:
0.0188636

Kuten tulokset osoittavat, sivuston nopean toiminnan kannalta on tärkeää että sisältö on tehokkaasti ja eri tavoin välimuistitettua. Demosivuston headerin tallentaminen transientiin nopeutti sivustoa kymmenien millisekuntien verran ja objektivälimuistin kanssa vielä enemmän, mikä voi olla iso juttu mm. hakukoneoptimointia (SEO) ajatellen.

Mittaa, tee muutoksia, mittaa uudelleen, analysoi.

Sivuston nopeusoptimoinnin mantra

Ulkoisten HTTP-pyyntöjen välimuistittaminen

Tämän oppaan alussa näytin tulosteen sivustolla käytetyistä lisäosista. Lisäosa log-http-requests on mukana tarkoituksella, sillä haluan seurata millaisia pyyntöjä sivusto tekee ulkoisiin palveluihin. Ohessa esimerkkejä tyypillisistä ulkoisista pyynnöistä:

  • Kaupalliset lisäosat ja teemat tarkistavat, onko niiden lisenssit voimassa
  • Sosiaalisen median lisäosat ja integraatiot hakevat samaa dataa jatkuvasti

Mikäli ulkoiset palvelut toimivatkin hitaasti – tai pahimmassa tapauksessa eivät vastaa ollenkaan – lisäosan tai teeman koodista riippuen ne saattavat aiheuttaa hitautta sivustolla, tai estää sivuston latautumisen kokonaan. Sivuston toiminnan kannalta ei ole kovin järkevää esim. tutkia joka minuutti jonkin lisäosan lisenssin voimassaoloa. Miksei kyselyn voisi suorittaa vaikkapa kerran tunnissa tai vain kerran päivässä?

log-http-requests-lisäosalla on oma sivunsa WordPressin hallintapaneelissa (wp-admin), mutta tietokantataulun tutkiminen Adminerin kautta on paljon nopeampi tapa kerätä dataa sivuston pyynnöistä. Tässä hitaiden pyyntöjen top 20 (korkeampi runtime -arvo = pidempään kestänyt pyyntö):

Jetpack näyttää ”soittelevan kotiin” kaiken aikaa. Seuraavilla sivuilla näemme, että kyseinen lisäosa lähettää pyyntöjä moniin eri paikkoihin:

Nämä Jetpackin pyynnöt tulisi käydä huolellisesti läpi, sillä ne voivat olla tärkeitä ja oleellisia, ja pyyntöjen sisältö muuttuu koko ajan. Jos ne tallennettaisiin välimuistiin, vastaan tulisi todennäköisesti ongelmia. Jetpack asennettiin sivustolle tarkoituksella, sillä se on hyvä esimerkki paljon ulkoisiin lähteisiin pyyntöjä tekevästä lisäosasta.

Rajoita pyyntöjä transientilla

Esimerkiksi sisältöfeedin noutaminen monta kertaa päivässä sivustolta https://planet.wordpress.org ei ole tarpeen. Tallennetaan näiden pyyntöjen vastaukset transientteihin. Seravo Pluginista löytyy tätä toimintoa varten ominaisuus.

Koska ominaisuus on sisäänrakennettu Seravo Pluginiin, voimme asettaa nämä ulkoiset pyynnöt välimuistiin tunniksi suotimen avulla. Lisäsin suotimet lapsiteeman functions.php-tiedostoon: yksi suodin POST-pyynnöille, toinen GET-pyynnöille.

add_filter('seravo_cache_http_post_planet_wordpress_org', '__return_true', 10, 1);
add_filter('seravo_cache_http_get_planet_wordpress_org', '__return_true', 10, 1);

Ja taas testataan

Jotta suotimen toimintaa voidaan testata, luodaan yksinkertainen PHP-tiedosto jonka voimme ajaa komennolla wp eval-file.

<?php
echo "Request 1";
echo "\n";
wp_remote_get('https://planet.wordpress.org/feed/');
echo "Request 2";
echo "\n";
wp_remote_get('https://planet.wordpress.org/feed/');
echo "Request 3";
echo "\n";
wp_remote_get('https://planet.wordpress.org/feed/');
echo "Request 4";
echo "\n";
wp_remote_get('https://planet.wordpress.org/feed/');
echo "Request 5";
echo "\n";
wp_remote_get('https://planet.wordpress.org/feed/');

Tallenna oheinen koodi vaikkapa nimellä cachetest.php, ja aja se komentorivillä WP-CLI:n avulla:

$ wp eval-file cachetest.php

Parissa ensimmäisessä pyynnössä tulisi kestää hetken, jonka jälkeen loput nopeutuvat. Aja komento muutaman kerran, ja mikäli kaikki toimii, kaikkien viiden pyynnön tulisi suoriutua paljon nopeammin. Voimme tuplatarkistaa asian katsomalla tietokannassa tauluawp_options:

Sivuston planet.wordpress.org sisältöfeed on nyt tallennettu transientiksi.

Nyt kun osaat tallentaa tietoja transienteiksi ja lisäksi tiedät mikä vaikutus ulkoisilla pyynnöillä on sivuston suorituskykyyn, toivon että näiden ohjeiden ja vinkkien avulla pääset tutkimaan oman sivustosi toimintaa aiempaa tarkemmin! Toivottavasti pääset poistamaan sivustosi pullonkauloja, eliminoimaan hitaita pyyntöjä ja lopulta luomaan aivan oman fragmentoidun välimuistin ratkaisun. Kunhan et unohda sivuston nopeusoptimoinnin mantraa: mittaa, tee muutoksia, mittaa uudelleen, analysoi.

Menestystä mittailuun!