Beren.it

All that you can leave behind

Troncare i log in SQLServer

Capita spesso che usando un DB SQLServer, sul server dove sono presenti il file MDF e quello LDF (il transaction log) lo spazio su disco diventi insufficiente per le dimensioni notevoli raggiunte dal transaction log. Ma mentre per le versioni di SQL 2000 e 2005 bastava il comando:

[T-SQL]

DBCC SHRINKFILE(TestDBLog, 1)
BACKUP LOG TestDB WITH TRUNCATE_ONLY
DBCC SHRINKFILE(TestDBLog, 1)
GO

per la versione 2008 tale comando ritorna l'errore: "‘TRUNCATE_ONLY’ is not a recognized BACKUP option.". In effetti un opportunità per troncare il file di log è data dal comando seguente:

[T-SQL]

USE [master]
GO
ALTER DATABASE DBNAMELOG SET RECOVERY SIMPLE WITH NO_WAIT
GO
USE DBNAMELOG
DBCC SHRINKFILE(DBNAMELOG, 1)
GO
USE [master]
ALTER DATABASE DBNAMELOG SET RECOVERY FULL WITH NO_WAIT
GO

 

Per maggiori info potete anche consultare questo post che fornisce ulteriori spiegazioni in merito. Va però rimarcato che questa procedura interrompe la catena dei log con conseguente impossibilità di restore in time. Dunque prima di procedere è vivamente consigliato di effettuare un full backup del DB.

SQL Cache Dependency con SQL Server, LINQ e ASP.NET 3.5

Riporto qui sotto una libera traduzione di un post molto interessante sul quale mi sono imbattuto qualche giorno fa sul tema del caching in sistemi multiserver. 

ASP.NET fornisce diverse opzioni di caching. In questo caso prenderemo in esame una feature poco conosciuta di ASP.NET 3.5 chiamata SQL Cache Dependency. SQL Cache Dependency nasce in realtà con SQL Server 2000, ma la sua utilità era limitata per via del meccanismo di polling che imponeva un continuo check del DB per individuarne le modifiche, e a modifica individuata, notificava alla web application che la cache andava aggiornata.

Con SQL Server 2005 e 2008, ASP.NET ha adottato finalmente un sistema più performante per la SQL Cache Dependency. Lo sviluppatore può ora infatti delegare direttamente a SQL Server il compito di informarlo quando una modifica ha avuto luogo. In pratica ora la web application non deve continuamente monitorare il DB per accorgersi di eventuali modifiche.

Nell'esempio seguente useremo SQL Server 2005 e il database Pubs che potete scaricare qui. Anzitutto per consentire a SQL Server di inviare le notifiche occorre abilitare il Microsoft SQL Server 2005 Service Broker. Per verificare se questo servizio è già abilitato sul Catalog (Pubs n.d.r.) specificato basta lanciare il seguente script SQL:

[T-SQL]


SELECT name, is_broker_enabled FROM sys.databases

Se la riga con il nome del catalog interessato ha un 1 nella colonna is_broken_enabled, significa che il servizio è già attivo altrimenti bisogna attivarlo. Per farlo occorre lanciare il seguente script che sfrutta il comando ALTER DATABASE:

[T-SQL]


ALTER DATABASE Pubs SET ENABLE_BROKER
GO

Qualora però il database sia in uso il comando precedente sembra non terminare mai, e googlando ho scoperto qui il modo per farlo terminare istantaneamente.

[T-SQL]


ALTER DATABASE Pubs SET ENABLE_BROKER WITH ROLLBACK IMMEDIATE
GO

A questo punto occorre informare SQL Server che l'account con cui gira Internet Information Services (IIS) ha i permessi per sottoscriversi alle notifiche. Lo script seguente spiega esattamente come effettuare questa operazione con l'account ASPNET sul server TESTSERVER:

[T-SQL]


GRANT SUBSCRIBE QUERY NOTIFICATIONS TO 'TESTSERVER\ASPNET'

Ora che il Database è stato correttamente configurato, procediamo con il creare il Website ASP.NET che utilizzi la SQL Cache Dependecy. Apriamo Visual Studio 2008 e clicchiamo su File > New > Web > ASP.NET Web Application.

t;p>Di default, Visual Studio 2008 crea una pagina Default.aspx alla creazione di una nuova Web Application. Useremo questa pagina per interrogare il Database, SQL Cache Dependency per "cachare" i dati ritornatici ed infine visualizzarli.

 

