Storage Replica–Windows Server vNext

Une des fonctionnalités phare de la prochaine version de Windows Server vNext est sans nul doute le storage replica (lire le blog de l’équipe storage Windows).

Imaginez vous, simplement, sans logiciels d’un quelconque éditeur/constructeur tiers, répliquer les données d’un volume depuis une baie de disque située dans un premier datacenter vers une seconde baie de disque située dans un second datacenter.

Attention on parle bien ici de solution de disaster recovery et non pas de haute disponibilité, même si cela pourrait fonctionner.

D’autres s’y sont attaqués avant, mais on bénéfice ici de toute l’interopérabilité avec les autres fonctionnalités Windows.

Dans cette Technical Preview, a priori, seul Hyper-V est “supporté”. Mais en ce qui me concerne, je préfère me focaliser sur SQL Server, et donc le Scale-Out File Server afin de supporter les bases d’un cluster SQL Server, dont j’avais parlé ici. mais cette fois-ci on ajoute la possibilité d’effectuer du disaster recovery, sans passer les groupes de disponibilité.

Le cluster SQL sera donc multi site, avec du stockage asynchrone, comme j’ai pu le faire au travers de DataKeeper.

Attention : a priori le Scale-Out File Server n’est pas supporté pour l’instant. Et SQL Server branché sur cette architecture encore moins …

J’espère seulement que la version finale de Windows et la prochaine release de SQL Server pourront directement bénéficier de cette fonctionnalité pour du FCI dans passer par un stockage SMB3.0. Attaquer directement un volume répliqué serait vraiment top.

Tout d’abord créons l’infrastructure disque avec le storage replica.

D’un point de vue préparatifs, j’ai créé 4 VMs, 2 sur chaque site. Je créé ensuite2 LUNS sur chaque baie (stockage asynchrone) avec un zoning ne permettant aux nœuds que de voir le stockage du site local. J’ai choisi d’utiliser le protocole iSCSI (avec masque sur mes iqn, pensez à modifier vos scripts), mais si vous avec du FC cela fonctionne aussi bien évidement. Je n’ai aussi testé avec du Shared VHDX. Cela fonctionne !!!

