Ottimizzare symfony su DreamHost

twitornot: symfony on dreamhost optimization Qualche giorno fa, come esperimento personale, ho aggiornato (grazie all’aiuto di Federico per il layout), TwitOrNot. Di per sè l’applicazione è abbastanza banale, ma l’esercizio è stato fatto per testare alcune idee che da qualche tempo mi giravano per il cervello.

Innanzitutto, TwitOrNot migra dalla versione 1.1 alla 1.2.x di symfony, nel suo piccolo implementa l’autenticazione oAuth di Twitter non salvando in locale le password ma gestendo tutto tramite le API di quest’ultimo. Altra miglioria è stata la sostituzione, indolore visto l’esiguo numero di tabelle, di Propel con Doctrine, usando per quest’ultimo gli script di migrazione per gestire future implementazioni.

La cosa su cui però mi sono divertito di più è stata l’ottimizzazione dell’applicazione per l’utilizzo su un hosting come DreamHost utilizzando ySlow come calibro del mio lavoro.

Utilizzando lo script di analisi di Yahoo!, l’applicazione, nuda e cruda, presentava un bel D (a volte E) come risultato. Successivamente portato, tramite tweak del file di default .htaccess di symfony ad un onestissimo B (a volte C).

Vediamo in dettaglio i passaggi fatti.

Settiamo come prima cosa nel file settings.yml della nostra applicazione il parametro etags a true proseguiamo l’opera di ottimizzazione modificando il file .htaccess presente nella directory web della nostra applicazione symfony così come segue.

Fortunatamente DreamHost mette a nostra disposizione i moduli mod_expires, mod_headers e mod_deflate per Apache già abilitati, altrimenti se state usando un vostro server darò per scontato che siano stati abilitati. Inoltre DreamHost utilizza FastCGI, purtroppo però con APC disabilitato (anche se è possibile, con qualche trick, abilitarlo).

Options +FollowSymLinks +ExecCGI

<IfModule mod_expires.c>
  FileETag None

  ExpiresActive On
  ExpiresDefault "access plus 300 seconds" 
  ExpiresByType text/html "access plus 1 day" 
  ExpiresByType text/css "access plus 10 day" 
  ExpiresByType text/javascript "access plus 10 day" 
  ExpiresByType image/gif "access plus 10 day" 
  ExpiresByType image/jpg "access plus 10 day" 
  ExpiresByType image/png "access plus 10 day" 
  ExpiresByType image/x-icon "access plus 90 day" 
</IfModule>

<IfModule mod_deflate.c>
  AddOutputFilterByType DEFLATE text/html text/plain text/xml text/javascript text/css application/javascript
</IfModule>

#il restante .htaccess ufficiale 

Vediamo punto per punto quello che è stato fatto.

<IfModule mod_expires.c>
  FileETag None

Innanzitutto, dopo aver controllato che il modulo mod_expires sia stato attivato, disabilito la generazione degli ETAG di Apache in modo da forzare il browser a credere agli header che saranno passati dal server senza controllare nella sua cache. Altra conseguenza del disabilitare gli ETAG è che, se la nostra applicazione prevede l’accesso a molti file contemporaneamente (non è il caso di TwitOrNot) viene ridotto il carico di lavoro di I/O in quanto non vengono controllate, ad ogni richiesta, le informazioni sulle risorse richieste.

  ExpiresActive On
  ExpiresDefault "access plus 300 seconds" 
  ExpiresByType text/html "access plus 1 day" 
  ExpiresByType text/css "access plus 10 day" 
  ExpiresByType text/javascript "access plus 10 day" 
  ExpiresByType image/gif "access plus 10 day" 
  ExpiresByType image/jpg "access plus 10 day" 
  ExpiresByType image/png "access plus 10 day" 
  ExpiresByType image/x-icon "access plus 90 day" 

Ecco quindi che inviamo gli header modificati in modo che il browser sappia cosa, e come, mettere in cache. E’ molto importante fare un attento tuning degli expire header da configurare. Infatti se è abbastanza sensato far si che la favicon sia salvata in cache per lunghi periodi di tempo ( ExpiresByType image/x-icon "access plus 90 day" ) lo è un pò di meno per i file html che potrebbero essere modificati anche più volte al giorno. Nel caso specifico di TwitOrNot ho preferito configurare l’expire ad 1 giorno, ma avrei potuto benissimo abbassare la soglia a qualche ora, così come avrei potuto alzare la soglia di javascript e css a valori più alti.

Sconsiglio di usare Modified come metodo di default di expire, in quanto se non modificate le immagini per lunghi periodi di tempo i browser cercheranno di riscaricarle ogni volta.

<IfModule mod_deflate.c>
  AddOutputFilterByType DEFLATE text/html text/plain text/xml text/javascript text/css application/javascript
</IfModule>

Dopo aver modificato gli header e gli expire, in modo da ottimizzare la cache del browser, è tempo di concentrarsi sulla riduzione della banda utilizzata. Per farlo ho usato mod_deflate, ottima alternativa al vecchio mod_gzip e (cosa importante) supportato pienamente anche su sistemi non LAMP. La configurazione proposta per il metodo è abbastanza banale, forzo che tutti i file di testo serviti dal server siano compressi. Se pensate che da 140kb di prototype (puro) si può passare a meno di 30k la soluzione è più che soddisfacente.

Usare mod_deflate è sicuramente un’ottima soluzione se la vostra applicazione offre delle API JSON, o genera un gran numero di pagine HTML di cui volete ridurre il bandwidth footprint. Se però avete solo qualche file javascript potreste limitarvi al solo YUI compressor per ridurre voi stessi le dimensioni degli stessi, piuttosto che andare ad incidere sulla CPU della vostra macchina.

Un altro trucchetto, da usare con symfony, consiste nell’inserire all’interno di una action particolarmente pesante, e che non deve essere aggiornata troppo di frequente il seguente comando:

  $this->getResponse()->setHttpHeader('Last-Modified', $this->getResponse()->getDate($timestamp));

questo forzerà all’interno dell’output di symfony l’inserimento di un header con valore Last-Modified uguale al $timestamp dichiarato. Se ad esempio la vostra action invoca un oggetto che ha l’attributo automagico updated_at, potreste usare quest’ultimo come valore del timestamp da applicare all’header.

Come ultimi approfondimenti, lato symfony, vi suggerisco di leggere il capitolo 18 della guida definitiva a symfony ed a dare un occhio alla documentazione di memcached (a cui, forse, dedicherò un post in futuro).

ciuaz

%d bloggers like this: