Check-list per applicazioni ASP .NET in produzione

Riporto di seguito una lista, in ordine sparso, tratta dalla mia esperienza sul campo dei possibili accorgimenti e/o errori che influiscono sulla scalabilità o sulle performance e che tornano quindi utili quando si trasferisce su di un server di produzione una applicazione ASP .NET complessa con elevato numero di accessi concorrenti:

  • Di default il numero di connessioni HTTP concorrenti su uno specifico indirizzo IP e per uno specifico AppDomain è pari a 2. Questo valore è sufficiente ad esempio nel caso di web services che vengono acceduti da un unica applicazione ASP .NET e le cui pagine non effettuano chiamate multiple allo stesso web service. Nell’ipotesi in cui il servizio web riceve richieste da più applicazioni o dalla stessa applicazione ma con chiamate multiple dalla stessa pagina, questo valore potrebbe causare che il worker thread che esegue la richiesta resti in attesa che si liberi una connessione, causando chiaramente una situazione di contesa delle risorse che porta a problemi di scalabilità e, nel peggiore dei casi. al riciclo dell’applicazione con esplicito messaggio di errore nel log degli eventi. Il numero delle connessioni può essere applicato per ogni singolo IP richiesto; il valore consigliato da Microsoft nel caso sopra esposto è di 12 * N ( dove N è il numero di CPU disponibili sul server)

  • Gli assembly usati da più di una applicazione devono essere firmati con strong name e installati in GAC. In questo modo 1 sola copia dell’assembly è presente in memoria a prescindere dal numero di applicazioni che lo usano; se, al contrario, lo stesso assembly viene installato nella directory bin di ogni applicazione che lo utilizza, esso sarà presente in memoria N volte, una per applicazione, con conseguente spreco di risorse.
  • Evitare situazioni che possono causare memory-leak. Ciò si verifica quando l’occupazione di memoria cresce in modo incontrollabile, ad es. perchè esistono oggetti i cui puntatori non possono essere gestiti nel modo corretto, tipico caso applicabile all’utilizzo di risorse unmanaged, e pertanto la memoria occupata da essi non può essere liberata dal GC ma solo da un riciclo dell’applicazione o peggio ancora da un riavvio di IIS. Le risorse unmanaged devono essere liberate implementando un distruttore per l’oggetto che ne fa uso oppure esplicitamente da codice attraverso l’interfaccia IDisposable. Tra le condizioni che possono provocare un alto utilizzo della memoria e quindi il rischio di memory leak meritano una menzione particolare l’utilizzo di oggetti del Framework che creano dinamicamente assembly. Ad esempio, se ogni volta che si intende serializzare un oggetto in XML si crea una istanza della classe xmlSerializer utilizzando una particolare versione di overload del suo costruttore, e cioè quella che prevede il passaggio degli extraTypes, per ogni istanza sarà creato un assembly dinamico dietro le quinte. L’assembly dinamico non potrà essere scaricato dalla memoria se non quando viene scaricato l’Application Domain che lo contiene. Altre situazioni analoghe a quest’ultima riguardano l’uso di RegEx o di xmlTransform. In quest’ultimo caso il problema è identico in quanto durante una trasformazione XSLT se si utilizzano blocchi di script inline il .NET Framework genera un assembly dinamico che resta in memoria fino al riciclo del processo host.  Maggiori informazioni qui e qui.
  • Il riciclo di un application pool avviene di default ogni 1740 minuti, pari a 29 ore. Questo valore non è chiaramente appropriato per tutti gli scenari. Sarebbe a mio avviso preferibile impostare il riciclo ad un’ora prefissata, magari notturna.
  • Quando un application pool viene riciclato per un qualsiasi motivo non sempre viene tracciato nel log degli eventi. Per permettere il log degli eventi al verificarsi di determinati eventi occorre modificare il metabase di IIS, impostando la proprietà LogEventOnRecycle con il parametro desiderato. A seconda della politica di riciclo impostata sul server di produzione è preferibile settare questo parametro al valore più opportuno per un troubleshooting più efficiente. Maggiori informazioni in questo articolo.
  • Il riciclo di un processo ASP .NET può avvenire anche al raggiungimento di una certa soglia di memoria occupata dallo stesso (parametro memoryLimit in machine.config o nelle proprietà dell’applicazione web in IIS6). Questa soglia è impostata di default al 70% di memoria fisica disponibile nel sistema, e andrebbe tarata su misura in base alle proprie esigenze. Da notare che il calcolo percentuale della memoria a disposizione viene influenzato dal parametro webGarden e cpuMask. Quindi, se si imposta il parametro webGarden a true e cpuMask a 4 CPU, la soglia di memoria RAM oltre la quale si scatena il riciclo del processo è 1/4 della percentuale impostata nel parametro memoryLimit, quindi una soglia decisamente bassa che potrebbe far aumentare il numero di ricicli che si verificano.
  • Il valore dell’attributo “debug” dell’elemento “compilation” nel file di configurazione “web.config” deve essere impostato a “false” in ambiente di produzione. Per una trattazione dei problemi a cui si può andare incontro lasciando il valore predefinito “true” è possibile far riferimento ad un mio precedente post sull’argomento.

