Categorie: Guide

WordPress: come velocizzare del 150% il tempo di risposta con Nginx Fastcgi Cache

Navigare un sito web veloce è piacevole: aumenta la permanenza degli utenti, ne agevola il ritorno e per gli e-commerce incide positivamente sul fatturato. Dedicare tempo e risorse per aumentare le prestazioni del proprio sito è oggi di fondamentale importanza.

Ma l’errore più comune è quello di pensare solo ad ottimizzazioni applicative mediante plugin di caching di ogni genere, quando invece bisognerebbe partire dalla base, ovvero dalla scelta di un buon hosting provider o di un buon server dedicato o virtuale.

Oggi vi illustrerò come velocizzare di oltre il 150% il tempo di risposta su un server dedicato/virtuale utilizzando Nginx Fastcgi Cache sul quale viene gestito un e-commerce WordPress. Per tempo di risposta si intende il tempo necessario per portare a termine una richiesta HTTP su una specifica URL e viene calcolato in tre parti:

  1. Time To First Byte, TTFB
  2. Tempo di ricezione degli header
  3. Tempo di ricaricamento del codice HTML

Il Time To First Byte è uno dei fattori di ranking per i motori di ricerca, come dimostrato dal test condotto da MOZ, pertanto un’ottimizzazione contribuirà positivamente alla vostra strategia SEO.

Cosa influenza il tempo di risposta

Prima di passare all’atto pratico è bene capire cosa influenza il tempo di risposta. I fattori sono sostanzialmente due:

  1. Il tempo che impiega la richiesta a propagarsi all’interno del web, da e verso il web server; ricordiamoci che le informazioni vengono codificate in segnali elettrici che dovranno viaggiare all’interno di cavi, quindi maggiore è la distanza geografica tra server e client e maggiore sarà il tempo necessario per lo scambio delle informazioni;
  2. Il tempo che impiega il web server ad elaborare la richiesta e generare la risposta;

Mentre il primo ed il secondo fattore sono legati alla posizione geografica di client e server, che è possibile ottimizzare per mezzo di altre tecniche come l’utilizzo di CDN come Cloudflare che consentono di effettuare cache HTML, il secondo fattore è legato a:

  • Tipologia di sever;
  • Risorse allocate;
  • Configurazione dell’intero stack del server;
  • Complessità dell’applicazione PHP;
  • Dipendenze e/o eccessivo utilizzo di risorse esterne come database o accessi al disco.

Per questo test è stata utilizzata la seguente configurazione:

  • Server VPS con 4 GB di RAM, 1 Gbit/s NIC, 100 GB di disco SSD e sistema operativo Ubuntu server 16.04.3 LTS;
  • Web server Nginx versione 1.13.3 con PHP-FPM 7.1 e MariaDB come DBMS;
  • WordPress 4.8.2 con WooCommerce 3.2.1 e tema Storefront con dati di prova forniti con il tema (una ventina di prodotti, qualche categoria e immagini).

Il Server VPS, dislocato in Germania, gestisce altri siti ed Nginx è stato configurato per un uso ottimale della CPU. Nonostante il dominio dell’e-commerce sia configurato per essere gestito da Cloudflare, durante il test sono state completamente disabilitate ottimizzazioni e cache del servizio CDN ad eccezione di HTTP/2, supporto in ogni caso già presente nella configurazione di Nginx.

WordPress è un CMS pesante?

La complessità e lentezza di WordPress sono direttamente proporzionali al numero di plugin installati che a parte aumentare l’elaborazione da parte del server molte volte introducono file statici (immagini, CSS e Javascript) che, se non opportunamente ottimizzati, bloccano il normale processo di rendering del parser del browser dando vita al famoso effetto “pagina bianca” che trasmette una sensazione di lentezza generale.

In realtà la caratteristica modulare WordPress, unita alla semplicità dell’interfaccia e del core, è quello che ha permesso a questo software di diventare il CMS più utilizzato al mondo. Naturalmente le configurazioni che lo rendono dinamico richiedono molti accessi al database, di fatto WordPress già nella sua versione nativa effettua molte query SQL.

Plugin e temi inoltre non sempre sono realizzati seguendo le regole degli sviluppatori del core, dando molto spesso vita a software che fanno abuso di risorse server o che introducono elementi inutili che rallentano il client.

Giusto a titolo informativo basti notare come numerosi plugin richiedono il download di file CSS e Javascript anche in pagine in cui non sono utilizzati o addirittura inseriscono script inline byepassando l’utilizzo della funzione wp_add_inline_script, non consentendo ai plugin di ottimizzazione di poterli maneggiare senza creare malfunzionamenti generali.