# ajout des fonctionalités 
$Servers = 'sr-site1-n1','sr-site1-n2','sr-site2-n3','sr-site2-n4'
$Servers | ForEach { Install-WindowsFeature -ComputerName $_ `
-Name File-services,WVR,Failover-Clustering -IncludeManagementTools -restart }


# formation du cluster
New-Cluster -Name sr-sofs -Node sr-site1-n1,sr-site1-n2,sr-site2-n3,sr-site2-n4 `
-NoStorage -StaticAddress 192.168.1.220

# Add a FSW
Set-ClusterQuorum -Cluster sr-sofs -FileShareWitness "\\DC\FSW"

Ensuite, on configure la partie disque sur les nœuds du site 1

#site 1


Invoke-Command -ComputerName 'sr-site1-n1','sr-site1-n2' -ScriptBlock {

    Set-Service -Name msiscsi -StartupType Automatic

    Start-Service msiscsi

    New-IscsiTargetPortal -TargetPortalAddress ds415

    Connect-IscsiTarget -NodeAddress "iqn.2000-01.com.xxx.target-site1.xxx"  `
     -IsPersistent $True 

    Get-iSCSISession | Get-Disk
}



Invoke-Command -ComputerName 'sr-site1-n1' -ScriptBlock {

 Get-Disk | Where-Object IsOffline -Eq $True | Set-Disk -IsOffline $False

 Initialize-Disk -Number 1 -PartitionStyle GPT
 Initialize-Disk -Number 2 -PartitionStyle GPT

 New-Partition -DiskNumber 1 -DriveLetter E -UseMaximumSize
 New-Partition -DiskNumber 2 -DriveLetter F -UseMaximumSize

 Format-Volume -DriveLetter E -AllocationUnitSize 65536 -FileSystem NTFS `
     -NewFileSystemLabel "Site1-Data" -confirm:$false
 Format-Volume -DriveLetter F -AllocationUnitSize 65536 -FileSystem NTFS `
    -NewFileSystemLabel "Site1-Log"  -confirm:$false
    
}


Invoke-Command -ComputerName 'sr-site1-n2' -ScriptBlock {

 Get-Disk | Where-Object IsOffline -Eq $True | Set-Disk -IsOffline $False
     
}




# add all disk to the available storage
Get-ClusterAvailableDisk -cluster sr-sofs | Add-ClusterDisk

Move-ClusterGroup -Cluster sr-sofs -Name "Available Storage" -Node sr-site1-n1


Invoke-Command -ComputerName 'sr-site1-n1' -ScriptBlock {

    $NewName = (get-volume -DriveLetter E).FileSystemLabel
    #write-host $NewName
    (Get-ClusterResource -Name "Cluster Disk 1" ).Name = $NewName

    $NewName = (get-volume -DriveLetter F).FileSystemLabel
    #write-host $NewName
    (Get-ClusterResource -Name "Cluster Disk 2" ).Name = $NewName
   
} 
      

Ensuite, on effectue la même opération sur les 2 nœuds du site 2.

# site 2

Invoke-Command -ComputerName 'sr-site2-n3','sr-site2-n4' -ScriptBlock {

    Set-Service -Name msiscsi -StartupType Automatic

    Start-Service msiscsi

    New-IscsiTargetPortal -TargetPortalAddress ds415

    Connect-IscsiTarget -NodeAddress "iqn.2000-01.com.xxx.target-site2.xxx"  `
    -IsPersistent $True 

    Get-iSCSISession | Get-Disk
}


restart-vm -ComputerName vnext2 -Name "sr-site2-n3" -Confirm:$false
restart-vm -ComputerName vnext2 -Name "sr-site2-n4" -Confirm:$false

Invoke-Command -ComputerName 'sr-site2-n3' -ScriptBlock {

 Get-Disk | Where-Object IsOffline -Eq $True | Set-Disk -IsOffline $False

 Initialize-Disk -Number 1 -PartitionStyle GPT
 Initialize-Disk -Number 2 -PartitionStyle GPT

 New-Partition -DiskNumber 1 -DriveLetter E -UseMaximumSize
 New-Partition -DiskNumber 2 -DriveLetter F -UseMaximumSize

 Format-Volume -DriveLetter E -AllocationUnitSize 65536 -FileSystem NTFS `
   -NewFileSystemLabel "Site2-Data" -confirm:$false
 Format-Volume -DriveLetter F -AllocationUnitSize 65536 -FileSystem NTFS `
   -NewFileSystemLabel "Site2-Log"  -confirm:$false     
}


Invoke-Command -ComputerName 'sr-site2-n4' -ScriptBlock {

 Get-Disk | Where-Object IsOffline -Eq $True | Set-Disk -IsOffline $False
     
}



# add all disk to the available storage
Get-ClusterAvailableDisk -cluster sr-sofs | Add-ClusterDisk

Move-ClusterGroup -Cluster sr-sofs -Name "Available Storage" -Node sr-site2-n3


Invoke-Command -ComputerName 'sr-site2-n3' -ScriptBlock {

    $NewName = (get-volume -DriveLetter E).FileSystemLabel
    #write-host $NewName
    (Get-ClusterResource -Name "Cluster Disk 1" ).Name = $NewName

    $NewName = (get-volume -DriveLetter F).FileSystemLabel
    #write-host $NewName
    (Get-ClusterResource -Name "Cluster Disk 2" ).Name = $NewName
   
} 

 

Reste maintenant à configurer le Storage Replica

# all disk resrouces on node 1
Move-ClusterGroup -Cluster sr-sofs -Name "Available Storage" -Node sr-site1-n1


# add the data disk to CSV volume
Add-ClusterSharedVolume -cluster sr-sofs -Name "Site1-Data"

Move-ClusterSharedVolume -cluster sr-sofs -Name "Site1-Data" -Node sr-site1-n1


Invoke-Command -ComputerName 'sr-site1-n1' -ScriptBlock {

New-SRPartnership -SourceComputerName sr-site1-n1 -SourceRGName rg01 `
  -SourceVolumeName "C:\ClusterStorage\Volume2" -SourceLogVolumeName f: `
  -DestinationComputerName sr-site2-n3 -DestinationRGName rg02 `
  -DestinationVolumeName e: -DestinationLogVolumeName f: -LogSizeInBytes 1gb
}

 

image

A compter de ce point, la réplication est effective entre les 2 sites. Il est possible de basculer les disques sur le site 2 et de voir les données.

Maintenant, ajout de la fonctionnalité Scale-Out File Server

# Add Role Filehare / SOFS
Add-ClusterScaleOutFileServerRole -Name "SR-SQL" -Cluster sr-sofs


# Create file share
Move-ClusterSharedVolume -Name "site1-Data" -Node sr-site1-n1

MD C:\ClusterStorage\Volume2\SQL
New-SmbShare -Name SQL -Path C:\ClusterStorage\Volume2\SQL `
   -FullAccess LAB\Administrator

 

image

Reste à présent à installer 2 autres VMs, une sur chaque site, afin d’héberger les 2 nœuds du cluster SQL (cela pourrait être un cluster 4 nœuds …).

Si cette architecture semble répondre à vos besoins, n’hésitez pas à me contacter pour en parler.

Stay tuned …

Enjoy !

Publié dans Windows | Marqué avec , , , , , | Laisser un commentaire

Qui a fait quoi sur ma table ?

Lors d’une discussion sur un réseau social bien connu avec Christian (@chrisql) et Isabelle (@sqlgrrl) autour du thème du qui a fait quoi dans une base, j’ai proposé une solution qui évitait le recours à des fonctionnalités de l’édition entreprise de SQL Server.

Et, au delà de cette discussion Francophone (Suisse, Belgique et France), la question m’a été souvent posée chez des clients.

Alors, bien sur la solution n’est pas parfaite, mais, elle fourni déjà beaucoup d’informations …

Reprenons l’histoire au début. Lorsque l’on veut savoir le qui-fait-quoi, dans SQL Server, il n’existe pas des dizaine de solutions.

  • On peut utiliser l’API SQL Trace, communément utilisée au travers du Profiler SQL Server, qui permet de tracer toutes les requêtes qui arrivent sur SQL Server. Fonctionnel. Mais attention aux ressources consommées sur le serveur !!! Même si vous filtrez votre trace, sachez que le filtre ne s’applique que lors du stockage de l’information, pas lors de la capture. Donc SQL Trace capture toutes les requêtes ! Je vus laisse imaginer le résultat sur un environnement bien sollicité …
  • Depuis SQL Server 2008 il est possible d’utiliser  les XEvent, les évènements étendus. Alors, oui, en 2008 et 2008R2, il fallait aimer el code SQL et le parsing de contenu XML pour s’en sortir. Autant le côté trace était opérationnel, autant le côté restitution laissait à désirer, soyons honnêtes. Enfin, en 2012, SSMS propose une IM qui permet à la fois de créer des sessions d’évènements étendus ET de visualiser / traiter le résultat. En live ou a postériori. Enfin une bonne solution, d’autant que le surtout n’est absolument pas comparable avec celui de SQL Trace. Cette fois ci le filtre se fait lors de la capture, bien plus efficace donc.
  • On peut également mettre en place des triggers sur les tables. Bon ça reste un trigger, donc ….

Ces 3 techniques sont intéressantes, mais quelque peu “brutes”, dirons nous.

Depuis SQL Server 2008, il existe aussi les audits de sécurité. Au final, ce ne sont que des XEvents qui ont été packagés dans une IHM spécifique. Ainsi, il est possible, de manière extrêmement simple de mettre en place un audit sur des opérations de type INSERT / UPDATE / DELETE sur des tables. Tout comme on pourrait auditer l’accès à des données sensibles sur du SELECT, ou bien du EXEC de procédure stockée.

Bref, voici un petit exemple :

-- Création de l'audit et activation de celui-ci
CREATE SERVER AUDIT AuditDataAccess
TO FILE ( FILEPATH ='C:\SQLAudit\'
		  ,MAXSIZE = 0 MB
		  ,MAX_ROLLOVER_FILES = 2147483647
		  ,RESERVE_DISK_SPACE = OFF )
WITH
(	QUEUE_DELAY = 1000
	,ON_FAILURE = CONTINUE
)
GO

ALTER SERVER AUDIT AuditDataAccess WITH (STATE = ON);
GO
USE [AdventureWorks]
GO

-- Création d'une spécification d'audit pour tracer
-- - les écritures sur la table empployee
-- - les select sur l'intégralité sur schéma
CREATE DATABASE AUDIT SPECIFICATION [AuditSpecificationHR]
FOR SERVER AUDIT [AuditDataAccess]
ADD (INSERT,UPDATE,DELETE 
          ON OBJECT::[HumanResources].[Employee] BY [public]),
ADD (SELECT,EXECUTE       
          ON SCHEMA::[HumanResources]            BY [public])
WITH (STATE = ON) ;
GO
-- Consultation de l'audit
SELECT * 
FROM sys.fn_get_audit_file ('C:\SQLAudit\AuditDataAccess*.sqlaudit',
                                                    default,default);

La consultation peut aussi se faire dans SSMS, de manière graphique.

La mariée serait elle trop belle? Possible, car cela sous entend que la session d’audit existait déjà (alors qu’il est peut être trop tard … l’update a déjà été fait) et …. que … l’on dispose de l’édition entreprise de SQL Server.

Comment, donc, rechercher qui a effectué une modification de donnée (oui, la solution n’est pas parfaite car elle ne couvre pas le SELECT) qui s’est produite quelques heures/jours auparavant ?

La première chose, car il faut quand même quelques prérequis : la base doit être en mode de récupération complet ou bulklogged. Sinon le contenu du journal de transaction est vidé lors du checkpoint, qui est issu lorsque le journal dépasse un seuil de remplissage de 70%.

Le journal de transaction, donc, est chargé, comme son nom l’indique d’enregistrer les transactions. Cela tombe bien, on recherche une transaction, une modification de données effectuée par erreur, par exemple. Il faut donc explorer le journal de transaction. Cela peut se faire (attention si vous passez cette commande sur la prod …) a l’aide de la fonction fn_dblog. Sauf que cela suppose que le journal n’a pas été sauvegardé ! EN mode de récupération complet et bulklogged, le journal est vidé lorsque une sauvegarde du journal est effectuée.

Notre salut viendrait donc des sauvegardes du journal de transaction ! Il est possible de lire ces sauvegardes a l’aide de la fonction fn_dump_dblog. Attention, une nouvelle fois. Utiliser ces fonctions n’est pas un jeu !!!

Mettons en place un petit scénario. 3 utilisateurs qui vont effectuer des requêtes sur une table. Je vais en profiter pour utiliser els bases partiellement contenues (nouveauté SQL Server 2012). Cela n’est en rien obligatoire dans cette démo.


EXEC sys.sp_configure N'contained database authentication', N'1'
GO
RECONFIGURE WITH OVERRIDE
GO

CREATE DATABASE DemoDB
GO

ALTER DATABASE [DemoDB] SET CONTAINMENT = PARTIAL WITH NO_WAIT
GO


USE DemoDB
GO


CREATE TABLE DemoTable
(
	ID INT IDENTITY(1,1) PRIMARY KEY,
	filler CHAR(50)
)


CREATE USER [SQLUser1] WITH PASSWORD=N'Password1', DEFAULT_SCHEMA=[dbo];
CREATE USER [SQLUser2] WITH PASSWORD=N'Password1', DEFAULT_SCHEMA=[dbo];
CREATE USER [SQLUser3] WITH PASSWORD=N'Password1', DEFAULT_SCHEMA=[dbo];
GO


CREATE ROLE [db_MyAppRole] AUTHORIZATION [dbo];
GO

GRANT INSERT,DELETE,UPDATE,SELECT ON SCHEMA::[dbo] TO [db_MyAppRole];
GO

ALTER ROLE [db_MyAppRole] ADD MEMBER [SQLUser1];
ALTER ROLE [db_MyAppRole] ADD MEMBER [SQLUser2];
ALTER ROLE [db_MyAppRole] ADD MEMBER [SQLUser3];
GO


-- Une sauveagarde pour vider le journal de transactions
BACKUP DATABASE DemoDB
TO DISK = 'NUL'; -- Attention, pas en prod !!!

BACKUP LOG DemoDB
TO DISK = 'NUL'; -- Attention, pas en prod !!!
GO

A présent, les requêtes de test. Il s’agit ici de transactions imbriquées, mais encore une fois, cela fonctionne avec des transactions “simples”.


-- Login as SQLUser1
BEGIN TRANSACTION SQLUser1_Transaction
	
	SELECT USER_NAME();
	
	INSERT INTO DemoTable (filler)
	VALUES ('SQLUser 1 - Insert');

	-- Login as SQLUser2
	BEGIN TRANSACTION SQLUser2_Transaction

		SELECT USER_NAME();

		INSERT INTO DemoTable (filler)
		VALUES ('SQLUser 2 - Insert');

		UPDATE DemoTable 
		SET filler = 'SQLUser 2 - Update'
		WHERE ID = 2;

		-- Login as SQLUser3
		BEGIN TRANSACTION SQLUser3_Transaction

			SELECT USER_NAME();

			INSERT INTO DemoTable (filler)
			VALUES ('SQLUser 3 - Insert');

			UPDATE DemoTable 
			SET filler = 'SQLUser 3 - Update'
			WHERE ID = 3;

			DELETE FROM DemoTable 
			WHERE ID = 3;

		COMMIT TRANSACTION SQLUser3_Transaction

		DELETE FROM DemoTable 
		WHERE ID = 2;

	COMMIT TRANSACTION SQLUser2_Transaction


	UPDATE DemoTable 
	SET filler = 'SQLUser 1 - Update'
	WHERE ID = 1;

	DELETE FROM DemoTable 
	WHERE ID = 1;

COMMIT TRANSACTION SQLUser1_Transaction

A présent, un backup du journal de transaction. Ce backup, et donc les transactions qu’il contient, peuvent dater de maintenant, hier ou bien avant, cela ne modifie en rien le scénario.

BACKUP Log DemoDB
TO DISK = 'c:\temp\DemoDB.trn'

Il est temps à présent d’explorer cette sauvegarde. Je vous suggère de recopier les données dans une table temporaire, afin de pouvoir travailler plus facilement et plus rapidement par la suite.

SELECT *
INTO #temp_dump_dblog
FROM   fn_dump_dblog( DEFAULT, DEFAULT,DEFAULT, DEFAULT,  
'C:\temp\DemoDB.trn', DEFAULT,DEFAULT,
DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT,
DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT,
DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT,
DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT,
DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT,
DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT,
DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT,
DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT,
DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT ) AS LogRecords;
GO

Maintenant, il ne reste plus qu’a requêter cette table temporaire, effectuer quelques opérations a l’aide de la clause OVER pour récupérer le début et la fin des transactions et de croiser les données avec database_pincipals. Je vous laisse ensuite remonter à server_principals et d’autres infos si vous le souhaitez.


-- Result ordered by LSN
WITH db_log AS 
(
SELECT [Current LSN],
FIRST_VALUE([Current LSN]) OVER (PARTITION BY [Transaction ID] 
	ORDER BY [Current LSN]) AS [Begin_Tran_LSN],
LAST_VALUE([Current LSN])  OVER (PARTITION BY [Transaction ID] 
	ORDER BY [Current LSN]
	ROWS BETWEEN CURRENT ROW	
		AND UNBOUNDED FOLLOWING    ) AS [End_Tran_LSN],
[Transaction ID],  
FIRST_VALUE([Transaction SID]) OVER (PARTITION BY [Transaction ID] 
	ORDER BY [Current LSN]) AS [Transaction SID],
FIRST_VALUE([Begin Time]) OVER (PARTITION BY [Transaction ID] 
	ORDER BY [Current LSN]) AS [Begin_Transaction_DT],
LAST_VALUE([End Time])  OVER (PARTITION BY [Transaction ID] 
	ORDER BY [Current LSN]
	ROWS BETWEEN CURRENT ROW	
		AND UNBOUNDED FOLLOWING    ) AS [End_Transaction_DT],
FIRST_VALUE([Transaction Name]) OVER (PARTITION BY [Transaction ID] 
	ORDER BY [Current LSN]) AS [Transaction Name],
[OPERATION]
FROM   #temp_dump_dblog l
		
),
    transaction_user AS
(
SELECT * 
FROM db_log l
INNER JOIN sys.database_principals p on p.sid = l.[Transaction SID] 
WHERE  [Transaction ID]  '0000:00000000'
AND [OPERATION] IN ('LOP_BEGIN_XACT','LOP_COMMIT_XACT',
'LOP_INSERT_ROWS','LOP_MODIFY_ROW','LOP_DELETE_ROWS') 
)
SELECT [Current LSN],[Begin_Tran_LSN],[End_Tran_LSN],
	   [Begin_Transaction_DT],[End_Transaction_DT],[Transaction Name],
	   [OPERATION],[Name] AS [User_Name]
FROM transaction_user
WHERE [Transaction Name] NOT IN ('Backup:CommitLogArchivePoint',
                                 'AllocFirstPage','Allocate Root') 
ORDER BY [Current LSN]





-- Result ordered by User / LSN
WITH db_log AS 
(
SELECT [Current LSN],
FIRST_VALUE([Current LSN]) OVER (PARTITION BY [Transaction ID] 
	ORDER BY [Current LSN]) AS [Begin_Tran_LSN],
LAST_VALUE([Current LSN])  OVER (PARTITION BY [Transaction ID] 
    ORDER BY [Current LSN]
	ROWS BETWEEN CURRENT ROW	
	 AND UNBOUNDED FOLLOWING    ) AS [End_Tran_LSN],
[Transaction ID],  
FIRST_VALUE([Transaction SID]) OVER (PARTITION BY [Transaction ID] 
	ORDER BY [Current LSN]) AS [Transaction SID],
FIRST_VALUE([Begin Time]) OVER (PARTITION BY [Transaction ID] 
	ORDER BY [Current LSN]) AS [Begin_Transaction_DT],
LAST_VALUE([End Time])  OVER (PARTITION BY [Transaction ID] 
	ORDER BY [Current LSN]
	ROWS BETWEEN CURRENT ROW	
	 AND UNBOUNDED FOLLOWING    ) AS [End_Transaction_DT],
FIRST_VALUE([Transaction Name]) OVER (PARTITION BY [Transaction ID] 
	ORDER BY [Current LSN]) AS [Transaction Name],
[OPERATION],
CASE
WHEN [Begin Time] IS NOT NULL THEN 1
ELSE 0
END AS [Is_Begin_tran],
CASE
WHEN [End Time] IS NOT NULL THEN 1
ELSE 0
END AS [Is_End_tran]
FROM   #temp_dump_dblog l
),
    transaction_user AS
(
SELECT * 
FROM db_log l
INNER JOIN sys.database_principals p on p.sid = l.[Transaction SID] 
WHERE  [Transaction ID]  '0000:00000000'
AND [OPERATION] IN ('LOP_BEGIN_XACT','LOP_COMMIT_XACT',
'LOP_INSERT_ROWS','LOP_MODIFY_ROW','LOP_DELETE_ROWS') 
)
SELECT [Current LSN],[Begin_Tran_LSN],[End_Tran_LSN],
[Begin_Transaction_DT],[End_Transaction_DT],[Transaction Name],
[Name] AS [User_Name],
CASE
 WHEN Is_Begin_tran + Is_End_tran = 0 THEN '    |->  ' + [Operation]
 ELSE [Operation]
END As [Operation]
FROM transaction_user
WHERE [Transaction Name] NOT IN ('Backup:CommitLogArchivePoint',
                                 'AllocFirstPage','Allocate Root') 
ORDER BY [Name],[Current LSN]

Désolé pour l’indentation du code.

Si vous chercher qui a supprimé une table, cherchez des DROP …

Alors, qui a fait quoi ???

Enjoy

Publié dans SQL Server | Marqué avec , | Un commentaire

Techdays 2015 – SQL Server et la virtualisation : 45 minutes inside

En attendant que les slides soient disponibles au téléchargement, vous avez été nombreux à me demander les scripts Powershell pour créer “rapidement” une machine virtuelle sous Hyper-V.

Pour télécharger les scripts rendez vous sur mon onedrive.

image

Pensez quand même à changer le fichier template et les chemins d’accès !

Happy Powershell !

Publié dans Hyper-V, PowerShell, SQL Server | Marqué avec , | Un commentaire

JSS 2014 – Compteurs de performance et DMVs DMFs

Publié dans Evènements | Laisser un commentaire

Order By – ordonner le résultat d’une vue

En formation, la question revient invariablement : comment ordonner les enregistrements résultant d’un select sur une vue…
La réponse est on ne peut plus simple : avec un ORDER BY !

Bon, OK, un peu d’humour ne fait pas de mal… Mais de quel order by parle t’on ? Du order by dans la déclaration de la vue ?

Prenons une requête de référence, ordonnée comme on le souhaite :

 

Use AdventureWorks
GO


SELECT  *
FROM sales.SalesOrderHeader 
ORDER BY OrderDate DESC,SalesOrderID  ASC;

image

 

Reprenons le code de ce select pour créer une vue :

CREATE VIEW vDemoOrderBy
AS
SELECT *
FROM sales.SalesOrderHeader 
ORDER BY OrderDate DESC,SalesOrderID  ASC
GO

 

image

 

Oups, cela ne fonctionne pas. Le message est clair : impossible d’utiliser une clause order by dans une vue, a moins d’utiliser un des opérateurs suivants : TOP, OFFSET ou FOR XML.

Les anciens, ayant connu SQL Server 2000, connaissaient l’astuce : ajouter une clause TOP, avec 100 PERCENT en argument et hop, le tour était joué !

 

CREATE VIEW vDemoOrderBy
AS
SELECT TOP 100 PERCENT *
FROM sales.SalesOrderHeader 
ORDER BY OrderDate DESC,SalesOrderID  ASC
GO

 

image

 

Cool, on vient donc de créer une vue, qui a priori renvoie un résultat trié puisque un order by est présent dans le code …

SELECT  *
FROM sales.SalesOrderHeader 
ORDER BY OrderDate DESC,SalesOrderID  ASC


SELECT *
FROM vDemoOrderBy


La première requête est notre requête de référence, alors que la seconde fait appel à la vue “triée”. Et le résultat n’est absolument pas identique :

image

Le comportement de SQL Server a changé depuis SQL Server 2005. Lorsque, dans une vue, les opérateurs ORDER BY et TOP sont combinés, le order by est ignoré !!!

L’affichage du plan d’exécution valide ce point : il n’y a pas de tri lors de l’utilisation de la vue.

image

 

 

Conclusion : ne faites pas confiance au order by contenu dans une vue. Si vous avez réellement besoin d’obtenir un résultat trié en sortie d’une vue, alors ajoutez le order by à la requête qui accède à la vue et chassez le mythe de la vue ordonnée !

Et, souvenez vous bien que le tri est couteux, en terme de ressources système (79% du cout de la requête dans le cas présent). Donc, ne triez le résultat que si vous avez réellement besoin que ce soit le cas. Sinon, abstenez vous, ou bien reportez le tri côté client si possible, et comparez les performances et la montée en charge.

 

Happy sorting !

Publié dans SQL Server | Laisser un commentaire

JSS2014 – Compteurs de performance et DMVs – la vidéo

La vidéo de la session Compteurs de performance et DMVs des JSS2014 est enfin en ligne. Le niveau technique n’est pas très élevé. Par contre, le rythme l’est un peu !

 

Enjoy

Publié dans Evènements, SQL Server | Marqué avec | Un commentaire

Violin Windows Flash Arrays and Scale-Out File Servers

Violin Memory, qui faisait partie des sponsors des JSS2014 ainsi que du SQL Saturday 323 à Paris est un acteur spécialisé dans les baies de stockage flash.

Et vous n’êtes pas sans savoir que j’apprécie ce type de techno qui qui final apporte de réels gains de performance. Alors, oui, argument contre lequel je ne peux pas aller contre : le prix.

Maintenant, plutôt que de raisonner sur un tarif au “$/Go”, je vous propose de raisonner sur un tarif “$/IOPS”. Car, oui, il est rare de nos jours de se confronter à des problématiques de volumétrie sur une baie de disque. Par contre, et je le vis quasiment au quotidien lors des audit performance, on est souvent restreint, d’un point de vue performance, par le nombre d’IO/seconde, et surtout par la latence disque, la bête noire de tout DBA.

Il ne faut pas s’étonner donc de voir une multitude d’acteurs se démener dans le monde du stockage flash (Violin, FusioIO, OCZ, Intel, LSI, Skyera, Pure Storage, Tegile et bien d’autres …) qui bousculent les acteurs historiques du SAN (EMC, NetAPP, HP, …).

L’émulation à du bon, les prix baissent. Et il n’est plus illusoire, et hors de prix,  à présent de penser à ajouter une baie flash dans votre infrastructure.

Si vous n’êtes pas convaincu de la performance, je vous invite à télécharger le white paper “Building High Performance Storage for Hyper-V Cluster on Scale-Out File Servers using Violin Windows Flash Arrays “.

Certes, le démonstration est faite pour Hyper-V. Mais point de différence entre stocker un VHDX ou un MDF …

Happy faster IOs ………..

Publié dans Hyper-V, SQL Server | Marqué avec , , | Laisser un commentaire

300 : Valeur minimale pour le PLE. Vrai ou faux ?

Lorsque l’on monitore un serveur SQL, un des premiers compteurs de performance que l’on observe avec attention est le PLE ou Page Life Expectancy. L’espérance de vie d’une page en mémoire.

Il est communément admis qu’une valeur de 300, exprimé en secondes, constitue une valeur seuil. 5 minutes, donc.

Est-ce que cette valeur est la réelle valeur critique ? La réponse lors de ma session des #JSS2014.

Inscrivez vous vite !!!

Publié dans Evènements | Marqué avec | Laisser un commentaire

Teasing #JSS2014 : faites vous confiance au compteur Page Splits/sec ?

Les #JSS2014 approchent à grand pas. Pas encore inscrit ? Il est temps … N’oubliez pas non plus de réserver votre lunch box !

Petit teasing pour ma sessions “Compteurs de performance et DMVs”  :

Avez vous confiance dans le compteur Page Splits/sec ? Est-ce qu’une valeur élevée pour ce compteur révèle un problème de performance ?

image

Et le compteur Forwarded Records/sec ? Faut-il en tenir compte ?

Un début de réponse lors des JSS …

Publié dans Evènements, SQL Server | Marqué avec | Laisser un commentaire

Outils pour tester les performances d’un disque : diskpsd

Afin de tester la performance des disques d’un serveur, un point crucial, différents outils sont disponibles sur le marché. Citons les plus connus, ou les plus utilisés dans le monde SQL :

 

Dernièrement, Microsoft vient de rentre public un outil supplémentaire : Diskpsd

image

Un must have dans la toolbox de tous els administrateurs …

Publié dans Windows | Marqué avec | Laisser un commentaire