A Ovest Di Paperino

Welcome to the dark side.

Caching

Come promesso in precedenza eccomi a parlare di codice e di come ho implementato il sistema di cache in blogoo. È diventato necessario quando il numero di commenti per post ha cominciato a superare una certa soglia: il mio ISP è molto generoso ma prevenire resta sempre meglio che curare. Senza la cache la pagina viene renderizzata ad-hoc ogni volta che viene invocata. Questo è un peccato perché buona parte dell’output restituito al browser è ‘facilmente’ sistemabile in una cache. Facilmente è tra virgolette perché gli elementi della sfida sono tanti:

  1. è interessante fare tenere nella cache sia le pagine che puntano ad un singolo post, sia le pagine che contengono più di un post (la home-page, gli archivi, le pagine per tag, ecc.), sia dei “pezzi di pagina” (gli ultimi commenti, tag-cloud, archivio, ecc.) 
  2. la cache deve gestire correttamente sia le pagine visibili solo dall’editor del blog, sia quelle visibili dagli utenti cercando di non fare confusione
  3. la cache deve essere invalidata solo quando necessario; nel dubbio se sia necessario o meno è meglio invalidare che servire contenuto “scaduto”.
  4. Se possibile è meglio invalidare solo lo stretto necessario: ad esempio la tag-cloud dovrebbe essere invalidata solo in caso di modifica/aggiunta di post o di categorie
  5. la cache deve essere invalidata quando del contenuto “post-datato” diventa automaticamente visibile

Analizzando la questione a tavolino sono emerse un paio di cose. Gli eventi invalidanti sono in generale 2: l’aggiunta/modifica di un commento, l’aggiunta/modifica di un post. Tutto il resto, ad esempio il punto 5., può essere ricondotto ad uno dei due casi. L’aggiunta/modifica di un post poi può a sua volta generare due tipi diversi di eventi: un evento che invalida la “collezione dei post” ed un evento che invalida il singolo post. Ad esempio un commento sul post X invaliderà la collezione dei post (in quanto tale collezione potrebbe mostrare il numero di commenti per quel post), invaliderà il post X ma non il post Y. Per cui se un utente clicca sul post Y dopo che è stato scritto un commento su un altro post, si ritroverà il post servito dalla cache.

L’invalidazione poi tiene conto di diversi fattori. Ad esempio cancellare un commento moderato non deve avere effetti sulla cache in quanto non visibile a priori. Stessa cosa per la pubblicazione di un draft e così via.

Fatto questo, il resto diventa un gioco da ragazzi: ogni elemento inserito nella cache porta in dote la sua lista di dipendenze. Un componente separato da me battezzato CacheManager si mette in ascolto dei vari eventi (creazione/modifica/cancellazione dei post/commenti) e in base allo stato della cache, al tipo di evento e le condizioni a contorno (era un draft? è diventato un draft? c’è qualche articolo postdatato in coda?) invalida zero, una o più dipendenze causando la rimozione a cascata di tutti gli elementi da esso dipendenti. L’unica accortezza è gestire correttamente la “scadenza” della cache: nel momento in cui del contenuto viene prelevato dalla cache va confrontata la data di “prelievo” con la data di "scadenza" e se la cache è scaduta la data di scadenza viene ricalcolata, ciò che va invalidato viene invalidato, e tutto il resto prosegue come prima.

Infine un’ultima nota: avrei potuto tenere una cache separata per gli editor, però mi è sembrato più saggio fare in modo che il contenuto fosse manipolato a posteriori.

I risultati sono sorprendenti: il post da quattro mucche caroline e 428 (!) commenti viene renderizzato in 5 secondi senza cache, appena 192 millisecondi se il post è già presente in cache. Considerato il tempo sprecato (mezza giornata circa) ed i risultati ottenuti, direi che ne è valsa davvero la pena.

Nota aneddotica: il baco più antipatico, l’Heisenbug che faceva sparire i commenti di Daniele B. dalla cache, era dovuto al fatto che il mio ISP usa un sistema arcano di load-balancing per cui le richieste al sito www finivano solo alcune volte per essere eseguite dallo stesso server che gestisce il sito senza www. Che poi non ho ancora capito perché a livello di hosting, visto che i due siti puntano allo stessissimo contenuto, sono configurati come due siti separati: individuato il problema, la soluzione è stata semplice e immediata. Redirect 301 di tutte le richieste www al sito principale. La cache è sempre sincronizzata e tutti i paperi (?) vissero felici e contenti.

-quack