L’obiettivo è dunque ridurre al minimo l’elaborazione PHP ed il numero di accessi al database per ridurre il tempo di risposta e consentire al server di poter gestire più utenti contemporaneamente.

Benchmark iniziale

Per misurare le prestazioni abbiamo utilizzato loader.io, un servizio che permette di misurare diverse metriche tra cui il tempo di risposta stressando il server con un preciso numero di utenti contemporaneamente connessi in una precisa unità di tempo.

In questo caso abbiamo condotto due test:

  • Test 1: 100 utenti connessi nell’arco di 60 secondi;
  • Test 2: 1000 utenti connessi nell’arco di 60 secondi.

Come prima cosa analizziamo il tempo di risposta medio senza ottimizzazioni.

Dalle immagini è possibile verificare come il tempo di risposta medio risultante del primo test sia di 341 ms mentre sale a 621 ms quello del secondo test.

Configurazione Nginx FastCGI cache

Per aumentare significativamente il tempo di risposta abbiamo configurato una cache HTML con un Time To Live (TTL, tempo di vita della cache) di 60 minuti affinché la pagina, se presente in cache, venga fornita direttamente da Nginx anziché essere elaborata sul momento, trasformando di fatti un sito dinamico come WordPress nel suo equivalente statico. Di seguito la configurazione adottata sul Virtual Host:

# Cache FastCGI
fastcgi_cache_path /etc/nginx/cache levels=1:2 keys_zone=MYAPP:100m inactive=60m;
fastcgi_cache_key "$scheme$request_method$host$request_uri";

# Redirect completo ad HTTPS
server {
  listen 80;
  listen [::]:80;
  server_name test.speedywordpress.it;
  return 301 https://$host$request_uri;
}

 

server {

  listen 443 ssl http2;

  server_name test.speedywordpress.it;

  ssl_certificate /etc/ssl/certs/nginx.pem;
  ssl_certificate_key /etc/ssl/private/nginx.key;

  root /var/www/test.speedywordpress.it/;
  index index.php

  access_log /var/log/nginx/access.log;
  error_log /var/log/nginx/error.log;

  if ($http_user_agent ~* LWP::Simple|libwww-perl) {
    return 403;
  }

  if ($http_user_agent ~ (msnbot|Purebot|Baiduspider|Lipperhey|Mail.Ru|scrapbot) ) {
    return 403;
  }

  # Fastcgi cache rules
  include /etc/nginx/fastcgi-cache-rules.conf;

  location / {

    try_files $uri $uri/ /index.php?$args;

    # Corregge l'errore di richiesta di file statici in Ajax
    error_page 405 = $uri;

  }

  location ~* \.(eot|ttf|woff|woff2)$ {
    add_header Access-Control-Allow-Origin *;
  }

  location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ {
    expires 365d;
  }

  location ~ \.php$ {
    try_files $uri =404;
    fastcgi_split_path_info ^(.+\.php)(/.+)$;
    fastcgi_pass unix:/var/run/php/php7.1-fpm.sock;
    fastcgi_index index.php;
    fastcgi_param SCRIPT_FILENAME $document_root/$fastcgi_script_name;
    include fastcgi_params;

    # Skip cache based on rules on /etc/nginx/fastcgi-cache-rules.conf;
    fastcgi_cache_bypass $skip_cache;
    fastcgi_no_cache $skip_cache;

    # Define memory zone for caching. Should match key_zone in fastcgi_cache_path above.
    fastcgi_cache MYAPP;

    # Define caching time.
    fastcgi_cache_valid 200 60m;
  }

}

Le prime due righe di codice identificano la directory di caching, la dimensione massima della cache, 100 MB in questo caso, e la chiave univoca per identificare una pagina. In questo caso abbiamo utilizzato tutto ciò che abbiamo per poter identificare univocamente la pagina in cache, ovvero:

  • Scheme: HTTP o HTTPS
  • Request method: GET, POST, ecc..
  • Host: il dominio del sito
  • Request URI: la pagina richiesta

Dopodiché all’interno del Virtual Host abbiamo incluso del regole di caching, presenti all’interno del file /etc/nginx/fastcgi-cache-rules.conf, le regole del bypass della cache in conformità con quanto riportato all’interno di fastcgi-cache-rules.conf ed infine il TTL di 60 minuti.

