<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>David Barbarin &#187; Performance</title>
	<atom:link href="https://blog.developpez.com/mikedavem/pcategory/performance/feed" rel="self" type="application/rss+xml" />
	<link>https://blog.developpez.com/mikedavem</link>
	<description>MVP DataPlatform - MCM SQL Server</description>
	<lastBuildDate>Thu, 09 Sep 2021 21:19:50 +0000</lastBuildDate>
	<language>fr-FR</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>https://wordpress.org/?v=4.1.42</generator>
	<item>
		<title>Graphing SQL Server wait stats on Prometheus and Grafana</title>
		<link>https://blog.developpez.com/mikedavem/p13209/devops/graphing-sql-server-wait-stats-on-prometheus-and-grafana</link>
		<comments>https://blog.developpez.com/mikedavem/p13209/devops/graphing-sql-server-wait-stats-on-prometheus-and-grafana#comments</comments>
		<pubDate>Thu, 09 Sep 2021 21:19:22 +0000</pubDate>
		<dc:creator><![CDATA[mikedavem]]></dc:creator>
				<category><![CDATA[DevOps]]></category>
		<category><![CDATA[Performance]]></category>
		<category><![CDATA[grafana]]></category>
		<category><![CDATA[monitoring]]></category>
		<category><![CDATA[observability]]></category>
		<category><![CDATA[prometheus]]></category>
		<category><![CDATA[prompQL]]></category>
		<category><![CDATA[SQL Server]]></category>
		<category><![CDATA[telegraf]]></category>

		<guid isPermaLink="false">http://blog.developpez.com/mikedavem/?p=1816</guid>
		<description><![CDATA[Wait stats are essential performance metrics for diagnosing SQL Server Performance problems. Related metrics can be monitored from different DMVs including sys.dm_os_wait_stats and sys.dm_db_wait_stats (Azure). As you probably know, there are 2 categories of DMVs in SQL Server: Point in &#8230; <a href="https://blog.developpez.com/mikedavem/p13209/devops/graphing-sql-server-wait-stats-on-prometheus-and-grafana">Lire la suite <span class="meta-nav">&#8594;</span></a>]]></description>
				<content:encoded><![CDATA[<p>Wait stats are essential performance metrics for diagnosing SQL Server Performance problems. Related metrics can be monitored from different DMVs including sys.dm_os_wait_stats and sys.dm_db_wait_stats (Azure).</p>
<p>As you probably know, there are 2 categories of DMVs in SQL Server: Point in time versus cumulative and DMVs mentioned previously are in the second category. It means data in these DMVs are accumulative and incremented every time wait events occur. Values reset only when SQL Server restarts or when you intentionally run DBCC SQLPERF command. Baselining these metric values require taking snapshots to compare day-to-day activity or maybe simply trends for a given timeline.  Paul Randal kindly provided a TSQL script for trend analysis in a specified time range in this <a href="https://www.sqlskills.com/blogs/paul/capturing-wait-statistics-period-time/" rel="noopener" target="_blank">blog post</a>.  The interesting part of this script is the focus of most relevant wait types and corresponding statistics. This is basically the kind of scripts I used for many years when I performed SQL Server audits at customer shops but today working as database administrator for a company, I can rely on our observability stack that includes Telegraf / Prometheus and Grafana to do the job.</p>
<p><span id="more-1816"></span></p>
<p>In a previous <a href="https://blog.developpez.com/mikedavem/p13203/sql-server-2014/why-we-moved-sql-server-monitoring-on-prometheus-and-grafana" rel="noopener" target="_blank">write-up</a>, I explained the choice of such platform for SQL Server. But transposing the Paul’s script logic to Prometheus and Grafana was not a trivial stuff, but the result was worthy. It was an interesting topic that I want to share with Ops and DBA who wants to baseline SQL Server telemetry on Prometheus and Grafana observability platform.  </p>
<p>So, let’s start with metrics provided by Telegraf collector agent and then scraped by Prometheus job:<br />
&#8211;	sqlserver_waitstats_wait_time_ms<br />
&#8211;	sqlserver_waitstats_waiting_tasks_count<br />
&#8211;	sqlserver_waitstats_resource_wait_time_ms<br />
&#8211;	sqlserver_waitstats_signe_wait_time_ms</p>
<p>In the context of the blog post we will focus only on the first 2 ones of the above list, but the same logic applies for others. </p>
<p>As a reminder, we want to graph most relevant wait types and their average value within a time range specified in a Grafana dashboard. In fact, this is a 2 steps process: </p>
<p>1) Identifying most relevant wait types by computing their ratio with the total amount of wait time within the specific time range.<br />
2) Graphing in Grafana these most relevant wait types with their corresponding average value for every Prometheus step in the time range.</p>
<p>To address the first point, we need to rely on special Prometheus <a href="https://prometheus.io/docs/prometheus/latest/querying/functions/#rate" rel="noopener" target="_blank">rate()</a> function and <a href="https://prometheus.io/docs/prometheus/latest/querying/operators/" rel="noopener" target="_blank">group_left</a> modifier. </p>
<p>As per the Prometheus documentation, rate() gives the per second average rate of change over the specified range interval by using the boundary metric points in it. That is exactly what we need to compute the total average of wait time (in ms) per wait type in a specified time range. rate() needs a range vector as input. Let’s illustrate what is a range vector with the following example. For a sake of simplicity, I filtered with sqlserver_waitstats_wait_time_ms metric to one specific SQL Server instance and wait type (PAGEIOLATCH_EX). Range vector is expressed with a range interval at the end of the query as you can see below:</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:650px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">sqlserver_waitstats_wait_time_ms{sql_instance=&quot;$Instance&quot;,wait_type=&quot;PAGEIOLATCH_EX&quot;}[1m]</div></div>
<p>The result is a set of data metrics within the specified range interval as show below:</p>
<p><a href="http://blog.developpez.com/mikedavem/files/2021/09/blog-177-range-vector.png"><img src="http://blog.developpez.com/mikedavem/files/2021/09/blog-177-range-vector.png" alt="blog 177 - range vector" width="238" height="256" class="alignnone size-full wp-image-1818" /></a></p>
<p>We got for each data metric the value and the corresponding timestamp in epoch format.  You can convert this epoch format to user friendly one by using <strong>date -r -j </strong> for example. Another important point here: The sqlserver_waitstats_wait_time_ms metric is a counter in Prometheus world because value keeps increasing over the time as you can see above (from top to bottom). The same concept exists in SQL Server with cumulative DMV category as explained at the beginning. It explains why we need to use rate() function for drawing the right representation of increase / decrease rate over the time between data metric points. We got 12 data metrics with an interval of 5s between each value. This is because in my context we defined a Prometheus scrape interval of 5s for SQL Server =&gt; 60s/5s = 12 data points and 11 steps. The next question is how rate calculates per-second rate of change between data points. Referring to my previous example, I can get the rate value by using the following prompQL query:</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:650px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">rate(sqlserver_waitstats_wait_time_ms{sql_instance=&quot;$Instance&quot;,wait_type=&quot;PAGEIOLATCH_EX&quot;}[1m])</div></div>
<p>&#8230; and the corresponding value:</p>
<p><a href="http://blog.developpez.com/mikedavem/files/2021/09/blog-177-rate-value.png"><img src="http://blog.developpez.com/mikedavem/files/2021/09/blog-177-rate-value.png" alt="blog 177 - rate value" width="211" height="67" class="alignnone size-full wp-image-1820" /></a></p>
<p>To understand this value, let’s have a good reminder of mathematic lesson at school with <a href="https://en.wikipedia.org/wiki/Slope" rel="noopener" target="_blank">slope calculation</a>. </p>
<p><a href="http://blog.developpez.com/mikedavem/files/2021/09/Tangent_function_animation.gif"><img src="http://blog.developpez.com/mikedavem/files/2021/09/Tangent_function_animation.gif" alt="Tangent_function_animation" width="300" height="285" class="alignnone size-full wp-image-1823" /></a></p>
<p><em>Image from Wikipedia</em></p>
<p>The basic idea of slope value is to find the rate of change of one variable compared to another. Less the distance between two data points we have, more chance we have to get a precise approximate value of the slope. And this is exactly what it is happening with Prometheus when you zoom in or out by changing the range interval. A good resolution is also determined by the Prometheus scraping interval especially when your metrics are extremely volatile. This is something to keep in mind with Prometheus. We are working with approximation by design. So let&rsquo;s do some math with a slope calculation of the above range vector:</p>
<p>Slope = DV/DT = (332628-332582)/(@1631125796.971 &#8211; @1631125746.962) =~ 0.83</p>
<p>Excellent! This is how rate() works and the beauty of this function is that slope calculation is doing automatically for all the steps within the range interval.</p>
<p>But let’s go back to the initial requirement. We need to calculate per wait type the average value of wait time between the first and last point in the specified range vector. We can now step further by using Prometheus aggregation operator as follows:</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:650px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">sum by (wait_type) (rate(sqlserver_waitstats_wait_time_ms{sql_instance=&quot;$Instance&quot;}[1m]))</div></div>
<p>Please note we could have written it another way without using the sum by aggregator but it allows naturally to exclude all unwanted labels for the result metric. It will be particularly helpful for the next part. Anyway, Here a sample of the output:</p>
<p><a href="http://blog.developpez.com/mikedavem/files/2021/09/blog-177-aggregation-by-waittype.png"><img src="http://blog.developpez.com/mikedavem/files/2021/09/blog-177-aggregation-by-waittype-1024x145.png" alt="blog 177 - aggregation by waittype" width="584" height="83" class="alignnone size-large wp-image-1826" /></a></p>
<p>Then we can compute label (wait type) ratio (or percentage). First attempt and naïve approach could be as follows:</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:650px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">sum by (wait_type) (rate(sqlserver_waitstats_wait_time_ms{sql_instance=&quot;$Instance&quot;}[1m]))/ sum(rate(sqlserver_waitstats_wait_time_ms{sql_instance='$Instance'}[1m]))</div></div>
<p>But we get empty query result. Bad joke right? We need to understand that. </p>
<p>First part of the query gives total amount of wait time per wait type. I put a sample of the results for simplicity:</p>
<p><a href="http://blog.developpez.com/mikedavem/files/2021/09/blog-177-aggregation-by-waittype1.png"><img src="http://blog.developpez.com/mikedavem/files/2021/09/blog-177-aggregation-by-waittype1-1024x145.png" alt="blog 177 - aggregation by waittype" width="584" height="83" class="alignnone size-large wp-image-1828" /></a></p>
<p>It results a new set of metrics with only one label for wait_type. Second part gives to total amount of wait time for all wait types as show below:</p>
<p><a href="http://blog.developpez.com/mikedavem/files/2021/09/blog-177-total-waits.png"><img src="http://blog.developpez.com/mikedavem/files/2021/09/blog-177-total-waits.png" alt="blog 177 - total waits" width="479" height="39" class="alignnone size-full wp-image-1829" /></a></p>
<p>With SQL statement, we instinctively select columns that have matching values in concerned tables. Those columns are often concerned by primary or foreign keys. In Prometheus world, vector matching is performing the same way by using all labels at the starting point. But samples are selected or dropped from the result vector based either on &laquo;&nbsp;ignoring&nbsp;&raquo; and &laquo;&nbsp;on&nbsp;&raquo; keywords. In my case, they are no matching labels so we must tell Prometheus to ignore the remaining label (wait_type) on the first part of the query:</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:650px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">sum by (wait_type) (rate(sqlserver_waitstats_wait_time_ms{sql_instance=&quot;$Instance&quot;}[1m]))/ ignoring(wait_type) sum(rate(sqlserver_waitstats_wait_time_ms{sql_instance='$Instance'}[1m]))</div></div>
<p>But another error message &#8230;</p>
<p><strong>Error executing query: multiple matches for labels: many-to-one matching must be explicit (group_left/group_right)</strong></p>
<p>In the many-to -one or one-to-many vector matching with Prometheus, samples are selected using keywords like group_left or group_right. In other words, we are telling Prometheus to perform a cross join in this case with this final query before performing division between values:</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:650px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">sum by (wait_type) (rate(sqlserver_waitstats_wait_time_ms{sql_instance=&quot;$Instance&quot;}[1m]))/ ignoring(wait_type) group_left sum(rate(sqlserver_waitstats_wait_time_ms{sql_instance='$Instance'}[1m]))</div></div>
<p>Here we go!</p>
<p><a href="http://blog.developpez.com/mikedavem/files/2021/09/blog-177-ratio-per-label.png"><img src="http://blog.developpez.com/mikedavem/files/2021/09/blog-177-ratio-per-label-1024x149.png" alt="blog 177 - ratio per label" width="584" height="85" class="alignnone size-large wp-image-1830" /></a></p>
<p>We finally managed to calculate ratio per wait type with a specified range interval. Last thing is to select most relevant wait types by excluding first irrelevant wait types. Most of wait types come from the exclusion list provided by Paul Randal’s script. We also decided to only focus on max top 5 wait types with ratio  &gt; 10% but it is up to you to change these values:</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:650px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">topk(5, sum by (wait_type) (rate(sqlserver_waitstats_wait_time_ms{sql_instance='$Instance',measurement_db_type=&quot;SQLServer&quot;,wait_type!~'(BROKER_EVENTHANDLER|BROKER_RECEIVE_WAITFOR|BROKER_TASK_STOP|BROKER_TO_FLUSH|BROKER_TRANSMITTER|CHECKPOINT_QUEUE|CHKPT|CLR_AUTO_EVENT|CLR_MANUAL_EVENT|CLR_SEMAPHORE|DBMIRROR_DBM_EVENT|DBMIRROR_EVENTS_QUEUE|DBMIRROR_WORKER_QUEUE|DBMIRRORING_CMD|DIRTY_PAGE_POLL|DISPATCHER_QUEUE_SEMAPHORE|EXECSYNC|FSAGENT|FT_IFTS_SCHEDULER_IDLE_WAIT|FT_IFTSHC_MUTEX|KSOURCE_WAKEUP|LAZYWRITER_SLEEP|LOGMGR_QUEUE|MEMORY_ALLOCATION_EXT|ONDEMAND_TASK_QUEUE|PARALLEL_REDO_DRAIN_WORKER|PARALLEL_REDO_LOG_CACHE|PARALLEL_REDO_TRAN_LIST|PARALLEL_REDO_WORKER_SYNC|PARALLEL_REDO_WORKER_WAIT_WORK|PREEMPTIVE_OS_FLUSHFILEBUFFERS|PREEMPTIVE_XE_GETTARGETSTATE|PWAIT_ALL_COMPONENTS_INITIALIZED|PWAIT_DIRECTLOGCONSUMER_GETNEXT|QDS_PERSIST_TASK_MAIN_LOOP_SLEEP|QDS_ASYNC_QUEUE|QDS_CLEANUP_STALE_QUERIES_TASK_MAIN_LOOP_SLEEP|QDS_SHUTDOWN_QUEUE|REDO_THREAD_PENDING_WORK|REQUEST_FOR_DEADLOCK_SEARCH|RESOURCE_QUEUE|SERVER_IDLE_CHECK|SLEEP_BPOOL_FLUSH|SLEEP_DBSTARTUP|SLEEP_DCOMSTARTUP|SLEEP_MASTERDBREADY|SLEEP_MASTERMDREADY|SLEEP_MASTERUPGRADED|SLEEP_MSDBSTARTUP|SLEEP_SYSTEMTASK|SLEEP_TASK|SLEEP_TEMPDBSTARTUP|SNI_HTTP_ACCEPT|SOS_WORK_DISPATCHER|SP_SERVER_DIAGNOSTICS_SLEEP|SQLTRACE_BUFFER_FLUSH|SQLTRACE_INCREMENTAL_FLUSH_SLEEP|SQLTRACE_WAIT_ENTRIES|VDI_CLIENT_OTHER|WAIT_FOR_RESULTS|WAITFOR|WAITFOR_TASKSHUTDOW|WAIT_XTP_RECOVERY|WAIT_XTP_HOST_WAIT|WAIT_XTP_OFFLINE_CKPT_NEW_LOG|WAIT_XTP_CKPT_CLOSE|XE_DISPATCHER_JOIN|XE_DISPATCHER_WAIT|XE_TIMER_EVENT|MEMORY_ALLOCATION_EXT|ONDEMAND_TASK_QUEUE|PREEMPTIVE_HADR_LEASE_MECHANISM|PREEMPTIVE_SP_SERVER_DIAGNOSTICS|PREEMPTIVE_ODBCOPS|PREEMPTIVE_OS_LIBRARYOPS|PREEMPTIVE_OS_COMOPS|PREEMPTIVE_OS_CRYPTOPS|PREEMPTIVE_OS_PIPEOPS|PREEMPTIVE_OS_AUTHENTICATIONOPS|PREEMPTIVE_OS_GENERICOPS|PREEMPTIVE_OS_VERIFYTRUST|PREEMPTIVE_OS_FILEOPS|PREEMPTIVE_OS_DEVICEOPS|PREEMPTIVE_OS_QUERYREGISTRY|PREEMPTIVE_OS_WRITEFILE|PREEMPTIVE_XE_CALLBACKEXECUTEPREEMPTIVE_XE_DISPATCHER|PREEMPTIVE_XE_GETTARGETSTATEPREEMPTIVE_XE_SESSIONCOMMIT|PREEMPTIVE_XE_TARGETINITPREEMPTIVE_XE_TARGETFINALIZE|PREEMPTIVE_XHTTP|PWAIT_EXTENSIBILITY_CLEANUP_TASK|PREEMPTIVE_OS_DISCONNECTNAMEDPIPE|PREEMPTIVE_OS_DELETESECURITYCONTEXT|PREEMPTIVE_OS_CRYPTACQUIRECONTEXT|PREEMPTIVE_HTTP_REQUEST|RESOURCE_GOVERNOR_IDLE|HADR_FABRIC_CALLBACK|PVS_PREALLOCATE)'}[1m])) / ignoring(wait_type) group_left sum(rate(sqlserver_waitstats_wait_time_ms{sql_instance='$Instance',measurement_db_type=&quot;SQLServer&quot;,wait_type!~'(BROKER_EVENTHANDLER|BROKER_RECEIVE_WAITFOR|BROKER_TASK_STOP|BROKER_TO_FLUSH|BROKER_TRANSMITTER|CHECKPOINT_QUEUE|CHKPT|CLR_AUTO_EVENT|CLR_MANUAL_EVENT|CLR_SEMAPHORE|DBMIRROR_DBM_EVENT|DBMIRROR_EVENTS_QUEUE|DBMIRROR_WORKER_QUEUE|DBMIRRORING_CMD|DIRTY_PAGE_POLL|DISPATCHER_QUEUE_SEMAPHORE|EXECSYNC|FSAGENT|FT_IFTS_SCHEDULER_IDLE_WAIT|FT_IFTSHC_MUTEX|KSOURCE_WAKEUP|LAZYWRITER_SLEEP|LOGMGR_QUEUE|MEMORY_ALLOCATION_EXT|ONDEMAND_TASK_QUEUE|PARALLEL_REDO_DRAIN_WORKER|PARALLEL_REDO_LOG_CACHE|PARALLEL_REDO_TRAN_LIST|PARALLEL_REDO_WORKER_SYNC|PARALLEL_REDO_WORKER_WAIT_WORK|PREEMPTIVE_OS_FLUSHFILEBUFFERS|PREEMPTIVE_XE_GETTARGETSTATE|PWAIT_ALL_COMPONENTS_INITIALIZED|PWAIT_DIRECTLOGCONSUMER_GETNEXT|QDS_PERSIST_TASK_MAIN_LOOP_SLEEP|QDS_ASYNC_QUEUE|QDS_CLEANUP_STALE_QUERIES_TASK_MAIN_LOOP_SLEEP|QDS_SHUTDOWN_QUEUE|REDO_THREAD_PENDING_WORK|REQUEST_FOR_DEADLOCK_SEARCH|RESOURCE_QUEUE|SERVER_IDLE_CHECK|SLEEP_BPOOL_FLUSH|SLEEP_DBSTARTUP|SLEEP_DCOMSTARTUP|SLEEP_MASTERDBREADY|SLEEP_MASTERMDREADY|SLEEP_MASTERUPGRADED|SLEEP_MSDBSTARTUP|SLEEP_SYSTEMTASK|SLEEP_TASK|SLEEP_TEMPDBSTARTUP|SNI_HTTP_ACCEPT|SOS_WORK_DISPATCHER|SP_SERVER_DIAGNOSTICS_SLEEP|SQLTRACE_BUFFER_FLUSH|SQLTRACE_INCREMENTAL_FLUSH_SLEEP|SQLTRACE_WAIT_ENTRIES|VDI_CLIENT_OTHER|WAIT_FOR_RESULTS|WAITFOR|WAITFOR_TASKSHUTDOW|WAIT_XTP_RECOVERY|WAIT_XTP_HOST_WAIT|WAIT_XTP_OFFLINE_CKPT_NEW_LOG|WAIT_XTP_CKPT_CLOSE|XE_DISPATCHER_JOIN|XE_DISPATCHER_WAIT|XE_TIMER_EVENT|MEMORY_ALLOCATION_EXT|ONDEMAND_TASK_QUEUE|PREEMPTIVE_HADR_LEASE_MECHANISM|PREEMPTIVE_SP_SERVER_DIAGNOSTICS|PREEMPTIVE_ODBCOPS|PREEMPTIVE_OS_LIBRARYOPS|PREEMPTIVE_OS_COMOPS|PREEMPTIVE_OS_CRYPTOPS|PREEMPTIVE_OS_PIPEOPS|PREEMPTIVE_OS_AUTHENTICATIONOPS|PREEMPTIVE_OS_GENERICOPS|PREEMPTIVE_OS_VERIFYTRUST|PREEMPTIVE_OS_FILEOPS|PREEMPTIVE_OS_DEVICEOPS|PREEMPTIVE_OS_QUERYREGISTRY|PREEMPTIVE_OS_WRITEFILE|PREEMPTIVE_XE_CALLBACKEXECUTEPREEMPTIVE_XE_DISPATCHER|PREEMPTIVE_XE_GETTARGETSTATEPREEMPTIVE_XE_SESSIONCOMMIT|PREEMPTIVE_XE_TARGETINITPREEMPTIVE_XE_TARGETFINALIZE|PREEMPTIVE_XHTTP|PWAIT_EXTENSIBILITY_CLEANUP_TASK|PREEMPTIVE_OS_DISCONNECTNAMEDPIPE|PREEMPTIVE_OS_DELETESECURITYCONTEXT|PREEMPTIVE_OS_CRYPTACQUIRECONTEXT|PREEMPTIVE_HTTP_REQUEST|RESOURCE_GOVERNOR_IDLE|HADR_FABRIC_CALLBACK|PVS_PREALLOCATE)'}[1m]))) &amp;gt;= 0.1</div></div>
<p>I got 3 relevant wait types with their correspond ratio in the specified time range.</p>
<p><a href="http://blog.developpez.com/mikedavem/files/2021/09/blog-177-ratio-per-label-top-5.png"><img src="http://blog.developpez.com/mikedavem/files/2021/09/blog-177-ratio-per-label-top-5-1024x67.png" alt="blog 177 - ratio per label top 5" width="584" height="38" class="alignnone size-large wp-image-1832" /></a></p>
<p>Pretty cool stuff but we must now to go through the second requirement. We want to graph the average value of the identified wait types within a specified time range in Grafana dashboard. First thing consists in including the above Prometheus query as variable in the Grafana dashboard. Here how I setup my Top5Waits variable in Grafana:</p>
<p><a href="http://blog.developpez.com/mikedavem/files/2021/09/blog-177-granafa-top5waits.png"><img src="http://blog.developpez.com/mikedavem/files/2021/09/blog-177-granafa-top5waits-1024x501.png" alt="blog 177 - granafa top5waits" width="584" height="286" class="alignnone size-large wp-image-1833" /></a></p>
<p>Some interesting points here: variable dependency kicks in with my $Top5Waits variable that depends hierarchically on another $Instance variable in my dashboard (from another Prometheus query). You probably have noticed the use of [${__range_s}s] to determine the range interval but depending on the Grafana $__interval may be a good fit as well. </p>
<p>In turn, $Top5Waits can be used from another query but this time directly in a Grafana dashboard panel with the average value of most relevant wait types as shown below:</p>
<p><a href="http://blog.developpez.com/mikedavem/files/2021/09/blog-177-grafana-avg-wait-stats.png"><img src="http://blog.developpez.com/mikedavem/files/2021/09/blog-177-grafana-avg-wait-stats-1024x400.png" alt="blog 177 - grafana avg wait stats" width="584" height="228" class="alignnone size-large wp-image-1834" /></a></p>
<p>Calculating wait type average is not a hard task by itself. In fact, we can apply the same methods than previously by matching the sqlserver_waitstats_wait_tine_ms and sqlserver_waitstats_waiting_task_count and to divide their corresponding values to obtain the average wait time (in ms) for each step within the time range (remember how the rate () function works). Both metrics own the same set of labels, so we don’t need to use &laquo;&nbsp;on&nbsp;&raquo; or &laquo;&nbsp;ignoring&nbsp;&raquo; keywords in this case. But we must introduce the $Top5Waits variable in the label filter in the first metric as follows:</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:650px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">rate(sqlserver_waitstats_wait_time_ms{sql_instance='$Instance',wait_type=~&quot;$Top5Waits&quot;,measurement_db_type=&quot;SQLServer&quot;}[$__rate_interval])/rate(sqlserver_waitstats_waiting_tasks_count{sql_instance='$Instance',wait_type=~&quot;$Top5Waits&quot;,measurement_db_type=&quot;SQLServer&quot;}[$__rate_interval])</div></div>
<p>We finally managed to get an interesting dynamic measurement of SQL Server telemetry wait stats. Hope this blog post helps!<br />
Let me know your feedback if your are using SQL Server wait stats in Prometheus and Grafana in a different way !</p>
]]></content:encoded>
			<wfw:commentRss></wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Why we moved SQL Server monitoring on Prometheus and Grafana</title>
		<link>https://blog.developpez.com/mikedavem/p13203/sql-server-2014/why-we-moved-sql-server-monitoring-on-prometheus-and-grafana</link>
		<comments>https://blog.developpez.com/mikedavem/p13203/sql-server-2014/why-we-moved-sql-server-monitoring-on-prometheus-and-grafana#comments</comments>
		<pubDate>Tue, 22 Dec 2020 16:55:12 +0000</pubDate>
		<dc:creator><![CDATA[mikedavem]]></dc:creator>
				<category><![CDATA[DevOps]]></category>
		<category><![CDATA[Performance]]></category>
		<category><![CDATA[SQL Server 2014]]></category>
		<category><![CDATA[SQL Server 2016]]></category>
		<category><![CDATA[SQL Server 2017]]></category>
		<category><![CDATA[SQL Server 2019]]></category>
		<category><![CDATA[Continuous Delivery]]></category>
		<category><![CDATA[database]]></category>
		<category><![CDATA[devops]]></category>
		<category><![CDATA[grafana]]></category>
		<category><![CDATA[monitoring]]></category>
		<category><![CDATA[observability]]></category>
		<category><![CDATA[prometheus]]></category>
		<category><![CDATA[RED]]></category>
		<category><![CDATA[sqlserver]]></category>
		<category><![CDATA[telegraf]]></category>
		<category><![CDATA[USE]]></category>

		<guid isPermaLink="false">http://blog.developpez.com/mikedavem/?p=1722</guid>
		<description><![CDATA[During this year, I spent a part of my job on understanding the processes and concepts around monitoring in my company. The DevOps mindset mainly drove the idea to move our SQL Server monitoring to the existing Prometheus and Grafana &#8230; <a href="https://blog.developpez.com/mikedavem/p13203/sql-server-2014/why-we-moved-sql-server-monitoring-on-prometheus-and-grafana">Lire la suite <span class="meta-nav">&#8594;</span></a>]]></description>
				<content:encoded><![CDATA[<p>During this year, I spent a part of my job on understanding the processes and concepts around monitoring in my company. The DevOps mindset mainly drove the idea to move our SQL Server monitoring to the existing Prometheus and Grafana infrastructure. Obviously, there were some technical decisions behind the scene, but the most important part of this write-up is dedicated to explaining other and likely most important reasons of this decision. </p>
<p><span id="more-1722"></span></p>
<p>But let’s precise first, this write-up doesn’t constitute any guidance or any kind of best practices for DBAs but only some sharing of my own experience on the topic. As usual, any comment will be appreciated.</p>
<p>That’s said, let’s continue with the context. At the beginning of this year, I started my new DBA position in a customer-centric company where DevOps culture, microservices and CI/CD are omnipresent. What does it mean exactly? To cut the story short, development and operation teams are used a common approach for agile software development and delivery. Tools and processes are used to automate build, test, deploy and to monitor applications with speed, quality and control. In other words, we are talking about Continuous Delivery and in my company, release cycle is faster than traditional shops I encountered so far with several releases per day including database changes. Another interesting point is that we are following the &laquo;&nbsp;Operate what you build&nbsp;&raquo; principle each team that develops a service is also responsible for operating and supporting it. It presents some advantages for both developers and operations but pushing out changes requires to get feedback and to observe impact on the system on both sides. </p>
<p>In addition, in operation teams we try to act as a centralized team and each member should understand the global scope and topics related to the infrastructure and its ecosystem. This is especially true when you&rsquo;re dealing with nightly on-calls. Each has its own segment responsibility (regarding their specialized areas) but following DevOps principles, we encourage shared ownership to break down internal silos for optimizing feedback and learning. It implies anyone should be able to temporarily overtake any operational task to some extent assuming the process is well-documented, and learnin has been done correctly. But world is not perfect and this model has its downsides. For example, it will prioritize effectiveness in broader domains leading to increase cognitive load of each team member and to lower visibility in for vertical topics when deeper expertise is sometimes required. Having an end-to-end observable system including infrastructure layer and databases may help to reduce time for investigating and fixing issues before end users experience them. </p>
<p><strong>The initial scenario</strong></p>
<p>Let me give some background info and illustration of the initial scenario:</p>
<p><a href="http://blog.developpez.com/mikedavem/files/2020/12/170-0-initial-scenario.jpg"><img src="http://blog.developpez.com/mikedavem/files/2020/12/170-0-initial-scenario-1024x704.jpg" alt="170 - 0 - initial scenario" width="584" height="402" class="alignnone size-large wp-image-1725" /></a></p>
<p>… and my feeling of what could be improved:</p>
<p>1) From a DBA perspective, at a first glance there are many potential issues. Indeed, a lot of automated or semi-manual deployment processes are out of the control and may have a direct impact on the database environment stability. Without better visibility, there is likely no easy way to address the famous question: He, we are experiencing performance degradations for two days, has something happened on database side?  </p>
<p>2) Silos are encouraged between DBA and DEVs in this scenario. Direct consequence is to limit drastically the adding value of the DBA role in a DevOps context. Obviously, primary concerns include production tasks like ensuring integrity, backups and maintenance of databases. But in a DevOps oriented company where we have automated &laquo;&nbsp;database-as-code&nbsp;&raquo; pipelines, they remain lots of unnecessary complexity and disruptive scripts that DBA should take care. If this role is placed only at the end of the delivery pipeline, collaboration and continuous learning with developer teams will restricted at minimum.  </p>
<p>3) There is a dedicated monitoring tool for SQL Server infrastructure and this is a good point. It provides necessary baselining and performance insights for DBAs. But in other hand, the tool in place targets only DBA profiles and its usage is limited to the infrastructure team. This doesn’t contribute to help improving the scalability in the operations team and beyond. Another issue with the existing tooling is correlation can be difficult with external events that come from either the continuous delivery pipeline or configuration changes performed by operations teams on the SQL Server instances. In this case, establishment of observability (the why) may be limited and this is what teams need to respond quickly and resolve emergencies in modern and distributed software.</p>
<p><strong>What is observability?</strong></p>
<p>You probably noticed the word &laquo;&nbsp;observability&nbsp;&raquo; in my previous sentence, so I think it deserves some explanations before to continue. Observability might seem like a buzzword but in fact it is not a new concept but became prominent in DevOps software development lifecycle (SDLC) methodologies and distributed infrastructure systems. Referring to the <a href="Implementing new monitoring stuff changed the way to observe the system (at least from a DBA perspective). Again, I believe the adding value of DBA role in a company with a strong DevOps mindset is being part of both production DBAs and Development DBAs. Making observability consistent across all the delivery pipeline including databases is likely part of the success and may help DBA getting a broader picture of system components. Referring to my context, I’m now able to get more interaction with developer teams on early phases and to provide them contextual feedbacks (and not generic feedbacks) for improvements regarding SQL production telemetry. They also have access to them and can check by themselves impact of their development.  In the same way, feedbacks and work with my team around database infrastructure topic may appear more relevant. It is finally a matter of collaboration " rel="noopener" target="_blank">Wikipedia</a> definition, <strong>Observability is the ability to infer internal states of a system based on the system’s external outputs</strong>. To be honest, it has not helped me very much and further readings were necessary to shed the light on what observability exactly is and what difference exist with monitoring. </p>
<p>Let’s start instead with monitoring which is the ability to translate infrastructure log metrics data into meaningful and actionable insights. It helps knowing when something goes wrong and starting your response quickly. This is the basis for monitoring tool and the existing one is doing a good job on it. In DBA world, monitoring is often related to performance but reporting performance is only as useful as that reporting accurately represents the internal state of the global system and not only your database environment. For example, in the past I went to some customer shops where I was in charge to audit their SQL Server infrastructure. Generally, customers were able to present their context, but they didn’t get the possibility to provide real facts or performance metrics of their application. In this case, you usually rely on a top-down approach and if you’re either lucky or experimented enough, you manage to find what is going wrong. But sometimes I got relevant SQL Server metrics that would have highlighted a database performance issue, but we didn’t make a clear correlation with those identified on application side. In this case, relying only on database performance metrics was not enough for inferring the internal state of the application. From my experience, many shops are concerned with such applications that have been designed for success and not for failure. They often lake of debuggability monitoring and telemetry is often missing. Collecting data is as the base of observability.</p>
<p>Observability provides not only the when of an error or issue, but more importantly the why. With modern software architectures including micro-services and the emphasis of DevOps, monitoring goals are no longer limited to collecting and processing log data, metrics, and event traces. Instead, it should be employed to improve observability by getting a better understanding of the properties of an application and its performance across distributed systems and delivery pipeline. Referring to the new context I&rsquo;m working now, metric capture and analysis is started with deployment of each micro-service and it provides better observability by measuring all the work done across all dependencies.</p>
<p><strong>White-Box vs. Black-Box Monitoring </strong></p>
<p>In my company as many other companies, different approaches are used when it comes monitoring: White-box and Black-Box monitoring.<br />
White-box monitoring focuses on exposing internals of a system. For example, this approach is used by many SQL Server performance tools on the market that make effort to set a map of the system with a bunch of internal statistic data about index or internal cache usage, existing wait stats, locks and so on …</p>
<p>In contrast, black-Box monitoring is symptom oriented and tests externally visible behavior as a user would see it. Goal is only monitoring the system from the outside and seeing ongoing problems in the system. There are many ways to achieve black-box monitoring and the first obvious one is using probes which will collect CPU or memory usage, network communications, HTTP health check or latency and so on … Another option is to use a set of integration tests that run all the time to test the system from a behavior / business perspective.</p>
<p>White-Box vs. Black-Box Monitoring: Which is finally more important? All are and can work together. In my company, both are used at different layers of the micro-service architecture including software and infrastructure components. </p>
<p><strong>RED vs USE monitoring</strong></p>
<p>When you’re working in a web-oriented and customer-centric company, you are quickly introduced to The Four Golden Signals monitoring concept which defines a series of metrics originally from <a href="https://sre.google/sre-book/monitoring-distributed-systems/" rel="noopener" target="_blank">Google Site Reliability Engineering</a> including latency, traffic, errors and saturation. The RED method is a subset of “Four Golden Signals” and focus on micro-service architectures and include following metrics:</p>
<ul>
<li>Rate: number of requests our service is serving per second</li>
<li>Error: number of failed requests per second </li>
<li>Duration: amount of time it takes to process a request</li>
</ul>
<p>Those metrics are relatively straightforward to understand and may reduce time to figure out which service was throwing the errors and then eventually look at the logs or to restart the service, whatever. </p>
<p>For HTTP Metrics the RED Method is a good fit while the USE Method is more suitable for infrastructure side where main concern is to keep physical resources under control. The latter is based on 3 metrics:</p>
<ul>
<li>Utilization: Mainly represented in percentage and indicates if a resource is in underload or overload state. </li>
<li>Saturation: Work in a queue and waiting to be processed</li>
<li>Errors: Count of event errors</li>
</ul>
<p>Those metrics are commonly used by DBAs to monitor performance. It is worth noting that utilization metric can be sometimes misinterpreted especially when maximum value depends of the context and can go over 100%. </p>
<p><strong>SQL Server infrastructure monitoring expectations</strong></p>
<p>Referring to the starting scenario and all concepts surfaced above, it was clear for us to evolve our existing SQL Server monitoring architecture to improve our ability to reach the following goals:</p>
<ul>
<li>Keeping analyzing long-term trends to respond usual questions like how my daily-workload is evolving? How big is my database? …</li>
<li>Alerting to respond for a broken issue we need to fix or for an issue that is going on and we must check soon.</li>
<li>Building comprehensive dashboards – dashboards should answer basic questions about our SQL Server instances, and should include some form of the advanced SQL telemetry and logging for deeper analysis.</li>
<li>Conducting an ad-hoc retrospective analysis with easier correlation: from example an http response latency that increased in one service. What happened around? Is-it related to database issue? Or blocking issue raised on the SQL Server instance? Is it related to a new query or schema change deployed from the automated delivery pipeline? In other words, good observability should be part of the new solution.</li>
<li>Automated discovery and telemetry collection for every SQL Server instance installed on our environment, either on VM or in container.</li>
<li>To rely entirely on the common platform monitoring based on Prometheus and Grafana. Having the same tooling make often communication easier between people (human factor is also an important aspect of DevOps) </li>
</ul>
<p><strong>Prometheus, Grafana and Telegraf</strong></p>
<p>Prometheus and Grafana are the central monitoring solution for our micro-service architecture. Some others exist but we’ll focus on these tools in the context of this write-up.<br />
Prometheus is an open-source ecosystem for monitoring and alerting. It uses a multi-dimensional data model based on time series data identified by metric name and key/value pairs. WQL is the query language used by Prometheus to aggregate data in real time and data are directly shown or consumed via HTTP API to allow external system like Grafana. Unlike previous tooling, we appreciated collecting SQL Server metrics as well as those of the underlying infrastructure like VMWare and others. It allows to comprehensive picture of a full path between the database services and infrastructure components they rely on. </p>
<p>Grafana is an open source software used to display time series analytics. It allows us to query, visualize and generate alerts from our metrics. It is also possible to integrate a variety of data sources in addition of Prometheus increasing the correlation and aggregation capabilities of metrics from different sources. Finally, Grafana comes with a native annotation store and the ability to add annotation events directly from the graph panel or via the HTTP API. This feature is especially useful to store annotations and tags related to external events and we decided to use it for tracking software releases or SQL Server configuration changes. Having such event directly on dashboard may reduce troubleshooting effort by responding faster to the why of an issue.  </p>
<p>For collecting data we use <a href="https://github.com/influxdata/telegraf/tree/master/plugins/inputs/sqlserver" rel="noopener" target="_blank">Telegraf plugin</a> for SQL Server. The plugin exposes all configured metrics to be polled by a Prometheus server. The plugin can be used for both on-prem and Azure instances including Azure SQL DB and Azure SQL MI. Automated deployment and configuration requires low effort as well. </p>
<p>The high-level overview of the new implemented monitoring solution is as follows:</p>
<p><a href="http://blog.developpez.com/mikedavem/files/2020/12/170-3-monitoring-architecture.jpg"><img src="http://blog.developpez.com/mikedavem/files/2020/12/170-3-monitoring-architecture-1024x776.jpg" alt="170 - 3 - monitoring architecture" width="584" height="443" class="alignnone size-large wp-image-1729" /></a></p>
<p>SQL Server telemetry is achieved through Telegraf + Prometheus and includes both Black-box and White-box oriented metrics. External events like automated deployment, server-level and database-level configuration changes are monitored through a centralized scheduled framework based on PowerShell. Then annotations + tags are written accordingly to Grafana and event details are recorded to logging tables for further troubleshooting.</p>
<p><strong>Did the new monitoring met our expectations?</strong></p>
<p>Well, having experienced the new monitoring solution during this year, I would say we are on a good track. We worked mainly on 2 dashboards. The first one exposes basic black-box metrics to show quickly if something is going wrong while the second one is DBA oriented with a plenty of internal counters to dig further and to perform retrospective analysis.</p>
<p>Here a sample of representative issues we faced this year and we managed to fix with the new monitoring solution:</p>
<p>1) Resource pressure and black-box monitoring in action:</p>
<p><a href="http://blog.developpez.com/mikedavem/files/2020/12/170-4-grafana-1.jpg"><img src="http://blog.developpez.com/mikedavem/files/2020/12/170-4-grafana-1-1024x168.jpg" alt="170 - 4 - grafana 1" width="584" height="96" class="alignnone size-large wp-image-1730" /></a></p>
<p>For this scenario, the first dashboard highlighted resource pressure issues, but it is worth noting that even if the infrastructure was burning, users didn’t experience any side effects or slowness on application side. After corresponding alerts raised on our side, we applied proactive and temporary fixes before users experience them. I would say, this scenario is something we would able to manage with previous monitoring and the good news is we didn’t notice any regression on this topic. </p>
<p>2) Better observability for better resolution of complex issue</p>
<p>This scenario was more interesting because the first symptom started from the application side without alerting the infrastructure layer. We started suffering from HTTP request slowness on November around 12:00am and developers got alerted with sporadic timeout issues from the logging system. After they traversed the service graph, they spotted on something went wrong on the database service by correlating http slowness with blocked processes on SQL Server dashboard as shown below. I put a simplified view on the dashboards, but we need to cross several routes between the front-end services and databases.</p>
<p><a href="http://blog.developpez.com/mikedavem/files/2020/12/170-6-grafana-3.jpg"><img src="http://blog.developpez.com/mikedavem/files/2020/12/170-6-grafana-3-1024x235.jpg" alt="170 - 6 - grafana 3" width="584" height="134" class="alignnone size-large wp-image-1732" /></a></p>
<p>Then I got a call from them and we started investigating blocking processes from the logging tables in place on SQL Server side. At a first glance, different queries with a longer execution time than usual and neither release deployments nor configuration updates may explain such sudden behavior change. The issue kept around and at 15:42 it started appearing more frequently to deserve a deeper look at the SQL Server internal metrics. We quickly found out some interesting correlation with other metrics and we finally managed to figure out why things went wrong as show below:</p>
<p><a href="http://blog.developpez.com/mikedavem/files/2020/12/170-7-grafana-4.jpg"><img src="http://blog.developpez.com/mikedavem/files/2020/12/170-7-grafana-4-706x1024.jpg" alt="170 - 7 - grafana 4" width="584" height="847" class="alignnone size-large wp-image-1733" /></a></p>
<p>Root cause was related to transaction replication slowness within Always On availability group databases and we directly jumped on storage issue according to error log details on secondary: </p>
<p><a href="http://blog.developpez.com/mikedavem/files/2020/12/170-8-errorlog.jpg"><img src="http://blog.developpez.com/mikedavem/files/2020/12/170-8-errorlog-1024x206.jpg" alt="170 - 8 - errorlog" width="584" height="117" class="alignnone size-large wp-image-1734" /></a></p>
<p>End-to-End observability by including the database services to the new monitoring system drastically reduces the time for finding the root cause. But we also learnt from this experience and to continuously improve the observability we added a black-box oriented metric related to availability group replication latency (see below) to detect faster any potential issue.</p>
<p><a href="http://blog.developpez.com/mikedavem/files/2020/12/170-9-avg-replication-metric.jpg"><img src="http://blog.developpez.com/mikedavem/files/2020/12/170-9-avg-replication-metric.jpg" alt="170 - 9 - avg replication metric" width="160" height="113" class="alignnone size-full wp-image-1736" /></a></p>
<p><strong>And what’s next? </strong></p>
<p>Having such monitoring is not the endpoint of this story. As said at the beginning of this write-up, continuous delivery comes with its own DBA challenges illustrated by the starting scenario. Traditionally the DBA role is siloed, turning requests or tickets into work and they can be lacking context about the broader business or technology used in the company. I experienced myself several situations where you get alerted during the night when developer’s query exceeds some usage threshold. Having discussed the point with many DBAs, they tend to be conservative about database changes (normal reaction?) especially when you are at the end of the delivery process without clear view of what will could deployed exactly. </p>
<p>Here the new situation:</p>
<p><a href="http://blog.developpez.com/mikedavem/files/2020/12/170-2-new-scenario.jpg"><img src="http://blog.developpez.com/mikedavem/files/2020/12/170-2-new-scenario-1024x641.jpg" alt="170 - 2 - new scenario" width="584" height="366" class="alignnone size-large wp-image-1737" /></a></p>
<p>Implementing new monitoring stuff changed the way to observe the system (at least from a DBA perspective). Again, I believe the adding value of DBA role in a company with a strong DevOps mindset is being part of both production DBAs and Development DBAs. Making observability consistent across all the delivery pipeline including databases is likely part of the success and may help DBA getting a broader picture of system components. Referring to my context, I’m now able to get more interaction with developer teams on early phases and to provide them contextual feedbacks (and not generic feedbacks) for improvements regarding SQL production telemetry. They also have access to them and can check by themselves impact of their development.  In the same way, feedbacks and work with my team around database infrastructure topic may appear more relevant. </p>
<p>It is finally a matter of collaboration </p>
]]></content:encoded>
			<wfw:commentRss></wfw:commentRss>
		<slash:comments>5</slash:comments>
		</item>
		<item>
		<title>Interesting use case of using dummy columnstore indexes and temp tables</title>
		<link>https://blog.developpez.com/mikedavem/p13202/sql-server-vnext/interesting-use-case-of-using-dummy-columnstore-indexes-and-temp-tables</link>
		<comments>https://blog.developpez.com/mikedavem/p13202/sql-server-vnext/interesting-use-case-of-using-dummy-columnstore-indexes-and-temp-tables#comments</comments>
		<pubDate>Fri, 20 Nov 2020 17:06:06 +0000</pubDate>
		<dc:creator><![CDATA[mikedavem]]></dc:creator>
				<category><![CDATA[Performance]]></category>
		<category><![CDATA[SQL Server 2017]]></category>
		<category><![CDATA[SQL Server 2019]]></category>
		<category><![CDATA[batch mode]]></category>
		<category><![CDATA[columnstore]]></category>
		<category><![CDATA[inline index]]></category>
		<category><![CDATA[operation analytics]]></category>
		<category><![CDATA[reporting]]></category>

		<guid isPermaLink="false">http://blog.developpez.com/mikedavem/?p=1710</guid>
		<description><![CDATA[Columnstore indexes are a very nice feature and well-suited for analytics queries. Using them for our datawarehouse helped to accelerate some big ETL processing and to reduce resource footprint such as CPU, IO and memory as well. In addition, SQL &#8230; <a href="https://blog.developpez.com/mikedavem/p13202/sql-server-vnext/interesting-use-case-of-using-dummy-columnstore-indexes-and-temp-tables">Lire la suite <span class="meta-nav">&#8594;</span></a>]]></description>
				<content:encoded><![CDATA[<p>Columnstore indexes are a very nice feature and well-suited for analytics queries. Using them for our datawarehouse helped to accelerate some big ETL processing and to reduce resource footprint such as CPU, IO and memory as well. In addition, SQL Server 2016 takes columnstore index to a new level and allows a fully updateable non-clustered columnstore index on a rowstore table making possible operational and analytics workloads. Non-clustered columnstore index are a different beast to manage with OLTP workload and we got both good and bad experiences on it. In this blog post, let’s talk about good effects and an interesting case where we use them for reducing CPU consumption of a big reporting query.</p>
<p><span id="more-1710"></span></p>
<p>In fact, the concerned query follows a common T-SQL anti-pattern for performance: A complex layer of nested views and CTEs which are an interesting mix to getting chances to prevent a cleaned-up execution plan. The SQL optimizer gets tricked easily in this case. So, for illustration let’s start with the following query pattern:</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:650px;height:450px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">;WITH CTE1 AS (<br />
&nbsp; &nbsp; SELECT col ..., SUM(col2), ...<br />
&nbsp; &nbsp; FROM [VIEW]<br />
&nbsp; &nbsp; GROUP BY col ...<br />
),<br />
CTE2 AS (<br />
&nbsp; &nbsp; SELECT col ..., ROW_NUMBER() <br />
&nbsp; &nbsp; FROM (<br />
&nbsp; &nbsp; &nbsp; &nbsp; SELECT col ...<br />
&nbsp; &nbsp; &nbsp; &nbsp; JOIN CTE1 ON ...<br />
&nbsp; &nbsp; &nbsp; &nbsp; JOIN [VIEW2] ON ...<br />
&nbsp; &nbsp; &nbsp; &nbsp; JOIN [TABLE] ON ...<br />
&nbsp; &nbsp; ) AS VT <br />
),<br />
CTE3 A (<br />
&nbsp; &nbsp; SELECT col ...<br />
&nbsp; &nbsp; FROM [VIEW]<br />
&nbsp; &nbsp; JOIN [VIEW4] ON ...<br />
)<br />
...<br />
SELECT col ...<br />
FROM (<br />
&nbsp; &nbsp; SELECT <br />
&nbsp; &nbsp; &nbsp; &nbsp; col,<br />
&nbsp; &nbsp; &nbsp; &nbsp; STUFF((SELECT '', '' + col <br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;FROM CTE2 <br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;WHERE CTE2.ID = CTE1.ID<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;FOR XML PATH('''')), 1, 1, '''') AS colconcat, &nbsp; <br />
&nbsp; &nbsp; &nbsp; &nbsp; ...<br />
&nbsp; &nbsp; FROM (<br />
&nbsp; &nbsp; &nbsp; &nbsp; SELECT col ...<br />
&nbsp; &nbsp; &nbsp; &nbsp; FROM CTE1<br />
&nbsp; &nbsp; &nbsp; &nbsp; LEFT JOIN CTE2 ON ... &nbsp;<br />
&nbsp; &nbsp; &nbsp; &nbsp; LEFT JOIN (<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; SELECT col <br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; FROM CTE3<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; GROUP BY col<br />
&nbsp; &nbsp; &nbsp; &nbsp; ) AS T1 ON ...<br />
&nbsp; &nbsp; ) AS T2 <br />
&nbsp; &nbsp; GROUP BY col ...<br />
)</div></div>
<p>Sometimes splitting the big query into small pieces and storing pre-aggregation within temporary tables may help. This is what it has been done and led to some good effects with a global reduction of the query execution time.</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:650px;height:450px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">CREATE TABLE #T1 ...<br />
CREATE TABLE #T2 ...<br />
CREATE TABLE #T3 ...<br />
<br />
<br />
;WITH CTE1 AS (<br />
&nbsp; &nbsp; SELECT col ..., SUM(col2), ...<br />
&nbsp; &nbsp; FROM [VIEW]<br />
&nbsp; &nbsp; GROUP BY col ...<br />
)<br />
INSERT INTO #T1 ...<br />
SELECT col FROM CTE1 ...<br />
;<br />
<br />
WITH CTE2 AS (<br />
&nbsp; &nbsp; SELECT col ..., ROW_NUMBER() <br />
&nbsp; &nbsp; FROM (<br />
&nbsp; &nbsp; &nbsp; &nbsp; SELECT col ...<br />
&nbsp; &nbsp; &nbsp; &nbsp; JOIN #T1 ON ...<br />
&nbsp; &nbsp; &nbsp; &nbsp; JOIN [VIEW2] ON ...<br />
&nbsp; &nbsp; &nbsp; &nbsp; JOIN [TABLE] ON ...<br />
&nbsp; &nbsp; ) AS VT <br />
)<br />
INSERT INTO #T2 ...<br />
SELECT col FROM CTE2 ...<br />
;<br />
<br />
WITH CTE3 A (<br />
&nbsp; &nbsp; SELECT col ...<br />
&nbsp; &nbsp; FROM [VIEW]<br />
&nbsp; &nbsp; JOIN [VIEW4] ON ...<br />
)<br />
INSERT INTO #T3 ...<br />
SELECT col FROM CTE3 ...<br />
;<br />
<br />
<br />
SELECT col ...<br />
FROM (<br />
&nbsp; &nbsp; SELECT <br />
&nbsp; &nbsp; &nbsp; &nbsp; col,<br />
&nbsp; &nbsp; &nbsp; &nbsp; STUFF((SELECT '', '' + col <br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;FROM CTE2 <br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;WHERE CTE2.ID = CTE1.ID<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;FOR XML PATH('''')), 1, 1, '''') AS colconcat, &nbsp; <br />
&nbsp; &nbsp; &nbsp; &nbsp; ...<br />
&nbsp; &nbsp; FROM (<br />
&nbsp; &nbsp; &nbsp; &nbsp; SELECT col ...<br />
&nbsp; &nbsp; &nbsp; &nbsp; FROM #T1<br />
&nbsp; &nbsp; &nbsp; &nbsp; LEFT JOIN #T2 ON ... &nbsp;<br />
&nbsp; &nbsp; &nbsp; &nbsp; LEFT JOIN (<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; SELECT col <br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; FROM #T3<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; GROUP BY col<br />
&nbsp; &nbsp; &nbsp; &nbsp; ) AS T1 ON ...<br />
&nbsp; &nbsp; ) AS T2 <br />
&nbsp; &nbsp; GROUP BY col ...<br />
)</div></div>
<p>However, it was not enough, and the query continued to consume a lot of CPU time as shown below:</p>
<p><a href="http://blog.developpez.com/mikedavem/files/2020/11/169-1-profiler-performance-current-e1605891229645.jpg"><img src="http://blog.developpez.com/mikedavem/files/2020/11/169-1-profiler-performance-current-e1605891229645.jpg" alt="169 - 1 - profiler performance current" width="800" height="65" class="alignnone size-full wp-image-1711" /></a></p>
<p>CPU time was around 20s per execution. CPU time is greater than duration time due to parallelization. Regarding the environment you are, you would say having such CPU time can be common for reporting queries and you’re probably right. But let’s say in my context where all reporting queries are offloaded in a secondary availability group replica (SQL Server 2017), we wanted to keep the read only CPU footprint as low as possible to guarantee a safety margin of CPU resources to address scenarios where all the traffic (including both R/W and R/O queries) is redirected to the primary replica (maintenance, failure and so on).  The concerned report is executed on-demand by users and mostly contribute to create high CPU spikes among other reporting queries as shown below:</p>
<p><a href="http://blog.developpez.com/mikedavem/files/2020/11/169-2-grafana-CPU-current.jpg"><img src="http://blog.developpez.com/mikedavem/files/2020/11/169-2-grafana-CPU-current.jpg" alt="169 - 2 - grafana CPU current" width="406" height="204" class="alignnone size-full wp-image-1712" /></a></p>
<p>Testing this query on DEV environment gave following statistic execution outcomes:</p>
<p>SQL Server Execution Times:<br />
   <strong>CPU time = 12988 ms,  elapsed time = 6084 ms.</strong><br />
SQL Server parse and compile time:<br />
   CPU time = 0 ms, elapsed time = 0 ms.</p>
<p>… with the related execution plan (not estimated). In fact, I put only the final SELECT step because it was the main culprit of high CPU consumption for this query – (plan was anonymized by SQL Sentry Plan Explorer):</p>
<p><a href="http://blog.developpez.com/mikedavem/files/2020/11/169-3-query-execution-plan-current.jpg"><img src="http://blog.developpez.com/mikedavem/files/2020/11/169-3-query-execution-plan-current-1024x149.jpg" alt="169 - 3 - query execution plan current" width="584" height="85" class="alignnone size-large wp-image-1713" /></a></p>
<p>Real content of the query doesn’t matter for this write-up, but you probably have noticed I put explicitly concatenation stuff with XML PATH construct previously and I identified the execution path in the query plan above. This point will be important in the last section of this write-up. </p>
<p>First, because CPU is my main concern, I only selected CPU cost and you may notice top consumers are repartition streams and hash match operators followed by Lazy spool used with XML PATH and correlated subquery. </p>
<p>Then rewriting the query could be a good option but we first tried to find out some quick wins to avoid engaging too much time for refactoring stuff. Focusing on the different branches of this query plan and operators engaged from the right to the left, we make assumption that experimenting <a href="https://techcommunity.microsoft.com/t5/sql-server/columnstore-index-performance-batchmode-execution/ba-p/385054" rel="noopener" target="_blank">batch mode</a> could help reducing the overall CPU time on the highlighted branch. But because we are not dealing with billion of rows within temporary tables, we didn’t want to get extra overhead of maintaining compressed columnstore index structure. I remembered reading an very interesting <a href="https://www.itprotoday.com/sql-server/what-you-need-know-about-batch-mode-window-aggregate-operator-sql-server-2016-part-1" rel="noopener" target="_blank">article</a> in 2016 about the creation of dummy non-clustered columnstore indexes (NCCI) with filter capabilities to enable batch mode and it seemed perfectly fit with our scenario. In parallel, we went through inline index creation capabilities to neither trigger recompilation of the batch statement nor to prevent temp table caching. The target is to save CPU time <img src="https://blog.developpez.com/mikedavem/wp-includes/images/smilies/icon_smile.gif" alt=":)" class="wp-smiley" /></p>
<p>So, the temp table and inline non-clustered columnstore  index DDL was as follows:</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:650px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">CREATE TABLE #T1 ( col ..., INDEX CCI_IDX_T1 NONCLUSTERED COLUMNSTORE (col) ) WHERE col &amp;lt; 1<br />
CREATE TABLE #T2 ( col ..., INDEX CCI_IDX_T2 NONCLUSTERED COLUMNSTORE (col) ) WHERE col &amp;lt; 1<br />
CREATE TABLE #T3 ( col ..., INDEX CCI_IDX_T3 NONCLUSTERED COLUMNSTORE (col) ) WHERE col &amp;lt; 1<br />
…</div></div>
<p>Note the WHERE clause here with an out-of-range value to create an empty NCCI. </p>
<p>After applying the changes here, the new statistic execution metrics we got:</p>
<p>SQL Server Execution Times:<br />
   <strong>CPU time = 2842 ms,  elapsed time = 6536 ms.</strong><br />
SQL Server parse and compile time:<br />
   CPU time = 0 ms, elapsed time = 0 ms.</p>
<p>&#8230; and the related execution plan:</p>
<p><a href="http://blog.developpez.com/mikedavem/files/2020/11/169-4-query-execution-plan-first-optimization.jpg"><img src="http://blog.developpez.com/mikedavem/files/2020/11/169-4-query-execution-plan-first-optimization-1024x277.jpg" alt="169 - 4 - query execution plan first optimization" width="584" height="158" class="alignnone size-large wp-image-1715" /></a></p>
<p>A drop of CPU time consumption (2.8s vs 12s) per execution when the batch mode kicked-in. A good news for sure but something continued to draw my attention because even if batch mode came into play here, it was not propagated to the left and seem to stop at the level of XML PATH execution. After reading my <a href="http://www.nikoport.com/2018/10/12/batch-mode-part-4-some-of-the-limitations/" rel="noopener" target="_blank">preferred reference</a> on this topic (thank you Niko), I was able to confirm my suspicion of unsupported XML operation with batch mode. Unfortunately, I was out of luck to confirm with <strong>column_store_expression_filter_apply</strong> extended event that seem to not work for me. </p>
<p>Well, to allow the propagation of batch mode to the left side of the execution plan, it was necessary to write and move the correlated subquery with XML path to a simple JOIN and STRING_AGG() function – available since SQL Server 2016:</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:650px;height:450px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">-- Concat with XML PATH<br />
SELECT <br />
&nbsp; &nbsp; col,<br />
&nbsp; &nbsp; STUFF((SELECT '', '' + col <br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; FROM CTE2 <br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; WHERE CTE2.col = CTE1.col<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; FOR XML PATH('''')), 1, 1, '''') AS colconcat,<br />
&nbsp; &nbsp; ...<br />
FROM [TABLE]<br />
<br />
-- Concat with STRING_AGG<br />
SELECT <br />
&nbsp; &nbsp; col,<br />
&nbsp; &nbsp; V.colconcat,<br />
&nbsp; &nbsp; ...<br />
FROM [TABLE] AS T<br />
JOIN (<br />
&nbsp; &nbsp; SELECT <br />
&nbsp; &nbsp; &nbsp; &nbsp; col,<br />
&nbsp; &nbsp; &nbsp; &nbsp; STRING_AGG(col2, ', ') AS colconcat<br />
&nbsp; &nbsp; FROM #T2 <br />
&nbsp; &nbsp; GROUP BY col<br />
) AS V ON V.col = T.col</div></div>
<p>The new change gave this following outcome:</p>
<p>SQL Server Execution Times:<br />
   <strong>CPU time = 2109 ms,  elapsed time = 1872 ms</strong>.</p>
<p>and new execution plan:</p>
<p><a href="http://blog.developpez.com/mikedavem/files/2020/11/169-5-query-execution-plan-2n-optimization.jpg"><img src="http://blog.developpez.com/mikedavem/files/2020/11/169-5-query-execution-plan-2n-optimization-1024x189.jpg" alt="169 - 5 - query execution plan 2n optimization" width="584" height="108" class="alignnone size-large wp-image-1719" /></a></p>
<p>First, batch mode is now propagated from the right to the left of the query execution plan because we eliminated all inhibitors including XML construct.  We got not real CPU reduction this time, but we managed to reduce global execution time. The hash match aggregate operator is the main CPU consumer and it is the main candidate to benefit from batch mode. All remaining operators on the left side process few rows and my guess is that batch mode may appear less efficient than with the main consumer in this case. But anyway, note we also got rid of the Lazy Spool operator with the refactoring of the XML path and correlated subquery by STRING_AGG() and JOIN construct.</p>
<p><a href="http://blog.developpez.com/mikedavem/files/2020/11/169-6-profiler-performance-optimization-e1605891681487.jpg"><img src="http://blog.developpez.com/mikedavem/files/2020/11/169-6-profiler-performance-optimization-1024x59.jpg" alt="169 - 6 - profiler performance optimization" width="584" height="34" class="alignnone size-large wp-image-1716" /></a></p>
<p>The new result is better by far compared to the initial scenario (New CPU time: 3s vs Old CPU Time: 20s). It also had good effect of the overall workload on the AG read only replica:</p>
<p><a href="http://blog.developpez.com/mikedavem/files/2020/11/169-7-grafana-CPU-optimization-e1605891720209.jpg"><img src="http://blog.developpez.com/mikedavem/files/2020/11/169-7-grafana-CPU-optimization-1024x199.jpg" alt="169 - 7 - grafana CPU optimization" width="584" height="113" class="alignnone size-large wp-image-1717" /></a></p>
<p>Not so bad for a quick win!<br />
See you</p>
]]></content:encoded>
			<wfw:commentRss></wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Universal usage of NVARCHAR type and performance impact</title>
		<link>https://blog.developpez.com/mikedavem/p13195/sql-server-vnext/universal-usage-of-nvarchar-type-and-performance-impact</link>
		<comments>https://blog.developpez.com/mikedavem/p13195/sql-server-vnext/universal-usage-of-nvarchar-type-and-performance-impact#comments</comments>
		<pubDate>Wed, 27 May 2020 17:06:24 +0000</pubDate>
		<dc:creator><![CDATA[mikedavem]]></dc:creator>
				<category><![CDATA[Performance]]></category>
		<category><![CDATA[SQL Server 2017]]></category>
		<category><![CDATA[convert_implicit]]></category>
		<category><![CDATA[nvarchar]]></category>
		<category><![CDATA[performance]]></category>
		<category><![CDATA[Query Store]]></category>
		<category><![CDATA[sqlserver]]></category>

		<guid isPermaLink="false">http://blog.developpez.com/mikedavem/?p=1604</guid>
		<description><![CDATA[A couple of weeks, I read an article from Brent Ozar about using NVARCHAR as a universal parameter. It was a good reminder and from my experience, I confirm this habit has never been a good idea. Although it depends &#8230; <a href="https://blog.developpez.com/mikedavem/p13195/sql-server-vnext/universal-usage-of-nvarchar-type-and-performance-impact">Lire la suite <span class="meta-nav">&#8594;</span></a>]]></description>
				<content:encoded><![CDATA[<p>A couple of weeks, I read an <a href="https://www.brentozar.com/archive/2020/04/can-you-use-nvarchar-as-a-universal-parameter-almost/" rel="noopener" target="_blank">article</a> from Brent Ozar about using NVARCHAR as a universal parameter. It was a good reminder and from my experience, I confirm this habit has never been a good idea. Although it depends on the context, chances are you will almost find an exception that proves the rule. </p>
<p><span id="more-1604"></span></p>
<p>A couple of days ago, I felt into a situation that illustrated perfectly this issue, and, in this blog, I decided to share my experience and demonstrate how the impact may be in a real production scenario.<br />
So, let’s start with the culprit. I voluntary masked some contextual information but the principal is here. The query is pretty simple:</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:650px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">DECLARE @P0 DATETIME <br />
DECLARE @P1 INT<br />
DECLARE @P2 NVARCHAR(4000) <br />
DECLARE @P3 DATETIME <br />
DECLARE @P4 NVARCHAR(4000)<br />
<br />
UPDATE TABLE SET DATE = @P0<br />
WHERE ID = @P1<br />
&nbsp;AND IDENTIFIER = @P2<br />
&nbsp;AND P_DATE &amp;gt;= @P3<br />
&nbsp;AND W_O_ID = (<br />
&nbsp; &nbsp;SELECT TOP 1 ID FROM TABLE2<br />
&nbsp; &nbsp;WHERE Identifier = @P4<br />
&nbsp; &nbsp;ORDER BY ID DESC)</div></div>
<p>And the corresponding execution plan: </p>
<p><a href="http://blog.developpez.com/mikedavem/files/2020/05/162-1-excution_plan_with_implicit_conversion-e1590596511773.jpg"><img src="http://blog.developpez.com/mikedavem/files/2020/05/162-1-excution_plan_with_implicit_conversion-e1590596511773.jpg" alt="162 - 1 - excution_plan_with_implicit_conversion" width="1000" height="380" class="alignnone size-full wp-image-1605" /></a></p>
<p>The most interesting part concerns the TABLE2 table. As you may notice the @P4 input parameter type is NVARCHAR and it is evident we get a CONVERT_IMPLICIT in the concerned Predicate section above. The CONVERT_IMPLICIT function is required because of <a href="https://docs.microsoft.com/en-us/sql/t-sql/data-types/data-type-precedence-transact-sql?view=sql-server-ver15" rel="noopener" target="_blank">data type precedence</a>. It results to a costly operator that will scan all the data from TABLE2. As you probably know, CONVERT_IMPLICT prevents sargable condition and normally this is something we could expect here referring to the distribution value in the statistic histogram and the underlying index on the Identifier column.</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:650px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">EXEC sp_helpindex 'TABLE2';</div></div>
<p><a href="http://blog.developpez.com/mikedavem/files/2020/05/162-8-index-config.jpg"><img src="http://blog.developpez.com/mikedavem/files/2020/05/162-8-index-config.jpg" alt="162 - 8 - index config" width="1035" height="135" class="alignnone size-full wp-image-1606" /></a></p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:650px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">DBCC SHOW_STATISTICS ('TABLE2', 'IX___IDENTIFIER')<br />
WITH HISTOGRAM;</div></div>
<p><a href="http://blog.developpez.com/mikedavem/files/2020/05/162-10-histogram-stats.jpg"><img src="http://blog.developpez.com/mikedavem/files/2020/05/162-10-histogram-stats.jpg" alt="162 - 10 - histogram stats" width="877" height="435" class="alignnone size-full wp-image-1620" /></a></p>
<p>Another important point to keep in mind is that scanning all the data from the TABLE 2 table may be at a certain cost (&gt; 1GB) even if data resides in memory.</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:650px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">EXEC sp_spaceused 'TABLE2'</div></div>
<p><a href="http://blog.developpez.com/mikedavem/files/2020/05/162-9-index-space-used.jpg"><img src="http://blog.developpez.com/mikedavem/files/2020/05/162-9-index-space-used.jpg" alt="162 - 9 - index space used" width="725" height="62" class="alignnone size-full wp-image-1607" /></a></p>
<p>The execution plan warning confirms the potential overhead of retrieving few rows in the TABLE2 table:</p>
<p><a href="http://blog.developpez.com/mikedavem/files/2020/05/162-2-excution_plan_with_implicit_conversion-arning.jpg"><img src="http://blog.developpez.com/mikedavem/files/2020/05/162-2-excution_plan_with_implicit_conversion-arning.jpg" alt="162 - 2 - excution_plan_with_implicit_conversion arning" width="1160" height="108" class="alignnone size-full wp-image-1608" /></a></p>
<p>To set a little bit more the context, the concerned application queries are mainly based on JDBC Prepared statements which imply using NVARCHAR(4000) with string parameters regardless the column type in the database (VARCHAR / NVARCHAR). This is at least what we noticed from during my investigations. </p>
<p>So, what? Well, in our DEV environment the impact was imperceptible, and we had interesting discussions with the DEV team on this topic and we basically need to improve the awareness and the visibility on this field. (Another discussion and probably another blog post) … </p>
<p>But chances are your PROD environment will tell you a different story when it comes a bigger workload and concurrent query executions. In my context, from an infrastructure standpoint, the symptom was an abnormal increase of the CPU consumption a couple of days ago. Usually, the CPU consumption was roughly 20% up to 30% and in fact, the issue was around for a longer period, but we didn’t catch it due to a &laquo;&nbsp;normal&nbsp;&raquo; CPU footprint on this server. </p>
<p><a href="http://blog.developpez.com/mikedavem/files/2020/05/162-3-SQL-Processor-dashboard.jpg"><img src="http://blog.developpez.com/mikedavem/files/2020/05/162-3-SQL-Processor-dashboard.jpg" alt="162 - 3 - SQL Processor dashboard" width="668" height="631" class="alignnone size-full wp-image-1612" /></a></p>
<p>So, what happened here? We&rsquo;re using SQL Server 2017 with Query Store enabled on the concerned database. This feature came to the rescue and brought attention to the first clue: A query plan regression that led increasing IO consumption in the second case (and implicitly the additional CPU resource consumption as well).</p>
<p><a href="http://blog.developpez.com/mikedavem/files/2020/05/162-4-QS-regression-plan-e1590597560224.jpg"><img src="http://blog.developpez.com/mikedavem/files/2020/05/162-4-QS-regression-plan-e1590597560224.jpg" alt="162 - 4 - QS regression plan" width="1000" height="575" class="alignnone size-full wp-image-1613" /></a></p>
<p>You have probably noticed both the execution plans are using an index scan at the right but the more expensive one (at the bottom) uses a different index strategy. Instead of using the primary key and clustered index (PK_xxx), a non-clustered index on the IX_xxx_Identifier column in the second query execution plan is used with the same CONVERT_IMPLICIT issue. </p>
<p>According to the query store statistics, number of executions per business day is roughly 25000 executions with ~ 8.5H of CPU time consumed during this period (18.05.2020 – 26.05.2020) that was a very different order of magnitude compared to what we may have in the DEV environment <img src="https://blog.developpez.com/mikedavem/wp-includes/images/smilies/icon_smile.gif" alt=":)" class="wp-smiley" /></p>
<p>At this stage, I would say investigating why a plan regression occurred doesn’t really matter because in both cases the most expensive operator concerns an index scan and again, we expect an index seek. Getting rid of the implicit conversion by using VARCHAR type to make the conditional clause sargable was a better option for us. Thus, the execution plan would be:</p>
<p><a href="http://blog.developpez.com/mikedavem/files/2020/05/162-7-Execution-plan-with-seek-e1590597830429.jpg"><img src="http://blog.developpez.com/mikedavem/files/2020/05/162-7-Execution-plan-with-seek-e1590597830429.jpg" alt="162 - 7 - Execution plan with seek" width="1000" height="158" class="alignnone size-full wp-image-1615" /></a></p>
<p>The first workaround in mind was to force the better plan in the query store (automatic tuning with FORCE_LAST_GOOD_PLAN = ON is disabled) but having discussed this point with the DEV team, we managed to deploy a fix very fast to address this issue and to reduce drastically the CPU consumption on this SQL Server instance as shown below. The picture is self-explanatory: </p>
<p><a href="http://blog.developpez.com/mikedavem/files/2020/05/162-6-SQL-Processor-dashboard-after-optimization-e1590597880869.jpg"><img src="http://blog.developpez.com/mikedavem/files/2020/05/162-6-SQL-Processor-dashboard-after-optimization-e1590597880869.jpg" alt="162 - 6 - SQL Processor dashboard after optimization" width="1000" height="460" class="alignnone size-full wp-image-1616" /></a></p>
<p>The fix consisted in adding CAST / CONVERT function to the right side of the equality (parameter and not the column) to avoid side effect on the JDBC driver. Therefore, we get another version of the query and a different query hash as well. The query update is pretty similar to the following one:</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:650px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">DECLARE @P0 DATETIME <br />
DECLARE @P1 INT<br />
DECLARE @P2 NVARCHAR(4000) <br />
DECLARE @P3 DATETIME <br />
DECLARE @P4 NVARCHAR(4000)<br />
<br />
UPDATE TABLE SET DATE = @P0<br />
WHERE ID = @P1<br />
&nbsp;AND IDENTIFIER = CAST(@P2 AS varchar(50))<br />
&nbsp;AND P_DATE &amp;gt;= @P3<br />
&nbsp;AND W_O_ID = (<br />
&nbsp; &nbsp;SELECT TOP 1 ID FROM TABLE2<br />
&nbsp; &nbsp;WHERE Identifier = CAST(@P4 AS varchar(50))<br />
&nbsp; &nbsp;ORDER BY ID DESC)</div></div>
<p>Sometime later, we gathered query store statistics of both the former and new query to confirm the performance improvement as shown below:</p>
<p><a href="http://blog.developpez.com/mikedavem/files/2020/05/162-5-QS-stats-after-optimization.jpg"><img src="http://blog.developpez.com/mikedavem/files/2020/05/162-5-QS-stats-after-optimization.jpg" alt="162 - 5 - QS stats after optimization" width="923" height="98" class="alignnone size-full wp-image-1617" /></a></p>
<p>Finally changing the data type led to enable using an index seek operator to reduce drastically the SQL Server CPU consumption and logical read operations by far. </p>
<p>QED!</p>
]]></content:encoded>
			<wfw:commentRss></wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>SQL Server on Linux and new FUA support for XFS filesystem</title>
		<link>https://blog.developpez.com/mikedavem/p13193/sql-server-vnext/sql-server-on-linux-and-new-fua-support-for-xfs-filesystem</link>
		<comments>https://blog.developpez.com/mikedavem/p13193/sql-server-vnext/sql-server-on-linux-and-new-fua-support-for-xfs-filesystem#comments</comments>
		<pubDate>Mon, 13 Apr 2020 17:34:32 +0000</pubDate>
		<dc:creator><![CDATA[mikedavem]]></dc:creator>
				<category><![CDATA[Performance]]></category>
		<category><![CDATA[SQL Server 2017]]></category>
		<category><![CDATA[SQL Server 2019]]></category>
		<category><![CDATA[blktrace]]></category>
		<category><![CDATA[FUA]]></category>
		<category><![CDATA[iostats]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[performance]]></category>
		<category><![CDATA[SQL Server]]></category>
		<category><![CDATA[xfs]]></category>

		<guid isPermaLink="false">http://blog.developpez.com/mikedavem/?p=1568</guid>
		<description><![CDATA[I wrote a (dbi services) blog post concerning Linux and SQL Server IO behavior changes before and after SQL Server 2017 CU6. Now, I was looking forward seeing some new improvements with Force Unit Access (FUA) that was implemented with &#8230; <a href="https://blog.developpez.com/mikedavem/p13193/sql-server-vnext/sql-server-on-linux-and-new-fua-support-for-xfs-filesystem">Lire la suite <span class="meta-nav">&#8594;</span></a>]]></description>
				<content:encoded><![CDATA[<p>I wrote a (dbi services) <a href="https://blog.dbi-services.com/sql-server-on-linux-io-internal-thoughts/" rel="noopener" target="_blank">blog pos</a>t concerning Linux and SQL Server IO behavior changes before and after SQL Server 2017 CU6.  Now, I was looking forward seeing some new improvements with Force Unit Access (FUA) that was implemented with Linux XFS enhancements since the Linux Kernel 4.18.</p>
<p><span id="more-1568"></span></p>
<p>As reminder, SQL Server 2017 CU6 provides added a way to guarantee data durability by using &laquo;&nbsp;forced flush&nbsp;&raquo; mechanism explained <a href="https://support.microsoft.com/en-us/help/4131496/enable-forced-flush-mechanism-in-sql-server-2017-on-linux" rel="noopener" target="_blank">here</a>. To cut the story short, SQL Server has strict storage requirement such as Write Ordering, FUA and things go differently on Linux than Windows to achieve durability. What is FUA and why is it important for SQL Server? From <a href="https://en.wikipedia.org/wiki/Disk_buffer#Force_Unit_Access_(FUA)" rel="noopener" target="_blank">Wikipedia</a>:  Force Unit Access (aka FUA) is an I/O write command option that forces written data all the way to stable storage. FUA appeared in the SCSI command set but good news, it was later adopted by other standards over the time. SQL Server relies on it to meet WAL and ACID capabilities. </p>
<p>On the Linux world and before the Kernel 4.18, FUA was handled and optimized only for the filesystem journaling. However, data storage always used the multi-step flush process that could introduce SQL Server IO storage slowness (Issue write to block device for the data + issue block device flush to ensure durability with O_DSYNC). </p>
<p>On the Windows world, installing and using a SQL Server instance assumes you are compliant with the Microsoft storage requirements and therefore the first RTM version shipped on Linux came only with O_DIRECT assuming you already ensure that SQL Server IO are able to be written directly into a non-volatile storage through the kernel, drivers and hardware before the acknowledgement. Forced flush mechanism &#8211; based on fdatasync() &#8211;  was then introduced to address scenarios with no safe DIRECT_IO capabilities. </p>
<p>But referring to the Bob Dorr <a href="https://bobsql.com/sql-server-on-linux-forced-unit-access-fua-internals/" rel="noopener" target="_blank">article</a>, Linux Kernel 4.18 comes with XFS enhancements to handle FUA for data storage and it is obviously of benefit to SQL Server.  FUA support is intended to improve write requests by shorten the path of write requests as shown below:</p>
<p><a href="http://blog.developpez.com/mikedavem/files/2020/04/160-1-IO-worklow-e1586796506268.jpg"><img src="http://blog.developpez.com/mikedavem/files/2020/04/160-1-IO-worklow-e1586796506268.jpg" alt="160 - 1 - IO worklow" width="1000" height="539" class="alignnone size-full wp-image-1569" /></a></p>
<p><em>Picture from existing IO workflow on Bob Dorr&rsquo;s article</em></p>
<p>This is an interesting improvement for write intensive workload and it seems to be confirmed from the tests performed by Microsoft and Bob Dorr in his article. </p>
<p>Let’s the experiment begins with my lab environment based on a Centos 7 on Hyper-V with an upgraded kernel version: 5.6.3-1.e17.elrepo.x86_64.</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:650px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">$uname -r<br />
5.6.3-1.el7.elrepo.x86_64<br />
<br />
$cat /etc/os-release | grep VERSION<br />
VERSION=&quot;7 (Core)&quot;<br />
VERSION_ID=&quot;7&quot;<br />
CENTOS_MANTISBT_PROJECT_VERSION=&quot;7&quot;<br />
REDHAT_SUPPORT_PRODUCT_VERSION=&quot;7&quot;</div></div>
<p>Let’s precise that my tests are purely experimental and instead of upgrading the Kernel to a newer version you may directly rely on RHEL 8 based distros which comes with kernel version 4.18 for example.</p>
<p>My lab environment includes 2 separate SSD disks to host the DATA + TLOG database files as follows:</p>
<p>I:\ drive : SQL Data volume (sdb – XFS filesystem)<br />
T:\ drive : SQL TLog volume (sda – XFS filesystem)</p>
<p>The general performance is not so bad <img src="https://blog.developpez.com/mikedavem/wp-includes/images/smilies/icon_smile.gif" alt=":)" class="wp-smiley" /></p>
<p><a href="http://blog.developpez.com/mikedavem/files/2020/04/160-6-diskmark-tests-storage-env-e1586796679451.jpg"><img src="http://blog.developpez.com/mikedavem/files/2020/04/160-6-diskmark-tests-storage-env-e1586796679451.jpg" alt="160 - 6 - diskmark tests storage env" width="1000" height="362" class="alignnone size-full wp-image-1571" /></a></p>
<p>Initially I just dedicated on disk for both SQL DATA and TLOG but I quickly noticed some IO waits (iostats output) leading to make me lunconfident with my test results</p>
<p><a href="http://blog.developpez.com/mikedavem/files/2020/04/160-3-iostats-before-optimization.jpg"><img src="http://blog.developpez.com/mikedavem/files/2020/04/160-3-iostats-before-optimization.jpg" alt="160 - 3 - iostats before optimization" width="975" height="447" class="alignnone size-full wp-image-1572" /></a></p>
<p>Spreading IO on physically separate volumes helped to reduce drastically these phenomena afterwards:</p>
<p><a href="http://blog.developpez.com/mikedavem/files/2020/04/160-4-iostats-after-optimization.jpg"><img src="http://blog.developpez.com/mikedavem/files/2020/04/160-4-iostats-after-optimization.jpg" alt="160 - 4 - iostats after optimization" width="984" height="531" class="alignnone size-full wp-image-1573" /></a> </p>
<p>First, I enabled FUA capabilities on Hyper-V side as follows:</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:650px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">Set-VMHardDiskDrive -VMName CENTOS7 -ControllerType SCSI -OverrideCacheAttributes WriteCacheAndFUAEnabled<br />
<br />
Get-VMHardDiskDrive -VMName CENTOS7 | `<br />
&nbsp; &nbsp; ft VMName, ControllerType, &nbsp;ControllerLocation, Path, WriteHardeningMethod -AutoSize</div></div>
<p>Then I checked if FUA is enabled and supported from an OS perspective including sda (TLOG) and sdb (SQL DATA) disks:</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:650px;height:450px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">$ lsblk -f<br />
NAME &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;FSTYPE &nbsp; &nbsp; &nbsp;LABEL UUID &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; MOUNTPOINT<br />
sdb<br />
└─sdb1 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;xfs &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 06910f69-27a3-4711-9093-f8bf80d15d72 &nbsp; /sqldata<br />
sr0<br />
sda<br />
├─sda2 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;xfs &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; f5a9bded-130f-4642-bd6f-9f27563a4e16 &nbsp; /boot<br />
├─sda3 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;LVM2_member &nbsp; &nbsp; &nbsp; QsbKEt-28yT-lpfZ-VCbj-v5W5-vnVr-2l7nih<br />
│ ├─centos-swap swap &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;7eebbb32-cef5-42e9-87c3-7df1a0b79f11 &nbsp; [SWAP]<br />
│ └─centos-root xfs &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 90f6eb2f-dd39-4bef-a7da-67aa75d1843d &nbsp; /<br />
└─sda1 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;vfat &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;7529-979E &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;/boot/efi<br />
<br />
$ dmesg | grep sda<br />
[ &nbsp; &nbsp;1.665478] sd 0:0:0:0: [sda] 83886080 512-byte logical blocks: (42.9 GB/40.0 GiB)<br />
[ &nbsp; &nbsp;1.665479] sd 0:0:0:0: [sda] 4096-byte physical blocks<br />
[ &nbsp; &nbsp;1.665774] sd 0:0:0:0: [sda] Write Protect is off<br />
[ &nbsp; &nbsp;1.665775] sd 0:0:0:0: [sda] Mode Sense: 0f 00 10 00<br />
[ &nbsp; &nbsp;1.670321] sd 0:0:0:0: [sda] Write cache: enabled, read cache: enabled, supports DPO and FUA<br />
[ &nbsp; &nbsp;1.683833] &nbsp;sda: sda1 sda2 sda3<br />
[ &nbsp; &nbsp;1.708938] sd 0:0:0:0: [sda] Attached SCSI disk<br />
[ &nbsp; &nbsp;5.607914] EXT4-fs (sda2): mounted filesystem with ordered data mode. Opts: (null)</div></div>
<p>Finally according to the documentation, I configured the <strong>trace flag 3979</strong> and <strong>control.alternatewritethrough=0</strong> parameters at startup parameters for my SQL Server instance.</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:650px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">$ /opt/mssql/bin/mssql-conf traceflag 3979 on<br />
<br />
$ /opt/mssql/bin/mssql-conf set control.alternatewritethrough 0<br />
<br />
$ systemctl restart mssql-server</div></div>
<p>The first I performed was pretty similar to those in my previous (dbi services) <a href="https://blog.dbi-services.com/sql-server-on-linux-io-internal-thoughts/">blog post</a>.</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:650px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">CREATE TABLE dummy_test (<br />
&nbsp; &nbsp; id INT IDENTITY,<br />
&nbsp; &nbsp; col1 VARCHAR(2000) DEFAULT REPLICATE('T', 2000)<br />
);<br />
<br />
INSERT INTO dummy_test DEFAULT VALUES;<br />
GO 67</div></div>
<p>For a sake of curiosity, I looked at the corresponding strace output:</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:650px;height:450px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">$ cat sql_strace_fua.txt<br />
% time &nbsp; &nbsp; seconds &nbsp;usecs/call &nbsp; &nbsp; calls &nbsp; &nbsp;errors syscall<br />
------ ----------- ----------- --------- --------- ----------------<br />
&nbsp;78.13 &nbsp;360.618066 &nbsp; &nbsp; &nbsp; 61739 &nbsp; &nbsp; &nbsp;5841 &nbsp; &nbsp; &nbsp;2219 futex<br />
&nbsp; 6.88 &nbsp; 31.731833 &nbsp; &nbsp; 1511040 &nbsp; &nbsp; &nbsp; &nbsp;21 &nbsp; &nbsp; &nbsp; &nbsp;15 restart_syscall<br />
&nbsp; 3.81 &nbsp; 17.592176 &nbsp; &nbsp; &nbsp;130312 &nbsp; &nbsp; &nbsp; 135 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; io_getevents<br />
&nbsp; 2.95 &nbsp; 13.607314 &nbsp; &nbsp; &nbsp; 98604 &nbsp; &nbsp; &nbsp; 138 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; epoll_wait<br />
&nbsp; 2.88 &nbsp; 13.313667 &nbsp; &nbsp; &nbsp;633984 &nbsp; &nbsp; &nbsp; &nbsp;21 &nbsp; &nbsp; &nbsp; &nbsp;21 rt_sigtimedwait<br />
&nbsp; 2.60 &nbsp; 11.997925 &nbsp; &nbsp; 1333103 &nbsp; &nbsp; &nbsp; &nbsp; 9 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; nanosleep<br />
&nbsp; 1.79 &nbsp; &nbsp;8.279781 &nbsp; &nbsp; &nbsp; &nbsp; 242 &nbsp; &nbsp; 34256 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; gettid<br />
&nbsp; 0.84 &nbsp; &nbsp;3.876021 &nbsp; &nbsp; &nbsp; &nbsp; 226 &nbsp; &nbsp; 17124 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; getcpu<br />
&nbsp; 0.03 &nbsp; &nbsp;0.138836 &nbsp; &nbsp; &nbsp; &nbsp; 347 &nbsp; &nbsp; &nbsp; 400 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; sched_yield<br />
&nbsp; 0.01 &nbsp; &nbsp;0.062348 &nbsp; &nbsp; &nbsp; &nbsp; 254 &nbsp; &nbsp; &nbsp; 245 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; getrusage<br />
&nbsp; 0.01 &nbsp; &nbsp;0.056065 &nbsp; &nbsp; &nbsp; &nbsp; 406 &nbsp; &nbsp; &nbsp; 138 &nbsp; &nbsp; &nbsp; &nbsp;69 readv<br />
&nbsp; 0.01 &nbsp; &nbsp;0.038107 &nbsp; &nbsp; &nbsp; &nbsp; 343 &nbsp; &nbsp; &nbsp; 111 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; read<br />
&nbsp; 0.01 &nbsp; &nbsp;0.037883 &nbsp; &nbsp; &nbsp; &nbsp; 743 &nbsp; &nbsp; &nbsp; &nbsp;51 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; mmap<br />
&nbsp; 0.01 &nbsp; &nbsp;0.037498 &nbsp; &nbsp; &nbsp; &nbsp; 180 &nbsp; &nbsp; &nbsp; 208 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; epoll_ctl<br />
&nbsp; 0.01 &nbsp; &nbsp;0.035654 &nbsp; &nbsp; &nbsp; &nbsp; 517 &nbsp; &nbsp; &nbsp; &nbsp;69 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; writev<br />
&nbsp; 0.01 &nbsp; &nbsp;0.025542 &nbsp; &nbsp; &nbsp; &nbsp; 370 &nbsp; &nbsp; &nbsp; &nbsp;69 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; io_submit<br />
&nbsp; 0.00 &nbsp; &nbsp;0.019760 &nbsp; &nbsp; &nbsp; &nbsp; 282 &nbsp; &nbsp; &nbsp; &nbsp;70 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; write<br />
&nbsp; 0.00 &nbsp; &nbsp;0.019555 &nbsp; &nbsp; &nbsp; &nbsp; 477 &nbsp; &nbsp; &nbsp; &nbsp;41 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; open<br />
&nbsp; 0.00 &nbsp; &nbsp;0.016285 &nbsp; &nbsp; &nbsp; &nbsp;1629 &nbsp; &nbsp; &nbsp; &nbsp;10 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; rt_sigaction<br />
&nbsp; 0.00 &nbsp; &nbsp;0.012359 &nbsp; &nbsp; &nbsp; &nbsp; 301 &nbsp; &nbsp; &nbsp; &nbsp;41 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; close<br />
&nbsp; 0.00 &nbsp; &nbsp;0.010069 &nbsp; &nbsp; &nbsp; &nbsp; 205 &nbsp; &nbsp; &nbsp; &nbsp;49 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; munmap<br />
&nbsp; 0.00 &nbsp; &nbsp;0.006977 &nbsp; &nbsp; &nbsp; &nbsp; 303 &nbsp; &nbsp; &nbsp; &nbsp;23 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; rt_sigprocmask<br />
&nbsp; 0.00 &nbsp; &nbsp;0.006256 &nbsp; &nbsp; &nbsp; &nbsp; 153 &nbsp; &nbsp; &nbsp; &nbsp;41 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; fstat<br />
&nbsp; 0.00 &nbsp; &nbsp;0.004646 &nbsp; &nbsp; &nbsp; &nbsp; 465 &nbsp; &nbsp; &nbsp; &nbsp;10 &nbsp; &nbsp; &nbsp; &nbsp;10 stat<br />
&nbsp; 0.00 &nbsp; &nbsp;0.000860 &nbsp; &nbsp; &nbsp; &nbsp; 215 &nbsp; &nbsp; &nbsp; &nbsp; 4 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; madvise<br />
&nbsp; 0.00 &nbsp; &nbsp;0.000321 &nbsp; &nbsp; &nbsp; &nbsp; 161 &nbsp; &nbsp; &nbsp; &nbsp; 2 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; sched_setaffinity<br />
&nbsp; 0.00 &nbsp; &nbsp;0.000295 &nbsp; &nbsp; &nbsp; &nbsp; 148 &nbsp; &nbsp; &nbsp; &nbsp; 2 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; set_robust_list<br />
&nbsp; 0.00 &nbsp; &nbsp;0.000281 &nbsp; &nbsp; &nbsp; &nbsp; 141 &nbsp; &nbsp; &nbsp; &nbsp; 2 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; clone<br />
&nbsp; 0.00 &nbsp; &nbsp;0.000236 &nbsp; &nbsp; &nbsp; &nbsp; 118 &nbsp; &nbsp; &nbsp; &nbsp; 2 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; sigaltstack<br />
&nbsp; 0.00 &nbsp; &nbsp;0.000093 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;47 &nbsp; &nbsp; &nbsp; &nbsp; 2 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; arch_prctl<br />
&nbsp; 0.00 &nbsp; &nbsp;0.000046 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;23 &nbsp; &nbsp; &nbsp; &nbsp; 2 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; sched_getaffinity<br />
------ ----------- ----------- --------- --------- ----------------<br />
100.00 &nbsp;461.546755 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 59137 &nbsp; &nbsp; &nbsp;2334 total</div></div>
<p>… And as I expected, with FUA enabled no fsync() / fdatasync() called anymore and writing to a stable storage is achieved directly by FUA commands. Now iomap_dio_rw() is determining if REQ_FUA can be used and issuing generic_write_sync() is still necessary. To dig further to the IO layer we need to rely to another tool blktrace (mentioned to the Bob Dorr&rsquo;s article as well).</p>
<p>In my case I got to different pictures of blktrace output between forced flushed mechanism (the default) and FUA oriented IO:</p>
<p>-&gt; With forced flush</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:650px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">34.694734500 &nbsp; &nbsp; &nbsp;14225 18425192 &nbsp; &nbsp; 8,16 &nbsp; 0 &nbsp; &nbsp;17164 &nbsp;A &nbsp;WS &nbsp; &nbsp; &nbsp; 2048 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; sqlservr<br />
34.694735000 &nbsp; &nbsp; &nbsp;14225 18425192 &nbsp; &nbsp; 8,16 &nbsp; 0 &nbsp; &nbsp;17165 &nbsp;Q &nbsp;WS &nbsp; &nbsp; &nbsp; 2048 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; sqlservr<br />
34.694737000 &nbsp; &nbsp; &nbsp;14225 18425192 &nbsp; &nbsp; 8,16 &nbsp; 0 &nbsp; &nbsp;17166 &nbsp;X &nbsp;WS &nbsp; &nbsp; &nbsp; 1024 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; sqlservr<br />
34.694738100 &nbsp; &nbsp; &nbsp;14225 18425192 &nbsp; &nbsp; 8,16 &nbsp; 0 &nbsp; &nbsp;17167 &nbsp;G &nbsp;WS &nbsp; &nbsp; &nbsp; 1024 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; sqlservr<br />
34.694739800 &nbsp; &nbsp; &nbsp;14225 18426216 &nbsp; &nbsp; 8,16 &nbsp; 0 &nbsp; &nbsp;17169 &nbsp;G &nbsp;WS &nbsp; &nbsp; &nbsp; 1024 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; sqlservr<br />
34.694740900 &nbsp; &nbsp; &nbsp;14225 18425192 &nbsp; &nbsp; 8,16 &nbsp; 0 &nbsp; &nbsp;17171 &nbsp;D &nbsp;WS &nbsp; &nbsp; &nbsp; 1024 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; sqlservr<br />
34.694747200 &nbsp; &nbsp; &nbsp;14225 18426216 &nbsp; &nbsp; 8,16 &nbsp; 0 &nbsp; &nbsp;17174 &nbsp;D &nbsp;WS &nbsp; &nbsp; &nbsp; 1024 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; sqlservr<br />
34.713665000 &nbsp; &nbsp; &nbsp;14225 0 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;8,16 &nbsp; 0 &nbsp; &nbsp;17175 &nbsp;Q FWS &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;0 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; sqlservr<br />
34.713668100 &nbsp; &nbsp; &nbsp;14225 0 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;8,16 &nbsp; 0 &nbsp; &nbsp;17176 &nbsp;G FWS &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;0 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; sqlservr</div></div>
<p>WS (Write Synchronous) is performed but SQL Server still needs to go through the multi-step flush process with the additional FWS (PERFLUSH|WRITE|SYNC).</p>
<p>-&gt; FUA</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:650px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">0.000000000 &nbsp; &nbsp; &nbsp;16305 55106536 &nbsp; &nbsp; 8,0 &nbsp; &nbsp;0 &nbsp; &nbsp; &nbsp; &nbsp;1 &nbsp;A WFS &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;8 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; sqlservr<br />
0.000000400 &nbsp; &nbsp; &nbsp;16305 57615336 &nbsp; &nbsp; 8,0 &nbsp; &nbsp;0 &nbsp; &nbsp; &nbsp; &nbsp;2 &nbsp;A WFS &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;8 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; sqlservr<br />
0.000001100 &nbsp; &nbsp; &nbsp;16305 57615336 &nbsp; &nbsp; 8,0 &nbsp; &nbsp;0 &nbsp; &nbsp; &nbsp; &nbsp;3 &nbsp;Q WFS &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;8 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; sqlservr<br />
0.000005200 &nbsp; &nbsp; &nbsp;16305 57615336 &nbsp; &nbsp; 8,0 &nbsp; &nbsp;0 &nbsp; &nbsp; &nbsp; &nbsp;4 &nbsp;G WFS &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;8 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; sqlservr<br />
0.001377800 &nbsp; &nbsp; &nbsp;16305 55106544 &nbsp; &nbsp; 8,0 &nbsp; &nbsp;0 &nbsp; &nbsp; &nbsp; &nbsp;6 &nbsp;A WFS &nbsp; &nbsp; &nbsp; &nbsp; 16 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; sqlservr</div></div>
<p>FWS has disappeared with only WFS commands which are basically <strong>REQ_WRITE with the REQ_FUA request</strong></p>
<p>I spent some times to read some interesting discussions in addition to the Bob Dorr&rsquo;s wonderful article. Here an interesting <a href="https://lkml.org/lkml/2019/12/3/316" rel="noopener" target="_blank">pointer</a> to a a discussion about REQ_FUA for instance.</p>
<p><strong>But what about performance gain? </strong></p>
<p>I had 2 simple scenarios to play with in order to bring out FUA helpfulness including the harden the dirty pages in the BP with checkpoint process and harden the log buffer to disk during the commit phase. When forced flush method is used, each component relies on additional FlushFileBuffers() function to achieve durability. This event can be easily tracked from an XE session including <strong>flush_file_buffers</strong> and <strong>make_writes_durable</strong> events.</p>
<p><a href="http://blog.developpez.com/mikedavem/files/2020/04/160-1-1-flushfilebuffers-worklflow.jpg"><img src="http://blog.developpez.com/mikedavem/files/2020/04/160-1-1-flushfilebuffers-worklflow.jpg" alt="160 - 1 - 1 - flushfilebuffers worklflow" width="839" height="505" class="alignnone size-full wp-image-1575" /></a></p>
<p><strong>First scenario (10K inserts within a transaction and checkpoint)</strong></p>
<p>In this scenario my intention was to stress the checkpoint process with a bunch of buffers and dirty pages to flush to disk when it kicks in.</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:650px;height:450px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">USE dummy;<br />
<br />
SET NOCOUNT ON;<br />
-- Disable checkpoint to control when it will kick in<br />
DBCC TRACEON(3505);<br />
-- Check traceflag<br />
DBCC TRACESTATUS;<br />
<br />
DECLARE @i INT = 0;<br />
DECLARE @iteration INT = 0;<br />
DECLARE @start_upd DATETIME;<br />
DECLARE @start_chkpt DATETIME;<br />
DECLARE @end_upd DATETIME;<br />
DECLARE @end_chkpt DATETIME;<br />
<br />
TRUNCATE TABLE dummy_test;<br />
<br />
WHILE @iteration &amp;lt; 251<br />
BEGIN<br />
&nbsp; &nbsp; <br />
&nbsp; &nbsp; SET @start_upd = GETDATE();<br />
<br />
&nbsp; &nbsp; BEGIN TRAN;<br />
<br />
&nbsp; &nbsp; WHILE @i &amp;lt;= 10000<br />
&nbsp; &nbsp; BEGIN<br />
&nbsp; &nbsp; &nbsp; &nbsp; INSERT INTO dummy_test DEFAULT VALUES;<br />
&nbsp; &nbsp; &nbsp; &nbsp; SET @i += 1;<br />
&nbsp; &nbsp; END<br />
&nbsp; &nbsp; <br />
&nbsp; &nbsp; COMMIT TRAN;<br />
<br />
&nbsp; &nbsp; SET @end_upd = GETDATE();<br />
<br />
&nbsp; &nbsp; SET @i = 0;<br />
&nbsp; &nbsp; <br />
&nbsp; &nbsp; SET @start_chkpt = GETDATE();<br />
&nbsp; &nbsp; CHECKPOINT;<br />
&nbsp; &nbsp; SET @end_chkpt = GETDATE();<br />
&nbsp; &nbsp; PRINT &amp;#039;INS: &amp;#039; + CAST(DATEDIFF(ms, @start_upd, @end_upd) AS VARCHAR(50)) + &amp;#039; - CHKPT: &amp;#039; + CAST(DATEDIFF(ms, @start_chkpt, @end_chkpt) AS VARCHAR(50));<br />
<br />
&nbsp; &nbsp; SET @iteration += 1;<br />
END</div></div>
<p>The result is as follows:</p>
<p><a href="http://blog.developpez.com/mikedavem/files/2020/04/160-5-test-perfs-250_10K_chkpt.jpg"><img src="http://blog.developpez.com/mikedavem/files/2020/04/160-5-test-perfs-250_10K_chkpt.jpg" alt="160 - 5 - test perfs 250_10K_chkpt" width="974" height="298" class="alignnone size-full wp-image-1576" /></a></p>
<p>In my case, I noticed ~ 17% of improvement for the checkpoint process and ~7% for the insert transaction including the commit phase with flushing data to the TLog. In parallel, looking at the extended event aggregated output confirms that FUA avoids a lot of additional operations to persist data on disk illustrated by flush_file_buffers and make_writes_durable events.</p>
<p><a href="http://blog.developpez.com/mikedavem/files/2020/04/160-6-xe-flush-file-buffers-e1586798220100.jpg"><img src="http://blog.developpez.com/mikedavem/files/2020/04/160-6-xe-flush-file-buffers-e1586798220100.jpg" alt="160 - 6 - xe flush file buffers" width="1000" height="178" class="alignnone size-full wp-image-1577" /></a></p>
<p><strong>Second scenario (100x 1 insert within a transaction and checkpoint)</strong></p>
<p>In this scenario, I wanted to stress the log writer by forcing a lot of small transactions to commit. I updated the TSQL code as shown below:</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:650px;height:450px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">USE dummy;<br />
<br />
SET NOCOUNT ON;<br />
-- Disable checkpoint to control when it will kick in<br />
DBCC TRACEON(3505);<br />
-- Check traceflag<br />
DBCC TRACESTATUS;<br />
<br />
DECLARE @i INT = 0;<br />
DECLARE @iteration INT = 0;<br />
DECLARE @start_upd DATETIME;<br />
DECLARE @start_chkpt DATETIME;<br />
DECLARE @end_upd DATETIME;<br />
DECLARE @end_chkpt DATETIME;<br />
<br />
TRUNCATE TABLE dummy_test;<br />
<br />
WHILE @iteration &amp;lt; 251<br />
BEGIN<br />
&nbsp; &nbsp; <br />
&nbsp; &nbsp; SET @start_upd = GETDATE();<br />
<br />
&nbsp; &nbsp; WHILE @i &amp;lt;= 100<br />
&nbsp; &nbsp; BEGIN<br />
&nbsp; &nbsp; &nbsp; &nbsp; INSERT INTO dummy_test DEFAULT VALUES;<br />
&nbsp; &nbsp; &nbsp; &nbsp; SET @i += 1;<br />
&nbsp; &nbsp; END<br />
<br />
&nbsp; &nbsp; SET @end_upd = GETDATE();<br />
<br />
&nbsp; &nbsp; SET @i = 0;<br />
&nbsp; &nbsp; <br />
&nbsp; &nbsp; SET @start_chkpt = GETDATE();<br />
&nbsp; &nbsp; CHECKPOINT;<br />
&nbsp; &nbsp; SET @end_chkpt = GETDATE();<br />
&nbsp; &nbsp; PRINT &amp;#039;INS: &amp;#039; + CAST(DATEDIFF(ms, @start_upd, @end_upd) AS VARCHAR(50)) + &amp;#039; - CHKPT: &amp;#039; + CAST(DATEDIFF(ms, @start_chkpt, @end_chkpt) AS VARCHAR(50));<br />
<br />
&nbsp; &nbsp; SET @iteration += 1;<br />
END</div></div>
<p>The new picture is the following:</p>
<p><a href="http://blog.developpez.com/mikedavem/files/2020/04/160-7-test-perfs-250_100_1K_chkpt.jpg"><img src="http://blog.developpez.com/mikedavem/files/2020/04/160-7-test-perfs-250_100_1K_chkpt.jpg" alt="160 - 7 - test perfs 250_100_1K_chkpt" width="974" height="298" class="alignnone size-full wp-image-1580" /></a></p>
<p>This time the improvement is definitely more impressive with a decrease of ~80% of the execution time about the INSERT + COMMIT and ~77% concerning the checkpoint phase!!!</p>
<p>Looking at the extended event session confirms the shorten IO path has something to do with it <img src="https://blog.developpez.com/mikedavem/wp-includes/images/smilies/icon_smile.gif" alt=":)" class="wp-smiley" /></p>
<p><a href="http://blog.developpez.com/mikedavem/files/2020/04/160-7-xe-flush-file-buffers-2-e1586798367112.jpg"><img src="http://blog.developpez.com/mikedavem/files/2020/04/160-7-xe-flush-file-buffers-2-e1586798367112.jpg" alt="160 - 7 - xe flush file buffers 2" width="1000" height="170" class="alignnone size-full wp-image-1578" /></a></p>
<p>Well, shortening the IO path and relying directing on initial FUA instructions was definitely a good idea both to join performance and to meet WAL and ACID capabilities. Anyway, I’m glad to see Microsoft to contribute improving to the Linux Kernel!!!</p>
]]></content:encoded>
			<wfw:commentRss></wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Mitigating Scalar UDF&#8217;s procedural code performance with SQL 2019 and  Scalar UDF Inlining capabilities</title>
		<link>https://blog.developpez.com/mikedavem/p13189/performance/mitigating-scalar-udf-procedural-code-performance-with-sql-2019-udf-inline-capabilites</link>
		<comments>https://blog.developpez.com/mikedavem/p13189/performance/mitigating-scalar-udf-procedural-code-performance-with-sql-2019-udf-inline-capabilites#comments</comments>
		<pubDate>Thu, 05 Mar 2020 15:10:02 +0000</pubDate>
		<dc:creator><![CDATA[mikedavem]]></dc:creator>
				<category><![CDATA[Performance]]></category>
		<category><![CDATA[SQL Server 2019]]></category>
		<category><![CDATA[Imperative]]></category>
		<category><![CDATA[performance]]></category>
		<category><![CDATA[Procedural code]]></category>
		<category><![CDATA[Programing]]></category>
		<category><![CDATA[Scalar UDF Inlining]]></category>

		<guid isPermaLink="false">http://blog.developpez.com/mikedavem/?p=1516</guid>
		<description><![CDATA[A couple of days ago, I read the write-up of my former colleague @FranckPachot about refactoring procedural code to SQL. This is recurrent subject in the database world and I was interested in transposing this article to SQL Server because &#8230; <a href="https://blog.developpez.com/mikedavem/p13189/performance/mitigating-scalar-udf-procedural-code-performance-with-sql-2019-udf-inline-capabilites">Lire la suite <span class="meta-nav">&#8594;</span></a>]]></description>
				<content:encoded><![CDATA[<p>A couple of days ago, I read the write-up of my former colleague <a href="https://twitter.com/FranckPachot" rel="noopener" target="_blank">@FranckPachot</a> about <a href="https://blog.dbi-services.com/refactoring-procedural-to-sql-an-example-with-mysql-sakila/" rel="noopener" target="_blank">refactoring procedural code to SQL</a>. This is recurrent subject in the database world and I was interested in transposing this article to SQL Server because it was about refactoring a  Scalar-Valued function to a SQL view. The latter one is a great alternative when it comes performance but something new was shipped with SQL Server 2019 and could address (or at least could mitigate) this recurrent scenario. </p>
<p><span id="more-1516"></span></p>
<p>First of all, Scalar-Valued functions (from the User Defined Function category) are interesting objects for code modularity, factoring and reusability. No surprise to see them widely used by DEVs. But they are not always suited to performance considerations especially when it concerns the “impedance mismatch” problem. This is term used to refer to the problems that occurs due to differences between the database model and the programming language model. Indeed, from one side, a database world with SQL language that is declarative, and with queries that are set or multiset-oriented. To another side, programing world with imperative-oriented languages requiring accessing each tuple individually for processing.</p>
<p>To cut the story short, Scalar UDF provides programing benefits for DEVs but when performance matters, we discourage to use them for the aforementioned reasons. Before continuing, let’s precise that all the scripts and demos in the next sections are based on <a href="https://github.com/jOOQ/jOOQ/tree/master/jOOQ-examples/Sakila" rel="noopener" target="_blank">salika-db</a> project on GitHub. Franck Pachot used the mysql version and fortunately there exists a sample for SQL Server as well. Furthermore, the mysql function used as initial example by Franck may be translated to SQL Server as follows:</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:650px;height:450px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">-- Scalar function<br />
CREATE OR ALTER FUNCTION inventory_in_stock (@p_inventory_id INT) <br />
RETURNS BIT<br />
BEGIN<br />
&nbsp; &nbsp; DECLARE @v_rentals INT;<br />
&nbsp; &nbsp; DECLARE @v_out &nbsp; &nbsp; INT;<br />
&nbsp; &nbsp; DECLARE @verif &nbsp; &nbsp; BIT;<br />
&nbsp; &nbsp; <br />
<br />
&nbsp; &nbsp; --AN ITEM IS IN-STOCK IF THERE ARE EITHER NO ROWS IN THE rental TABLE<br />
&nbsp; &nbsp; --FOR THE ITEM OR ALL ROWS HAVE return_date POPULATED<br />
<br />
&nbsp; &nbsp; SET @v_rentals = (SELECT COUNT(*) FROM rental WHERE inventory_id = @p_inventory_id);<br />
<br />
&nbsp; &nbsp; IF @v_rentals = 0 <br />
&nbsp; &nbsp; BEGIN<br />
&nbsp; &nbsp; &nbsp; &nbsp; SET @verif = 1<br />
&nbsp; &nbsp; END<br />
&nbsp; &nbsp; ELSE<br />
&nbsp; &nbsp; BEGIN<br />
&nbsp; &nbsp; &nbsp; &nbsp; SET @v_out = (SELECT COUNT(rental_id) <br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; FROM inventory <br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; LEFT JOIN rental ON inventory.inventory_id = rental.inventory_id<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; WHERE inventory.inventory_id = @p_inventory_id<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; AND rental.return_date IS NULL)<br />
<br />
&nbsp; &nbsp; &nbsp; &nbsp; IF @v_out &amp;gt; 0 <br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; SET @verif = 0;<br />
&nbsp; &nbsp; &nbsp; &nbsp; ELSE<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; SET @verif = 1;<br />
&nbsp; &nbsp; END;<br />
<br />
&nbsp; &nbsp; RETURN @verif;<br />
END <br />
GO</div></div>
<p>During his write-up, Franck provided a natural alternative of this UDF based on a SQL view and here a similar solution applied to SQL Server:</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:650px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">CREATE OR ALTER VIEW v_inventory_stock_status <br />
AS<br />
<br />
SELECT <br />
&nbsp; &nbsp; i.inventory_id,<br />
&nbsp; &nbsp; CASE <br />
&nbsp; &nbsp; &nbsp; &nbsp; WHEN NOT EXISTS (SELECT 1 FROM dbo.rental AS r WHERE r.inventory_id = &nbsp;i.inventory_id AND r.return_date IS NULL) THEN 1<br />
&nbsp; &nbsp; &nbsp; &nbsp; ELSE 0<br />
&nbsp; &nbsp; END AS inventory_in_stock<br />
FROM dbo.inventory AS i<br />
GO</div></div>
<p>Then similar to what Franck did, we can join this view with the inventory table to get the expected outcome:</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:650px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">select count(v.inventory_id),inventory_in_stock<br />
from inventory AS i<br />
left join v_inventory_stock_status AS v ON i.inventory_id = v.inventory_id<br />
group by v.inventory_in_stock;<br />
go</div></div>
<p><a href="http://blog.developpez.com/mikedavem/files/2020/03/156-1-Query-OutPut.jpg"><img src="http://blog.developpez.com/mikedavem/files/2020/03/156-1-Query-OutPut.jpg" alt="156 - 1 - Query OutPut" width="356" height="82" class="alignnone size-full wp-image-1518" /></a></p>
<p>There is another alternative that could be use here base on a CTE rather than a TSQL view as follows. However, the performance is similar in both cases and it is up to each DEV which solution fits with their needs:</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:650px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">;with cte<br />
as<br />
(<br />
&nbsp; &nbsp; SELECT <br />
&nbsp; &nbsp; &nbsp; &nbsp; i.inventory_id,<br />
&nbsp; &nbsp; &nbsp; &nbsp; CASE <br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; WHEN NOT EXISTS (SELECT 1 FROM dbo.rental AS r WHERE r.inventory_id = &nbsp;i.inventory_id AND r.return_date IS NULL) THEN 1<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ELSE 0<br />
&nbsp; &nbsp; &nbsp; &nbsp; END AS inventory_in_stock<br />
&nbsp; &nbsp; FROM dbo.inventory AS i<br />
)<br />
select count(v.inventory_id),inventory_in_stock<br />
from inventory AS i<br />
left join cte AS v ON i.inventory_id = v.inventory_id<br />
group by v.inventory_in_stock;<br />
go</div></div>
<p>I compared then the performance between the UDF based version and the TSQL view:</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:650px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">-- udf<br />
select count(*),dbo.inventory_in_stock(inventory_id) <br />
from inventory <br />
group by dbo.inventory_in_stock(inventory_id)<br />
GO<br />
-- view<br />
select count(v.inventory_id),inventory_in_stock<br />
from inventory AS i<br />
left join v_inventory_stock_status AS v ON i.inventory_id = v.inventory_id<br />
group by v.inventory_in_stock;<br />
go</div></div>
<p>The outcome below (CPU, Reads, Writes, Duration) is as expected. The SQL view is the winner by far. </p>
<p><a href="http://blog.developpez.com/mikedavem/files/2020/03/156-2-UDF-vs-View-performance-e1583417465245.jpg"><img src="http://blog.developpez.com/mikedavem/files/2020/03/156-2-UDF-vs-View-performance-e1583417465245.jpg" alt="156 - 2 - UDF vs View performance" width="1000" height="166" class="alignnone size-full wp-image-1519" /></a></p>
<p>Similar to Franck’s finding, the performance gain is as the cost of rewriting the code for DEVs in this scenario. But SQL Server 2019 provides another interesting way to continue using the UDF abstraction  without compromising on performance: <a href="https://docs.microsoft.com/en-us/sql/relational-databases/user-defined-functions/scalar-udf-inlining?view=sql-server-ver15" rel="noopener" target="_blank">Scalar T-SQL UDF Inlining</a> feature and I was curious to see how much improvement we get with such capabilities for this scenario. </p>
<p>First time I executed the following UDF-based TSQL script on SQL Server 2019 RTM (be sure to be in 150 compatibility mode), I ran into some OOM issues for the second query:</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:650px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">-- SQL 2017-<br />
ALTER DATABASE SCOPED CONFIGURATION SET TSQL_SCALAR_UDF_INLINING = OFF;<br />
GO<br />
SELECT dbo.inventory_in_stock(10)<br />
GO<br />
-- SQL 2019+<br />
ALTER DATABASE SCOPED CONFIGURATION SET TSQL_SCALAR_UDF_INLINING = ON;<br />
GO<br />
SELECT dbo.inventory_in_stock(10)</div></div>
<p></p>
<blockquote><p>Msg 8624, Level 16, State 17, Line 14<br />
Internal Query Processor Error: The query processor could not produce a query plan. For more information, contact Customer Support Services.</p></blockquote>
<p>To be honest, not a surprise to be honest because I already aware of it by reading the blog post of <a href="https://twitter.com/sqL_handLe" rel="noopener" target="_blank">@sqL_handle</a> a couple of weeks ago. Updating to CU2 fixed my issue. The second shot revealed some interesting outcomes.<br />
The query plan of first query (&lt;= SQL 2017) is as we may expected usually from executing a TSQL scalar function. From an execution perspective, this black box is materialized in the form of the compute scalar operator as shown below:</p>
<p><a href="http://blog.developpez.com/mikedavem/files/2020/03/156-3-UDF-2017-query-plan.jpg"><img src="http://blog.developpez.com/mikedavem/files/2020/03/156-3-UDF-2017-query-plan.jpg" alt="156 - 3 - UDF 2017 query plan" width="610" height="203" class="alignnone size-full wp-image-1521" /></a></p>
<p>But the story has changed with Scalar UDF Inlining capability. This is illustrated by the below pictures which are sample of a larger execution plan:</p>
<p><a href="http://blog.developpez.com/mikedavem/files/2020/03/156-3-UDF-2019-query-plan-e1583417692798.jpg"><img src="http://blog.developpez.com/mikedavem/files/2020/03/156-3-UDF-2019-query-plan-e1583417692798.jpg" alt="156 - 3 - UDF 2019 query plan" width="1000" height="375" class="alignnone size-full wp-image-1522" /></a></p>
<p>&#8230;</p>
<p><a href="http://blog.developpez.com/mikedavem/files/2020/03/156-3-UDF-2019-2-query-plan-e1583420250648.jpg"><img src="http://blog.developpez.com/mikedavem/files/2020/03/156-3-UDF-2019-2-query-plan-e1583420250648.jpg" alt="156 - 3 - UDF 2019 2 query plan" width="1000" height="329" class="alignnone size-full wp-image-1524" /></a></p>
<p>The query optimizer has inferred some relation operations from my (imperative based) scalar UDF based on the <a href="https://www.microsoft.com/en-us/research/project/froid/" rel="noopener" target="_blank">Froid framework</a> and provides several benefits including compiler optimization and parallelism (initially not possible with UDFs).</p>
<p>Let’s perform the same benchmark test that I performed between the UDF-based and the TSQL view based queries. In fact, I had to propose a slightly variation of the query to hope kicking in the Scalar UDF Inline capability:</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:650px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">-- First UDF query <br />
select count(*),dbo.inventory_in_stock(inventory_id) <br />
from inventory <br />
group by dbo.inventory_in_stock(inventory_id)<br />
GO<br />
<br />
-- Variation of the first query<br />
;with cte<br />
as<br />
(<br />
&nbsp; &nbsp; select inventory_id,dbo.inventory_in_stock(inventory_id) as inventory_in_stock<br />
&nbsp; &nbsp; from inventory <br />
)<br />
select &nbsp;count(*), inventory_in_stock<br />
from cte<br />
group by inventory_in_stock<br />
GO</div></div>
<p><a href="http://blog.developpez.com/mikedavem/files/2020/03/156-4-UDF-2019-benchmark-query-plan-e1583420352841.jpg"><img src="http://blog.developpez.com/mikedavem/files/2020/03/156-4-UDF-2019-benchmark-query-plan-e1583420352841.jpg" alt="156 - 4 - UDF 2019 benchmark query plan" width="1000" height="357" class="alignnone size-full wp-image-1525" /></a></p>
<p>From a performance perspective, it is worth noting the improvement is not necessarily on the read operation but more the CPU and Duration times.</p>
<p><a href="http://blog.developpez.com/mikedavem/files/2020/03/156-5-UDF-vs-UDF-inline-performance-e1583420400334.jpg"><img src="http://blog.developpez.com/mikedavem/files/2020/03/156-5-UDF-vs-UDF-inline-performance-e1583420400334.jpg" alt="156 - 5 - UDF vs UDF inline performance" width="1000" height="110" class="alignnone size-full wp-image-1527" /></a></p>
<p>But let’s push the tests further by increasing the amount of data. As a reminder, the performance of the test is tied to the number of UDF execution and implicitly number of records in the Inventory table. </p>
<p>So, let’s add a bunch of records to the Inventory table …</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:650px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">INSERT inventory (film_id, store_id, last_update)<br />
SELECT <br />
&nbsp; &nbsp; film_id,<br />
&nbsp; &nbsp; store_id,<br />
&nbsp; &nbsp; GETDATE()<br />
FROM inventory;</div></div>
<p>&#8230; and let&rsquo;s execute this script to get respectively a total of 146592 and 2345472 rows for each test. Here the corresponding performance outcomes:</p>
<p><a href="http://blog.developpez.com/mikedavem/files/2020/03/156-6-UDF-vs-UDF-inline-performance-add-more-rows-e1583420498722.jpg"><img src="http://blog.developpez.com/mikedavem/files/2020/03/156-6-UDF-vs-UDF-inline-performance-add-more-rows-e1583420498722.jpg" alt="156 - 6 - UDF vs UDF inline performance - add more rows" width="1000" height="225" class="alignnone size-full wp-image-1528" /></a></p>
<p>I noticed more rows there are in the inventory table better performance we get for each corresponding test:</p>
<p><a href="http://blog.developpez.com/mikedavem/files/2020/03/156-7-UDF-vs-UDF-inline-performance-chart-cpu.jpg"><img src="http://blog.developpez.com/mikedavem/files/2020/03/156-7-UDF-vs-UDF-inline-performance-chart-cpu.jpg" alt="156 - 7 - UDF vs UDF inline performance - chart cpu" width="876" height="384" class="alignnone size-full wp-image-1529" /></a></p>
<p>&#8230;</p>
<p><a href="http://blog.developpez.com/mikedavem/files/2020/03/156-8-UDF-vs-UDF-inline-performance-chart-duration.jpg"><img src="http://blog.developpez.com/mikedavem/files/2020/03/156-8-UDF-vs-UDF-inline-performance-chart-duration.jpg" alt="156 - 8 - UDF vs UDF inline performance - chart duration" width="868" height="384" class="alignnone size-full wp-image-1530" /></a></p>
<p>Well, interesting outcome without rewriting any code isn’t it? An 80% decrease in average for query duration time and 61% for CPU time execution. For a sake of curiosity let’s take a look at the different query plans:</p>
<p><strong>Scalar UDF Inlining not enabled</strong></p>
<p><a href="http://blog.developpez.com/mikedavem/files/2020/03/156-10-UDF-more-rows-execution-plan-e1583420738157.jpg"><img src="http://blog.developpez.com/mikedavem/files/2020/03/156-10-UDF-more-rows-execution-plan-e1583420738157.jpg" alt="156 - 10 - UDF - more rows execution plan" width="1000" height="155" class="alignnone size-full wp-image-1531" /></a></p>
<p>Again, the real cost is hidden by the UDF black box through the compute scalar operator but we guess easily that every row processed by compute Scalar operator implies the dbo.inventory_in_stock() function. </p>
<p><strong>Scalar UDF Inlining enabled</strong></p>
<p><a href="http://blog.developpez.com/mikedavem/files/2020/03/156-11-UDF-inlining-more-rows-execution-plan-e1583420789863.jpg"><img src="http://blog.developpez.com/mikedavem/files/2020/03/156-11-UDF-inlining-more-rows-execution-plan-e1583420789863.jpg" alt="156 - 11 - UDF inlining - more rows execution plan" width="1000" height="255" class="alignnone size-full wp-image-1532" /></a></p>
<p>Without going into details of the execution plan, something that draw attention is compiler optimizer tricks kicked in including parallelism. All the optimization stuff done by the query processor is helpful to improve the overall performance of the query.</p>
<p>So last point, does Scalar UDF Inlining scale better than the SQL view? </p>
<p><a href="http://blog.developpez.com/mikedavem/files/2020/03/156-9-UDF-inline-vs-view-performance-e1583420871437.jpg"><img src="http://blog.developpez.com/mikedavem/files/2020/03/156-9-UDF-inline-vs-view-performance-e1583420871437.jpg" alt="156 - 9 - UDF inline vs view performance" width="1200" height="74" class="alignnone size-full wp-image-1533" /></a></p>
<p>This last output seems to confirm the SQL view remains the winner among the alternatives in this specific scenario and you will have to choose best solution and likely the acceptable tradeoff that will fit with your context.</p>
<p>See you!</p>
]]></content:encoded>
			<wfw:commentRss></wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Collaborative way and tooling to debug SQL Server blocked processes scenarios</title>
		<link>https://blog.developpez.com/mikedavem/p13186/devops/collaborative-way-and-tooling-to-debug-sql-server-blocked-processes-scenarios</link>
		<comments>https://blog.developpez.com/mikedavem/p13186/devops/collaborative-way-and-tooling-to-debug-sql-server-blocked-processes-scenarios#comments</comments>
		<pubDate>Thu, 30 Jan 2020 14:13:48 +0000</pubDate>
		<dc:creator><![CDATA[mikedavem]]></dc:creator>
				<category><![CDATA[DevOps]]></category>
		<category><![CDATA[Performance]]></category>
		<category><![CDATA[devops]]></category>
		<category><![CDATA[Extended events]]></category>
		<category><![CDATA[locks]]></category>
		<category><![CDATA[orphan transactions]]></category>
		<category><![CDATA[sp_WhoIsActive]]></category>
		<category><![CDATA[sqlserver]]></category>

		<guid isPermaLink="false">http://blog.developpez.com/mikedavem/?p=1437</guid>
		<description><![CDATA[A quick blog post to show how helpful an extended event and few other tools can be to help fixing orphan transactions in a real use case scenario. I often gave training with customers about SQL Server performance and tools, &#8230; <a href="https://blog.developpez.com/mikedavem/p13186/devops/collaborative-way-and-tooling-to-debug-sql-server-blocked-processes-scenarios">Lire la suite <span class="meta-nav">&#8594;</span></a>]]></description>
				<content:encoded><![CDATA[<p>A quick blog post to show how helpful an extended event and few other tools can be to help fixing orphan transactions in a real use case scenario. I often gave training with customers about SQL Server performance and tools, but I noticed how difficult it can be to explain the importance of a tool if you only explain theory without any illustration with a real customer case.<br />
Well, let’s start my own story that began a couple of days ago with an SQL alert to indicate a blocking scenario issue. Looking at our SQL dashboard (below), we were able to confirm quickly we are running into an annoying issue and it would be getting worse over if we do nothing.</p>
<p><span id="more-1437"></span></p>
<p><a href="http://blog.developpez.com/mikedavem/files/2020/01/153-1-SQL-dashboard.jpg"><img src="http://blog.developpez.com/mikedavem/files/2020/01/153-1-SQL-dashboard.jpg" alt="153 - 1 - SQL dashboard" width="1339" height="669" class="alignnone size-full wp-image-1438" /></a></p>
<p>&#8230;</p>
<p><a href="http://blog.developpez.com/mikedavem/files/2020/01/153-2-SQL-dashboard.jpg"><img src="http://blog.developpez.com/mikedavem/files/2020/01/153-2-SQL-dashboard.jpg" alt="153 - 2 - SQL dashboard" width="1874" height="663" class="alignnone size-full wp-image-1439" /></a></p>
<p>Let’s precise before continuing there are a plenty of tools to help digging into blocked processes scenarios and my intention is not to favor one specific tool over another one. So, in my case the first tool I used was the <a href="http://whoisactive.com/" rel="noopener" target="_blank">sp_WhoIsActive</a> procedure from Adam Machanic. One great feature of it is to get a comprehensive picture of what is happening on your system at the moment you’re executing the procedure.<br />
Here a sample of output I got. Let’s precise it doesn’t reflect exactly my context (which was a little more complex) but anyway my intention is not to focus on this specific part  </p>
<p><a href="http://blog.developpez.com/mikedavem/files/2020/01/153-3-sp_WhoIsActive.jpg"><img src="http://blog.developpez.com/mikedavem/files/2020/01/153-3-sp_WhoIsActive.jpg" alt="153 - 3 - sp_WhoIsActive" width="1339" height="328" class="alignnone size-full wp-image-1440" /></a></p>
<p>As you may see, I got quickly interesting information about the blocking leaders including session_id, the application name and the command executed as this moment. But the interesting part of this story was to get a request_id to NULL as well as a status value to SLEEPING. After some researches, having these values indicate likely that SQL Server has completed the command and the connection is waiting for the next command to come from the client. In addition, looking at the open_tran_count value (=1) confirmed the transaction was still opened. We started monitoring the transaction a couple of minutes to see if the application could manage to commit (or to rollback) the transaction but nothing happened. So, we had to kill the corresponding session to get back to a normal situation. A few minutes later, the situation came back with the same pattern and we applied the same temporary fix (KILL session).<br />
The next step consisted in discussing with the DEV team to fix this annoying issue once and for all. We managed to reproduce this scenario in DEV environment, but it was not clear what happened exactly inside the application because we got not specific errors even when looking at the tracing infrastructure. To help the DEV team investigating the issue, we decided to create a special extended event session that both monitor all activity scoped to the concerned application and the transactions that remain open during the tracing timeframe. Events will be easily correlated relying on causality tracking capabilities of extended events.  </p>
<p>So, the extended event session used two targets including the Event File and Histogram. Respectively, the first one was intended to write workload activity into a file on disk and the second one aimed identifying quickly opened transactions.</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:650px;height:450px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">CREATE EVENT SESSION [OrphanedTransactionHunter] ON SERVER <br />
ADD EVENT sqlserver.database_transaction_begin(<br />
&nbsp; &nbsp; ACTION(sqlserver.database_id,sqlserver.session_id,sqlserver.tsql_stack)),<br />
ADD EVENT sqlserver.database_transaction_end(<br />
&nbsp; &nbsp; ACTION(sqlserver.database_id,sqlserver.session_id,sqlserver.tsql_stack)),<br />
ADD EVENT sqlserver.error_reported(<br />
&nbsp; &nbsp; ACTION(sqlserver.database_id,sqlserver.session_id,sqlserver.tsql_stack)),<br />
ADD EVENT sqlserver.module_end(<br />
&nbsp; &nbsp; ACTION(sqlserver.database_id,sqlserver.session_id,sqlserver.tsql_stack)),<br />
ADD EVENT sqlserver.module_start(<br />
&nbsp; &nbsp; ACTION(sqlserver.database_id,sqlserver.session_id,sqlserver.sql_text,sqlserver.tsql_stack)),<br />
ADD EVENT sqlserver.rpc_completed(<br />
&nbsp; &nbsp; ACTION(sqlserver.database_id,sqlserver.session_id,sqlserver.tsql_stack)),<br />
ADD EVENT sqlserver.rpc_starting(<br />
&nbsp; &nbsp; ACTION(sqlserver.database_id,sqlserver.session_id,sqlserver.sql_text,sqlserver.tsql_stack)),<br />
ADD EVENT sqlserver.sp_statement_completed(<br />
&nbsp; &nbsp; ACTION(sqlserver.database_id,sqlserver.session_id,sqlserver.tsql_stack)),<br />
ADD EVENT sqlserver.sp_statement_starting(<br />
&nbsp; &nbsp; ACTION(sqlserver.database_id,sqlserver.session_id,sqlserver.sql_text,sqlserver.tsql_stack)),<br />
ADD EVENT sqlserver.sql_statement_completed(<br />
&nbsp; &nbsp; ACTION(sqlserver.database_id,sqlserver.session_id,sqlserver.tsql_stack)),<br />
ADD EVENT sqlserver.sql_statement_starting(<br />
&nbsp; &nbsp; ACTION(sqlserver.database_id,sqlserver.session_id,sqlserver.sql_text,sqlserver.tsql_stack))<br />
ADD TARGET package0.event_file(SET filename=N'OrphanedTransactionHunter'),<br />
ADD TARGET package0.pair_matching(SET begin_event=N'sqlserver.database_transaction_begin',begin_matching_actions=N'sqlserver.session_id',end_event=N'sqlserver.database_transaction_end',end_matching_actions=N'sqlserver.session_id',respond_to_memory_pressure=(1))<br />
WITH (MAX_MEMORY=4096 KB,EVENT_RETENTION_MODE=ALLOW_SINGLE_EVENT_LOSS,MAX_DISPATCH_LATENCY=5 SECONDS,MAX_EVENT_SIZE=0 KB,MEMORY_PARTITION_MODE=NONE,TRACK_CAUSALITY=ON,STARTUP_STATE=OFF)<br />
GO</div></div>
<p>The outputs were as follows:</p>
<p>&gt; Target histogram (with opened transactions)</p>
<p><a href="http://blog.developpez.com/mikedavem/files/2020/01/153-4-xe-event-histogram.jpg"><img src="http://blog.developpez.com/mikedavem/files/2020/01/153-4-xe-event-histogram.jpg" alt="153 - 4 - xe event histogram" width="1473" height="69" class="alignnone size-full wp-image-1441" /></a></p>
<p>The only transaction that remained opened during our test (just be careful other noisy records not relevant at the moment when you start the XE session) concerned the session_id = 873. The attach_activity_id, attach_activity_id_xfer and session column values were helpful here to correlate events recorded to the event file target. </p>
<p>&gt; Event File (Workload activity)</p>
<p>Here the events after applying a filter with above values. </p>
<p><a href="http://blog.developpez.com/mikedavem/files/2020/01/153-5-xe-event-file.jpg"><img src="http://blog.developpez.com/mikedavem/files/2020/01/153-5-xe-event-file.jpg" alt="153 - 5 - xe event file" width="1333" height="303" class="alignnone size-full wp-image-1442" /></a></p>
<p>We noticed the transaction with session_id = 873 was started but never ended. In addition, we were able to identify the sequence of code executed by the application (mainly based on prepared statement and stored procedures in our context).This information helps the DEV team to focus on the right portion of code to fix. Without getting into details, it was very interesting to see this root cause was a SQL statement and duplicate key issue not thrown and managed correctly by the application. I was just surprised the application didn’t catch any errors in a such case. We finally understood that prepared statements and stored procedure calls were done through the DButils class including the closeQuietly() method to close connections. Referring to the <a href="https://commons.apache.org/proper/commons-dbutils/apidocs/org/apache/commons/dbutils/DbUtils.html" rel="noopener" target="_blank">Apache documentation</a>, closeQuietly() was designed to hide SQL Exceptions when happen which definitely not help identifying easily the issue from an application side. Never mind, thanks to the collaboration with the DEV team we managed to get rid of this issue <img src="https://blog.developpez.com/mikedavem/wp-includes/images/smilies/icon_smile.gif" alt=":)" class="wp-smiley" /></p>
<p>David Barbarin </p>
]]></content:encoded>
			<wfw:commentRss></wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
	</channel>
</rss>
