<?xml version="1.0" encoding="utf-8"?>
<rss xmlns:atom="https://www.w3.org/2005/Atom" xmlns:sy="https://purl.org/rss/1.0/modules/syndication/" xmlns:dc="https://purl.org/dc/elements/1.1/" xmlns:content="https://purl.org/rss/1.0/modules/content/" xmlns:wfw="https://wellformedweb.org/CommentAPI/" xmlns:slash="https://purl.org/rss/1.0/modules/slash/"
  version="2.0">
  <channel>
    <title>SQL Server Customer Advisory Team</title>
    <atom:link
      href="https://docs.microsoft.com/archive/blogs/sqlcat/feed.xml"
      rel="self"
      type="application/rss+xml" />
    <link>https://docs.microsoft.com/archive/blogs/sqlcat/feed.xml</link>
    <description />
    <lastBuildDate>Wed, 19 Dec 2018 04:00:35 GMT</lastBuildDate>
    <language>en-US</language>
    <sy:updatePeriod>hourly</sy:updatePeriod>
    <sy:updateFrequency>1</sy:updateFrequency>
    <item>
      <title>SQLCAT at PASS Summit 2018</title>
      <link>https://docs.microsoft.com/archive/blogs/sqlcat/sqlcat-at-pass-summit-2018</link>
      <pubDate>Mon, 05 Nov 2018 16:23:47 GMT</pubDate>
      <dc:creator><![CDATA[Dimitri Furman]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.msdn.microsoft.com/sqlcat/?p=7555</guid>
      <description><![CDATA[This week in Seattle, PASS Summit 2018 will be happening for the twentieth time. As for many years...]]></description>
      <content:encoded><![CDATA[This week in Seattle, <a href="https://www.pass.org/summit/2018/Home.aspx">PASS Summit 2018</a> will be happening for the twentieth time. As for many years before, the SQLCAT (a.k.a. DataCAT) team will be at the conference. This year, we will present three sessions:

On Thursday Nov 8 at 4:45 PM in room 6B, <a href="https://www.pass.org/summit/2018/Speakers/Details.aspx?spid=963">Alexei Khalyako</a>, <a href="https://www.pass.org/summit/2018/Speakers/Details.aspx?spid=514">John Hoang</a>, and <a href="https://www.pass.org/summit/2018/Speakers/Details.aspx?spid=620">Mike Weiner</a> will talk about <a href="https://www.pass.org/summit/2018/Sessions/Details.aspx?sid=84748">Learnings and Best Practices in Building an Enterprise Data Warehouse Using Azure SQL DW</a>. Our team has had the opportunity to work with a number of customers moving their data warehouse workloads to Azure SQL DW. In this session we’ll share learnings around the most common design patterns with Azure SQL DW and go deeper on data loading and performance tuning and optimization considerations with Azure SQL DW (Gen2). Come to this session to learn from our real-world experiences in working with customers on adopting Azure SQL DW.

On Friday Nov 9 at 8:00 AM in room 615, <a href="https://www.pass.org/summit/2018/Speakers/Details.aspx?spid=5328">Arvind Shyamsundar</a> will show how to tackle <a href="https://www.pass.org/summit/2018/Sessions/Details.aspx?sid=84732">Advanced SQL Server Troubleshooting with SQLCallStackResolver</a>. This Level 400 session is a product of many real-world customer engagements that our team has worked on. Arvind will show you some tools and techniques to tackle the hardest-to-investigate performance issues in SQL Server and he will demo one such scenario involving spinlock contention on high-end servers. Most importantly, the information in the session is very practical and directly usable. Guaranteed to help you be much more efficient and precise when troubleshooting SQL Server performance!

On Friday Nov 9 at 9:30 AM in the TCC room Tahoma 5, <a href="https://www.pass.org/Summit/2018/Speakers/Details.aspx?spid=459">Kun Cheng</a>, <a href="https://www.pass.org/Summit/2018/Speakers/Details.aspx?spid=6651">Dimitri Furman</a>, and <a href="https://www.pass.org/summit/2018/Speakers/Details.aspx?spid=620">Mike Weiner</a> will talk about <a href="https://www.pass.org/summit/2018/Sessions/Details.aspx?sid=84754">SQL DB Managed Instance - Best Practices and Lessons Learned</a>. For well over a year, since the early private preview in summer of 2017 and through General Availability in fall of 2018, we have been collaborating with Microsoft customers who are the early adopters of Managed Instance, and with the Managed Instance engineering team, to ensure successful implementations and to improve the offering. If you want to learn how Managed Instance works and how to use it optimally, from performance tips to migration best practices, this session is for you.

In addition to presenting, we will also be at the <a href="https://www.pass.org/summit/2018/Attend/MicrosoftatSummit.aspx#microsoft-data-clinic">Data Clinic</a>, where together with our colleagues from the SQL Server engineering team, the Tiger team, the CSS team, and many others we will help you solve real life problems and answer hard (or easy <span style="margin: 0px;line-height: 107%;font-family: 'Segoe UI Emoji',sans-serif;font-size: 11pt"><span style="color: #000000">😊</span></span>) questions about SQL Server and other data technologies we work with.

See you at the Summit!]]></content:encoded>
    </item>
    <item>
      <title>Real-time performance monitoring for Azure SQL Database Managed Instance</title>
      <link>https://docs.microsoft.com/archive/blogs/sqlcat/real-time-performance-monitoring-for-azure-sql-database-managed-instance</link>
      <pubDate>Wed, 26 Sep 2018 16:38:40 GMT</pubDate>
      <dc:creator><![CDATA[Denzil Ribeiro]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.msdn.microsoft.com/sqlcat/?p=7475</guid>
      <description><![CDATA[This article was co-authored by Dimitri Furman and Denzil Ribeiro

Reviewed by: Danimir Ljepava,...]]></description>
      <content:encoded><![CDATA[<i>This article was co-authored by Dimitri Furman and Denzil Ribeiro</i>

<i>Reviewed by: Danimir Ljepava, Borko Novakovic, Jovan Popovic, Rajesh Setlem, Mike Weiner</i>
<h3>Introduction</h3>
In our ongoing engagements with the SQL DB Managed Instance preview customers a common requirement has been to monitor database workload performance in real time. This includes not just monitoring of a single (or a few) performance metrics, but being able to look at many relevant metrics at once, within seconds of events occurring. While much of the needed performance data is available in various DMVs, consuming it via T-SQL queries and looking at the results in SSMS query windows is quite cumbersome, and does not provide a simple way to look at real time data, or at recent historical data and trends.

More than a year ago, we had similar monitoring requirements for SQL Server on Linux, and had blogged about it here: <a href="https://blogs.msdn.microsoft.com/sqlcat/2017/07/03/how-the-sqlcat-customer-lab-is-monitoring-sql-on-linux/">How the SQLCAT Customer Lab is Monitoring SQL on Linux</a>. For monitoring Managed Instance, we used <a href="https://www.influxdata.com/time-series-platform/telegraf/">Telegraf</a>. Telegraf is a general-purpose agent for collecting metrics, and is broadly adopted in the community. It can be used for monitoring SQL Server on both Windows and Linux using the <a href="https://github.com/influxdata/telegraf/tree/master/plugins/inputs/sqlserver">SQL Server plugin</a>. In <a href="https://portal.influxdata.com/downloads">Telegraf version 1.8.0</a>, this plugin has been updated to support SQL DB Managed Instance as well. Telegraf connects to Managed Instance remotely, and collects performance data from DMVs. <a href="https://www.influxdata.com/">InfluxDB</a> is the time series database for the monitoring data collected by Telegraf, while new <a href="https://grafana.com/">Grafana</a> dashboards, specific to Managed Instance, were developed to visualize this data. Big thanks to <a href="https://github.com/danielnelson">Daniel Nelson</a> and <a href="https://github.com/m82labs">Mark Wilkinson</a> for accepting pull requests with the changes to the Telegraf SQL Server plugin needed to support Managed Instance.

This blog will describe a solution for real time performance monitoring of Managed Instance in sufficient detail for customers to use it for the same purpose. The code we developed is open source and is hosted in the <a href="https://github.com/denzilribeiro/sqlmimonitoring">sqlmimonitoring</a> GitHub repo. We welcome community contributions to improve and expand this solution. Contributions to the Telegraf collector should be made via pull requests to the <a href="https://github.com/influxdata/telegraf/tree/master/plugins/inputs/sqlserver">SQL Server Input Plugin</a> for Telegraf. For changes to Grafana dashboards, pull requests should be made to the <a href="https://github.com/denzilribeiro/sqlmimonitoring">sqlmimonitoring</a> GitHub repo.

The detailed discussion below is intended specifically for real time performance monitoring and troubleshooting. For other monitoring needs, such as query-level performance monitoring, historical trend investigations, and resource consumption monitoring, consider using <a href="https://docs.microsoft.com/en-us/azure/log-analytics/log-analytics-azure-sql#managed-instance-and-databases-in-managed-instance-view">Azure SQL Analytics</a>. In near future, <a href="https://docs.microsoft.com/en-us/azure/monitoring-and-diagnostics/monitoring-get-started">Azure Monitor</a> will provide support for monitoring of Managed Instance metrics and sending notifications.
<h3>Dashboard examples</h3>
Before describing the solution in detail, here are a few examples of Managed Instance Grafana dashboards, captured while a customer workload is running.

<a href="https://msdnshared.blob.core.windows.net/media/2018/09/clip_image002_thumb3.jpg"><img width="1144" height="1520" class="alignnone size-full wp-image-7265" alt="" src="https://msdnshared.blob.core.windows.net/media/2018/09/clip_image002_thumb3.jpg" /></a>

<b>Figure 1</b>. Performance dashboard. Additional panels not shown: Memory Manager, Memory Clerks, PLE, Buffer Manager, Access Methods, SQL Statistics.

<a href="https://msdnshared.blob.core.windows.net/media/2018/09/clip_image004.jpg"><img width="1101" height="1128" title="clip_image004" alt="clip_image004" src="https://msdnshared.blob.core.windows.net/media/2018/09/clip_image004_thumb.jpg" border="0" /></a>

<b>Figure 2</b>. Database IO Stats dashboard. Additional panels not shown: Data File Size, Log File Size, Log File Used.

<a href="https://msdnshared.blob.core.windows.net/media/2018/09/clip_image006.jpg"><img width="1102" height="1127" title="clip_image006" alt="clip_image006" src="https://msdnshared.blob.core.windows.net/media/2018/09/clip_image006_thumb.jpg" border="0" /></a>

<b>Figure 3</b>. HADR dashboard.
<h3>Setup and configuration</h3>
Here is the conceptual diagram of the solution. The arrows show the flow of monitoring data from source to destination. Telegraf pulls data from Managed Instances and loads it into InfluxDB. Grafana queries InfluxDB to visualize the data on web dashboards.

<a href="https://msdnshared.blob.core.windows.net/media/2018/09/MI-monitoring-diagram.png"><img width="1134" height="586" class="alignnone size-full wp-image-7485" alt="" src="https://msdnshared.blob.core.windows.net/media/2018/09/MI-monitoring-diagram.png" /></a>
<h4>Prerequisites</h4>
<ul>
 	<li>Basic knowledge of Linux.</li>
 	<li> Linux VM(s) with internet connectivity, which is needed to download Docker images during initial configuration. In a typical configuration shown on the diagram, a single VM runs all three components (Telegraf, InfluxDB, and Grafana).</li>
 	<li> Network connectivity between the Telegraf VM and monitored Managed Instances. Traffic from the Telegraf VM to the Managed Instances on TCP port 1433 must be allowed. The Telegraf VM can be in the same VNet where monitored instances are, or in a peered VNet, or on premises if the on-prem network is connected to the VNet over ExpressRoute or VPN. For details on supported Managed Instance connectivity scenarios, see this <a href="https://docs.microsoft.com/en-us/azure/sql-database/sql-database-managed-instance-connect-app">documentation</a> article.</li>
 	<li>Inbound traffic to the VM(s) on the following ports must be allowed. If deploying in Azure, modify NSGs accordingly.
<ul>
 	<li>TCP port 8086 – this is the default InfluxDB port. This is only required on the InfluxDB VM, and only if lnfluxDB is running on a machine different from the Grafana machine.</li>
 	<li>TCP port 3000 – this is the Grafana default web port to access the web-based dashboards. This is only required on the Grafana VM.</li>
</ul>
</li>
</ul>
In our test environment, we use a single VM in the same Azure VNet as the monitored instances. If monitoring just a couple of instances, the VM can be quite small and inexpensive. A single F1 VM (1 vCore and 2 GB of memory) with one P10 (128 GB) data disk for InfluxDB was sufficient for monitoring two instances. However, what we describe is a central monitoring solution, and as such it can be used to monitor many Managed Instance and SQL Server instances. For large deployments, you will likely need to use larger VMs, and/or use a separate VM for InfluxDB. If dashboard refreshes start taking too long, then more resources for InfluxDB are needed.
<h4>Step 1</h4>
Install Git and Docker, if they are not already installed, otherwise skip this step.

<b>Ubuntu: </b>

[code language="text"]
sudo apt-get install git -y
wget -qO- https://get.docker.com/ | sudo sh
[/code]

<b>RHEL/CentOS:</b>

[code language="text"]
sudo yum install git -y
sudo yum install docker -y
sudo systemctl enable docker
sudo systemctl start docker
[/code]

<b>Note:</b> On RHEL, SELinux is enabled by default. For the InfluxDB and Grafana containers to start successfully, we either have to disable SELinux, or add the right SELinux policy. With the default RHEL configuration, SELINUX configuration is set to <i>enforcing</i>. Changing this configuration to <i>permissive</i> or <i>disabled</i> will work around this issue. Alternatively set the right SELinux policy as described <a href="https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux_atomic_host/7/html/container_security_guide/docker_selinux_security_policy">here</a>. See this <a href="https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux_atomic_host/7/html/container_security_guide/docker_selinux_security_policy">documentation</a> article for more information.

[code language="text"]
sudo vi /etc/selinux/config
[/code]

<h4>Step 2</h4>
Clone the GitHub repo.

[code language="text"]
cd $HOME
sudo git clone https://github.com/denzilribeiro/sqlmimonitoring.git/
[/code]

<h4>Step 3</h4>
Install, configure, and start InfluxDB.

a. When creating the Azure VM, we added a separate data disk for InfluxDB. Now, mount this disk as /influxdb, following the steps in this <a href="https://docs.microsoft.com/en-us/azure/virtual-machines/linux/attach-disk-portal#connect-to-the-linux-vm-to-mount-the-new-disk">documentation article</a> (replace <i>datadrive</i> with <i>influxdb</i>).

b. Optionally, edit the runinfluxdb.sh file and modify the INFLUXDB_HOST_DIRECTORY variable to point to the directory where you want the InfluxDB volume to be mounted, if it is something other than the default of /influxdb.

[code language="text"]
cd $HOME/sqlmimonitoring/influxdb
sudo vi runinfluxdb.sh
[/code]

If you are new to vi (a text editor), use the <a href="https://www.lagmonster.org/docs/vi.html">cheat sheet</a>.

c. Pull the InfluxDB Docker image and start InfluxDB.

[code language="text"]
cd $HOME/sqlmimonitoring/influxdb
sudo ./runinfluxdb.sh
[/code]

<h4>Step 4</h4>
Pull the Grafana Docker image and start Grafana.

[code language="text"]
cd $HOME/sqlmimonitoring/grafana
sudo ./rungrafana.sh
[/code]

<h4>Step 5</h4>
Optionally, if the firewall is enabled on the VM, create an exception for TCP port 3000 to allow web connections to Grafana.

<b>Ubuntu:</b>

[code language="text"]
sudo ufw allow 3000/tcp
sudo ufw reload
[/code]

<b>RHEL/CentOS:</b>

[code language="text"]
sudo firewall-cmd --zone=public --add-port=3000/tcp -permanent
sudo firewall-cmd --reload
[/code]

<h4>Step 6</h4>
Install Telegraf version 1.8.0 or later. Support for MI was first introduced in this version.

<b>Ubuntu:</b>

[code language="text"]
cd $HOME
wget https://dl.influxdata.com/telegraf/releases/telegraf_1.8.0-1_amd64.deb
sudo dpkg -i telegraf_1.8.0-1_amd64.deb
[/code]

<b>RHEL/CentOS:</b>

[code language="text"]
cd $HOME
wget https://dl.influxdata.com/telegraf/releases/telegraf-1.8.0-1.x86_64.rpm
sudo yum localinstall telegraf-1.8.0-1.x86_64.rpm -y
[/code]

<h4>Step 7</h4>
Create a login for Telegraf on each instance you want to monitor, and grant permissions. Here we create a login named <i>telegraf</i>, which is referenced in the Telegraf config file later.

[code language="sql"]
USE master;
CREATE LOGIN telegraf WITH PASSWORD = N'MyComplexPassword1!', CHECK_POLICY = ON;
GRANT VIEW SERVER STATE TO telegraf;
GRANT VIEW ANY DEFINITION TO telegraf;
[/code]

<h4>Step 8</h4>
Configure and start Telegraf.

a. The Telegraf configuration file (telegraf.conf) is included in sqlmimonitoring/telegraf, and includes the configuration settings used by the solution. Edit the file to specify your monitored instances in the [[inputs.sqlserver]] section, and then copy it to /etc/telegraf/telegraf.conf, overwriting the default configuration file.

[code language="text"]
sudo mv /etc/telegraf/telegraf.conf /etc/telegraf/telegraf_original.conf
sudo cp $HOME/sqlmimonitoring/telegraf/telegraf.conf /etc/telegraf/telegraf.conf
sudo chown root:root /etc/telegraf/telegraf.conf
sudo chmod 644 /etc/telegraf/telegraf.conf
[/code]

b. Alternatively, edit the /etc/telegraf/telegraf.conf file in place. This is preferred if you would like to keep all other Telegraf settings present in the file as commented lines.

[code language="text"]
sudo vi /etc/telegraf/telegraf.conf
[/code]

Uncomment every line shown below, and add a connection string for every instance/server you would like to monitor in the [[inputs.sqlserver]] section. When editing telegraf.conf in the vi editor, search for [[inputs.sqlserver]] by typing “/” followed by “inputs.sqlserver”. Ensure the URL and database name for InfluxDB in the outputs.influxdb section are correct.

[code language="text"]
[[inputs.sqlserver]]
servers = [&quot;Server=server1.xxx.database.windows.net;User Id=telegraf;Password=MyComplexPassword1!;app name=telegraf;&quot;
,&quot;Server=server2.xxx.database.windows.net;User Id=telegraf;Password=MyComplexPassword1!;app name=telegraf;&quot;]

query_version = 2

[[outputs.influxdb]]
urls = [&quot;https://127.0.0.1:8086&quot;]
database = &quot;telegraf&quot;
[/code]

c. Note that the default polling interval in Telegraf is 10 seconds. If you want to change this, i.e. for more precise metrics, you will have to change the <i>interval</i> parameter in the <i>[agent]</i> section in the telegraf.conf file.

d. Once the changes are made, start the service.

[code language="text"]
sudo systemctl start telegraf
[/code]

e. Confirm that Telegraf is running.

[code language="text"]
sudo systemctl status telegraf
[/code]

<h4>Step 9</h4>
In Grafana, create the data source for InfluxDB, and import Managed Instance dashboards.

In this step, [GRAFANA_HOSTNAME_OR_IP_ADDRESS] refers to the public hostname or IP address of the Grafana VM, and [INFLUXDB_HOSTNAME_OR_IP_ADDRESS] refers to the hostname or IP address of the InfluxDB VM (either public or private) that is accessible from the Grafana VM. If using a single VM for all components, these values are the same.

a. Browse to your Grafana instance - https://[GRAFANA_IP_ADDRESS_OR_SERVERNAME]:3000.

Login with the default user <i>admin</i> with password <i>admin</i>. Grafana will prompt you to change the password on first login.

b. Add a data source for InfluxDB. Detailed instructions are at <a href="https://docs.grafana.org/features/datasources/influxdb/">https://docs.grafana.org/features/datasources/influxdb/</a>

Click “Add data source”
Name: influxdb-01
Type: InfluxDB
URL: https://[INFLUXDB_HOSTNAME_OR_IP_ADDRESS]:8086. The default of https://localhost:8086 works if Grafana and InfluxDB are on the same machine; make sure to explicitly enter this URL in the field.
Database: telegraf

Click “Save &amp; Test”. You should see the message “Data source is working”.

c. Download Grafana dashboard JSON definitions from the repo <a href="https://github.com/denzilribeiro/sqlmimonitoring/tree/master/dashboards">dashboards</a> folder for all three dashboards, and then <a href="https://docs.grafana.org/reference/export_import/#importing-a-dashboard">import</a> them into Grafana. When importing each dashboard, make sure to use the dropdown labeled <i>InfluxDB-01</i> to select the data source created in the previous step.
<h4>Step 10</h4>
Optionally, configure a retention policy for data in InfluxDB. By default, monitoring data is maintained indefinitely.

a. Start InfluxDB console.

[code language="text"]
sudo docker exec -it influxdb influx
[/code]

b. View current retention policies in InfluxDB.

[code language="text"]
use telegraf;
show retention policies;
[/code]

c. Create a <a href="https://docs.influxdata.com/influxdb/v1.6/query_language/database_management/#create-retention-policies-with-create-retention-policy">retention policy</a> and exit InfluxDB console.

[code language="text"]
create retention policy retain30days on telegraf duration 30d replication 1 default;
quit
[/code]

In this example, we created a 30-day retention policy for the telegraf database, setting it as the default policy.
<h4>Step 11</h4>
You are done! The dashboards should now display data from all instances specified in the Telegraf config file. You can switch between monitored instances using the dropdown at the top of each dashboard. You can change the time scale by using the pull-down menu in upper right. You can switch between different dashboards using the “Other Managed Instance Dashboards” button.
<h3>Troubleshooting examples</h3>
Here we will show some examples of actual troubleshooting scenarios on Managed Instance where this monitoring solution helped us identify the root cause.
<h4>A six-minute workload stall</h4>
In this case, we were testing an OLTP application workload. During one test, we noticed a complete stall in workload throughput.

<a href="https://msdnshared.blob.core.windows.net/media/2018/09/clip_image010.jpg"><img width="1251" height="638" title="clip_image010" alt="clip_image010" src="https://msdnshared.blob.core.windows.net/media/2018/09/clip_image010_thumb.jpg" border="0" /></a>

During the stall, we saw no current waits on the instance at all, so this was not the kind of problem where the workload was stalled because of waiting for some resource, such as disk IO or memory allocation.

<a href="https://msdnshared.blob.core.windows.net/media/2018/09/clip_image012.jpg"><img width="1250" height="281" title="clip_image012" alt="clip_image012" src="https://msdnshared.blob.core.windows.net/media/2018/09/clip_image012_thumb.jpg" border="0" /></a>

Then, after about six minutes, the workload resumed at the previous rate.

<a href="https://msdnshared.blob.core.windows.net/media/2018/09/clip_image014.jpg"><img width="1251" height="644" title="clip_image014" alt="clip_image014" src="https://msdnshared.blob.core.windows.net/media/2018/09/clip_image014_thumb.jpg" border="0" /></a>

Looking at the waits again, we now saw a very large spike in LCK_IX_M waits, dwarfing all other waits.

<a href="https://msdnshared.blob.core.windows.net/media/2018/09/clip_image016.jpg"><img width="1247" height="287" title="clip_image016" alt="clip_image016" src="https://msdnshared.blob.core.windows.net/media/2018/09/clip_image016_thumb.jpg" border="0" /></a>

This immediately pointed to a blocking problem, which was indeed the case. As a part of each transaction, the application was inserting a row into a history table. That table was created as a heap. We found that during the stall, a single DELETE statement affecting a large number of rows in the history table was executed under the serializable transaction isolation level. Running a DML statement targeting a heap table under serializable isolation always causes lock escalation. Thus, while the DELETE query ran for six minutes, it held an X lock at the table level. This OLTP workload inserts a row into the history table as a part of each transaction, therefore it was completely blocked.

What is interesting about monitoring this kind of typical blocking scenario is that the LCK_IX_M waits did not appear on the dashboard until after the stall has ended. This is because SQL Server reports waits in <a href="https://docs.microsoft.com/en-us/sql/relational-databases/system-dynamic-management-views/sys-dm-os-wait-stats-transact-sql">sys.dm_os_wait_stats</a> DMV only once they have completed, and none of the LCK_IX_M waits completed until the X lock went away and the workload had resumed.
<h4>Periodic drops in throughput</h4>
The next example is more interesting, and is specific to Managed Instance. During an otherwise steady high volume OLTP workload on a General Purpose instance, we observed sharp drops in workload throughput at five-minute intervals.

<a href="https://msdnshared.blob.core.windows.net/media/2018/09/clip_image018.jpg"><img width="1251" height="649" title="clip_image018" alt="clip_image018" src="https://msdnshared.blob.core.windows.net/media/2018/09/clip_image018_thumb.jpg" border="0" /></a>

Looking at the graph of wait types, we saw corresponding spikes in BACKUPIO and WRITELOG waits, at the same five-minute intervals.

<a href="https://msdnshared.blob.core.windows.net/media/2018/09/clip_image020.jpg"><img width="1250" height="283" title="clip_image020" alt="clip_image020" src="https://msdnshared.blob.core.windows.net/media/2018/09/clip_image020_thumb.jpg" border="0" /></a>

Since log backups on Managed Instance run every five minutes, this seemed to imply a strong correlation between drops in throughput and log backups. But why do log backups cause drops in throughput? Looking at the Database IO Stats dashboard, we saw corresponding five-minute spikes in Read Bytes/sec specifically for the transaction log file, which is indeed expected while a transaction log backup is running.

<a href="https://msdnshared.blob.core.windows.net/media/2018/09/clip_image022.jpg"><img width="1252" height="648" title="clip_image022" alt="clip_image022" src="https://msdnshared.blob.core.windows.net/media/2018/09/clip_image022_thumb.jpg" border="0" /></a>

The magnitude of these spikes in read throughput pointed to the root cause of the problem. The spikes were close to the throughput limit of the transaction log blob. At the high level of workload throughput we were driving (~ 8K batch requests/second), the combined read and write IO against the log blob during log backup was enough to cause Azure Storage <a href="https://docs.microsoft.com/en-us/azure/virtual-machines/windows/premium-storage#throttling">throttling</a>. When throttling occurs, it affects both reads and writes. This made transaction log writes slower by orders of magnitude, which naturally caused major drops in workload throughput. This problem is specific to General Purpose Managed Instance with its remote storage, and does not occur on Business Critical Managed Instance. For General Purpose Managed Instance, the engineering team is looking for ways to minimize or eliminate this performance impact in a future update.
<h3>Conclusion</h3>
In this article, we describe a solution for real time performance monitoring of Azure SQL Database Managed Instance. In our customer engagements, we found it indispensable for monitoring workload performance, and for diagnosing a broad range of performance issues in customer workloads. We hope that customers using Managed Instance will find it useful for database workload monitoring and troubleshooting, as they migrate their on-premises applications to Azure. We hope many of you will start your journey with <a href="https://docs.microsoft.com/en-us/azure/sql-database/#5-minute-quickstarts">Managed Instance</a> soon, and are looking forward to hearing about your experiences, as well as feedback and contributions to this monitoring solution.]]></content:encoded>
    </item>
    <item>
      <title>Storage performance best practices and considerations for Azure SQL DB Managed Instance (General Purpose)</title>
      <link>https://docs.microsoft.com/archive/blogs/sqlcat/storage-performance-best-practices-and-considerations-for-azure-sql-db-managed-instance-general-purpose</link>
      <pubDate>Fri, 20 Jul 2018 09:44:28 GMT</pubDate>
      <dc:creator><![CDATA[Dimitri Furman]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.msdn.microsoft.com/sqlcat/?p=7165</guid>
      <description><![CDATA[Reviewed by: Kun Cheng, Borko Novakovic, Jovan Popovic, Denzil Ribeiro, Rajesh Setlem, Arvind...]]></description>
      <content:encoded><![CDATA[<i>Reviewed by: Kun Cheng, Borko Novakovic, Jovan Popovic, Denzil Ribeiro, Rajesh Setlem, Arvind Shyamsundar, Branislav Uzelac</i>

In this article, we describe database storage architecture on <a href="https://docs.microsoft.com/en-us/azure/sql-database/sql-database-managed-instance">Azure SQL Database Managed Instance</a> (MI), for General Purpose (GP) instances specifically. We also provide a set of best practices to help optimize storage performance.

At the time of this writing (July 2018), Managed Instance, both General Purpose and Business Critical, is in public preview. Some aspects of MI storage architecture will likely change as MI progresses from the current preview to general availability and beyond. This article reflects the current state of the offering.
<h3>Database storage architecture on MI GP</h3>
MI GP uses Azure Premium Storage to store database files for all databases, except for the <i>tempdb</i> database. From the perspective of the database engine, this storage type is remote, i.e. it is accessed over the network, using Azure network infrastructure. To use Azure Premium Storage, MI GP takes advantage of SQL Server native capability to use <a href="https://docs.microsoft.com/en-us/sql/relational-databases/databases/sql-server-data-files-in-microsoft-azure?view=sql-server-2017">database files directly in Azure Blob Storage</a>. This means that there is not a disk or a network share that hosts database files; instead, file path is an HTTPS URL, and each database file is a page blob in Azure Blob Storage.

Since Azure Premium Storage is used, its performance characteristics, limits, and scalability goals fully apply to MI GP. The <a href="https://docs.microsoft.com/en-us/azure/virtual-machines/windows/premium-storage">High-performance Premium Storage and managed disks for VMs</a> documentation article includes a section describing <a href="https://docs.microsoft.com/en-us/azure/virtual-machines/windows/premium-storage#premium-storage-disk-limits">Premium Storage disk limits</a>. While the topic is written in the context of VMs and Azure disks, which is the most common usage scenario for Azure Premium Storage, the documented limits are also applicable to blobs. As shown in the limits table in the documentation, the size of the blob determines the maximum IOPS and throughput that can be achieved against the blob. For MI GP, this means that the size of a database file determines the maximum IOPS and throughput that is achievable against the file.

The disk/blob size shown in the limits table is the maximum size for which the corresponding limit applies. For example, a blob that is &gt; 64 GB and &lt;= 128 GB (equivalent to a P10 disk) can achieve up to 500 IOPS and up to 100 MB/second throughput.

The current implementation of MI GP does not use blobs smaller than 128 GB (P10). The system will use 128 GB (P10) blobs even for very small database files, to avoid negative performance impact that would be likely with smaller blob sizes (P4 and P6). Additionally, when allocating blobs in Premium Storage, MI always uses the maximum blob size within a storage performance tier. For example, if database file size is 900 GB, MI GP will use a 1 TB (P30) blob for that file. In other words, blob size is snapped up to the maximum size of each storage performance tier. If the file grows above that limit, the system automatically increases blob size to the maximum of the next performance tier. Conversely, if the file is shrunk below the maximum size of a performance tier, blob size is automatically reduced as well.

For billing purposes, MI uses the configured instance file size limit (8 TB or less), not the larger blob size allocated in Azure Premium Storage.

As mentioned earlier, the <i>tempdb</i> database is not using Azure Premium Storage. It is located on the local SSD storage, which provides very low latency and high IOPS/throughput. This article focuses on databases that use remote storage.

Managed Instance <a href="https://docs.microsoft.com/en-us/azure/sql-database/sql-database-managed-instance#business-critical-service-tier">Business Critical</a> (BC) instances do not use remote Azure Premium Storage, but instead use local SSD storage. Storage performance considerations discussed in this article do not apply to BC instances.
<h3>Azure Storage throttling</h3>
When the IOPS or throughput generated by the database workload exceed the limits of a database file/blob, storage <a href="https://docs.microsoft.com/en-us/azure/virtual-machines/windows/premium-storage#throttling">throttling</a> occurs. For MI GP instances, a typical symptom of storage throttling is high IO latency. It is possible to see IO latency spike to hundreds of milliseconds when being throttled, while without throttling, average storage latency at the OS level would be in the 2-10 millisecond range. Another symptom of storage throttling is long PAGEIOLATCH waits (for data file IO) and WRITELOG waits (for the transaction log file IO). Needless to say, when storage throttling is occurring, there is a substantial negative effect on database performance.

If you observe the symptoms of storage throttling when running workloads on MI GP, we recommend increasing database file size to get more IOPS/throughput from Azure Premium Storage. More specific recommendations, including a <a href="https://github.com/dimitri-furman/managed-instance/blob/master/MI-GP-storage-perf/MI-GP-storage-perf.sql">script</a> to determine if storage IO generated by the database workload approaches Azure Premium Storage IOPS/throughput limits, are provided further in the article.
<h3>Storage limits on MI GP</h3>
There are two storage-related limits that should be considered when using MI GP.

The first limit is on the total size of all database files on the instance. System databases including <i>tempdb</i> are counted towards this limit, and all data files and transaction log files are considered. Currently, this limit is 8 TB per instance, though a customer can configure their instance to use a lower limit. Note that this limit applies specifically to file size as it appears in the <i>size</i> column in the <a href="https://docs.microsoft.com/en-us/sql/relational-databases/system-catalog-views/sys-master-files-transact-sql">sys.master_files</a> DMV, not to the space actually used to store data within each file.

The second limit is the Azure Premium Storage limit on the maximum space in a storage account, which is currently limited to 35 TB. Each MI GP instance uses a single Premium Storage account.

The up to 8 TB file size limit implies that if there are many databases on the instance, or if a database has many files, then it may be necessary to reduce the size of individual database files to stay within this limit. In that case, the IOPS/throughput against these files would be reduced as well. The more files an MI instance has, the more pronounced is the impact of instance file size limit on storage performance. In the worst case, the size of individual database files may have to be reduced to the point where they all end up using 128 GB (P10) blobs, even if the workload could take advantage of better performance available with larger files.

Whether this limit is relevant for a particular workload depends on its storage performance needs. Many workloads will perform sufficiently well even with smaller database files. The IOPS/throughput that can be obtained for a particular file size can be determined in advance from the Azure Premium Storage <a href="https://docs.microsoft.com/en-us/azure/virtual-machines/windows/premium-storage#premium-storage-disk-limits">limits</a> table.

In the context of migration to MI, customers can measure actual IOPS/throughput on the source system being migrated, to see if it is necessary to increase database file size on MI GP to provide comparable storage performance. For SQL Server on Windows, use the <i>Disk Transfers/sec</i> and <i>Disk Bytes/sec</i> PerfMon counters, for IOPS and throughput respectively. For SQL Server on Linux, use iostat, looking at the sum of <i>r/s</i> and <i>w/s</i> values for IOPS, and the sum of <i>rKB/s</i> and <i>wKB/s</i> for throughput. Alternatively, the <a href="https://docs.microsoft.com/en-us/sql/relational-databases/system-dynamic-management-views/sys-dm-io-virtual-file-stats-transact-sql">sys.dm_io_virtual_file_stats</a>() DMF can be used as well. The <a href="https://github.com/dimitri-furman/managed-instance/blob/master/MI-GP-storage-perf/MI-GP-storage-perf.sql">script</a> referenced in the best practices section below provides an example of using this DMF to determine IOPS and throughput per database file.

The fact that MI GP does not use blobs smaller than 128 GB (P10), coupled with the fact that the blob size is snapped up to the maximum size of its performance tier, can make the 35 TB limit relevant for some MI GP deployments. As <a href="https://docs.microsoft.com/en-us/azure/sql-database/sql-database-managed-instance-transact-sql-information#exceeding-storage-space-with-small-database-files">described</a> in documentation, when there are many database files with sizes smaller than the sizes of the blobs they use, then the 35 TB blob size limit may be reached sooner than the 8 TB file size limit. To see the total file and blob sizes for an instance, customers can use the following query. It returns a single row with two columns, one showing the total file size, where the 8 TB limit is relevant, and the other showing the total blob size, where the 35 TB limit is relevant:

[code language="sql"]
WITH DatabaseFile AS
(
SELECT database_id AS DatabaseID,
CAST(size * 8. / 1024 / 1024 AS decimal(12,4)) AS FileSizeGB
FROM sys.master_files
)
SELECT SUM(FileSizeGB) AS FileSizeGB,
       SUM(
          IIF(
             DatabaseID &lt;&gt; 2,
             CASE WHEN FileSizeGB &lt;= 128 THEN 128 WHEN FileSizeGB &gt; 128 AND FileSizeGB &lt;= 256 THEN 256 WHEN FileSizeGB &gt; 256 AND FileSizeGB &lt;= 512 THEN 512 WHEN FileSizeGB &gt; 512 AND FileSizeGB &lt;= 1024 THEN 1024 WHEN FileSizeGB &gt; 1024 AND FileSizeGB &lt;= 2048 THEN 2048 WHEN FileSizeGB &gt; 2048 AND FileSizeGB &lt;= 4096 THEN 4096
                  ELSE 8192
             END,
             0
             )
          )
       AS BlobSizeGB
FROM DatabaseFile;
[/code]

For example, this result set shows different file and blob total sizes for a MI GP instance:

[code language="text"]
FileSizeGB  BlobSizeGB
----------- -----------
2048.4474   2944
[/code]

<h3>Comparison with SQL Server in Azure IaaS VM</h3>
In a typical deployment of SQL Server on an Azure IaaS VM (SQL VM), database files are placed on Azure disks attached to the VM. Sometimes, a Storage Spaces pool (on Windows) or an LVM volume (on Linux) is created using multiple disks, to aggregate IOPS/throughput provided by multiple disks, and to increase storage size. There are notable differences between this model, and the model used by MI GP. For SQL VMs, available IOPS/throughput are shared among all database files using a disk (or a storage pool), whereas for MI GP, each file/blob gets its own allocation of IOPS/throughput.

Each model has its pros and cons. When there are multiple databases on the instance, MI GP makes it possible to provide a fixed allocation of IOPS/throughput per database file, avoiding the “noisy neighbor” effect of other databases on the instance. On the other hand, each database on a SQL VM can take advantage of higher aggregated IOPS/throughput provided by multiple disks, assuming that all databases do not create spikes in storage utilization at the same time.

One other important difference between MI GP and SQL VM is that the per-VM IOPS/throughput limits, <a href="https://docs.microsoft.com/en-us/azure/virtual-machines/windows/sizes">documented</a> for each VM type as <i>Max uncached disk throughput: IOPS / MBps</i>, do not apply to MI. When we talked about throttling earlier in this article, we referred specifically to Azure Premium Storage throttling, and not VM-level throttling. VM-level throttling can occur on SQL VM when cumulative IO requests against all Azure disks attached to the VM exceed the per-VM limits. However, MI GP does not use Azure disks for database files; therefore, the per-VM disk limits are not applicable, and MI instances do not experience VM-level throttling in the way that SQL VMs do.
<h3>Performance impact of database file size on MI GP – an example</h3>
To illustrate the performance impact of database file size on MI GP, we ran a series of tests using a generic OLTP workload on an 8 vCore instance with 8 TB storage. This example <i>is</i> <i>not intended as a benchmark</i>, and the numbers shown <i>should not be taken as representative for MI GP in general</i>. Storage performance is highly dependent on workload characteristics, and the results obtained using the same MI instance type and storage layout used in these tests, but a different workload, may be very different. These results are shown only to demonstrate the <i>relative</i> <i>difference </i>in performance when different file size configurations on MI GP are used.

In these tests, we used a database with 3 data files and 1 log file. In the initial test, the size of every file was 32 GB (as noted earlier, the system actually uses 128 GB P10 blobs in this case). In following tests, we first increased the size of the transaction log file, and then gradually increased the size of data files. Finally, we increased the size of all files to over 1 TB (to use 2 TB P40 blobs) to get maximum storage performance possible with this file layout. We observed the changes in workload throughput and behavior using metrics such as batch requests/second (BRPS), CPU utilization percentage, PAGEIOLATCH and WRITELOG wait percentage relative to all other non-ignorable waits, and write latency for data files and log file (measured using the <a href="https://docs.microsoft.com/en-us/sql/relational-databases/system-dynamic-management-views/sys-dm-io-virtual-file-stats-transact-sql">sys.dm_io_virtual_file_stats</a>() DMF).

At the storage level, the workload had a 60/40 read/write ratio with cold cache. The database was small enough to fit in memory, so during the test, that ratio changed to nearly 100% writes as most of the data pages became cached in the buffer pool. For this reason, we show only write latency in test results.

Test results are presented in the following table:
<table border="1" cellspacing="0" cellpadding="0">
<tbody>
<tr>
<td width="132" valign="top"><strong>File layout</strong></td>
<td width="66" valign="top"><strong>Average BRPS</strong></td>
<td width="84" valign="top"><strong>CPU utilization (%)</strong></td>
<td width="84" valign="top"><strong>WRITELOG waits (%)</strong></td>
<td width="102" valign="top"><strong>PAGEIOLATCH waits (%)</strong></td>
<td width="78" valign="top"><strong>Average data write latency (ms/write)</strong></td>
<td width="78" valign="top"><b><strong>Average log write latency (ms/write)</strong></b></td>
</tr>
<tr>
<td width="132" valign="top">3 x 32 GB data files, 32 GB log file</td>
<td width="66" valign="top">1457</td>
<td width="84" valign="top">10</td>
<td width="84" valign="top">21</td>
<td width="102" valign="top">70</td>
<td width="78" valign="top">132</td>
<td width="78" valign="top">16</td>
</tr>
<tr>
<td width="132" valign="top">3 x 32 GB data files, 512 GB log file</td>
<td width="66" valign="top">1732</td>
<td width="84" valign="top">12</td>
<td width="84" valign="top">13</td>
<td width="102" valign="top">85</td>
<td width="78" valign="top">477</td>
<td width="78" valign="top">4.5</td>
</tr>
<tr>
<td width="132" valign="top">3 x 256 GB data files, 512 GB log file</td>
<td width="66" valign="top">2422</td>
<td width="84" valign="top">12</td>
<td width="84" valign="top">12</td>
<td width="102" valign="top">80</td>
<td width="78" valign="top">155</td>
<td width="78" valign="top">4.7</td>
</tr>
<tr>
<td width="132" valign="top">3 x 512 GB data files, 512 GB log file</td>
<td width="66" valign="top">2706</td>
<td width="84" valign="top">12</td>
<td width="84" valign="top">12</td>
<td width="102" valign="top">77</td>
<td width="78" valign="top">165</td>
<td width="78" valign="top">4.4</td>
</tr>
<tr>
<td width="132" valign="top">3 x 1.1 TB data files, 2 TB log file</td>
<td width="66" valign="top">7022</td>
<td width="84" valign="top">46</td>
<td width="84" valign="top">74</td>
<td width="102" valign="top">4</td>
<td width="78" valign="top">49</td>
<td width="78" valign="top">4.5</td>
</tr>
</tbody>
</table>
We can see that for this workload, incrementally growing data and log files up to 512 GB in the first four tests provided gradual improvements. Using 512 GB files, workload throughput (BRPS) was almost twice as high as with the 32 GB files used initially. But increasing the size of all files from 512 GB to over 1 TB provided a 4.8x increase in workload throughput, compared to the initial test throughput. In this last test, removing the bottleneck in data file IO drastically increased both BRPS and CPU utilization, and shifted the dominant wait type from PAGEIOLATCH to WRITELOG as many more write transactions were processed.

To reiterate, the degree of performance improvement from increasing file size on MI GP is workload-dependent. For some workloads, sufficient performance levels can be achieved without using large files. Furthermore, other factors besides storage performance could affect the overall workload performance. For example, increasing file size on a CPU-constrained MI GP instance would not necessarily provide a significant performance increase.
<h3>Storage performance best practices for MI GP</h3>
In closing, we present a set of storage performance best practices for MI GP instances. These are not absolutes. We encourage the readers to understand the reasons behind these best practices that were discussed earlier in the article, and consider each recommendation in the context of their specific application/workload.

1. Determine if the storage IO generated by the database workload is exceeding Azure Premium Storage limits. A script that examines IOPS/throughput per database file over a period of time, and compares them against the limits, is <a href="https://github.com/dimitri-furman/managed-instance/blob/master/MI-GP-storage-perf/MI-GP-storage-perf.sql">available</a> on GitHub. If storage IO is near the limits, allocate more IOPS/throughput for the database as described below.

2. When possible, increase space allocation at the MI instance level. This will allow you to increase the size of individual database files, resulting in higher IOPS/throughput limits per file.

3. Increase the size of individual database files as much as possible to utilize available instance space, even if a large portion of each file would remain empty. But consider Azure Premium Storage <a href="https://docs.microsoft.com/en-us/azure/virtual-machines/windows/premium-storage#premium-storage-disk-limits">limits</a> as well. For example, if the size of a file is 150 GB, then increasing it up to 256 GB would not provide any additional IOPS/throughput. MI already uses a 256 GB (P15) blob in this case. This larger file would take some of the instance storage space that may be better used to increase IOPS/throughput for other database files on the same instance. In this example, to get the advantage of the next storage performance tier for this file, it would be necessary and sufficient to increase file size to 257 GB. MI GP would then snap up the blob size for this file to 512 GB (P20).

4. For many write-intensive workloads, increasing the size of the transaction log file provides higher performance improvement than increasing the size of data files.

5. Make all data files equally sized with the same growth increment. While this recommendation is not specific to MI, it is particularly relevant here. On MI GP, a single small file may negatively impact performance of the entire workload, due to storage throttling becoming more likely for that small file.

6. Avoid using many small files for a database. While the aggregate IOPS/throughput from many small files may be comparable to the IOPS/throughput provided by fewer large files, each individual small file is more likely to encounter storage throttling, which affects the entire workload.

7. Consider not only increasing file size, but also adding data files. For example, consider an MI instance with a single database. If this database has a single data file, it can get at most 7500 IOPS for data IO, by making that file larger than 1 TB in size (a P40 or P50 blob would be used depending on the exact file size). But using N files, each larger than 1 TB, would provide up to 7500*N IOPS for data IO, and 7500 IOPS for log IO. For example, you could use six 1.1 TB data files, and one 1.1 TB log file. The total file size would be 7.7 TB, while the total blob size would be 14 TB, remaining within the 8 TB and 35 TB limits respectively. Keep in mind that simply adding empty files to an existing database would not improve performance for most workloads; it is also necessary to reallocate existing data over all files.
<h3>Conclusion</h3>
In this article, we show how database file size and layout can have a significant performance impact on MI GP instances. We provide recommendations to help customers achieve optimal storage performance, and to avoid common pitfalls. This article applies to General Purpose MI instances. The complexities and limitations we describe do not apply to <a href="https://docs.microsoft.com/en-us/azure/sql-database/sql-database-managed-instance#business-critical-service-tier">Business Critical</a> instances, which are recommended for mission critical workloads with high storage performance requirements. If sufficient storage performance cannot be achieved on a General Purpose instance even after following best practices described above, then switching to a Business Critical instance is recommended.

<strong>Update 2018-12-18</strong>: As of this writing, both the General Purpose and Business Critical MI service tiers implement log rate governance at the instance level. As documented in the Managed Instance <a target="_blank" href="https://docs.microsoft.com/en-us/azure/sql-database/sql-database-managed-instance-resource-limits#service-tier-characteristics" rel="noopener">resource limits</a> article, this limits log throughput to 22 MB/s per instance for General Purpose instances, and to 3 MB/s per vCore for Business Critical instances. When the maximum instance log rate is reached, one of the dominant wait types will be INSTANCE_LOG_RATE_GOVERNOR. For write intensive workloads such as bulk data loads, this limits write throughput regardless of the transaction log blob size.

There are two potential workarounds to improve data load performance on MI. One is to use tempdb as a temporary staging area for data loads. Tempdb is not subject to instance log rate governance, and provides minimal logging as well. Another workaround is to use clustered columnstore indexes (CCI), which reduce the volume of  transaction log writes due to high data compression.]]></content:encoded>
    </item>
    <item>
      <title>DataCAT team at Data Platform Summit 2018</title>
      <link>https://docs.microsoft.com/archive/blogs/sqlcat/datacat-team-at-data-platform-summit-2018</link>
      <pubDate>Mon, 16 Jul 2018 18:44:59 GMT</pubDate>
      <dc:creator><![CDATA[Arvind Shyamsundar]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.msdn.microsoft.com/sqlcat/?p=7155</guid>
      <description><![CDATA[Team members from Microsoft AzureCAT (a.k.a. DataCAT / SQLCAT) will be presenting a full-day...]]></description>
      <content:encoded><![CDATA[<p>Team members from Microsoft AzureCAT (a.k.a. DataCAT / SQLCAT) will be presenting a <a href="https://www.dataplatformgeeks.com/dps2018/pre-conference-training-classes/" target="_blank">full-day pre-conference</a> on “Building A Scalable Data Architecture In Azure” at the upcoming <a href="https://www.dataplatformgeeks.com/dps2018/" target="_blank">DPS 2018</a> conference in Bengaluru, India. We are very excited to be part of the 4th edition of Asia’s largest Microsoft data-focused conference, with over 1000 attendees expected overall.</p><p>The motivation for this full-day pre-conference training is to share with attendees typical scenarios and considerations in architecting and implementing solutions on Microsoft Azure. Whether you are migrating an existing on-premises data pipeline, or implementing a greenfield project in the cloud, the variety of choices in terms of technologies can be bewildering and confusing. We intend to share scenarios and patterns and practices which have worked in those scenarios. With this background, the attendee has a head-start on the technical front and more importantly, confidence that the architectural patterns work in real-world scenarios.</p><p>The pre-conference is structured into the following sections:</p><ul><li>Overall Azure Data Services landscape</li><li>Transactional systems and structured data with Azure SQL DB</li><li>Not Only SQL systems (NoSQL) with Cosmos DB</li><li>Relational Data Warehousing using Azure SQL DW</li><li>Handling streaming data in Azure with Event Hubs, Kafka etc.</li><li>Building Data Lakes in Azure</li><li>Data Analytics and AI / ML at scale using Apache Spark, Azure Data Lake Analytics etc.</li><li>Options for orchestrating data pipelines: Azure Data Factory etc.</li><li>Interactive Querying using Power BI, Azure Analysis Services etc.</li><li>Top lessons learnt from real-world customer engagements</li></ul><p>Each section will be accompanied by demos highlighting practical considerations and recommendations beyond just the basics. The team members who are part of this conference are all very experienced in the industry and are true believers in sharing information and learnings:</p><ul><li><a href="https://www.sqlservergeeks.com/dps-2018-speaker-announcement-sanjay-mishra-principal-program-manager-datacat-team-microsoft-usa/" target="_blank">Sanjay Mishra</a> leads the DataCAT group within AzureCAT and is based in the Microsoft HQ at Redmond, USA. His current focus is on overall architecture patterns in the cloud.</li><li><a href="https://www.dataplatformgeeks.com/dps2018/speaker/rangarajan-srirangam/" target="_blank">Rangarajan Srirangam</a> is part of AzureCAT based in Bengaluru, India. He has helped major customers adopt and use Microsoft Azure. He brings to the table practical considerations and architectural recommendations based on his hard-earned experiences.</li><li><a href="https://www.dataplatformgeeks.com/dps2018/speaker/arvind-shyamsundar/" target="_blank">Arvind Shyamsundar</a> is part of the DataCAT group based in Redmond, USA. His focus areas include relational (SQL) systems and distributed computing using Apache Spark.</li><li><a href="https://www.dataplatformgeeks.com/dps2018/speaker/mandar-inamdar/" target="_blank">Mandar Inamdar</a> is part of the AzureCAT team and is based in Mumbai, India. His focus areas are Azure SQL DW, Interactive querying using Power BI and AAS.</li><li><a href="https://www.dataplatformgeeks.com/dps2018/speaker/guru-charan/" target="_blank">Guru Charan Bulusu</a> is part of AzureCAT and works in the Microsoft Bengaluru office. His key focus areas are Streaming data, NoSQL, AI and ML.</li></ul><p>We hope you are as excited as we are to bring this content to you. We hope you can <a href="https://www.dataplatformgeeks.com/dps2018/registration-details/" target="_blank">join us</a> on August 8th at the DPS 10 pre-conference!</p>]]></content:encoded>
    </item>
    <item>
      <title>Turbo boost data loads from Spark using SQL Spark connector</title>
      <link>https://docs.microsoft.com/archive/blogs/sqlcat/turbo-boost-data-loads-from-spark-using-sql-spark-connector</link>
      <pubDate>Sat, 12 May 2018 10:40:43 GMT</pubDate>
      <dc:creator><![CDATA[Denzil Ribeiro]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.msdn.microsoft.com/sqlcat/?p=7005</guid>
      <description><![CDATA[Reviewed by: Dimitri Furman, Xiaochen Wu

Apache Spark is a distributed processing framework...]]></description>
      <content:encoded><![CDATA[<strong>Reviewed by: Dimitri Furman, Xiaochen Wu</strong>

Apache Spark is a distributed processing framework commonly found in big data environments. Spark is often used to transform, manipulate, and aggregate data. This data often lands in a database serving layer like SQL Server or Azure SQL Database, where it is consumed by dashboards and other reporting applications. Prior to the release of the <a target="_blank" href="https://docs.microsoft.com/en-us/azure/sql-database/sql-database-spark-connector" rel="noopener">SQL Spark connector</a>, access to SQL databases from Spark was implemented using the <a target="_blank" href="https://spark.apache.org/docs/latest/sql-programming-guide.html#jdbc-to-other-databases" rel="noopener">JDBC connector</a>, which gives the ability to connect to several relational databases. However, compared to the SQL Spark connector, the JDBC connector isn’t optimized for data loading, and this can substantially affect data load throughput.

As an example, utilizing the SQLBulkCopy API that the SQL Spark connector uses, <a target="_blank" href="https://customers.microsoft.com/en-us/story/dv01" rel="noopener">dv01</a>, a financial industry customer, was able to achieve 15X performance improvements in their ETL pipeline, loading millions of rows into a columnstore table that is used to provide analytical insights through their application dashboards.

In this blog, we will describe several experiments that demonstrate the major performance improvement provided by the SQL Spark connector.

You can download the SQL Spark Connector <a target="_blank" href="https://github.com/Azure/azure-sqldb-spark" rel="noopener">here</a>
<h2><span style="color: #3366ff">Dataset</span></h2>
<strong>Dataset size:</strong> 117 million rows
<strong>Source:</strong> Azure Blob Storage containing 50 parquet files.
<strong>Spark Cluster:</strong> 8+1 node cluster, each node is a DS3V2 Azure VM (4 cores, 17 GB RAM)
<h2><span style="color: #3366ff">Scenario 1: Loading data into SQL Server</span></h2>
<strong>SQL Version:</strong> SQL Server 2017 CU 5 on RedHat 7.4
<strong>Azure VM Size:</strong> DS16sV3 (16 cores, 64 GB RAM)
<strong>Storage:</strong> 8 P30 disks in Azure Blob Storage
<strong>Database Recovery Model:</strong> Simple

Performance in SQL on windows v/s SQL on Linux is comparable and for brevity we only depict results on SQL Server on Linux.
<p style="padding-left: 30px"><a target="_blank" href="https://msdnshared.blob.core.windows.net/media/2018/05/spark1.png" rel="noopener"><img src="https://msdnshared.blob.core.windows.net/media/2018/05/spark1.png" alt="" width="991" height="452" class="wp-image-7015 aligncenter" /></a></p>

<h3><span style="color: #3366ff">Loading into a heap</span></h3>
<h4 style="padding-left: 30px">Using Spark JDBC connector</h4>
<p style="padding-left: 30px">Here is a snippet of the code to write out the Data Frame when using the Spark JDBC connector. We used the batch size of 200,000 rows. Changing the batch size to 50,000 did not produce a material difference in performance.</p>


[code lang="scala"]
dfOrders.write.mode(&quot;overwrite&quot;).format(&quot;jdbc&quot;) 
  .option(&quot;driver&quot;, &quot;com.microsoft.sqlserver.jdbc.SQLServerDriver&quot;)
  .option(&quot;url&quot;, &quot;jdbc:sqlserver://server.westus.cloudapp.azure.com;databaseName=TestDB&quot;)
  .option(&quot;dbtable&quot;, &quot;TestDB.dbo.orders&quot;)
  .option(&quot;user&quot;, &quot;myuser&quot;)
  .option(&quot;batchsize&quot;,&quot;200000&quot;) 
  .option(&quot;password&quot;, &quot;MyComplexPassword!001&quot;) 
  .save()
[/code]

<p style="padding-left: 30px">Since the load was taking longer than expected, we examined the <a target="_blank" href="https://docs.microsoft.com/en-us/sql/relational-databases/system-dynamic-management-views/sys-dm-exec-requests-transact-sql?view=sql-server-2017" rel="noopener">sys.dm_exec_requests</a> DMV while load was running, and saw that there was a fair amount of latch contention on various pages, which wouldn’t not be expected if data was being loaded via a bulk API.</p>
<p style="padding-left: 30px"><a target="_blank" href="https://msdnshared.blob.core.windows.net/media/2018/05/spark11.png" rel="noopener"><img src="https://msdnshared.blob.core.windows.net/media/2018/05/spark11.png" alt="" width="1211" height="281" class="wp-image-7115 aligncenter" /></a></p>
<p style="padding-left: 30px">Examining the statements being executed, we saw that the JDBC driver uses <a target="_blank" href="https://docs.microsoft.com/en-us/sql/relational-databases/system-stored-procedures/sp-prepare-transact-sql?view=sql-server-2017" rel="noopener">sp_prepare</a> followed by <a target="_blank" href="https://docs.microsoft.com/en-us/sql/relational-databases/system-stored-procedures/sp-execute-transact-sql?view=sql-server-2017" rel="noopener">sp_execute</a> for each inserted row; therefore, the operation is not a bulk insert. One can further example the Spark JDBC connector <a target="_blank" href="https://github.com/apache/spark/blob/v2.1.0/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/jdbc/JdbcUtils.scala#L589-" rel="noopener"> source code</a>, it builds a batch consisting of singleton insert statements, and then executes the batch via the prep/exec model.</p>
<p style="padding-left: 30px"><a target="_blank" href="https://msdnshared.blob.core.windows.net/media/2018/05/spark2.png" rel="noopener"><img src="https://msdnshared.blob.core.windows.net/media/2018/05/spark2.png" alt="" width="892" height="279" class="wp-image-7025 aligncenter" /></a></p>
<p style="padding-left: 30px">This is an 8-node Spark cluster, each executor with 4 CPU’s and due to sparks default parallelism, there were 32 tasks running simultaneously with multiple insert statements batched together. The primary contention was PAGELATCH_EX and just like any latch contention the more parallel sessions requesting for the same resource, the more the contention.</p>
<p style="padding-left: 30px"><a target="_blank" href="https://msdnshared.blob.core.windows.net/media/2018/05/spark3.png" rel="noopener"><img src="https://msdnshared.blob.core.windows.net/media/2018/05/spark3.png" alt="" width="1252" height="92" class="size-full wp-image-7035 aligncenter" /></a></p>

<h4></h4>
<h4 style="padding-left: 30px">Using SQL Spark connector</h4>
<p style="padding-left: 30px">The SQL Spark connector also uses the Microsoft JDBC driver. However, unlike the Spark JDBC connector, it specifically uses the JDBC <a target="_blank" href="https://docs.microsoft.com/en-us/sql/connect/jdbc/using-bulk-copy-with-the-jdbc-driver" rel="noopener">SQLServerBulkCopy</a> class to efficiently load data into a SQL Server table. Given that in this case the table is a heap, we also use the TABLOCK hint ( "bulkCopyTableLock" -&gt; "true") in the code below to enable parallel streams to be able to bulk load, as discussed <a target="_blank" href="https://docs.microsoft.com/en-us/previous-versions/sql/sql-server-2008-r2/ms177445(v=sql.105)#importing-data-in-parallel-with-table-level-locking-from-multiple-clients-or-streams" rel="noopener">here</a>. It is a best practice to use the BulkCopyMetadata class to define the structure of the table. Otherwise, there is additional overhead querying the database to determine table schema.</p>


[code lang="scala"]
import com.microsoft.azure.sqldb.spark.config._
import com.microsoft.azure.sqldb.spark.connect._
import com.microsoft.azure.sqldb.spark.query._
import com.microsoft.azure.sqldb.spark._
import com.microsoft.azure.sqldb.spark.bulkcopy._

var bulkCopyMetadata = new BulkCopyMetadata
bulkCopyMetadata.addColumnMetadata(1, &quot;o_orderkey&quot;, java.sql.Types.INTEGER, 0, 0)
bulkCopyMetadata.addColumnMetadata(2, &quot;o_custkey&quot;, java.sql.Types.INTEGER, 0, 0)
//trimming other columns for brevity… only showing the first 2 columns being added to BulkCopyMetadata

val bulkCopyConfig = Config(Map(
  &quot;url&quot;               -&gt; &quot;server.westus.cloudapp.azure.com&quot;,
  &quot;databaseName&quot;      -&gt; &quot;testdb&quot;,
  &quot;user&quot;              -&gt; &quot;denzilr&quot;,
  &quot;password&quot;          -&gt; &quot;MyComplexPassword1!&quot;,
  &quot;dbTable&quot;           -&gt; &quot;dbo.orders&quot;,
  &quot;bulkCopyBatchSize&quot; -&gt; &quot;200000&quot;,
  &quot;bulkCopyTableLock&quot; -&gt; &quot;true&quot;,
  &quot;bulkCopyTimeout&quot;   -&gt; &quot;600&quot;
))
dfOrders.bulkCopyToSqlDB(bulkCopyConfig)
[/code]

<p style="padding-left: 30px">Looking at the Spark UI, we see a total of 50 tasks that this DataFrame write is broken into, each loading a subset of the data:</p>
<p style="padding-left: 30px"><a target="_blank" href="https://msdnshared.blob.core.windows.net/media/2018/05/spark4.png" rel="noopener"><img src="https://msdnshared.blob.core.windows.net/media/2018/05/spark4.png" alt="" width="1677" height="624" class="size-full wp-image-7045 aligncenter" /></a></p>
<p style="padding-left: 30px">Further investigating the statements, we see the familiar INSERT BULK statement, which is an internal statement used by the SQL Server bulk load APIs. This proves that we are indeed using the <a target="_blank" href="https://msdnshared.blob.core.windows.net/media/2018/05/spark5.png" rel="noopener"><img src="https://msdnshared.blob.core.windows.net/media/2018/05/spark5.png" alt="" width="1507" height="303" class="alignleft size-full wp-image-7055" /></a>SQLBulkCopy API.</p>
<p style="padding-left: 30px">There is no longer page latch contention, rather now we are waiting on network IO or client fetching the results</p>
<p style="padding-left: 30px"><a target="_blank" href="https://msdnshared.blob.core.windows.net/media/2018/05/spark6.png" rel="noopener"><img src="https://msdnshared.blob.core.windows.net/media/2018/05/spark6.png" alt="" width="1313" height="226" class="size-full wp-image-7065 aligncenter" /></a></p>

<h3><span style="color: #3366ff">Loading into a clustered columnstore table</span></h3>
<h4 style="padding-left: 30px">With JDBC connector</h4>
<p style="padding-left: 30px">Load performance in the columnstore case will be far worse with the JDBC connector than in the heap case. Given that the JDBC connector emits single row insert statements, all this data <a target="_blank" href="https://docs.microsoft.com/en-us/sql/relational-databases/indexes/columnstore-indexes-data-loading-guidance?view=sql-server-2017" rel="noopener">lands in the delta store</a>. And we are back with more severe latch contention this time around. You can read about many more details of loading data into columnstore tables in this <a target="_blank" href="https://blogs.msdn.microsoft.com/sqlcat/2015/03/11/data-loading-performance-considerations-with-clustered-columnstore-indexes/" rel="noopener">blog</a> 😊.</p>
<p style="padding-left: 30px"><a target="_blank" href="https://msdnshared.blob.core.windows.net/media/2018/05/spark7.png" rel="noopener"><img src="https://msdnshared.blob.core.windows.net/media/2018/05/spark7.png" alt="" width="1501" height="527" class="size-full wp-image-7075 aligncenter" /></a></p>
<p style="padding-left: 30px">If we examine the <a target="_blank" href="https://docs.microsoft.com/en-us/sql/relational-databases/system-dynamic-management-views/sys-dm-db-column-store-row-group-physical-stats-transact-sql?view=sql-server-2017" rel="noopener">sys.dm_db_column_store_index_physical_stats</a> DMV, we notice that all rows are going into an OPEN rowgroup that is a delta store until that rowgroup is filled up and closed. These rows will then have to be compressed by tuple mover into compressed segments later.</p>
<p style="padding-left: 30px"><a target="_blank" href="https://msdnshared.blob.core.windows.net/media/2018/05/spark12.png" rel="noopener"><img src="https://msdnshared.blob.core.windows.net/media/2018/05/spark12.png" alt="" width="1337" height="96" class="size-full wp-image-7125 aligncenter" /></a></p>

<h4 style="padding-left: 30px">Using SQL Spark connector</h4>
<p style="padding-left: 30px">For the bulk load into clustered columnstore table, we adjusted the batch size to 1048576 rows, which is the maximum number of rows per rowgroup, to maximize compression benefits. Having batch size &gt; 102400 rows enables the data to go into a compressed rowgroup directly, bypassing the delta store. Also, you have to set TABLOCK hint to false, else you will be serializing the parallel streams.</p>


[code lang="scala"]
import com.microsoft.azure.sqldb.spark.config._
import com.microsoft.azure.sqldb.spark.connect._
import com.microsoft.azure.sqldb.spark.query._
import com.microsoft.azure.sqldb.spark._
import com.microsoft.azure.sqldb.spark.bulkcopy._
val bulkCopyConfig = Config(Map(
  &quot;url&quot;               -&gt; &quot;server.westus.cloudapp.azure.com&quot;,
  &quot;databaseName&quot;      -&gt; &quot;testdb&quot;,
  &quot;user&quot;              -&gt; &quot;denzilr&quot;,
  &quot;password&quot;          -&gt; &quot;MyComplexPassword1!&quot;,
  &quot;dbTable&quot;           -&gt; &quot;dbo.orders&quot;,
  &quot;bulkCopyBatchSize&quot; -&gt; &quot;1048576&quot;, 
  &quot;bulkCopyTableLock&quot; -&gt; &quot;false&quot;,
  &quot;bulkCopyTimeout&quot;   -&gt; &quot;600&quot;
))
dfOrders.bulkCopyToSqlDB(bulkCopyConfig)
[/code]

<p style="padding-left: 30px">When bulk loading in parallel into a columnstore table, there are a few considerations:</p>

<ul>
 	<li>Memory grants and RESOURCE_SEMAPHORE waits. Depending on how many parallel streams, you could run into this issue, and it could end up bulk inserting into delta row groups. For more information, see this <a target="_blank" href="https://blogs.msdn.microsoft.com/sqlcat/2017/06/02/performance-impact-of-memory-grants-on-data-loads-into-columnstore-tables/" rel="noopener">blog</a>.</li>
 	<li>Compressed rowgroups could be trimmed due to memory pressure. You would see the trim_reason_description column in the sys.dm_db_column_store_row_group_physical_stats DMV as <em>“MEMORY_LIMITATION”.</em></li>
</ul>
<p style="padding-left: 30px">In this case, you see that rows land in the compressed rowgroup directly. We had 32 parallel streams going against a DS16sV3 VM (64GB Ram). There are cases where having too many parallel streams, as a result of memory requirements for a bulk insert can cause rowgroups to get trimmed due to memory limitations.</p>
<p style="padding-left: 30px"><a target="_blank" href="https://msdnshared.blob.core.windows.net/media/2018/05/spark8.png" rel="noopener"><img src="https://msdnshared.blob.core.windows.net/media/2018/05/spark8.png" alt="" width="871" height="129" class="size-full wp-image-7085 aligncenter" /></a></p>

<h2><span style="color: #3366ff">Scenario 2: Loading data into Azure SQL Database</span></h2>
SQL version: Azure SQL Database
Database performance level: P11

There are a couple of differences that need to be noted, which make the Azure SQL Database tests fundamentally different than the tests with SQL Server on a VM.
<ul>
 	<li>Database recovery model is Full, vs. Simple recovery model that was used with SQL Server in VM. This prevents the use of minimal logging in Azure SQL Database.</li>
 	<li>P11 is a Premium database, therefore it is in an availability group used to provide built-in HA within the service. This adds an overhead of committing every transaction on multiple replicas.</li>
</ul>
<a target="_blank" href="https://msdnshared.blob.core.windows.net/media/2018/05/spark9.png" rel="noopener"><img src="https://msdnshared.blob.core.windows.net/media/2018/05/spark9.png" alt="" width="933" height="428" class="size-full wp-image-7095 aligncenter" /></a>

When loading into Azure SQL Database, depending on the performance level of the database, you may see other wait types such as LOG_RATE_GOVERNOR, which would be an indicator of a bottleneck. There are multiple ways to monitor resource utilization in Azure SQL Database to detect resource bottleneck, e.g. the <a target="_blank" href="https://docs.microsoft.com/en-us/sql/relational-databases/system-dynamic-management-views/sys-dm-db-resource-stats-azure-sql-database?view=azuresqldb-current" rel="noopener">sys.dm_db_resource_stats</a> DMV. If a resource bottleneck exists, the database can be easily scaled up to a higher performance level to achieve higher throughput during data loads. More on Azure SQL Database monitoring can be found <a target="_blank" href="https://docs.microsoft.com/en-us/azure/sql-database/sql-database-single-database-monitor" rel="noopener">here</a>.

<a target="_blank" href="https://msdnshared.blob.core.windows.net/media/2018/05/spark10.png" rel="noopener"><img src="https://msdnshared.blob.core.windows.net/media/2018/05/spark10.png" alt="" width="1556" height="412" class="alignleft size-full wp-image-7105" /></a>

Recapping a few considerations relevant to data loading from Spark into SQL Server or Azure SQL Database:
<ul>
 	<li>Use the Spark SQL connector. We have just shown that in the bulk insert scenario, we get fundamentally better performance, by an order of magnitude, than with the Spark JDBC connector.</li>
 	<li>For tables that are heaps, use the TABLOCK hint to allow parallel streams. This is particularly relevant for staging tables, which tend to be heaps.</li>
 	<li>For bulk loads into columnstore tables, do not use the TABLOCK hint, as that would serialize parallel streams.</li>
 	<li>For bulk loads into columnstore tables, ensure that batch size is &gt;= 102400 so that row go directly into a compressed rowgroup. Ideally start with the batch size of 1048576</li>
 	<li>For partitioned tables, see the section on partitioned tables in <em>the </em><a target="_blank" href="https://blogs.msdn.microsoft.com/sqlcat/2015/03/11/data-loading-performance-considerations-with-clustered-columnstore-indexes/" rel="noopener"><em>Data Loading performance considerations with Clustered Columnstore indexes</em></a> Depending on the number of rows per partition, they could land in the delta store, which would affect bulk insert performance.</li>
</ul>]]></content:encoded>
    </item>
    <item>
      <title>CPU and Memory Allocation on Azure SQL Database Managed Instance</title>
      <link>https://docs.microsoft.com/archive/blogs/sqlcat/cpu-and-memory-allocation-on-azure-sql-database-managed-instance</link>
      <pubDate>Fri, 04 May 2018 15:44:55 GMT</pubDate>
      <dc:creator><![CDATA[Dimitri Furman]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.msdn.microsoft.com/sqlcat/?p=6995</guid>
      <description><![CDATA[Reviewed By: Ajay Kalhan, Borko Novakovic, Drazen Sumic, Branislav Uzelac

In the current Azure...]]></description>
      <content:encoded><![CDATA[<i>Reviewed By: Ajay Kalhan, Borko Novakovic, Drazen Sumic, Branislav Uzelac</i>

In the current Azure SQL Database Managed Instance (MI) preview, when customers create a new instance, they can allocate a certain number of CPU vCores and a certain amount of disk storage space for the instance. However, there is no explicit configuration option for the amount of memory allocated to the instance, because on MI, memory allocation is proportional to the number of vCores used.

How can a customer determine the actual amount of memory their MI instance can use, in GB? The answer is less obvious than it may seem. Using the traditional SQL Server methods will not provide the right answer on MI. In this article, we will go over the technical details of CPU and memory allocation on MI, and describe the correct way to answer this question.

The information and behavior described in this article are as of the time of writing (April 2018). Some aspects of MI behavior, including the visibility of certain compute resource allocations, <u>may be temporary and will likely change</u> as MI progresses from the current preview to general availability and beyond. Nevertheless, customers using MI in preview will find that this article answers some of the common questions about MI resource allocation.
<h3>First glance at CPU and memory on MI</h3>
We will use a MI instance with 8 vCores as an example. On the traditional SQL Server, most customers would look at the Server Properties dialog in SSMS to see the compute resources available to the instance. On our example MI instance, this is what we see:

<a href="https://msdnshared.blob.core.windows.net/media/2018/05/clip_image0026.jpg"><img width="1251" height="595" title="clip_image002" alt="clip_image002" src="https://msdnshared.blob.core.windows.net/media/2018/05/clip_image002_thumb5.jpg" border="0" /></a>

We should right away note that the resource numbers in this dialog, as well as in several other sources (DMVs) described later, can change over the lifetime of a given MI instance. These changes can be relatively frequent. Customers should not take any dependencies, or make any conclusions based on these numbers. Later in the article, we will describe the correct way to determine actual compute resource allocation on MI.

An immediate question is why we see 24 processors here, when we have created this instance with only 8 vCores/processors. To determine the actual number of logical processors available to this instance, we can look at the number of VISIBLE ONLINE schedulers in the <a href="https://docs.microsoft.com/en-us/sql/relational-databases/system-dynamic-management-views/sys-dm-os-schedulers-transact-sql">sys.dm_os_schedulers</a> DMV:

[code language="sql"]
SELECT COUNT(1) AS SchedulerCount
FROM sys.dm_os_schedulers
WHERE status = 'VISIBLE ONLINE';
[/code]


[code language="text"]
SchedulerCount
--------------
8
[/code]

This is in line with the number of vCores we have for this MI instance. Then why does SSMS show that 24 processors are available?
<h3>CPU and memory resources are managed differently on MI</h3>
To answer this question, we need to take a high-level look at the MI architecture. Each MI instance runs in a virtual machine (VM). Each VM may host multiple MI instances of varying sizes, in terms of compute resources allocated to the instance.

It is important to note here that all MI instances on a given VM always belong to the same customer; there is no multi-tenancy at the VM level. In effect, the VM hosting MI instances serves as an additional isolation boundary for customer workloads. This does not mean that if a customer creates multiple MI instances, they will necessarily be packed on the same VM. In reality, this does not happen very often. The service intelligently allocates instances to VMs to always provide guaranteed SLAs and ensure good customer experience.

What SSMS shows in the Server Properties dialog is the number of processors and the amount of memory at the OS level on the VM that happens to currently host the instance. This works the same way for the traditional SQL Server, where SSMS also shows OS level numbers. SQL Server error log, which is accessible on MI, shows the same information during MI instance startup:

[code language="text"]
SQL Server detected 2 sockets with 12 cores per socket and 12 logical processors per socket, 24 total logical processors; using 24 logical processors based on SQL Server licensing. This is an informational message; no user action is required.

Detected 172031 MB of RAM. This is an informational message; no user action is required.
[/code]

For our example MI instance, this means that there are 24 processors accessible to the OS on the underlying VM. However, given the number of visible online schedulers, the MI instance can only use 8 of these processors, as expected given its current provisioned size.

To reiterate, the number of processors and the amount of memory at the VM level (24 processors and 172031 MB in this example) is not fixed. It can change over time as the MI instance moves across VMs allocated to the customer, for example when it is scaled up or scaled down. These values will, however, always be larger than or equal to the resource values actually allocated to the instance.

But what about memory? Does this MI instance have 168 GB of memory, as shown in SSMS and in the error log? Let’s look at some DMVs.

[code language="sql"]
SELECT cpu_count,
       physical_memory_kb,
       committed_target_kb
FROM sys.dm_os_sys_info;
[/code]

[code language="text"]
cpu_count   physical_memory_kb   committed_target_kb
----------- -------------------- --------------------
8           176,160,308          48,586,752
[/code]

[code language="sql"]
SELECT cntr_value
FROM sys.dm_os_performance_counters
WHERE object_name LIKE '%Memory Manager%'
      AND
      counter_name = 'Target Server Memory (KB)';
[/code]

[code language="text"]
cntr_value
----------
48,586,752
[/code]

Both of these show that the server target memory, which is commonly used to measure the amount of memory available to the instance, is about 46 GB, while the total physical memory at the OS level is 168 GB, as seen in SSMS and in the error log. This shows that not all memory available at the VM OS level is allocated to this MI instance.

On the traditional SQL Server, the usual reason for the target memory to be much lower than the available OS physical memory is configuring a limit on server maximum memory using <a href="https://docs.microsoft.com/en-us/sql/relational-databases/system-stored-procedures/sp-configure-transact-sql">sp_configure</a>. Is that the case here?

[code language="sql"]
SELECT value_in_use
FROM sys.configurations
WHERE name = 'max server memory (MB)';
[/code]

[code language="text"]
value_in_use
------------
2147483647
[/code]

This large value shows that the maximum server memory for this instance is not limited, and the instance should be able to allocate all available physical memory. What is causing target memory to be much less than the total physical memory for this instance? Is there some other mechanism that can impose a lower limit?

For MI, this mechanism is <a href="https://msdn.microsoft.com/en-us/library/windows/desktop/ms684161.aspx">Job Objects</a>. Running a process such as SQL Server in a job object provides resource governance for the process at the OS level, including CPU, memory, and IO. This resource governance is what allows the service to share the same VM among multiple instances belonging to the same customer, without resource contention and “noisy neighbor” problems. At the same time, this mechanism guarantees a dedicated allocation of vCores and memory for each instance. Memory is allocated according to the GB/vCore ratio for the instance size selected by the customer. There is no overcommit of either vCores or memory across instances on the same VM. In other words, the instance always gets the resources specified during provisioning.

Can we see the configuration of the job object that contains our MI instance? Yes, we can:

[code language="sql"]
SELECT cpu_rate,
       cpu_affinity_mask,
       process_memory_limit_mb,
       non_sos_mem_gap_mb
FROM sys.dm_os_job_object;
[/code]

[code language="text"]
cpu_rate    cpu_affinity_mask    process_memory_limit_mb non_sos_mem_gap_mb
----------- -------------------- ----------------------- --------------------
100         255                  57,176                  9,728
[/code]

The <a href="https://docs.microsoft.com/en-us/sql/relational-databases/system-dynamic-management-views/sys-dm-os-job-object-transact-sql?view=azuresqldb-current">sys.dm_os_job_object</a> DMV exposes the configuration of the job object that hosts the MI instance. For our current topic, there are four columns in the DMV that are particularly relevant:

<i>cpu_rate</i>: this is set to 100%, showing that each vCore can be utilized by the MI instance to its full capacity.

<i>cpu_affinity_mask</i>: this is set to 11111111 (in binary), showing that only eight OS level processors can be used by the process hosted in the job object (SQL Server). This is in line with the number of vCores provisioned for this instance.

<i>process_memory_limit_mb</i>: this is set to about 56 GB, and is the total amount of memory allocated to the process within the job object. Note that this is larger than the server target memory, which, as we saw earlier, is about 46 GB. The next column provides the explanation.

<i>non_sos_mem_gap_mb</i>: this is the amount of memory that is a part of total process memory, but is not available for SQL Server SOS (SQL OS) memory allocations, i.e. is reserved for things like thread stacks and DLLs loaded into the SQL Server process space. The difference between <i>process_memory_limit_mb</i> and <i>non_sos_mem_gap_mb</i> is 46 GB, which is exactly the server target memory that we saw earlier.

To elaborate on the last point, even though SQL Server target memory visible in DMVs and in the output of DBCC MEMORYSTATUS is less than the total memory allocated to the instance, this difference, known as the non-SOS memory gap, is still being used by the instance. In fact, a sufficiently large allocation of non-SOS memory is required for the instance to function reliably.
<h3>Conclusion</h3>
To summarize the technical details above:

1. MI compute resource values shown in SSMS and the instance error log reflect resources at the underlying OS level, not the actual resources available to the MI instance. The resource values at the OS level can change over time, without affecting the resources allocated to the MI instance in any way.

2. MI instance is resource-governed at the OS level using a job object.

3. The <i>sys.dm_os_job_object</i> DMV exposes job object configuration for the MI instance. This is the DMV that should be used to determine the actual compute resources (vCores and memory) allocated to the MI instance, as described above.

We hope that the information in this article will help customers using Managed Instance to accurately and confidently determine the amount of compute resources allocated to their instances, and avoid potential confusion in this area due to architectural differences between the traditional SQL Server and Managed Instance.]]></content:encoded>
    </item>
    <item>
      <title>Azure SQL DB Managed Instance - sp_readmierrorlog</title>
      <link>https://docs.microsoft.com/archive/blogs/sqlcat/azure-sql-db-managed-instance-sp_readmierrorlog</link>
      <pubDate>Fri, 04 May 2018 15:18:34 GMT</pubDate>
      <dc:creator><![CDATA[Dimitri Furman]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.msdn.microsoft.com/sqlcat/?p=6965</guid>
      <description><![CDATA[Reviewed by: Kun Cheng, Borko Novakovic, Arvind Shyamsundar, Mike Weiner

Azure SQL Database...]]></description>
      <content:encoded><![CDATA[<i>Reviewed by: Kun Cheng, Borko Novakovic, Arvind Shyamsundar, Mike Weiner</i>

Azure SQL Database Managed Instance is a new offering that provides an instance-based SQL PaaS service in Azure. If you are not yet familiar with this new Azure SQL Database capability, you can start with the <a href="https://docs.microsoft.com/en-us/azure/sql-database/sql-database-managed-instance-custom-dns">What is Managed Instance</a> documentation topic. Since early private preview of Managed Instance (MI), SQLCAT has been working with early adopter customers to help them evaluate MI as a new platform for their applications, gather their feedback, and improve the offering for everyone. Azure SQL Database Managed Instance is now available in public preview.
<h3>SQL error log is available on MI</h3>
The primary goal of MI is close compatibility with the traditional SQL Server, to help facilitate migrations from on-premises environments to the Azure SQL Database PaaS service. In this article, we will discuss one MI capability that it shares with SQL Server, namely the ability to see the instance error log. For SQL Server DBAs, the error log is one of the first things to check when troubleshooting application issues, and that is still the case when using a managed service such as MI. The availability of the instance error log highlights the MI focus on compatibility with SQL Server; by comparison, in Azure SQL Database, diagnostics and error information are exposed in different ways, i.e. using the <a href="https://docs.microsoft.com/en-us/sql/relational-databases/system-catalog-views/sys-event-log-azure-sql-database">sys.event_log</a> DMV and <a href="https://docs.microsoft.com/en-us/azure/sql-database/sql-database-metrics-diag-logging#errors-dataset">diagnostics logging</a>.

When you connect to an MI instance, you can right away look at its error log, using either Log File Viewer in SSMS (<a href="https://docs.microsoft.com/en-us/sql/ssms/download-sql-server-management-studio-ssms">latest SSMS</a> is strongly recommended), or the <i>sp_readerrorlog</i> stored procedure. If you do that on an instance of SQL Server that has just started, you will typically see 100-200 lines of output, depending on server configuration, the number of databases, etc. But when you look at the MI error log, you may be surprised by the large volume of messages. For example, in the first minute after instance startup, more than 2500 messages are logged. While some of them are the familiar messages you would find in a SQL Server error log, many others might look a bit cryptic, and are not actionable from an end-user standpoint.

Why is all this information in the MI error log? This diagnostic data is needed for Microsoft engineers to manage the service and troubleshoot any problems efficiently. As an aside, the MI error log is so detailed, that a curious user with relatively advanced knowledge of SQL Server internals can glean many interesting details about internal workings of MI, even without full knowledge of the MI architecture.

An important note is that in current MI preview, error logs are not persisted across instance restarts and failovers. Therefore, when looking at older logs, gaps are possible.
<h3>A new way to look at the MI error log</h3>
But for most customers, the high volume of debug-level messages in the log just makes it harder to see the messages relevant to their applications/databases, which are lost in the noise, as it were. This is the reason why we wrote <a href="https://github.com/dimitri-furman/managed-instance/tree/master/sp_readmierrorlog">sp_read<b><strong>mi</strong></b>errorlog</a>. This is a simple stored procedure that returns the contents of the instance error log, filtering out messages that are unlikely to be useful to an MI customer.

<i>sp_readmierrorlog</i> has the same familiar parameters and result set that <i>sp_readerrorlog</i> has. It can be created in the master database of each MI instance you use, making it easy to call the procedure from the context of any database on the instance. As an example, on a mostly idle MI instance, <i>sp_readmierrorlog</i> reduced a 150,000-line error log, generated over one day, to 760 lines. This is much more manageable, while still providing useful diagnostic information to MI customers. For customers requiring a more in-depth look into instance behavior and diagnostics, access to the unfiltered log is still available using the built-in <i>sp_readerrorlog</i> procedure.

The procedure is open source and is <a href="https://github.com/dimitri-furman/managed-instance/tree/master/sp_readmierrorlog">hosted on GitHub</a>. The filtering logic used by the procedure can be easily customized to include/exclude specific messages from the result set. If you find that the procedure is filtering out too much or too little, we would welcome pull requests with changes that make the stored procedure better for all MI customers.]]></content:encoded>
    </item>
    <item>
      <title>SQL Server VLDB in Azure: DBA Tasks Made Simple</title>
      <link>https://docs.microsoft.com/archive/blogs/sqlcat/sql-server-vldb-in-azure-dba-tasks-made-simple</link>
      <pubDate>Mon, 22 Jan 2018 14:29:20 GMT</pubDate>
      <dc:creator><![CDATA[Dimitri Furman]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.msdn.microsoft.com/sqlcat/?p=6955</guid>
      <description><![CDATA[Reviewed by: Rajesh Setlem, Mike Weiner, Xiaochen Wu

With thanks to Joey D’Antoni (blog) for...]]></description>
      <content:encoded><![CDATA[<i>Reviewed by: Rajesh Setlem, Mike Weiner, Xiaochen Wu</i>

<i>With thanks to Joey D’Antoni (</i><a href="https://www.dcac.co/author/joeydcac-co"><i>blog</i></a><i>) for asking a great question that prompted this article.</i>

As any experienced DBA knows, supporting a very large database (VLDB) tends to be exponentially more complex than supporting smaller databases. The definition of VLDB has been shifting in recent years, but most DBAs would agree that complexities become significant starting with database size in the 3-5 TB range. A few obvious examples of tasks that are more challenging with a VLDB are backup/restore, consistency checking, HA/DR, partitioning, etc.

While these challenges are inherently present in any VLDB, the platform used for hosting the database can sometimes make them easier. In this article, we will show how using Azure as the cloud platform for SQL Server can simplify some of these common challenges.

To be clear from the start, we will be talking about databases in the 3-30 TB range running on Azure IaaS VMs. At those sizes, the traditional database operation methods tend to be inefficient, or sometimes break down completely. For smaller databases, the approaches described in this article may be unnecessary (though they would still work).
<h3>A VLDB database</h3>
In the examples in this article, we will be using a 12 TB database, appropriately named VLDB01. The database was created on a SQL Server VM in Azure by loading data from text files in Azure Blob Storage, using <a href="https://docs.microsoft.com/en-us/sql/relational-databases/polybase/polybase-guide">PolyBase</a>. The database was created with files stored <a href="https://docs.microsoft.com/en-us/sql/relational-databases/databases/sql-server-data-files-in-microsoft-azure">directly in Azure blob storage</a>, using a Premium Storage account.

The ability to store database files directly in blob storage, rather than on disk, is what enables the scenarios described in this article. Starting with SQL Server 2014, in addition to using files stored on a local disk or on a UNC path, SQL Server also supports placing database files as page blobs in Azure Blob Storage, and accessing them over an http(s) endpoint. This lets SQL Server use certain features of Azure Blob Storage that are not necessarily available in the more traditional storage subsystems. One example of this is described in <a href="https://blogs.msdn.microsoft.com/sqlcat/2017/09/11/instant-log-initialization-for-sql-server-in-azure/">Instant Log Initialization for SQL Server in Azure</a>. This article will cover additional scenarios, with emphasis on simplifying operational tasks for a VLDB.

Database file layout for the VLDB01 database is described by the output from the following query:

[code language="sql"]
SELECT name,
       physical_name,
       SUM(CAST(FILEPROPERTY(name, 'SpaceUsed') AS bigint) * 8192.0/1024/1024/1024/1024) AS space_used_TB,
       SUM(CAST((size/128.0) AS decimal(15,2))/1024/1024) AS file_size_TB
FROM sys.database_files
GROUP BY GROUPING SETS ((name, physical_name),());
[/code]

<table border="1" cellspacing="0" cellpadding="0">
<tbody>
<tr>
<td width="87" valign="bottom"><b>name</b></td>
<td width="515" valign="bottom"><b>physical_name</b></td>
<td width="99" valign="bottom"><b>space_used_TB</b></td>
<td width="88" valign="bottom"><b>file_size_TB</b></td>
</tr>
<tr>
<td width="87" valign="bottom">VLDB01</td>
<td width="515" valign="bottom">https://premiumstorageaccount.blob.core.windows.net/mssqldata/VLDB01.mdf</td>
<td width="99" valign="bottom">0.0000068</td>
<td width="88" valign="bottom">0.0000687</td>
</tr>
<tr>
<td width="87" valign="bottom">VLDB01_File1</td>
<td width="515" valign="bottom">https://premiumstorageaccount.blob.core.windows.net/mssqldata/VLDB01_File1.ndf</td>
<td width="99" valign="bottom">1.5332245</td>
<td width="88" valign="bottom">2.0000000</td>
</tr>
<tr>
<td width="87" valign="bottom">VLDB01_File2</td>
<td width="515" valign="bottom">https://premiumstorageaccount.blob.core.windows.net/mssqldata/VLDB01_File2.ndf</td>
<td width="99" valign="bottom">1.5351921</td>
<td width="88" valign="bottom">2.0000000</td>
</tr>
<tr>
<td width="87" valign="bottom">VLDB01_File3</td>
<td width="515" valign="bottom">https://premiumstorageaccount.blob.core.windows.net/mssqldata/VLDB01_File3.ndf</td>
<td width="99" valign="bottom">1.5322309</td>
<td width="88" valign="bottom">2.0000000</td>
</tr>
<tr>
<td width="87" valign="bottom">VLDB01_File4</td>
<td width="515" valign="bottom">https://premiumstorageaccount.blob.core.windows.net/mssqldata/VLDB01_File4.ndf</td>
<td width="99" valign="bottom">1.5393468</td>
<td width="88" valign="bottom">2.0000000</td>
</tr>
<tr>
<td width="87" valign="bottom">VLDB01_File5</td>
<td width="515" valign="bottom">https://premiumstorageaccount.blob.core.windows.net/mssqldata/VLDB01_File5.ndf</td>
<td width="99" valign="bottom">1.5361704</td>
<td width="88" valign="bottom">2.0000000</td>
</tr>
<tr>
<td width="87" valign="bottom">VLDB01_File6</td>
<td width="515" valign="bottom">https://premiumstorageaccount.blob.core.windows.net/mssqldata/VLDB01_File6.ndf</td>
<td width="99" valign="bottom">1.5354084</td>
<td width="88" valign="bottom">2.0000000</td>
</tr>
<tr>
<td width="87" valign="bottom">VLDB01_File7</td>
<td width="515" valign="bottom">https://premiumstorageaccount.blob.core.windows.net/mssqldata/VLDB01_File7.ndf</td>
<td width="99" valign="bottom">1.5347077</td>
<td width="88" valign="bottom">2.0000000</td>
</tr>
<tr>
<td width="87" valign="bottom">VLDB01_File8</td>
<td width="515" valign="bottom">https://premiumstorageaccount.blob.core.windows.net/mssqldata/VLDB01_File8.ndf</td>
<td width="99" valign="bottom">1.5317377</td>
<td width="88" valign="bottom">2.0000000</td>
</tr>
<tr>
<td width="87" valign="bottom">VLDB01_log</td>
<td width="515" valign="bottom">https://premiumstorageaccount.blob.core.windows.net/mssqldata/VLDB01_log.ldf</td>
<td width="99" valign="bottom">0.0281003</td>
<td width="88" valign="bottom">0.1874998</td>
</tr>
<tr>
<td width="87" valign="bottom"></td>
<td width="515" valign="bottom"></td>
<td width="99" valign="bottom">12.3061257</td>
<td width="88" valign="bottom">16.1875685</td>
</tr>
</tbody>
</table>
Each data file, other than the PRIMARY filegroup data file and the log file, uses a 2 TB Premium Storage page blob, the equivalent of a P40 disk. This provides maximum per-blob storage throughput and IOPS available in Azure Premium Storage today. The number of data files, eight for this particular database, was chosen to allow the database to grow by another ~4 TB without any changes in the storage layout. Even if the data could fit into fewer data files, using more files lets us increase the overall storage throughput and IOPS for the database (subject to <a href="https://docs.microsoft.com/en-us/azure/virtual-machines/windows/sizes">VM level limits</a>; more on this in the Limitations section below).
<h3>Backup and restore</h3>
Let’s see how long it would take to back up this 12 TB database using the traditional streaming backup to URL. Since each SQL Server backup blob cannot exceed ~195 GB (see <a href="https://blogs.msdn.microsoft.com/sqlcat/2017/03/10/backing-up-a-vldb-to-azure-blob-storage/">Backing up a VLDB to Azure Blob Storage</a> for details), we have to stripe the backup over multiple blobs. In this case, we are using the maximum allowed number of stripes, 64, to ensure we do not hit the 195 GB per-stripe limit. That said, for a 12 TB database, we could use fewer stripes, assuming reasonably high backup compression ratio. Here is the abbreviated BACKUP DATABASE command (omitting most stripes), with parameters optimized for backup to URL as described in <a href="https://blogs.msdn.microsoft.com/sqlcat/2017/03/10/backing-up-a-vldb-to-azure-blob-storage/">Backing up a VLDB to Azure Blob Storage</a>:

[code language="sql"]
BACKUP DATABASE VLDB01 TO
URL = 'https://storageaccount.blob.core.windows.net/backup/vldb01_stripe1.bak',
...
URL = 'https://storageaccount.blob.core.windows.net/backup/vldb01_stripe64.bak'
WITH COMPRESSION, CHECKSUM, MAXTRANSFERSIZE = 4194304, BLOCKSIZE = 65536, STATS = 1;
[/code]

On a GS5 VM (32 cores, 448 GB RAM, 20000 Mbps network bandwidth), this took nearly 17 hours:

[code language="text"]
...
99 percent processed.
Processed 848 pages for database 'VLDB01', file 'VLDB01' on file 1.
Processed 206278432 pages for database 'VLDB01', file 'VLDB01_File2' on file 1.
Processed 206014904 pages for database 'VLDB01', file 'VLDB01_File1' on file 1.
Processed 205881752 pages for database 'VLDB01', file 'VLDB01_File3' on file 1.
Processed 206835160 pages for database 'VLDB01', file 'VLDB01_File4' on file 1.
Processed 206409496 pages for database 'VLDB01', file 'VLDB01_File5' on file 1.
Processed 206307512 pages for database 'VLDB01', file 'VLDB01_File6' on file 1.
Processed 206213568 pages for database 'VLDB01', file 'VLDB01_File7' on file 1.
Processed 205815696 pages for database 'VLDB01', file 'VLDB01_File8' on file 1.
100 percent processed.
Processed 2 pages for database 'VLDB01', file 'VLDB01_log' on file 1.
BACKUP DATABASE successfully processed 1649757370 pages in 60288.832 seconds (213.783 MB/sec).
[/code]

While for some applications this might be acceptable (after all, this is a potentially infrequent full backup; more frequent differential and transaction log backups would take less time), a restore of this database would take at least as long as the full backup took, i.e. close to a day. There are few applications that can accept a downtime that long.

Let’s see what would happen if instead of a streaming backup, we use a <a href="https://docs.microsoft.com/en-us/sql/relational-databases/backup-restore/file-snapshot-backups-for-database-files-in-azure">file-snapshot</a> backup, available if database files are stored directly in Azure Blob Storage.

[code language="sql"]
BACKUP DATABASE VLDB01
TO URL = 'https://standardstorageaccount.blob.core.windows.net/backup/VLDB01_snapshot1.bak'
WITH FILE_SNAPSHOT;
[/code]


[code language="text"]
Processed 0 pages for database 'VLDB01', file 'VLDB01' on file 1.
Processed 0 pages for database 'VLDB01', file 'VLDB01_File1' on file 1.
Processed 0 pages for database 'VLDB01', file 'VLDB01_File2' on file 1.
Processed 0 pages for database 'VLDB01', file 'VLDB01_File3' on file 1.
Processed 0 pages for database 'VLDB01', file 'VLDB01_File4' on file 1.
Processed 0 pages for database 'VLDB01', file 'VLDB01_File5' on file 1.
Processed 0 pages for database 'VLDB01', file 'VLDB01_File6' on file 1.
Processed 0 pages for database 'VLDB01', file 'VLDB01_File7' on file 1.
Processed 0 pages for database 'VLDB01', file 'VLDB01_File8' on file 1.
Processed 0 pages for database 'VLDB01', file 'VLDB01_log' on file 1.
BACKUP DATABASE successfully processed 0 pages in 0.151 seconds (0.000 MB/sec).
[/code]

This backup completed in 16 seconds! This is possible because no data movement has actually occurred, unlike in the streaming backup case. Instead, a storage snapshot was created for each database file, with SQL Server ensuring backup consistency for the entire database by briefly freezing write IO on the database. The backup file referenced in the BACKUP DATABASE statement above (<i>VLDB01_snapshot1.bak</i>) stores pointers to these snapshots, and other backup metadata.

We should note that the database was idle during this backup. When taking the same backup on the same GS5 VM running queries with 60% CPU utilization, and reading ~1 GB/s from database files in blob storage, the backup took 1 minute 29 seconds. The backup took 1 minute 55 seconds when a bulk load into a table was running. But even though file-snapshot backups get slower on a busy instance, they are still orders of magnitude faster than streaming backups.

Let’s try restoring this backup on another SQL Server instance on a different VM:

[code language="sql"]
RESTORE DATABASE VLDB01 FROM URL = 'https://standardstorageaccount.blob.core.windows.net/backup/VLDB01_snapshot1.bak'
WITH
MOVE 'VLDB01' TO 'https://premiumstorageaccount.blob.core.windows.net/mssqldata/VLDB01.mdf',
MOVE 'VLDB01_File1' TO 'https://premiumstorageaccount.blob.core.windows.net/mssqldata/VLDB01_File1.ndf',
MOVE 'VLDB01_File2' TO 'https://premiumstorageaccount.blob.core.windows.net/mssqldata/VLDB01_File2.ndf',
MOVE 'VLDB01_File3' TO 'https://premiumstorageaccount.blob.core.windows.net/mssqldata/VLDB01_File3.ndf',
MOVE 'VLDB01_File4' TO 'https://premiumstorageaccount.blob.core.windows.net/mssqldata/VLDB01_File4.ndf',
MOVE 'VLDB01_File5' TO 'https://premiumstorageaccount.blob.core.windows.net/mssqldata/VLDB01_File5.ndf',
MOVE 'VLDB01_File6' TO 'https://premiumstorageaccount.blob.core.windows.net/mssqldata/VLDB01_File6.ndf',
MOVE 'VLDB01_File7' TO 'https://premiumstorageaccount.blob.core.windows.net/mssqldata/VLDB01_File7.ndf',
MOVE 'VLDB01_File8' TO 'https://premiumstorageaccount.blob.core.windows.net/mssqldata/VLDB01_File8.ndf',
MOVE 'VLDB01_log' TO 'https://premiumstorageaccount.blob.core.windows.net/mssqldata/VLDB01_log.ldf',
RECOVERY, REPLACE;
[/code]


[code language="text"]
Processed 0 pages for database 'VLDB01', file 'VLDB01' on file 1.
Processed 0 pages for database 'VLDB01', file 'VLDB01_File1' on file 1.
Processed 0 pages for database 'VLDB01', file 'VLDB01_File2' on file 1.
Processed 0 pages for database 'VLDB01', file 'VLDB01_File3' on file 1.
Processed 0 pages for database 'VLDB01', file 'VLDB01_File4' on file 1.
Processed 0 pages for database 'VLDB01', file 'VLDB01_File5' on file 1.
Processed 0 pages for database 'VLDB01', file 'VLDB01_File6' on file 1.
Processed 0 pages for database 'VLDB01', file 'VLDB01_File7' on file 1.
Processed 0 pages for database 'VLDB01', file 'VLDB01_File8' on file 1.
Processed 0 pages for database 'VLDB01', file 'VLDB01_log' on file 1.
RESTORE DATABASE successfully processed 0 pages in 1.530 seconds (0.000 MB/sec).
[/code]

This restore completed in 8 seconds on an idle instance. For restore, concurrent load does not affect timing as much as it does for backup. On a busy instance with the same CPU and storage utilization as above, restore completed in 10 seconds. During restore, no data movement is required if the same storage account is used for the restored database, even though a new set of database files (blobs) is created. Azure Blob Storage ensures that the initial content of these new blobs is identical to the content of the backup snapshots.

You might notice that the output from the backup and restore commands above looks a little unusual, with the page and throughput statistics reported as zeroes. This is because SQL Server does not have them. The backup and restore are actually performed at the Azure Blob Storage level, with SQL Server calling blob storage API to create storage snapshots of all database files as a part of backup, or create blobs from snapshots as a part of restore. You may also notice that the backup and restore times in the output are much shorter compared to the actual duration. This is because they are reported for a specific phase (data copy) of the overall backup/restore process, which for file-snapshot backup/restore is very quick, compared to the overall duration of the operation. See this <a href="https://blogs.msdn.microsoft.com/ialonso/2014/06/06/when-doing-a-database-restore-what-actions-is-the-engine-taking-after-it-gets-past-the-100/">blog</a> by Nacho Alonso Portillo for details.

These examples show that for a VLDB with files stored directly in Azure Blob Storage, backup/restore scenarios can be greatly simplified by using file-snapshot backups, in much the same way as SAN storage snapshots, if used correctly, simplify these scenarios for large on-premises databases.
<h3>Consistency checking</h3>
What about another task that is challenging with a VLDB, namely consistency checking (DBCC CHECKDB)? For a multi-TB database, the traditional on-premises solutions usually involve some combination of limited (PHYSICAL_ONLY) and/or partial (at the table/filegroup level) checks. This compromise is often required to complete the check within the available maintenance window, and without causing excessive resource contention between the check and the regular database workload. Sometimes, the check is done only on a replica (if using availability groups), or on a restored copy of the database. Neither is ideal: using an AG replica increases resource utilization on the replica, potentially affecting other workloads running on the replica and AG replication itself (not to mention that corruption could exist on some replicas but not on others). Checking a copy of the database restored on a separate instance requires a lengthy (hours, days) restore process, implying that corruption on the source database could potentially occur while this restore is in progress, and remain undetected until the next check.

How does this look in Azure? We have just shown that the entire backup/restore process for a 12 TB database with files in Azure blob storage completed in a couple of minutes, even on a busy instance. This provides an elegant solution for checking consistency of a VLDB in Azure: restore a file-snapshot backup on a separate VM, and run consistency check against the restored copy of the database. This VM can be stopped most of the time to save costs, and only started periodically to run consistency checks. The size of this VM can be different from the size of the VM hosting the database, to strike the right balance between the VM cost and the duration of the check, which is determined by available compute resources. Most importantly, the checks will be done on a “fresh” copy of the database, minimizing the likelihood of corruption happening on the source database while the copy is being created.
<h3>Reporting and analytics</h3>
If we take a step back, we will realize that with file-snapshot backups, we effectively have a way to create a copy of the database in near real-time, within minutes or even seconds. This opens a possibility to use such a copy (or multiple copies, in a scale-out fashion) for reporting and analytics, to offload the part of these workloads that can tolerate some data latency from the primary database. Other scenarios may be possible as well, e.g. quickly creating an “as-of” copy of the VLDB for research and audit purposes.
<h3>Limitations</h3>
As promising as all this may look, before everyone starts deploying VLDBs with files stored directly in blob storage, we must describe the limitations.

1. Each Premium Storage account is limited to 35 TB, and all files for a database must reside in the same storage account. Using Standard storage with its 500 TB per-account limit, while supported, is usually not recommended for performance reasons. For the restore from file-snapshot backup to be fast, the files of the restored database must reside in the same storage account as the files of the source database. Restoring to a different storage account is possible, but requires a size-of-data operation in blob storage, which for a VLDB could take hours or days. This implies that for practical purposes, there is a limited number of database copies that can be created using this approach. For example, for a 12 TB database used in the examples above, we can create at most one other copy in the same storage account via file-snapshot backup/restore, and for databases larger than ~17 TB, no additional copies may be created. Attempting to restore a file-snapshot backup when there is not enough space in the storage account results in an error:

[code language="text"]
Msg 5120, Level 16, State 152, Line 127
Unable to open the physical file &quot;https://premiumstorageaccount.blob.core.windows.net/mssqldata/VLDB01_File3.ndf&quot;. Operating system error 1450: &quot;1450(Insufficient system resources exist to complete the requested service.)&quot;.
Msg 3013, Level 16, State 1, Line 127
RESTORE DATABASE is terminating abnormally.
[/code]

The mitigating factor here is that the snapshots taken for file-snapshot backups are not counted towards the 35 TB limit. As mentioned in the Scalability and Performance section of Premium Storage <a href="https://docs.microsoft.com/en-us/azure/virtual-machines/windows/premium-storage">documentation</a>, the snapshot capacity of a Premium Storage account is 10 TB. The extent to which this <i>additional</i> space is used depends on the volume of changes in the database between each pair of consecutive file-snapshot backups. This is analogous to the way space is consumed by sparse files used in SQL Server <a href="https://docs.microsoft.com/en-us/sql/relational-databases/databases/database-snapshots-sql-server">database snapshots</a> (not to be confused with Azure Blob Storage snapshots used in file-snapshot backups).

2. Since we create database files directly in a storage account, we cannot take advantage of the <a href="https://docs.microsoft.com/en-us/azure/virtual-machines/windows/managed-disks-overview">Managed Disks</a> capabilities. In particular, for databases using availability groups, we cannot guarantee that the different storage accounts behind each availability replica are on different storage scale units. Using the same storage scale unit for more than one replica creates a single point of failure. While the likelihood of storage scale unit failure is not high, there are known cases when database availability had been negatively affected due to this, before Managed Disks became available to avoid this issue.

3. Because database files are accessed over http, disk IO becomes a part of network traffic for all intents and purposes. This means that unlike with Azure VM disks, there is no local SSD cache to potentially improve storage performance. Additionally, the same network bandwidth is shared between the VM network traffic, and the database storage traffic, which can affect network intensive applications. Further, the VM limits on disk throughput and IOPS that are published in Azure VM <a href="https://docs.microsoft.com/en-us/azure/virtual-machines/windows/sizes">documentation</a> do not apply to SQL Server database files stored directly in blob storage, though the network bandwidth limits are still applicable.

4. There are additional considerations and limitations to consider, documented in <a href="https://docs.microsoft.com/en-us/sql/relational-databases/backup-restore/file-snapshot-backups-for-database-files-in-azure">File-Snapshot Backups for Database Files in Azure</a>.

That said, even with these limitations, managing a SQL Server VLDB in Azure can be greatly simplified if files are stored directly in blob storage, allowing the use of techniques described in this article. We strongly recommend considering this option for your next project involving a SQL Server VLDB in Azure.]]></content:encoded>
    </item>
    <item>
      <title>Recent Updates to setting up SQL Server Availability Groups in Azure VM with AAD Domain Services</title>
      <link>https://docs.microsoft.com/archive/blogs/sqlcat/recent-updates-to-setting-up-sql-server-availability-groups-in-azure-vm-with-aad-domain-services</link>
      <pubDate>Mon, 30 Oct 2017 16:07:30 GMT</pubDate>
      <dc:creator><![CDATA[Rajesh Setlem]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.msdn.microsoft.com/sqlcat/?p=6936</guid>
      <description><![CDATA[Reviewed by: Dimitri Furman, Kun Cheng

This blog is an extension to the one that was published in...]]></description>
      <content:encoded><![CDATA[<em>Reviewed by: Dimitri Furman, Kun Cheng</em>

This blog is an extension to the <a href="https://blogs.msdn.microsoft.com/sqlcat/2017/02/23/sql-server-availability-groups-in-azure-vm-setup-with-aad-domain-services/">one</a> that was published in February 2017 . In these eight months there has been some notable improvements to AAD Domain Services (AAD DS) and Azure Virtual Network (VNET) capabilities. Now, it's even more easier and convenient to leverage AAD DS for setting up SQL Server Availability Groups (AG) in Azure VMs.

As a recap, following scenarios were covered in the blog linked above:

<strong><u>Scenario 1: </u></strong>Enabling AAD Domain Services in Classic Virtual Network, deploying two SQL Server 2016 Classic VMs (Windows Server 2012), and then setting up AG

<strong><u>Scenario 2: </u></strong>Leveraging AAD Domain Services enabled in Classic Virtual Network from an ARM virtual network by adding an ARM based SQL Server 2016 VM as a replica to the existing AG.

Summary of recent improvements to AAD DS and new VNET capability:
<ol>
 	<li>AAD DS is generally available in many Azure regions now. Check <a href="https://azure.microsoft.com/en-us/regions/services/">here</a> to find out availability by region (AAD DS is under Security + Identity).</li>
 	<li>General availability of AAD DS support for Azure Resource Manager (ARM) based VNET. This was eagerly expected by customers. You can read more about it <a href="https://azure.microsoft.com/en-us/updates/azure-ad-domain-services-support-for-resource-manager-virtual-networks-ga/">here</a>.</li>
 	<li>General availability of AAD DS in the new Azure Portal (portal.azure.com).</li>
 	<li>Public preview for Global VNET Peering. VNET peering is a powerful feature allowing you to peer virtual networks in Azure. Peering VNETs in the same region is generally available, and the ability to peer VNETs across different Azure regions is in preview. You can read more about it <a href="https://azure.microsoft.com/en-us/updates/global-vnet-peering-preview/">here</a>.</li>
</ol>
With these improvements, scenarios covered in previous blog can be done in a different way. A new scenario is enabled as well.

<strong><u>Scenario 1: </u></strong>This can be implemented in ARM only mode with no need to create any classic resources. Good news is it can be done in new Azure Portal (portal.azure.com).

<strong><u>Scenario 2: </u></strong>Since AAD DS can be enabled on ARM VNET now, you can peer it with another ARM VNET, or with a Classic VNET if there is a specific need.

<strong><u>Scenario 3: </u></strong>This is a new scenario possible with Global VNET Peering to connect an Azure region designated as your DR with your primary Azure region. Asynchronous AG replica in your Azure DR region would connect to primary replica over VNET Peering and provide failover capabilities in the event of disaster. We tried this setup and it works just as expected.

Go ahead and explore these new capabilities and scenarios and let us know if you have any questions or comments.

&nbsp;]]></content:encoded>
    </item>
    <item>
      <title>SQLCAT @PASS Summit 2017</title>
      <link>https://docs.microsoft.com/archive/blogs/sqlcat/sqlcat-pass-summit-2017</link>
      <pubDate>Mon, 30 Oct 2017 08:57:31 GMT</pubDate>
      <dc:creator><![CDATA[Kun Cheng (SQLCAT)]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.msdn.microsoft.com/sqlcat/?p=6946</guid>
      <description><![CDATA[Are you coming to the PASS Summit 2017 in Seattle? SQLCAT will be in full force at the PASS Summit...]]></description>
      <content:encoded><![CDATA[Are you coming to the <a href="https://www.pass.org/summit/2017/Home.aspx">PASS Summit 2017</a> in Seattle? SQLCAT will be in full force at the PASS Summit 2017, and we will also bring along our colleagues from the broader AzureCAT team, including the newly formed DataCAT team.
<h5><span style="color: #2e7480">SQLCAT Sessions</span></h5>
SQLCAT sessions are unique. We bring in real customer stories and present their deployments, architectures, challenges and lessons learned. This year at the PASS Summit, we will have 4 sessions – each one filled with rich learnings from real world customer deployments.

<a href="https://www.pass.org/summit/2017/Sessions/Details.aspx?sid=69503">SQL Server on Linux: DBA Focused Lessons Learned from Early Deployments </a>

<a href="https://www.pass.org/summit/2017/Sessions/Details.aspx?sid=69111">Building a Graph Database Application with SQL Server 2017 and Azure SQL Database</a>

<a href="https://www.pass.org/summit/2017/Sessions/Details.aspx?sid=69501">Real-world SQL Server R Services</a>

<a href="https://www.pass.org/summit/2017/Sessions/Details.aspx?sid=69502">How Microsoft Sales Evolved from a Monolithic On-Prem Solution to a Scaleout Solution in Azure</a>
<h5><span style="color: #2e7480">SQL Clinic</span></h5>
Have a technical question, a troubleshooting challenge, want to have an architecture discussion, want to find ways to move your data solutions to Azure, or want to find best ways to upgrade your SQL Server? SQL Clinic is the place you want to be at. SQL Clinic is the hub of technical experts from SQLCAT, Tiger team, SQL PG, CSS and others. Whether you want a makeover of your deployment or need an open-heart surgery, the experts at SQL Clinic will have the right advice for you. Find all your answers in one place!
<h5><span style="color: #2e7480">Bonus</span></h5>
AzureCAT is hiring for various opportunities across the team, including data experts in the areas of Oracle, Teradata, open source data solutions, big data technologies, data science and related areas. Come talk to a CAT!
<h5><span style="color: #2e7480">And More …</span></h5>
That’s not all. SQLCAT will be involved in many more events and customer conversations during the Summit. If you have a suggestion on how we can make your experience at the PASS Summit more effective and more productive, don’t hesitate to leave a note.

Thanks, and see you all at the PASS Summit 2017 in Seattle. You are coming, right?

&nbsp;]]></content:encoded>
    </item>
  </channel>
</rss>