Come regole di caching abbiamo le seguenti:

  • Mai effettuare cache di richieste HTTP POST;
  • Non inserire in cache le pagine le cui URL presentano parametri GET;
  • Non effettuare cache per utenti loggati o per richieste a determinate pagine, special modo quelle dinamiche dell’e-commerce;

Di seguito il contenuto di fastcgi-cache-rules.conf:

# Allow caching of requests which contain the following headers.
fastcgi_ignore_headers Cache-Control Expires Set-Cookie;

# Show the cache status in server responses.
add_header Fastcgi-Cache $upstream_cache_status;

set $skip_cache 0;

# POST requests and urls with a query string should always go to PHP
if ($request_method = POST) {
  set $skip_cache 1;
}

# Non fare il caching di pagine con parametri
if ($query_string != "") {
  set $skip_cache 1;
}

# Don't cache uris containing the following segments
if ($request_uri ~* "/wp-admin/|/xmlrpc.php|wp-.*.php|/feed/|index.php|sitemap(_index)?.xml") {
  set $skip_cache 1;
}

# Skip cache on WooCommerce pages
if ($request_uri ~* "/store.*|/cart.*|/my-account.*|/checkout.*|/addons.*") {
  set $skip_cache 1;
}

# Don't use the cache for logged in users or recent commenters
if ($http_cookie ~* "comment_author|wordpress_[a-f0-9]+|wp-postpass|wordpress_no_cache|wordpress_logged_in") {
  set $skip_cache 1;
}

Verifica del funzionamento di Fastcgi Cache

Per verificare il funzionamento basta aprire la console di Google Chrome, ricaricare la pagina e visualizzare gli header di risposta. In caso di cache bypass otterremo il seguente risultato:

Da notare la voce fastcgi-cache. In questo caso la richiesta è stata effettuata durante una sessione di login, che secondo le regole di caching impostate forza il bypass della cache. In caso di pagina non in cache, il server risponderà con un MISS, contrariamente con un HIT:

Benchmark finale

Non rimane che riavviare Nginx con la nuova configurazione e riavviare i due test per ottenere il benchmark finale.

Come potete ben notare, il tempo di risposta per il secondo test è sceso da 621 ms a 244 ms, un miglioramento di oltre il 150%. Il test è stato eseguito per 5 volte ed il tempo di risposta si è stabilizzato sotto i 250 ms.

Come svuotare la cache FastCGI con una richiesta HTTP

Per svuotare la cache di una precisa pagina con una richiesta HTTP occorre installare Nginx con il modulo fastcgi_cache_purge. Su Ubuntu il modulo è disponibile nella versione extras di Nginx:

sudo add-apt-repository ppa:nginx/development
sudo apt-get update
sudo apt-get remove nginx*
sudo apt-get install nginx-extras

Completata l’installazione, assicuratevi che all’interno di /etc/nginx.conf siano inclusi i moduli abilitati in /etc/nginx/modules-enabled con la seguente direttiva inserita prima di http {}

