Business Geek

Aller au contenu | Aller au menu | Aller à la recherche

dimanche 12 octobre 2008

Reporting Services 2005 et 2008 side by side : No Report Servers were found

Je teste les versions beta de 2008 depuis plus d'un an déjà et je n'ai quasiment pas rencontré de problème d'incompatibilité avec la version 2005. (bon, j'avoue, une fois avec la lecture d'un package SSIS 2008 dans le SSIS Store 2005 mais je l'ai bien cherché :-)).

J'ai refait ma machine récemment pour nettoyer les 3 CTP qui trainaient et ne garder que la version RTM de SQL Server 2008 et une version 2005 car beaucoup de mes clients n'ont pas encore migré. Une fois tout terminé, pour les besoins d'une démo je configure Reporting Services 2008 via l'outil de configuration dédié et là, une erreur inattendue et surtout incompréhensible. A la sélection de l'instance de Reporting Services à paramétrer :

No Report Servers were found. Invalid Namespace.

Même erreur en forçant le nom de l'instance manuellement.

La solution est la suivante (merci Google et Justin King) :

  1. Ouvrez le fichier suivant : C:\Program Files\Microsoft SQL Server\MSRS10.SQL2008\Reporting Services\ReportServer\bin\reportingservices.mof (faites en une copie avant)
  2. Remplacez les références à RS_SQL2008 (ou RS_<le nom de l'instance>) par SQL2008 (ou le nom de votre instance 2008)
  3. Dans une commande DOS, dans le répertoire en question, lancez : MOFCOMP reportingservices.mof

Et voila, vous pouvez relancer le Reporting Services Configuration Manager. Sa requête WMI pour trouver les instances disponibles fonctionnera correctement.

PS : apparemment, cela n'arriverait que sur Vista

Identité d'un pool d'application IIS - Droits minimums

Par défaut, dans IIS 6.0, un Worker Process (le processus représentant un pool d'applications au runtime), tourne avec le compte Network Service (Service Réseau). C'est largement suffisant dans la majorité des cas mais il arrive que l'on souhaite utiliser un compte particulier, pour faire de la sécurité intégrée avec SQL Server, pour accéder à un répertoire sensible, etc.

Dans ce cas, on modifie les paramètres du Pool, section Identity (Identité), pour préciser un compte d'exécution. Bien sûr, vous vous doutez bien qu'il va falloir des droits à ce compte pour accéder au minimum requis ASP.NET (fichiers du sites, GAC, répertoire de compilation temporaire, etc.).

Vous trouverez la liste à ce lien :  http://msdn.microsoft.com/en-us/library/kwzs111e.aspx

Normalement, vous n'avez pas à configurer tout cela, il suffit juste d'ajouter le compte utilisé dans le groupe IIS_WPG, local à la machine. Mais il est possible que cela ne fonctionne pas, surtout si vous avez copié le site d'une machine à une autre (problème de SID).

Attention : tout ceci ne fonctionne que pour IIS 6.0, pour IIS 7.0, c'est un petit peu différent.

 

dimanche 28 septembre 2008

MS Days à Toulouse

Encore un nouvel événement Microsoft me direz-vous... Et bien oui et on ne s'en lasse pas, même si c'est événement n'est autre que le Microsoft Technet Tour de la fin d'année.

Au menu, des sessions et des échanges avec les speakers Microsoft sur les dernières technologies Microsoft : SQL Server 2008, Hyper-V, Visual Studio 2008, le Framework .NET 3.5, etc.

Le session sur la Business Intelligence avec SQL Server 2008 et Performance Point sera animée par votre serviteur. Je vous invite aussi à aller voir la session de Patrick Guimonet de Microsoft sur SQL Server 2008 dont le lancement en version finale a eu lieu pendant les vacances.

L'événement est gratuit et a lieu ces lundi et mardi (oui, le post arrive au dernier moment). Il vous reste donc quelques heures pour valider votre inscription et venir découvrir ou poser des questions sur les solutions Microsoft.

 

Retour aux affaires

Je suis rentré de congés il y a une semaine et le retour a été très chargé ce qui explique l'absence de post.

Comme habituellement, je fais le petit post qui va bien avec une belle photo ensoleillée de mon lieu de vacances : la Toscane en Italie.

Je m'y suis bien reposé, c'était beau et bon mais je n'ai pas réussi à redresser la tour de Pise.

Allez, STOP sur les vacances, c'est la rentrée et il faut se remettre au turbin. Comme je n'aime pas mélanger les sujets, rendez-vous sur le prochain post pour parler séminaire technique...

dimanche 31 août 2008

Analysis Services - Support du format monétaire dans Excel

Dans un projet décisionnel, il est nécessaire lors de la restitution des données de les formater. Par exemple, si vous présentez des données financières, il va falloir les afficher correctement.

Dans une architecture applicative classique, on laisse ce travail à la couche de présentation. Dans un contexte décisionnel, cette couche de présentation peut être Reporting Services qui gère le formatage mais ce peut-être aussi un client OLAP autonome comme Excel.

Excel sait gérer aussi le formatage mais c'est aux utilisateurs de le configurer via les options de la PivotTable :

Cette manipulation n'est pas à faire par les utilisateurs finaux, surtout pour des formats triviaux comme des devises.

 

Analysis Services permet d'ajouter des informations de formatage sur chaque cellule pour guider les clients OLAP dans leur réprésentation des données. Ainsi, quand on déclare un membre calculé par exemple, on ajoute la clause FORMAT_STRING décrivant le membre :

CREATE MEMBER CURRENTCUBE.[Measures].[Prix TTC]
AS [Measures].[Prix Vendu]*1.196,
FORMAT_STRING = "Currency"

Cette configuration se fait aussi via l'assistant :

 

Un client OLAP peut récupérer cette information en précisant une clause dans la requête MDX qu'il fait à Analysis Services.

SELECT {[Measures].[Prix TTC]} ON COLUMNS
FROM [CubSales]
CELL PROPERTIES VALUE, FORMAT_STRING

Excel précise bien cette clause dans ses échanges avec SSAS. Or il se peut qu'il n'arrive pas à représenter un membre calculé de type monétaire malgré l'information provenant du cube.

 

Une des solutions pour résoudre ce problème serait d'ajouter la clause FORMATTED_VALUE pour forcer le formatage des cellules par Analysis Services avant de renvoyer les données. On peut le vérifier avec une requête MDX dans Management Studio :

SELECT {[Measures].[Prix TTC]} ON COLUMNS
FROM [CubSales]
CELL PROPERTIES VALUE, FORMAT_STRING, FORMATTED_VALUE

 

Mais Excel ne permet pas de préciser ce paramètre donc il nous faut trouver une solution. Dans le cadre de données monétaires, il est important de connaître le pays pour déterminer la bonne devise. En effet, si mon cube contient des montants en euros, je ne veux pas voir des dollars si le client ou le serveur est en version US. En conséquence, il faut préciser la culture (Locale) en plus du formatage dans les méta-données des cellules. Ainsi, corrigeons notre membre calculé :

CREATE MEMBER CURRENTCUBE.[Measures].[Prix TTC]
AS [Measures].[Prix Vendu]*1.196,
FORMAT_STRING = "Currency", LANGUAGE = 1036 //fr-FR

 

Et là, plus de problème dans Excel pour afficher vos membres calculés en € de façon automatique. Attention, cette manipulation n'est pas possible avec le formulaire d'édition du membre calculé, il vous faudra passé par le script MDX de création.

samedi 30 août 2008

Transactions - REPEATABLE READ

Voici le problème que je vais traiter : je veux pouvoir m'assurer dans une transaction qu'un jeu d'enregistrements ne sera pas touché pendant que j'opère une série de modifications. Par exemple, je ne veux pas qu'on puisse modifier une série d'adresses pendant que je mets à jour le contact qui les possède. Pour être plus concret, je veux bloquer les adresses 31 et 52 pendant que je modifie le contact John Smith. Par exemple :

BEGIN TRAN

SELECT * INTO #TempTable FROM Person.Address Where AddressID in (31,52)

UPDATE Person.Person Set Title = 'Lord' Where LastName = 'Smith' and FirstName = 'John'
UPDATE Person.PersonPhone Set [...]

Par défaut, le niveau d'isolation de transaction de SQL Server est READ COMMITED. En d'autres termes, cela signifie que l'on ne peut lire ou modifier que des données qui ont validées (comitées pour reprendre le barbarisme franglais). Selon le moteur de transactions, les adresses peuvent être modifiées pendant que ma transaction a lieu.

Techniquement, dans le moteur SQL, cela se traduit par des locks partagés (Shared) sur la table Address qui durent le temps du premier SELECT et des locks exclusifs (eXclusive) sur les tables modifiées. Une fois le SELECT passé, les locks Shared sont libérés, laissant la place pour des modifications. En revanche les locks exclusifs sont maintenus jusqu'au COMMIT (ou ROLLBACK), empêchant les modifications. On peut le vérifier en regardant les verrous sur les tables en question :

SET TRANSACTION ISOLATION LEVEL READ COMMITTED --mode par défaut

BEGIN TRAN

SELECT *
FROM Person.Address
WHERE AddressID IN (31,52)

select resource_type, resource_description, request_mode, request_type
from sys.dm_tran_locks

ROLLBACK

 

 

Lors de la consultation des locks, il n'y en a aucun donc une instruction parallèle peut modifier les adresses. Nous allons donc utiliser un autre niveau de transaction pour changer le comportement du moteur.

En choisissant le niveau juste au dessus, REPEATABLE READ, le moteur va poser des locks Shared et les maintenir le temps de la transaction. C'est ce que l'on appelle des lectures répétables. Pour modifier un enregistrement, le moteur doit poser un lock exclusif et donc doit attendre de n'avoir plus de locks Shared. Voyons le comportement des verrous, pour cela, il suffit de changer le niveau d'isolation dans la première ligne : 

SET TRANSACTION ISOLATION LEVEL REPEATABLE READ

BEGIN TRAN

SELECT *
FROM Person.Address
WHERE AddressID IN (31,52)

select resource_type, resource_description, request_mode, request_type
from sys.dm_tran_locks

ROLLBACK

 

 

On voit bien que des locks Shared (S et IS) sont maintenus après le SELECT. Ceci garantie que ces enregistrements ne seront pas modifiés le temps de ma transaction. De plus, du fait que ce soit des locks Shared, une autre transaction peut lire ces enregistrements.

 

Bien évidemment, la gestion des locks est très fine dans SQL Server et se complexifie avec l'ensemble des requêtes à un instant T. SQL Server va tenter de verrouiller le niveau le plus fin et le moins contraignant pour respecter vos niveaux d'isolation et garantir la transactionalité. Et tout ceci se combine avec les différents objets liés à la table (index, allocations, etc.).

 

Vous aurez noté au passage l'utilisation de la vue système sys.dm_tran_locks qui remplace depuis 2005 l'ancien sp_lock et qui vous aidera à comprendre le fonctionnement du moteur transactionnel et à minimiser la contention sur vos bases.

Bonne optimisation...

jeudi 28 août 2008

Agrégat CLR – Nouveauté 2008

En ce moment, je teste pas mal SQL Server 2008 et en particulier les améliorations par rapport à la version précédente. Aujourd’hui, je me suis intéressé aux fonctionnalités de la SQLCLR et j’ai voulu savoir si une des limitations de 2005 était levée.

Le problème était le suivant (le fait que je parle à l’imparfait devrait vous aiguiller sur la réponse) : dans une fonction d’agrégat CLR, il n’était possible de fournir qu’un seul paramètre à la méthode Accumulate. Ceci était pénalisant pour certaines fonctions à double entrée ou pour paramétrer le comportement d’autre.

Par exemple, sur une fonction de concaténation (très utile dans les reports), il était impossible de préciser le caractère à utiliser comme séparateur. Cela a entraîné (chez nous en tout cas) de grands débats sur le choix caractère à utiliser dans le code .NET. Cela impliquait aussi l’utilisation de méthodes de Replace dans les couches supérieures pour traiter le caractère.

Venons-en à la solution que propose SQL Server 2008 maintenant.  Je suis en fait tombé sur la documentation suivante :

CREATE AGGREGATE [ schema_name . ] aggregate_name
        (@param_name <input_sqltype> 
        [ ,...n ] )

J’ai été intrigué et admiratif sur le ..n dans la liste de paramètres qui suppose que les fonctions d’agrégat de 2008 supportent le multi-paramètre.
Je me suis empressé de modifier quelque peu l’agrégat de concaténation des exemples de SQL Server et j’ai modifié la méthode Accumulate de cette façon :

[Microsoft.SqlServer.Server.SqlUserDefinedAggregate(…)]
public struct Concat {
    […]
    public void Accumulate(SqlString Value, SqlString separator)
    {
        if (!isFirst)
            sb.Append(separator);
        else
            isFirst = false;
        sb.Append(Value.Value);
    }

Après avoir intégré l’Assembly dans ma base, je mappe la fonction d’agrégat :

CREATE AGGREGATE [dbo].[Concat] (
  @value [nvarchar](4000),
  @separator [nvarchar](10))
RETURNS[nvarchar](4000)
EXTERNAL NAME [BewiseUtils].[Bewise.SqlServer.Concat]

Et le test final :

SELECT dbo.Concat(LastName, ', ')
FROM Person.Person

Et voila le travail… Chacun peu choisir son séparateur dans la fonction de concaténation.

mercredi 13 août 2008

Indexation - Gérer la fragmentation

Je continue ma série sur les index avant de faire une petite pause pour préparer une traduction d'article sur les clauses WHERE dynamiques d'un collègue MVP suédois.

Dans ce post, je vais m'intéresser à la fragmentation des index. J'espère que le terme fragmentation rappelera à certains le temps où on passait notre samedi après midi à défragmenter nos disques de 40Mo (oui, méga-octets) pour gagner quelques octets et installer Phantasmagoria.

Le principe est le même, l'idée est de "ranger" un index pour gagner un peu de place et surtout qu'il soit plus performant. La fragmentation est un processus "naturel", dû aux modifications des données. La base vit, grandit et donc il faut opérer de la maintenance régulièrement.

On a 2 types de fragmentation :

  • celle qui touche le niveau feuille de l'index et qui correspond à un ordre sur le disque différent de l'ordre logique dans l'index. Le résultat est trop d'accès disque lors de scans sur ce niveau
  • celle qui touche l'arbre de l'index et qui correspond à des pages trop vides ou trop pleines

On peut utiliser 2 approches pour traiter avec la fragmentation :

  • Traiter systématiquement l'ensemble de sa base de données. C'est la méthode que j'utilise pour des petites volumétries et quand on dispose d'une fenêtre de tir pour faire les opérations de maintenance sans gêner personne (par exemple une base qui est utilisée avec des horaires de bureau).
  • Traiter uniquement les index fragmentés et de façon intelligente. C'est la méthode que j'utilise pour des grosses volumétries et des bases en accès 24/7.

Pour le premier cas, on fait juste un plan de maintenance dans lequel on demande une réorganisation ou une reconstruction des index sur toute la base. Sauf cas contraire, je fais un REORGANIZE tous les soirs et un REBUILD le week-end (cela ne reste qu'une stratégie parmi d'autres). Pour les puristes, les instructions T-SQL sont :

ALTER INDEX IX_abcd ON maTable REORGANIZE

ALTER INDEX IX_abcd ON maTable REBUILD

Pour le second cas, il faut déterminer si un index a besoin d'une reconstruction ou d'une réorganisation et connaître la volumétrie (et par conséquent le temps que ça va prendre) pour limiter l'impact sur la production.

On connait la fragmentation d'un index avec la vue système sys.dm_db_index_physical_stats et plus précisément le champ avg_fragmentation_in_percent. Ainsi on peut connaître pour chaque index son niveau de fragmentation :

SELECT DB_NAME(database_id), OBJECT_NAME(object_id), index_id, avg_fragmentation_in_percent
FROM sys.dm_db_index_physical_stats(
   DB_ID('MaBase'),
   OBJECT_ID('MaTable'),
   NULL, NULL, NULL)

Je ne vais pas rentrer ici dans le détail du pourcentage remonté par SQL Server, je vais lui faire confiance pour le calcul (note : SQL Server 2008 est plus précis dans le calcul car il prend en compte plus de cas).
Sur l'action a mener en fonction de la fragmentation, on trouve toutes les stratégies. Selon Microsoft, on réorganise pour une fragmentation < 30% et on reconstruit au-delà. Moi je n'ai pas de seuil arrêté, il m'arrive de faire un REBUILD quand la fragmentation est > 10%.

Voila, ce post est loin d'être exhaustif car je ne parle pas de la fragmentation par niveau de l'index, du paramètre FILL_FACTOR ou de la reconstruction ONLINE qui permet de reconstruire un index avec peu d'impact sur la production (version Enterprise uniquement).

PS : pour connaître le nom de l'index visé (la requête ci-dessus ne remonte que l'id), il va falloir faire une jointure sur sys.indexes.

Mise à Jour : pour illustrer le problème de la fragmentation d'un index, voici 2 images montrant un index "propre" et un index fragmenté.

Index non-fragmenté

Index fragmenté

Et comme il faut rendre à César ce qui lui appatient, voici mes sources : http://technet.microsoft.com/en-us/magazine/cc671165.aspx

mardi 12 août 2008

BACKUP WITH COPY_ONLY

Trop souvent je vois en consulting les développeurs faire eux-mêmes des backups de la base de production pour les remonter sur un environnement de développement. Et bien évidemment, mauvaise habitude, ils le font avec le wizard de Management Studio.

Le souci c'est que la version 2005 de cet outil ne prend pas en charge une option essentielle pour la création de ce type de backup : WITH COPY_ONLY.

Cette option permet de ne pas casser la stratégie de backup que le DBA, votre collègue ou même vous, auriez mis en place. Sans cette option, SQL Server garde des traces de la sauvegarde et vide le LOG, comme si elle était une continuité du plan en cours.

La conséquence à cela est que si votre base plante entre cette manipulation et le prochain BACKUP FULL "planifié", il manquera à l'administrateur des pièces du puzzle pour remonter la base puisque votre manip sera considérée comme le dernier backup complet valide. Et comme en général, ce fichier part à la poubelle...vous risquez de ne pas pouvoir remonter votre base.

Bien sûr, l'idée n'est pas de brider ces chers développeurs mais de leur enseigner l'art des choses bien faites. La solution :

  • apprendre le T-SQL

BACKUP DATABASE DataWarehouse TO DISK = 'c:\temp\dw.bak' WITH COPY_ONLY, NAME = 'Copie pour Dev'

  • Utiliser Management Studio 2008 qui inclut cette option dans le wizard

Indexation - Faites la chasse aux index HEAP

Un premier post qui j'espère me conduira à une série sur l'indexation dans SQL Server. Je traiterai les points un peu dans le désordre mais n'est-ce pas là l'attrait d'un blog ? Je sais que je ne commence pas par les bases mais bon, j'attend vos questions :-).

Un index de type HEAP indique en réalité le fait qu'il n'y ait aucun index sur la table et surtout pas d'index CLUSTERED. C'est à dire que seul le CREATE TABLE a été exécuté, sans aucune option, sans PRIMARY KEY.

Cela signifie que les données sont rangées n'importe comment ou plutôt dans l'ordre de leur création. C'est comme si je prenais un annuaire et que je mélangais les villes et les noms.

La conséquence d'avoir ce type d'indexation dans sa base de données est très souvent d'avoir des performances catastrophiques lors de la selection de données. D'autant plus quand la volumétrie augmente. Ceci vient du fait que l'opération qui est faite sur la table pour récupérer un jeu d'enregistrement est un TABLE SCAN (aussi appelé Full Scan).

Pour en revenir à mon annuaire, c'est comme si je vous demandais de me trouver toutes les personnes qui habitent au 12, rue des saules, quelque soit la ville. Vous seriez obligé de lire (scanner) toutes les pages.

Pour limiter cela, on peut mettre un index clustered ou une PRIMARY KEY (équivalent d'un index unique non null) pour optimiser la recherche.

Pour retrouver toutes les tables concernées :

select OBJECT_NAME(sys.indexes.object_id)
from sys.indexes
  inner join sys.tables on sys.tables.object_id = sys.indexes.object_id
where sys.indexes.[type] = 0 --0 = HEAP

Donc faite un petit check-up de vos bases et vérifier vos tables stockées sur un index HEAP... Et pourquoi pas les mettre sur un index CLUSTERED... mais je reviendrai sur ce point ;-)

 

 

 

 

 

- page 3 de 11 -