Premesso che:
- Il software non lo avevo ne progettato ne realizzato io, per cui non conoscevo molto della sua struttura.
- Che non c'era documentazione (grave errore) a mia disposizione (ovviamente come nella maggior parte delle situazioni in cui mi sono trovato,
mi pare che sia un po un vizio degli sviluppatori non lasciare mai traccia). - Che avevo i sorgenti a disposizione, ma che purtroppo non c'era un briciolo di commento su nessun file, neanche in testa, sai quelle cose del tipo:"questa classe ha questo compito", insomma un milione o forse piu di righe di c# (non le ho contate)
- Che avevo poco tempo per inventarmi qualcosa per far velocizzare il software.
Possiamo proseguire....
Aggiungo al tutto, a mo' di sale nell'insalata mista, che Il software salvava i propri dati in una base dati MSSQLSERVER 2005.
Vi dico con sincerità che Il mio approccio fu quello di dire:
"Cavolo ho i sorgenti! per cui sono come DIO quando creò l'uomo e la donna", ma avrei dovuto capire subito che non era la strada corretta, non tanto per l'essere o non essere DIO, quanto al fatto che mi pare che anche a DIO con l'uomo e la donna non abbia fatto proprio un bel lavoro!!
Comunque ho iniziato a cercare nel codice, ma da subito ho capito che era una via poco percorribile, allora come si dice:" ho attaccato il profiler" ( cioè si avvia un software che monitorizza le attività del server sql mostrandoti eventuali query o istruzioni che possono causare rallentamenti).
Della serie, non si sa mai quale sia la via corretta! |
Dopo un po di analisi ho pensato che quella era la strada giusta, le query erano molte e si ripetevano inspiegabilmente decine di volte, sempre le stesse, e alcune query di queste erano particolarmente pesanti.
L'idea mi ronzava nella testa:
"Poter creare uno strato di cache su SQL in modo, da evitare il ripetersi delle query, e evitare di ripetere oltre tutto le query molto lunghe".
Il problema era sicuramente come incastrare uno strato di cache sql dentro un programma, in modo piu o meno trasparente, e piu o meno indolore.
Una bella sfida!
L'idea è arrivata, dovevo creare una classe, che fungesse da cache, in cui mettere tutti gli oggetti che volevo in una tabella di HASH, per avere il massimo nella ricerca, e nella flessibilità.
In aggiunta potevo utilizzare i GENERICS, per rendere totalmente astratta la tabella, e in piu utilizzando il PATTERN SINGLETON, potevo rendere la mia classe persistente per tutto il ciclo di vita del software.
l'idea era veramente buona (melo dico pure da solo)
La cosa importante era che potevo inserire in cache qualsiasi cosa, e riutilizzarla in qualsiasi momento nel programma in esecuzioni, ma ci sono dei MA.
MA - PERO'
Ovviamente ci sono anche i ma e i però da considerare, in modo da mettervi in alert nel caso vogliate fare anche voi questa scelta.
Il primo e più importante di tutti è che qui ci giochiamo la nostra esperienza nella conoscenza della programmazione ad oggetti, nel classico diatriba di cosa fa l'assegnazione di un oggetto ad un altro: nel 99,99% dei casi assegnare un oggetto ad un altro vuol dire passare il riferimento a quell'oggetto e non copiare un oggetto in un altro, questo è fondamentale perché se noi assegniamo un oggetto ad una tabella HASH stiamo dicendo che con un KEY, possiamo recuperare il riferimento a quell'oggetto e non l'oggetto stesso, percui se qualche altro oggetto al di fuori della nostra cache modifica l'oggetto, modificherà anche l'oggetto che ho messo in cache...
Facciamo un metafora culinaria:
Io ho un piatto di pasta, e vorrei metterlo in cache perche dopo potrei averne bisogno, il piatto si trova in frigorifero. Assegnando il piatto di pasta alla cache, io non faccio altro che scrivere in un mio block note che il piatto di pasta è in frigorifero, ma purtroppo il frigorifero lo condivido con mi moglie, presa da un attacco di fame, aperto il frigorifero si magia tutta la pasta, lasciando il piatto vuoto. Quando a me verrà fame, prenderò il mio taccuino (cache) e cercherò piatto di pasta, e troverò scritto si trova in frigorifero, aprendo in frigorifero troverò il piatto vuoto!!!
Questo problema è risolvibile in informatica, poichè è possibile, prima di inserire in cache un qualsiasi oggetto, clonare l'oggetto stesso, purtroppo clonare un piatto di pasta è un pochino piu complesso!!!
Altro Però, da mettere in risalto, è che sarebbe stato bello mettere in cache l'oggetto DATAREADER
ma purtroppo non è possibile, ne metterlo in cache come reference, ne si può clonare (come la pasta alcuni oggetti in informatica non si possono clonare), ma dei dettagli tecnici ne parleremo in un altro post.
Adesso mettiamo giu un po di codice, cosi potete vedere se vi piace:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Text;
using System.Reflection;
using System.Reflection.Emit;
using System.Threading;
using System.IO;
using System.Xml;
namespace XXXXXX.Common
{
public delegate TResult Func<T1, TResult>(T1 arg1);
public sealed class AOCache<T>
{
#region Cache cleaner parameters
//Default: 15 minuti
private int cc_interval = 15;
private string cc_intervalType = "M";
private DateTime lastCacheCleaning;
#endregion
private static volatile AOCache<T> instance;
private static object syncRoot = new Object();
public Dictionary<string,T> cache;
//FUNZIONE MADRE con questo istanzio la cache in singleton
public static AOCache<T> Instance
{
get
{
if (instance == null)
{
lock (syncRoot)
{
if (instance == null)
instance = new AOCache<T>();
}
}
instance.checkCache_toClean();
return instance;
}
}
/// <summary>
/// Legge le impostazioni sul file di configurazione e setta il timeout per il CC
/// </summary>
private void settaCacheCleaner()
{
string config_path =Path.Combine(AppDomain.CurrentDomain.BaseDirectory,"program.config");
XmlDocument doc = new XmlDocument();
try
{
doc.Load(config_path);
XmlNode CC = doc.DocumentElement.SelectSingleNode("cacheCleaner");
this.cc_interval = int.Parse(CC.InnerText);
this.cc_intervalType = CC.Attributes["type"].Value;
}
catch
{
//lasciamo i parametri di default
}
}
/// <summary>
/// Puliamo la cache se abbiamo oltrepassato il limite di tempo
/// </summary>
private void checkCache_toClean()
{
DateTime limit;
try
{
if (this.cc_interval == 0) //Non svuotare mai la cache
throw new Exception();
switch (this.cc_intervalType)
{
case "S": //Secondi
limit = this.lastCacheCleaning.AddSeconds(this.cc_interval);
break;
case "M": //Minuti
limit = this.lastCacheCleaning.AddMinutes(this.cc_interval);
break;
case "H": //Ore
limit = this.lastCacheCleaning.AddHours(this.cc_interval);
break;
default: //Nessuno dei tre caratteri ammessi: non svuotiamo la cache
throw new Exception();
}
System.Reflection.Assembly a = System.Reflection.Assembly.GetEntryAssembly();
if (DateTime.Compare(DateTime.Now, limit) > 0 || a.FullName.Contains("program2"))
cache.Clear();
}
catch
{
// Non interveniamo sulla cache
}
}
private AOCache()
{
cache =new Dictionary<string,T>();
ok = utlizzoCache();
this.lastCacheCleaning = DateTime.Now;
AOCacheManager.Manager.Add(this);
}
//Leggo il file di configurazione per verificare se la cache è gestita
private bool utlizzoCache()
{
return true;
}
public void add(string Key, T value) {
if (cache.ContainsKey(Key))
cache.Remove(Key);
cache.Add(Key, value);
}
public void remove(string Key)
{
if (cache.ContainsKey(Key))
cache.Remove(Key);
}
public void clear()
{
cache.Clear();
this.lastCacheCleaning = DateTime.Now;
}
public bool TryGetValue(string key,out T value)
{
if (ok)
{
return cache.TryGetValue(key, out value);
}
else
{
value = default(T);
return ok;
}
}
Quella in grassetto è la funzione madre, che istanzia l'oggetto cache e in cui si vede applicato il Singleton, le altre sono funzioni a corredo.
Ringrazio ufficialmente il mio collega JO (detto Jocondo) che mi ha aiutato nel completare e debbuggare le funzionalità.
Nessun commento:
Posta un commento