include /etc/nginx/modules-enabled/*.conf;

Nel nostro caso abbiamo incluso solo il modulo interessato:

include /etc/nginx/modules-enabled/50-mod-http-cache-purge.conf;

A questo punto non rimane che inserire la seguente regola nel Virtual Host e riavviare Nginx:

location ~ /fastcgi-purge(/.*) {
  fastcgi_cache_purge MYAPP "$scheme$request_method$host$1";
}

Per invalidare la cache basterà visitare la pagina seguita da fastcgi-purge. Ad esempio se volessimo invalidare la pagina /contatti, basterà visitare /fastcgi-purge/contatti. Nota bene: nella versione free di Nginx non è possibile utilizzare fastcgi_cache_purge per effettuare un purge in wildcard, come ad esempio /fastcgi-purge/categoria/*

Montare FastCGI Cache in RAM

Purtroppo Nginx conserva la cache su disco. Nel caso di dischi SSD potrebbe non essere un grosso problema mentre lo è per i vecchi dischi SATA, i cui accessi al disco sono molto lenti. La RAM è sicuramente più veloce di qualsiasi disco e lavorando in RAM è possibile ottenere tempi di risposta ancora migliori. Per farlo seguite i seguenti passi:

  1. Aprite il file /etc/fstab
  2. Inserite la seguente riga per creare una partizione in RAM da 100 MB sulla stessa directory di caching: tmpfs /etc/nginx/cache tmpfs defaults,size=100M 0 0
  3. Salvate il file
  4. Montate la partizione lanciando il seguente comando: sudo mount -a
  5. Riavviate Nginx

FastCGI Cache come Microcaching

Per portali web molto trafficati con contenuti aggiornati molto velocemente, un TTL di 60 minuti potrebbe non essere tollerabile. In casi come questi, per migliorare ugualmente le prestazioni e consentire al server di gestire molti più utenti contemporaneamente connessi, può essere utile abbassare il TTL a 10-15 secondi dando vita ad una microcache.

Può sembrare una sciocchezza ma su siti molto trafficati potrebbe fare la differenza tra un sito non raggiungibile ed un sito fruibile, aumentando anche di oltre il 2000% il numero di richieste normalmente gestibili. 

FastCGI Cache e Mobile

A volte può tornare utile separare la cache desktop da quella mobile. Capita infatti che temi e plugin funzionino in modo diverso dal punto di vista server se la pagina viene visitata da un dispositivo mobile pertanto è corretto suddividere le due cache per evitare conflitti.

Farlo è molto semplice e richiede solo alcune piccole modifiche. Come prima cosa occorre aggiungere le seguenti regole in alto al blocco http{ } del file nginx.conf:

map $http_user_agent $mobile_request
{
 default fullversion;

"~*ipad" fullversion;

"~*android.*mobile" mobileversion;
 "~*iphone" mobileversion;
 "~*ipod.*mobile" mobileversion;
 "~*BlackBerry*Mobile Safari" mobileversion;
 "~*BB*Mobile Safari" mobileversion;
 "~*Opera.*Mini/7" mobileversion;
 "~*IEMobile/10.*Touch" mobileversion;
 "~*IEMobile/11.*Touch" mobileversion;
 "~*IEMobile/7.0" mobileversion;
 "~*IEMobile/9.0" mobileversion;
 "~*Firefox.*Mobile" mobileversion;
 "~*webOS" mobileversion;
}

Fatto questo modificare fastcgi-cache-rules.conf ed aggiungere le seguenti righe subito dopo set $skip_cache 0;:

set $var_desktop "fullversion";
set $var_mobile "mobileversion";

ed in fondo al file la seguente regola:

if ($http_cookie ~ 'wptouch-pro-view=desktop') {
 set $mobile_request fullversion;
}

A questo punto non rimane che aggiungere $mobile_request alla direttiva fastcgi_cache_key:

fastcgi_cache_key "$scheme$request_method$host$request_uri$mobile_request";

E sostituire l’eventuale regola di purge con richiesta HTTP con le seguenti:

location ~ /fastcgi-purge(/.*) {
 access_log off ; log_not_found off;
 fastcgi_cache_purge MYAPP $scheme$request_method$host$1$var_desktop;
}
location ~ /fastcgi-mpurge(/.*) {
 access_log off ; log_not_found off;
 fastcgi_cache_purge MYAPP $scheme$request_method$host$1$var_mobile;
}

E riavviare Nginx. In questo caso per effettuare il purge della versione mobile della cache della pagina /contatti basterà visitare /fastcgi-mpurge/contatti.

Conclusione

Se usati correttamente, i sistemi di caching possono aumentare notevolmente le prestazioni di un server consentendogli di gestire molti più utenti rispetto a quelli che potrebbe se dovesse elaborare da zero ogni richiesta. Se anche tu desideri avere un server veloce e far decollare le prestazioni del tuo sito WordPress, commenta o contattami per ricevere una consulenza.

Salvatore Fresta

Disqus Comments Loading...
Share
Pubblicato da
Salvatore Fresta
Tag: wordpress

Recent Posts

Cloudflare: come identificare la location del server

Cloudflare si antepone tra il server di origine e la richiesta dell'utente per servire file…

18 Marzo 2019

Ottimizzazione MySQL: come scegliere il valore di InnoDB Buffer Pool

L'ottimizzazione del database MySQL è tra i tuning più importanti in tema di performance web.…

5 Marzo 2019

Cache di post e pagine WordPress con Cloudflare

Cloudflare è un ottimo servizio utilizzato sia per migliorare le performance in caso di traffico…

19 Settembre 2018

WordPress, CDN, offloading e compressione immagini

Una delle tante pratiche di ottimizzazione del tempo di caricamento pagina quando si riceve un…

22 Maggio 2018

WordPress e WebP: guida completa

WebP è un formato di immagine sviluppato da Google ed appositamente pensato per ottimizzare il…

17 Maggio 2018

Caching avanzato con i Service Worker

Qualche settimana fa abbiamo visto cosa sono i service worker e come utilizzarli per creare…

12 Maggio 2018