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:
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.
Indice dei contenuti
Prima di passare all’atto pratico è bene capire cosa influenza il tempo di risposta. I fattori sono sostanzialmente due:
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:
Per questo test è stata utilizzata la seguente configurazione:
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.
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.
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:
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.
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:
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:
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; }
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:
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.
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/*
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:
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.
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.
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.