Rails performances
Uno degli aspetti piu’ importanti riguardanti lo sviluppo di un’applicazione web riguarda sicuramente l’ottimizzazione delle sue prestazioni.
Ottimizzare le prestazioni significa rendere disponibili i risultati della richiesta nel minore tempo possibile.
Due sono gli aspetti cruciali che entrano in gioco ad ogni richiesta dell’utente:
* la computazione della richiesta lato backend, con accesso al database ed il recupero dei dati
* il rendering della pagina di risposta
In questi giorni ho iniziato a tenere sotto controllo le performance della mia applicazione in via di sviluppo: non disponendo del grande budget necessario a garantirle un server dedicato, ho bisogno di ridurre al minimo le latenze, in modo da garantire dei tempi di risposta dignitosi in uno shared hosting con 120 MB di memoria.
Una delle prime azioni e’ stata ottimizzare le query necessarie a recuperare i dati da mostrare in pagina.
Molto spesso infatti capitava di eseguire nella action una find del tipo
e nella view ciclare
per visualizzarne i concerti.
Tutto questo al costo di n+1 accessi al database.
La soluzione adottata consiste nell’eager loading, cioe’ recuperare con un’unica query sia l’artista che i suoi concerti:
Una seconda ottimizzazione e’ stata sostituire nelle pagine le occorrenze di
con
evitando cosi’ un’ulteriore accesso al database.
Con queste ottmizzazioni, sono riuscito a garantire una riduzione dei tempi di risposta del 40% circa.
Tutto questo non e’ pero’ abbastanza per rendere la mia applicazione performante e scalabile.
Nonostante i miglioramenti apportati, nel caso in cui piu’ utenti richiedono la stessa pagina, per ciascuno l’azione accedera’ al database per calcolare i medesimi risultati.
Per evitare questo spreco di risorse, e’ necessario utilizzare dei meccanismi di caching.
Rails mette a disposizione dello sviluppatore tre meccanismi di caching:
* page caching
* action caching
* fragment caching
Non mi dilungo con la descizione dettagliata di questi meccanismi, in quanto gia’ trattata da innumerevoli altri articoli, come:
Riassumendo la loro utilita’, si puo’ affermare che il page caching e’ sicuramente il piu’ performante, in quanto viene servita all’utente una pagina html statica senza dover passare attraverso il controller di Rails.
Questo meccanismo e’ indicato per servire pagine che rimangono prevalentemente invariate nel corso del tempo e servibili a tutti gli utenti indifferentemente.
L’ action caching e’ meno veloce della precedente, in quanto il risultato dell’azione viene cacheato, ma tutti i before e after filters vengono comunque invocati.
Questo tipo di caching e’ invitante quando si devono servire pagine ristrette ad utenti loggati o con un particolare ruolo.
Il terzo tipo di caching fornisce invece allo sviluppatore un grado di liberta’ maggiore, dandogli la possibilita’ di cacheare piccoli frammenti di view, a scapito pero’ delle prestazioni.
La mia applicazione, come ogni social network, presenta un layout personalizzato in base alla presenza di un utente autenticato o meno.
Questo aspetto in un primo momento mi ha fatto scartare il page caching (ovviamente) e l’action caching, facendomi concentrare sul fragment caching.
Purtroppo, il guadagno in prestazione del fragment caching e’ veramente irrisorio se la action deve comunque computare dei risultati necessari per la personalizzazione della pagina.
La soluzione adottata infine e’ stata l’utilizzo della conditional cache
Questo plugin permette di appendere al frammento di cache una stringa custom:
def cache_tag
if logged_in?
return '-' + current_user.to_param
else
return '-guest'
end
end
#User.rb
def to_param
"#{id}-#{login.gsub(/[^a-z1-9]+/i, '-')}"
end
In questo modo, sono riuscito a produrre frammenti di cache personalizzati per ogni utente e per visitatori guest.
Per intenderci, la action show di un artista (artistxx, id 1) produrra’ questi frammenti:
1-artistxx-guest.cache per tutti i visitatori non autenticati
1-artistxx-3-stefano.cache per lo user stefano con id 3
1-artistxx-4-pippo.cache per lo user pippo con id 4
Quindi, la prima volta che un utente visualizzara’ la pagina show dell’artista artistxx la action verra’ computata interamente, per tutte le successive verra’ usata la cache.
Un approccio di questo tipo ha comportato un grande lavoro per gestire lo sweeping (a suon di espressioni regolari
) in seguito a create, update o destroy dei vari modelli.
L’utilizzo di una cache personalizzata di queto tipo mi ha permesso di abbattere i costi di computazione delle action.
Il mio unico dubbio deriva dalla proliferazione di frammenti di cache sul filesystem: non vorrei che a lungo andare l’accesso al filesystem potesse diventare un collo di bottiglia.
Purtroppo, per il momento non posso dare una risposta valida a questo interrogativo, dal momento che l’applicazione e’ ancora in sviluppo.
Dal mio punto di vista, mi pare di aver comunque scelto l’approccio migliore…