“Tracking” SQL Server (and SQLSat #271 – ABQ)

**
Update
I’ve noticed some range and scale issues so I took out the explicit schema creation (for now). For right now it just selects into a table, dropping the table if it already exists.
**

Back in October I had the opportunity to attend Jonathan Kehayias’ “SQL Server Archaeology” presentation. I’d been trying to wrap my brain around Extended Events for a while, but his presentation got me motivated to dive into the system_health Event Session.

Additionally, I also decided to submit a presentation idea to SQL Saturday #271 (Albuquerque, NM) titled, “Extended Events In Real Life.” The whole point of this presentation is to take some examples of ways that I’ve used Extended Events to solve problems or gain insight into the processes on SQL Server. I’m pretty excited (and a bit nervous) about this opportunity to speak.

To this end, I’ve also been given the opportunity to present a portion of “XEIRL” at our local SQL Server User Group this Thursday. This has accelerated both the nerves and the preparation. Since it’s been a while since I’ve posted anything, I figured I’d post the demo scripts that I am going to be using in this user group presentation.

Let’s say that someone comes to you and says “I wanted to tell you that ‘the database’ was slow yesterday but never got a chance because my keyboard was abducted by aliens, my legs were sent off to NCAR for analysis of their impact on barometric pressure, and I was using my phone to get past level 239 of Candy Crush.” What do you do? Hopefully you have a good monitoring tool (if you don’t, I recommend SQL Sentry Performance Advisor) and you can get an idea of what happened. If you don’t, or if you’re like me and just like to figure out “internals” stuff, and you’re on SQL Server 2012, then it could be system_health to the rescue.

This session has been created by the product team to help troubleshoot potential performance issues on the database server. System_health does exist in SQL Server 2008, but XEs were fairly new and seem to be a somewhat incomplete implementation making that version inadequate. Most of our environment is 2012 so I’ve been in luck with a more complete implementation of system_health. Read up on it here. In order to maintain some sembalence of brevity, let’s cut to the chase. You want to see what happened. In the linked file (below), you can see the setup for how I handle the system_health data. I take the XML data from the event file and dump it into a temp table and then process the XML from this temp table. My brain thinks in terms of manageable chunks and this fit the bill.

Now, what happened?
One of the beautiful things about the 2012 version of system_health is the addition of sp_server_diagnostics output. This gives a snapshot of query processing, IO, system, and resource “health” (if you run the procedure independent of the event session you’ll get a few more components) at the moment of execution. So first, let’s look for any of these components that may be unhealthy:

select d.grp,a.* 
from [system_health].[system_health_spsd] a
join (
	select	
		ROW_NUMBER() OVER (ORDER BY event_timestamp) as grp,
		event_timestamp 
	from [system_health].[system_health_xlat] b where b.spsd_component_state <> 'CLEAN'
) d
on a.event_timestamp <= DATEADD(MINUTE,6,d.event_timestamp)
		and a.event_timestamp >= DATEADD(MINUTE,-6,d.event_timestamp)
where exists (select 1 from [system_health].[system_health_xlat] b where b.spsd_component_state <> 'CLEAN'
	and a.event_timestamp <= DATEADD(MINUTE,6,b.event_timestamp)
		and a.event_timestamp >= DATEADD(MINUTE,-6,b.event_timestamp)
		);

Using this as our base, we can even trend waits (I chose non-preemptive waits by count, but the same logic applies for any combination of preemptive and by duration waits) to get a better look at what was going on:



-- QUERY PROCESSING IN WARNING STATE

select d.grp,a.* 
from [system_health].[system_health_spsd] a
join (
	select	
		ROW_NUMBER() OVER (ORDER BY event_timestamp) as grp,
		event_timestamp 
	from [system_health].[system_health_xlat] b where b.spsd_component_state <> 'CLEAN'
) d
on a.event_timestamp <= DATEADD(MINUTE,6,d.event_timestamp)
		and a.event_timestamp >= DATEADD(MINUTE,-6,d.event_timestamp)
where exists (select 1 from [system_health].[system_health_xlat] b where b.spsd_component_state <> 'CLEAN'
	and a.event_timestamp <= DATEADD(MINUTE,6,b.event_timestamp)
		and a.event_timestamp >= DATEADD(MINUTE,-6,b.event_timestamp)
		)

-- GET EVERYTHING SURROUNDING QP IN WARNING STATE
select d.grp,a.* 
from [system_health].[system_health_xlat] a
join (
	select	
		ROW_NUMBER() OVER (ORDER BY event_timestamp) as grp,
		event_timestamp 
	from [system_health].[system_health_xlat] b where b.spsd_component_state <> 'CLEAN'
) d
on a.event_timestamp <= DATEADD(MINUTE,6,d.event_timestamp)
		and a.event_timestamp >= DATEADD(MINUTE,-6,d.event_timestamp)
where exists (select 1 from [system_health].[system_health_xlat] b where b.spsd_component_state <> 'CLEAN'
	and a.event_timestamp <= DATEADD(MINUTE,6,b.event_timestamp)
		and a.event_timestamp >= DATEADD(MINUTE,-6,b.event_timestamp)
		)
and event_name <> 'security_error_ring_buffer_recorded'

-- WAITS ANALYSIS

SELECT 
	grp, 
	event_sequence,
	spsd_component_state,
	event_timestamp,
	wait_type,
	waits, 	
	waits - LAG(waits,1,0) over (partition by grp,wait_type order by event_sequence) as wait_delta,
	CASE
		WHEN waits - LAG(waits,1,0) over (partition by grp,wait_type order by event_sequence) = waits
			AND LAG(waits,1,0) over (partition by grp,wait_type order by event_sequence) = 0
			THEN 'NEW WAIT'
		WHEN waits - LAG(waits,1,0) over (partition by grp,wait_type order by event_sequence) > 0
			THEN 'INCREASE'
		WHEN waits - LAG(waits,1,0) over (partition by grp,wait_type order by event_sequence) < 0
			THEN 'DECREASE'
		WHEN waits - LAG(waits,1,0) over (partition by grp,wait_type order by event_sequence) = 0
			THEN 'NO CHANGE'
	END as wait_delta_desc,
	avg_wait_time, 
	avg_wait_time - LAG(avg_wait_time,1,0) over (partition by grp,wait_type order by event_sequence) as avg_wait_time_delta,
	CASE
		WHEN avg_wait_time - LAG(avg_wait_time,1,0) over (partition by grp,wait_type order by event_sequence) = avg_wait_time
			AND LAG(avg_wait_time,1,0) over (partition by grp,wait_type order by event_sequence) = 0
			THEN 'NEW WAIT'
		WHEN avg_wait_time - LAG(avg_wait_time,1,0) over (partition by grp,wait_type order by event_sequence) > 0
			THEN 'INCREASE'
		WHEN avg_wait_time - LAG(avg_wait_time,1,0) over (partition by grp,wait_type order by event_sequence) < 0
			THEN 'DECREASE'
		WHEN avg_wait_time - LAG(avg_wait_time,1,0) over (partition by grp,wait_type order by event_sequence) = 0
			THEN 'NO CHANGE'
	END as avg_wait_time_delta_desc,
	max_wait_time, 
	max_wait_time - LAG(max_wait_time,1,0) over (partition by grp,wait_type order by event_sequence) as avg_wait_time_delta,
	CASE
		WHEN max_wait_time - LAG(max_wait_time,1,0) over (partition by grp,wait_type order by event_sequence) = max_wait_time
			AND LAG(max_wait_time,1,0) over (partition by grp,wait_type order by event_sequence) = 0
			THEN 'NEW WAIT'
		WHEN max_wait_time - LAG(max_wait_time,1,0) over (partition by grp,wait_type order by event_sequence) > 0
			THEN 'INCREASE'
		WHEN max_wait_time - LAG(max_wait_time,1,0) over (partition by grp,wait_type order by event_sequence) < 0
			THEN 'DECREASE'
		WHEN max_wait_time - LAG(max_wait_time,1,0) over (partition by grp,wait_type order by event_sequence) = 0
			THEN 'NO CHANGE'
	END as avg_wait_time_delta_desc
FROM (

		select 
			d.grp, 
			DENSE_RANK() OVER (PARTITION BY d.grp ORDER BY a.event_timestamp) as event_sequence,
			a.spsd_component,
			a.spsd_component_state,
			a.event_timestamp,
			bc.n.value('@waitType','sysname') wait_type,
			bc.n.value('@waits','bigint') waits,	
			bc.n.value('@averageWaitTime','bigint') avg_wait_time,	
			bc.n.value('@maxWaitTime','bigint') max_wait_time,	
			event_data
		from [system_health].[system_health_spsd] a
		join (
			select	
				ROW_NUMBER() OVER (ORDER BY event_timestamp) as grp,
				event_timestamp 
			from [system_health].[system_health_xlat] b where b.spsd_component_state <> 'CLEAN'
		) d
		on a.event_timestamp <= DATEADD(MINUTE,6,d.event_timestamp)
				and a.event_timestamp >= DATEADD(MINUTE,-6,d.event_timestamp)
		OUTER APPLY npwaits_bycount.nodes('/byCount/wait') bc(n)
		where exists (select 1 from [system_health].[system_health_xlat] b where b.spsd_component_state <> 'CLEAN'
			and a.event_timestamp <= DATEADD(MINUTE,6,b.event_timestamp)
				and a.event_timestamp >= DATEADD(MINUTE,-6,b.event_timestamp)
				)
			and a.spsd_component = 'QUERY_PROCESSING'
	) agg
	order by grp,event_sequence

-- DEEPER DIVE INTO WAITS, WHERE THERE ANY RECORDED?
-- SOS_WORKER: Waiting for a thread
-- LCK_M_S: Typical locking/blocking
select d.grp,a.* 
from [system_health].[system_health_waits] a
join (
	select	
		ROW_NUMBER() OVER (ORDER BY event_timestamp) as grp,
		event_timestamp 
	from [system_health].[system_health_xlat] b where b.spsd_component_state <> 'CLEAN'
) d
on a.event_timestamp <= DATEADD(MINUTE,6,d.event_timestamp)
		and a.event_timestamp >= DATEADD(MINUTE,-6,d.event_timestamp)
where exists (select 1 from [system_health].[system_health_xlat] b where b.spsd_component_state <> 'CLEAN'
	and a.event_timestamp <= DATEADD(MINUTE,6,b.event_timestamp)
		and a.event_timestamp >= DATEADD(MINUTE,-6,b.event_timestamp)
		)		

--GO BACK AND CONFIRM THAT THERE WAS A BLOCKED PROCESS
select d.grp,a.* 
from [system_health].[system_health_spsd] a
join (
	select	
		ROW_NUMBER() OVER (ORDER BY event_timestamp) as grp,
		event_timestamp 
	from [system_health].[system_health_xlat] b where b.spsd_component_state <> 'CLEAN'
) d
on a.event_timestamp <= DATEADD(MINUTE,6,d.event_timestamp)
		and a.event_timestamp >= DATEADD(MINUTE,-6,d.event_timestamp)
where exists (select 1 from [system_health].[system_health_xlat] b where b.spsd_component_state <> 'CLEAN'
	and a.event_timestamp <= DATEADD(MINUTE,6,b.event_timestamp)
		and a.event_timestamp >= DATEADD(MINUTE,-6,b.event_timestamp)
		)

-- CHECK FOR ANY ERRORS
select d.grp,a.* 
from [system_health].[system_health_errors] a
join (
	select	
		ROW_NUMBER() OVER (ORDER BY event_timestamp) as grp,
		event_timestamp 
	from [system_health].[system_health_xlat] b where b.spsd_component_state <> 'CLEAN'
) d
on a.event_timestamp <= DATEADD(MINUTE,6,d.event_timestamp)
		and a.event_timestamp >= DATEADD(MINUTE,-6,d.event_timestamp)
where exists (select 1 from [system_health].[system_health_xlat] b where b.spsd_component_state <> 'CLEAN'
	and a.event_timestamp <= DATEADD(MINUTE,6,b.event_timestamp)
		and a.event_timestamp >= DATEADD(MINUTE,-6,b.event_timestamp)
		)


-- CHECK FOR DEADLOCKS
select d.grp,a.* 
from [system_health].[system_health_deadlocks] a
join (
	select	
		ROW_NUMBER() OVER (ORDER BY event_timestamp) as grp,
		event_timestamp 
	from [system_health].[system_health_xlat] b where b.spsd_component_state <> 'CLEAN'
) d
on a.event_timestamp <= DATEADD(MINUTE,6,d.event_timestamp)
		and a.event_timestamp >= DATEADD(MINUTE,-6,d.event_timestamp)
where exists (select 1 from [system_health].[system_health_xlat] b where b.spsd_component_state <> 'CLEAN'
	and a.event_timestamp <= DATEADD(MINUTE,6,b.event_timestamp)
		and a.event_timestamp >= DATEADD(MINUTE,-6,b.event_timestamp)
		)

-- FINALLY, CHECK THE RINGBUFFERS
select d.grp,a.* 
from [system_health].[system_health_ring_buffers] a
join (
	select	
		ROW_NUMBER() OVER (ORDER BY event_timestamp) as grp,
		event_timestamp 
	from [system_health].[system_health_xlat] b where b.spsd_component_state <> 'CLEAN'
) d
on a.event_timestamp <= DATEADD(MINUTE,6,d.event_timestamp)
		and a.event_timestamp >= DATEADD(MINUTE,-6,d.event_timestamp)
where exists (select 1 from [system_health].[system_health_xlat] b where b.spsd_component_state <> 'CLEAN'
	and a.event_timestamp <= DATEADD(MINUTE,6,b.event_timestamp)
		and a.event_timestamp >= DATEADD(MINUTE,-6,b.event_timestamp)
		)
		-- I ELIMINATE security_error_ring_buffer_recorded api_name='ImpersonateSecurityContext' because
		--	it's noisy and is only tellingvyou that it's still alive checking for security errors
	and event_name <> 'security_error_ring_buffer_recorded'

On and on and on you can go to further plumb the depths of what was happening at that time. You can look for deadlocks, errors, and analyze the output of ring buffers to really get a good idea of what was happening. I’ve attached all scripts so that you can have a good time seeing what you can get out of your system_health event session.

**
Update
I’ve noticed some range and scale issues so I took out the explicit schema creation (for now). For right now it just selects into a table, dropping the table if it already exists.

Advertisements

One thought on ““Tracking” SQL Server (and SQLSat #271 – ABQ)

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s