Apriamo la pagina e trasciniamoci dentro un DropDownBox control, infine rinominiamolo cboDiscount.

[ASPX/HTML]

<form id="form1" runat="server">
   <div>
      <asp:DropDownList id="cboDiscount" runat="server" >
      </asp:DropDownList>
   </div>
</form>

In questo caso useremo LINQ to SQL come access data layer. Aggiungiamo quindi un nuovo file di tipo LINQ to SQL Classes al progetto:

Apriamo il file appena creato e trasciniamo attraverso il designer la tabella Discounts al suo interno.

Trascinando la tabella il designer creerà una nuova chiave di tipo Connection String nel web.config simile a questa:

[XML]

<connectionStrings>
   <add name="pubsConnectionString" 
            connectionString="Data Source=dotnet-testbed;Initial Catalog=Pubs;Integrated Security=True"  
            providerName="System.Data.SqlClient" />
</connectionStrings>

Per abilitare la ricezione delle notifiche occorre creare un listener. Il punto migliore per collocarlo è nel file Global.asax che solitamente si trova nel root folder delle applicazioni ASP. Aggiungiamone uno al progetto e quindi andiamo ad inserire nel codice l'attivazione e la disattivazione del listener:

[C#]

protected void Application_Start(object sender,EventArgs  e)
{
   SQLDependency.Start(ConfigurationManager.ConnectionString["pubsConnectionString"].ConnectionString); 
}
protected void Application_End(object sender,EventArgs  e)
{
   SQLDependency.Stop(ConfigurationManager.ConnectionString["pubsConnectionString"].ConnectionString); 
}

Il prossimo passo è scrivere la parte di codice che setti la cache dependency. In questo caso il codice è inserito in un Extension Method introdotti da C# 3.0 and VB.NET 9.0, che consente allo sviluppatore di estendere una classe esistente creando dei metodi che possono essere aggiunti alla classe esistente. Per farlo aggiungiamo una nuovo file di tipo Class e lo nominiamo MyExtensions. Gli Extension Method sono statici e perciò vanno marcati con la keyword static.

[C#]

public static class MyExtensions
{
   public static List<T> LinqCache<T> (this Table<T> query) where T : class
   {
      string tableName = query.Context.Mapping.GetTable(typeof(T)).TableName;
      List<T> result = HttpContext.Current.Cache[tableName] as List<T>;

      if (result == null)
      {
         using(SqlConnection  cn = new SqlConnection(query.Context.Connection.ConnectionString))
         {
            cn.Open();
            SqlCommand  cmd = new SqlCommand(query.Context.GetCommand(query).CommandText, cn);
            cmd.Notification = null;
            cmd.NotificationAutoEnlist = null;
            SqlCacheDependencyAdmin.EnableNotifications(query.Context.Connection.ConnectionString);
            if (!SqlCacheDependencyAdmin.GetTablesEnabledForNotifications(query.Context.Connection.ConnectionString).Contains(tableName))
            {
               SqlCacheDependencyAdmin.EnableTableForNotifications(query.Context.Connection.ConnectionString, tableName);
            }
            SqlCacheDependency dependency = new SqlCacheDependency(cmd);
            cmd.ExecuteNonQuery();

            result = query.ToList();
            HttpContext.Current.Cache.Insert(tableName, result, dependency);
        }
     }
     return result;
   }
}

Questo è a tutti gli effetti un Extension Method per la classe System.Data.Linq.Table<> e può essere utilizza con qualsiasi LINQ query. Il cuore di questo metodo è SqlCacheDependencyAdmin.EnableNotifications che effettua il check per capire se il Service Broker è abilitato. Se così è verifica che la tabella sia già abilitata alla notifica tramite SqlCacheDependencyAdmin.GetTablesEnabledForNotifications. Se così non fosse SqlCacheDependencyAdmin.EnableTableForNotifications abilita le notifiche sulla tabella.

La prima volta che questo Extension Method verrà invocato verrà creata la tabella AspNet_SqlCacheTablesForChangeNotification nel database Pubs.

Aprendola si noterà che è stata aggiunta una riga a testimonianza che il Service Broker è funzionante.

A questo punto, come riprova, cambiando un valore nella tabella vi accorgerete che la cache ASP.NET verrà correttamente pulita.

Sebbene questo sistema non migliori sensibilmente le performance di una web application, certamente può essere di aiuto nel caso di sistemi distribuiti su più server. In particolare con tutte quelle tabelle che non varino in continuazione. Qui potete trovare il codice della solution, sempre riconducibile al post citato in partenza.