Articoli di programmazione

Quando il gioco si fa duro, i duri mettono gli oggetti in cache

Una piccola classe per gestire la strategia di caching degli oggetti. Il codice di esempio è scritto in C# e sfrutta il design pattern Memoizer, unito al blocco delle risorse (Lock)

cache.jpg
Prima domanda: cos'è un oggetto pesante? E' un oggetto che costa molta CPU, memoria, tempo e genericamente risorse per essere generato o mantenuto attivo.
Molti sistemi usano oggetti pesanti, che sono il risultato spesso di letture remote da Web API oppure che vengono generati sulla base di pesanti elaborazioni dei dati in #Database. Per questo nelle applicazioni si cerca sempre di fare un uso intensivo della #cache, per cercare di rendere il sistema più performante possibile.
Uno dei problemi con i quali è possibile scontrarsi, è legato al tempo di generazione degli oggetti da inserire in cache. Infatti, anche se tendenzialmente è una cosa da evitare, capita sovente di imbattersi in oggetti che impiegano diversi secondi, se non minuti per generarsi.

Una volta generato l'oggetto, questo viene messo in cache (il design pattern di riferimento è #Memoizer) e per un po' possiamo stare tranquilli, ma cosa succede nel tempo di attesa? Se siamo particolarmente sfortunati, ad esempio nel caso si una applicazione multiutente, l'oggetto rischia di essere richiesto da più utenti a breve distanza di tempo e questo fa partire in parallelo più richieste di generazione dell'oggetto stesso, col rischio di vedere il server accasciarsi esanime.

Un modo per risolvere la questione può consistere nell'utilizzare un Lock in modo da scongiurare più esecuzioni di generazione in parallelo.
La classe di esempio mostra come è possibile gestire il #Lock e dialogare con la classe accoppiata CacheWrapper che si occupa di inserire e leggere l'oggetto dalla #Cache. In questo modo, eventuali richieste a breve distanza vengono messe in attesa e, quando la prima esecuzione che valorizza l'oggetto avrà terminato, verranno servite con l'oggetto appena messo in cache. Questo ci garantisce che venga eseguita una sola esecuzione di generazione dell'oggetto.

La classe CacheWrapper utilizzerà al proprio interno la tecnologia preferita per il caching (pattern Strategy), da un semplice HttpRunttime.Cache fino all'uso di un Database #NOSQL come ad esempio #Redis, per memorizzare l'oggetto.

Per una semplice applicazione web possiamo sfruttare la #Cache di HttpRuntime. Per un ambiente distribuito su più server, magari bilanciato, è consigliabile avere un sistema centralizzato adibito al caching, eventualmente un semplice DB come una istanza #Redis, per far sì che l'oggetto in cache sia fruibile da tutti i livelli applicativi e da tutti i tenants.


La funzione createCacheObjectFunct dovrà essere valorizzata, in fase di chiamata, con il codice che esegue la valorizzazione dell'oggetto.

Ecco di seguito il codice della #classe:

public class CacheStrategy
{
public static object RetrieveWithLock(object lockObject,
string key,
Func createCacheObjectFunct,
int cacheTimeSeconds)
{
var cachedObject = CacheWrapper.GetData(key);
if (cachedObject != null)
{
return cachedObject;
}
else
{
lock (lockObject)
{
var cachedObjectTwo = CacheWrapper.GetData(key);
if (cachedObjectTwo != null)
{
return cachedObjectTwo;
}
else
{
var createdObject = createCacheObjectFunct();
CacheWrapper.SetData(key, createdObject, cacheTimeSeconds);
return createdObject;
}
}
}
}
}


#tips #csharp #programmazione #caching