Abbiamo visto che Rails offre un potenza ed un'espressività enormi, ma al mondo non c'è niente che sia gratis. Sebbene Ruby sia un un'interprete piuttosto veloce, Rails è composto da un numero notevole di astrazioni sulle funzionalità di base e per alcune applicazioni particolari questo potrebbe degradare troppo la velocità del framework, rendendolo inadatto. Ovviamente questo non è un problema specifico di Rails, ma di qualunque ambiente di livello molto alto, un po' lo stesso problema che si propone sviluppando in Java piuttosto che in Assembly. La presenza di un framework però può non essere solo una cosa negativa, in quanto è possibile fornire un meccanismo per affrontare questi problemi.
In particolare, Rails include un sistema particolarmente potente ed articolato di caching, che permette di raggiungere ottime prestazioni con una fatica ridotta. Esistono diverse applicazioni web scritte con questa piattaforma che sono in grado di gestire milioni di accessi al giorno senza avere setup hardware eccessivi, e anche se non sempre ci si trova di fronte a problemi di questo tipo, è importante sapere che esiste un modo provato per affrontarli.
Page caching
Il sottosistema di caching di Rails lavora a tre livelli di granularità: caching delle pagine, delle azioni e dei frammenti. Il caching delle pagine salva il risultato di un'azione come file HTML in una directory di default (ma configurabile) accessibile dal Web server, in modo che nelle richieste seguenti venga servito un file statico piuttosto che svolgere di nuovo le operazioni che potrebbero avere un impatto prestazionale notevole. Sebbene questo modello di caching non sia adatto a tutte le situazioni, esso permette un'aumento di prestazioni impressionante, in quanto il Web server è in grado di servire le pagine html alla massima velocità possibile, ed anche su macchine "domestiche" si può arrivare tranquillamente a 3000 pagine al secondo.
Nella nostra applicazione d'esempio non ci sono molte pagine che si prestano a questa ottimizzazione, in quanto abbiamo disegnato l'applicazione in modo che molte pagine cambino a seconda che l'utente abbia eseguito o no il login, ma possiamo ad esempio applicare questa ottimizzazione ai feed. Per far si che sia effettuato il caching della pagina relative ad rss è sufficiente aggiungere una linea al controller:
class FeedController < ApplicationController
caches_page :rss
Come è evidente, un meccanismo di caching come questo è straordinariamente facile da applicare, ed è perfettamente ragionevole cercare di far si che alcune pagine particolarmente visitate siano riproducibili staticamente in questo modo. La cosa, ad esempio è quasi sempre perfetta per i feed, in quanto questi file tendono ad essere oggetto di moltissime richieste pur non avendo sostanzialmente nessuna interazione con l'utente.
Ma ovviamente, dopo aver salvato una pagina, è necessario far sì che la cache venga in qualche modo invalidata quando i dati non sono più aggiornati. Rails fa si che sia possibile raccogliere queste funzionalità di pulitura della cache in apposite classi, gli sweeper. Creiamo dunque una classe FeedSweeper
in app/models/feed_sweeper.rb
, fatta in questo modo:
class FeedSweeper < ActionController::Caching::Sweeper
observe Message
def after_save(message)
expire_page(:controller=>'feed', :action=>'rss')
end
end
Il metodo observe
serve a dire allo sweeper che deve attivarsi quando ci sono interventi su determinati oggetti. Si possono attivare diversi metodi di callback, che cioè verranno richiamati quando succede qualcosa, relativi ai vari momenti della vita di un oggetto, come la creazione, la distruzione o la scrittura nel database.
Ovviamente, il metodo expire_page
serve a cancellare uno dei file della cache, e prende in input il solito insieme di argomenti per costruire un url. Ma uno sweeper deve essere associato a determinate azioni, in modo che possa intervenire solo quando vengono effettuate determinate azioni, quindi bisogna usare la direttiva cache_sweeper
in un controller, come in questo caso:
class FeedController < ApplicationController
cache_sweeper :feed_sweeper, :only=>[:add_message, :add_topic]
Ancora una volta, troviamo gli argomenti :only
ed :except
, che ci permettono di associare le operazioni dello sweeper a determinate azioni. Non sarebbe utile effettuare un controllo per azioni come index
, o show
, dove gli oggetti non possono essere creati. Per quel che riguarda add_topic
, invece, lo sweeper verrà attivato, ma non sarà eseguita nessuna callback se l'oggetto non viene creato, il che farà si che non venga invalidata la cache esattamente come vogliamo.
Sebbene il page caching sia straordinariamente efficace, i suo ambiti d'uso sono abbastanza ristretti. Ad esempio non possiamo utilizzarlo per nessuna pagina che cambi dopo il login. Fortunatamente Rails mette a disposizione altri due meccanismi di caching, il fragment caching e l'action caching.