Migliorare la gestione del purge caching in Plone: collective.purgebyid

collective.purgebyid è un approccio alternativo alla gestione del purge caching per Plone, perché si basa sugli UUID degli oggetti anzichè sulle URL.

Premessa

Quando il traffico di un sito web è elevato, è opportuno affiancare un servizio di HTTP accelerator al sistema di gestione dei contenuti. Quindi un layer che faccia da reverse proxy e allo stesso tempo da cache dei contenuti medesimi: in questo ambito Varnish è sicuramente uno dei sistemi free più flessibili e performanti.

Nella pratica si presente però spesso un tema sottovalutato, ma che è fondamentale: definire la corretta politica di cache invalidation. Qual è il tempo di vita degli oggetti nella cache ? Quali sono gli eventi con i quali si puó forzare la scadenza di un oggetto ?

Plone.app.caching: purge by URL

Il CMS Plone ha un ottimo prodotto in grado di gestire in maniera molto precisa queste regole: plone.app.caching. Con p.a.caching è possibile definire delle regole di validità per una pagina web o per una risorsa (immagine, css, javascript, ...) in modo molto preciso, ad esempio in base alla tipologia del contenuto o alla vista applicata allo stesso, oppure è possibile definire una validità condizionata alla data di modifica o all'E-tag e molto altro. È infine possibile definire delle regole di invalidazione immediata (purging) su un sistema esterno di caching (nel nostro caso appunto Varnish).

I tipi di cache

I tipi definiti in p.a.caching sono quattro (c'è anche una modalità di concatenazione - Chain - che peró non ci interessa): Strong Caching, Moderate Caching, Weak Caching, No Caching. Le ultime due si basano essenzialmente su una verifica della validità del contenuto fatta direttamente da Plone, la prima cerca di sfruttare al massimo la cache del browser e per questo motivo è usata in genere unicamente per contenuti che variano al variare della url richiesta, mentre la seconda (Moderate) è per noi la più interessante:

Moderate caching
Cache in browser but expire immediately (same as weak caching), and cache in proxy (default: 24 hours). Use a purgable caching reverse proxy for best results. Caution: If proxy cannot be purged reliably (for example, in the case of composite pages where it may be difficult to track when a dependency has changed) then stale responses might be seen until the cached entry expires. A similar caution applies even if in the purgeable case, if the proxy cannot be configured to disallow caching in other intermediate proxies that may exist between the local proxies and the browser (see the example proxy configs included with this package for some solutions to this problem).

In genere quando si ha a che fare con siti ad alto traffico, l'approccio è quello di minimizzare il numero di richieste all'application server (nel nostro caso il CMS Plone) rispetto al numero totale di richieste. Quando è possibile controllare l'aggiornamento di un contenuto, è conveniente definire dei tempi lunghi di permanenza nella cache ed invalidarla puntualmente solo a fronte della modifica del contenuto stesso. Questa modalità viene proposta usualmente per i contenuti di tipo file e immagini.

È fondamentale, per il corretto funzionamento del servizio, che il sistema di notifica invalidi immediatamente tutti i contenuti modificati, altrimenti si rischia di visualizzare, ad esempio, delle immagini non aggiornate e questo è assolutamente fastidioso per qualsiasi redattore di contenuti web.

plone.cachepurging e il problema di definire le tutte URL da invalidare

p.a.caching a propra volta usa plone.cachepurging per determinare tutte le URL da invalidare nel sistema di caching e fare le opportune chiamate di purge al medesimo. Il funzionamento di base di p.cachepurging è quello di invalidare delle pagine a fronte della modifica, rinomina o  cancellazione di un contenuto e quindi la difficoltà e la complessità dell'operazione deriva dal fatto che le URL da invalidare sono determinate in base al contenuto e alla propria tipologia. Prendiamo ad esempio questa immagine: http://2013.ploneconf.org/ploneconf/images/ccug: nel momento in cui essa venisse modificata probabilmente i sistemi di caching riceverebbero le seguenti richieste (o qualcosa di simile a queste):

PURGE http://2013.ploneconf.org/ploneconf/images/ccug
PURGE http://2013.ploneconf.org/ploneconf/images/ccug/
PURGE http://2013.ploneconf.org/ploneconf/images/ccug/view
PURGE http://2013.ploneconf.org/ploneconf/images/ccug/image_mini
PURGE http://2013.ploneconf.org/ploneconf/images/ccug/image_thumb
...

e molte altre simili: il problema risiede nel fatto che non si può avere sempre la certezza di avere intercettato tutte le URL possibili utilizzate per visualizzare quell'immagine. Essa potrebbe essere stata scalata dinamicamente in altri contesti (usando plone.scale ad esempio) o qualche prodotto simile, oppure, in modo errato o consapevole, potrebbe essere stata riferita con un URL come la seguente http://2013.ploneconf.org/ploneconf/images/ploneconf/ccug, o con una querystring.

Per risolvere questi casi d'uso, l'approccio di p.a.caching è quello di creare di volta in volta dei nuovi adapter, volti a determinare le nuove URL di cui fare il purge per Plone, e in alcuni casi questa soluzione non risulta essere molto efficace.

Enter purge by id: invalidare la cache con gli uuid

Leggendo un interessante articolo nel blog di varnish sulla cache invalidation mi è venuta l'idea di utilizzare gli identificativi univoci delle risorse Plone (uuid) per identificarle e utilizzare quell'informazione ai fini dell'invalidazione. L'idea è molto semplice e dalle prime verifiche anche efficace: collective.purgebyid è un'implementazione di questa idea, liberamente utilizzabile (GPL).

Come funziona

In breve, il protocollo è il seguente:

  1. alla response di Plone viene aggiunto un nuovo header X-Ids-Involved con gli uuid degli oggetti coinvolti. Per esempio, nel caso base di un'immagine, il singolo uuid dell'oggetto immagine:

    % wget -S http://localhost:8080/Plone/image01
    ...
      X-Ids-Involved: #c8d7c0bc2b794325b916d990de91d7ee#
  2. una nuova regola di purge rewrite aggiunge alle URL di cui fare il purge una URL particolare nella forma /@@purgebyid/<UUID> con lo uuid dell'oggetto relativo alla URL di cui fare il purge,
  3. nella configurazione dell'HTTP accelerator a fronte di una operazione di purge e di una URL nella forma /@@purgebyid viene fatto un ban specifico (termine usato da Varnish, si veda la configurazione sottostante) per eliminare dalla cache tutti gli oggetti che abbiano nell'header X-Ids-Involved un'occorrenza dello UUID segnalato.

Un esempio di configurazione

Un esempio di configurazione per Varnish è il seguente:

sub vcl_recv {
    if (req.request == "PURGE") {
        if (!client.ip ~ purge) {
            error 405 "Not allowed.";
        }
        if (req.url ~ "^/@@purgebyid/") {
            ban("obj.http.x-ids-involved ~ #" + regsub(req.url, "^/@@purgebyid/", "") + "#");
            error 200 "Ban added";
        }
    }
}

sub vcl_deliver {
    unset resp.http.x-ids-involved;
}

Come si vede, è piuttosto semplice. Al momento la versione presente su https://github.com/collective/collective.purgebyid è ancora da considerarsi beta, ma le prove sono soddisfacenti: se pensate di utilizzare questo prodotto, dateci un feedback a riguardo !