Chiaramente questa non vuole essere una lista esaustiva circa tutti i possibili scenari a cui si può andare incontro quando si effettua il deployment in produzione di una applicazione web, ma solo una semplice check-list pronta all’uso che provvederò magari ad aggiornare man mano che entrano in gioco altri fattori.

Data caching: considerazioni

L’utilizzo della cache in applicazioni web non è sempre intuitivo come potrebbe sembrare. Se inseriamo un oggetto in cache assegnandogli, ad es., una scadenza assoluta di 1 ora, potremmo pensare che lo stesso rimanga in cache fino a che non scade. Invece non è così. Un oggetto in cache può in qualsiasi momento antecedente la sua scadenza (cioè quando è ancora valido) essere eletto per una operazione di garbage collection; quindi può essere distrutto e, in tal caso, la memoria da esso occupata viene liberata, a prescindere se il suo periodo di validità si è esaurito oppure no. Questo comportamento potrebbe verificarsi nei casi in cui il sistema richieda memoria per compiere una qualche operazione e nello stesso tempo la memoria cache risulta occupata. Morale: non bisogna mai dare per scontato che un oggetto inserito in cache sia disponibile anche se la sua scadenza è molto ampia. Questo meccanismo può essere disabilitato impostando l’enumerazione CacheItemPriority a CacheItemPriority.NotRemovable. In questo caso un oggetto in cache entrerà a far parte di un garbage collector solo se è scaduto, altrimenti sarà sempre valido. Questa impostazione torna utile nei casi in cui non è possibile ricreare l’oggetto dopo averlo messo in cache; infatti essa viene utilizzata dalla sessione InProcess di ASP .NET.

Un altro aspetto da considerare è che la cache è specifica di un certo Application Domain, e non cross appdomain. Questo significa che è globale a livello della applicazione che ne fa uso, e quindi 2 applicazioni (ovvero 2 appdomain) utilizzano sempre cache diverse e quindi non condivise. Ma come fare allora se vogliamo condividere la cache in più di una applicazione, in modo, ad es., che se inserisco un oggetto in cache dalla applicazione A anche l’applicazione B possa vederlo ? In uno scenario complesso e, soprattutto, in una web farm, è possibile memorizzare gli oggetti in un repository differente dalla memoria cache, ad. es. in un database. Questo approccio garantisce chiaramente una altissima disponibilità del servizio di caching, anche in caso di riavvio del sistema. Un approccio più semplice consiste nel creare un web service (quindi un’altra web application) che espone metodi get/set per gli oggetti da inserire in cache usando il meccanismo nativo di ASP .NET. In questo modo qualsiasi applicazione che usa il web service potrà utilizzare la cache, che sarà condivisa perchè l’application domain è sempre lo stesso.

.NET Framework 3.0 RC1 download

Oramai le attenzioni degli sviluppatori sono concentrate sull’accoppiata Windows Vista e .NET Framework 3.0, con tutte le tecnologie annesse che non sto ad elencare. Approfitto di questo post dell’amico Mighell del nuovo user group del sud Italia dotNetSide  (a proposito, complimenti per l’iniziativa e per quanto fatto sinora !), per mettere nel mio blog il riferimento necessario ai vari link per il download del .NET Framework 3.0 RC1. Buon download a tutti !