This commit is contained in:
Carl Rabeler
2017-03-14 17:22:27 -07:00
6795 changed files with 897166 additions and 395 deletions
+14 -1
View File
@@ -2,6 +2,19 @@
Contains samples for managing Microsoft's SQL databases including SQL Server, Azure SQL Database, and Azure SQL Data Warehouse.
Samples are coming soon!
## Automatically Export your databases with Azure Automation
This includes samples for setting up Azure Automation and exporting your databases to azure blob storage.
## Collect and monitor resource usage data across multiple pools in a subscription
This Solution Quick Start provides a solution for collecting and monitoring Azure SQL Database resource usage across multiple pools in a subscription. When you have a large number of databases in a subscription, it is cumbersome to monitor each elastic pool separately. To solve this, you can combine SQL database PowerShell cmdlets and T-SQL queries to collect resource usage data from multiple pools and their databases for monitoring and analysis of resource usage.
[Manage Mulitiple Elastic Pools in SQL Database Using PowerShell and Power BI](https://github.com/Microsoft/sql-server-samples/tree/master/samples/manage/azure-sql-db-elastic-pools) in the GitHub SQL Server samples repository provides a set of powershell scripts and T-SQL queries along with documentation on what it does and how to use it.
## Get started using Elastic Pools in a SaaS scenario
This Solution Quick Start provides a solution for a Softwware-as-a-Solution (SaaS) scenario that leverages Elastic Pools to provide a cost-effective, scalable database back-end of a SaaS application. In this solution, you will walk-though the implementation of a web app that lets you visualize the load created on an Elastic Pool by a load generator using a custom dashboard that supplements the Azure Portal.
[saas-scenario-with-elastic-pools](https://github.com/Microsoft/sql-server-samples/tree/master/samples/manage/azure-sql-db-elastic-pools-custom-dashboard) in the GitHub SQL Server samples repository provides a load generator and monitoring web app along with the documentation on what it does and how to use it.
## Windows Containers
This includes samples for setting up mssql-server in Windows Containers. Currently it includes the following:
- __[windows-containers] (windows-containers/)__
@@ -0,0 +1,289 @@
# The Array that will hold the list of databases objects.
$dbs = New-Object System.Collections.ArrayList;
# The Enums that describe what state a database is in.
Add-Type -TypeDefinition @"
public enum DatabaseState
{
ToCopy,
Copying,
ToExport,
Exporting,
ToDrop,
Finished
}
"@
# The database and server pairs that will be exported.
$databaseServerPairs =
@([pscustomobject]@{serverName="SAMPLESERVER1";databaseName="SAMPLEDATABASE1"},
[pscustomobject]@{serverName="SAMPLESERVER1";databaseName="SAMPLEDATABASE2"},
[pscustomobject]@{serverName="SAMPLESERVER2";databaseName="SAMPLEDATABASE3"});
$serverCred = Get-AutomationPSCredential -Name 'NAMEOFSERVERCREDENTIAL1';
$serverCred2 = Get-AutomationPSCredential -Name 'NAMEOFSERVERCREDENTIAL2';
$serverCredentialsDictionary = @{'SAMPLESERVER1'=$serverCred;'SAMPLESERVER2'=$serverCred2}
# The number of databases you want to have running at the same time.
$batchingLimit = 10;
# The number of times you want to retry if there is a failure.
$retryLimit = 5;
# The number of minutes you want to wait for an operation to finish before you fail.
$waitInMinutes = 30;
$storageKeyVariableName = "STORAGEKEYVARIABLENAME";
$storageAccountName = "STORAGEACCOUNTNAME";
$automationCertificateName = "CERTIFICATENAME";
$subId = "00000000-0000-0000-0000-000000000000";
$subName = "SUBSCRIPTIONNAME";
function LogMessage($message)
{
$timestamp = Get-Date -format "yyyy-MM-dd_HH:mm.ss";
echo ($timestamp + " " + $message)
}
# This function takes the database and server names and creates a database object to use for the export.
function CreateDatabaseObject($databaseName, $serverName)
{
# Create the new object.
$dbObj = New-Object System.Object;
# Add the DatabaseName property and set it.
$dbObj | Add-Member -type NoteProperty -name DatabaseName -Value $databaseName;
# Add a unique time at the end of DatabaseCopyName so that we have a unique database name every time.
$currentTime = Get-Date -format "_yyyy-MM-dd_HH:mm.ss";
$dbCopyName = $databaseName + $currentTime;
# Add the DatabaseCopyName property and set it.
$dbObj | Add-Member -type NoteProperty -name DatabaseCopyName -Value $dbCopyName;
# Add the ServerName property and set it.
$dbObj | Add-Member -type NoteProperty -name ServerName -Value $serverName;
# Add the Export property and set it to $null for now. This will be used to look up the export after it has been started.
$dbObj | Add-Member -type NoteProperty -name Export -Value $null;
# Add the DatabaseState property and set it to ToCopy so that the "state machine" knows to start the copy of the database.
$dbObj | Add-Member -type NoteProperty -name DatabaseState -Value ([DatabaseState]::ToCopy);
# Add the RetryCount property and set it to 0. This will be used to count the number of time we retry each failable operation.
$dbObj | Add-Member -type NoteProperty -name RetryCount -Value 0;
# Add the OperationStartTime property and set it to $null for now. This will be used when an operation starts to correcly do timeouts.
$dbObj | Add-Member -type NoteProperty -name OperationStartTime -Value $null;
# Return the newly created object.
return $dbObj;
}
# This function starts the copy of the database. If there is an error, we set the state to ToDrop. Otherwise, we set the state to Copying.
function StartCopy($dbObj)
{
# Start the copy of the database.
Start-AzureSqlDatabaseCopy -ServerName $dbObj.ServerName -DatabaseName $dbObj.DatabaseName -PartnerDatabase $dbObj.DatabaseCopyName;
# $? is true if the last command succeeded and false if the last command failed. If it is false, go to the ToDrop state.
if(-not $? -and $global:retryLimit -ile $dbObj.RetryCount)
{
LogMessage ("Error occurred while starting copy of " + $dbObj.DatabaseName + ". It will not be copied. Deleting the database copy named " + $dbObj.DatabaseCopyName + ".");
# Set state to ToDrop in case something does get copied.
$dbsCopying[$i].DatabaseState = ([DatabaseState]::ToDrop);
# Return so we don't execute the rest of the function.
return;
}
elseif(-not $?)
{
# We failed but we haven't hit the retry limit yet so increment RetryCount and return so we try again.
LogMessage ("Retrying with database " + $dbObj.DatabaseName);
$dbObj.RetryCount++;
return;
}
# Set the state of the database object to Copying.
$dbObj.DatabaseState = ([DatabaseState]::Copying);
LogMessage ("Copying " + $dbObj.DatabaseName + " to " + $dbObj.DatabaseCopyName);
$dbObj.OperationStartTime = Get-Date;
}
# This function checks the progress of the copy. If there is an error, we set the state to ToDrop. Otherwise, we set the state to ToExport.
function CheckCopy($dbObj)
{
# Get the status of the database copy.
$check = Get-AzureSqlDatabaseCopy -ServerName $dbObj.ServerName -DatabaseName $dbObj.DatabaseName -PartnerDatabase $dbObj.DatabaseCopyName;
$currentTime = Get-Date;
# $? is true if the last command succeeded and false if the last command failed. If it is false, go to the ToDrop state.
if((-not $? -and $global:retryLimit -ile $dbObj.RetryCount) -or ($currentTime - $dbObj.OperationStartTime).TotalMinutes -gt $global:waitInMinutes)
{
LogMessage ("Error occurred during copy of " + $dbObj.DatabaseName + ". It will not be exported. Deleting the database copy named " + $dbObj.DatabaseCopyName + ".");
# Set state to ToDrop in case something did get copied.
$dbsCopying[$i].DatabaseState = ([DatabaseState]::ToDrop);
# Return so we don't execute the rest of the function.
return;
}
elseif(-not $?)
{
# We failed but we haven't hit the retry limit yet so increment RetryCount and return so we try again.
LogMessage ("Retrying with database " + $dbObj.DatabaseName);
$dbObj.RetryCount++;
return;
}
# Get the percent complete from the status to check if the database copy is done.
$i = $check.PercentComplete
# $i will be $null when the copy is complete.
if($i -eq $null)
{
# The copy is complete so set the state to ToExport.
$dbObj.DatabaseState = ([DatabaseState]::ToExport);
$dbObj.RetryCount = 0;
}
}
# This function starts the export. If there is an error, we set the state to ToDrop. Otherwise, we set the state to Exporting.
function StartExport($dbObj)
{
# Setup the server connection that the storage account is on.
$serverManageUrl = "https://autoexportserver.database.windows.net";
# Get the current time to use as a unique identifier for the blob name.
$currentTime = Get-Date -format "_yyyy-MM-dd_HH:mm.ss";
$blobName = $dbObj.DatabaseName + "_ExportBlob" + $currentTime;
# Use the stored credential to create a server credential to use to login to the server.
$servercredential = $global:serverCredentialsDictionary[$dbObj.ServerName];
# Set up a SQL connection context to use when exporting.
$ctx = New-AzureSqlDatabaseServerContext -ServerName $dbObj.ServerName -Credential $servercredential;
# Get the storage key to setup the storage context.
$storageKey = Get-AutomationVariable -Name $global:storageKeyVariableName;
# Get the storage context.
$stgctx = New-AzureStorageContext -StorageAccountName $global:storageAccountName -StorageAccountKey $storageKey;
# Start the export. If there is an error, stop the export and set the state to ToDrop.
$dbObj.Export = Start-AzureSqlDatabaseExport -SqlConnectionContext $ctx -StorageContext $stgctx -StorageContainerName autoexportcontainer -DatabaseName $dbObj.DatabaseCopyName -BlobName $blobName;
# $? is true if the last command succeeded and false if the last command failed. If it is false, go to the ToDrop state.
if (-not $? -and $global:retryLimit -ile $dbObj.RetryCount)
{
LogMessage ("Error occurred while starting export of " + $dbObj.DatabaseName + ". It will not be exported. Deleting the database copy named " + $dbObj.DatabaseCopyName + ".");
# Set state to ToDrop so that we drop the copied database since there was an error exporting it.
$dbsToExport[$i].DatabaseState = ([DatabaseState]::ToDrop);
# Return so we don't execute the rest of the function.
return
}
elseif(-not $?)
{
# We failed but we haven't hit the retry limit yet so increment RetryCount and return so we try again.
LogMessage ("Retrying with database " + $dbObj.DatabaseName);
$dbObj.RetryCount++;
return;
}
# Set the state to Exporting.
$dbObj.DatabaseState = ([DatabaseState]::Exporting);
LogMessage ("Exporting " + $dbObj.DatabaseCopyName);
$dbObj.OperationStartTime = Get-Date;
}
# This function monitors the export progress.
function CheckExport($dbObj)
{
# Get the progress of the database's export.
$check = Get-AzureSqlDatabaseImportExportStatus -Request $dbObj.Export;
$currentTime = Get-Date;
# The export is complete when Status is "Completed". Wait for that to happen.
if($check.Status -eq "Completed")
{
# The export id one, set the state to ToDrop because it was successful.
$dbObj.DatabaseState = ([DatabaseState]::ToDrop);
$dbObj.RetryCount = 0;
}
elseif($check.Status -eq "Failed" -and $dbObj.RetryCount -lt $global:retryLimit)
{
# If the status is "Failed" and we have more retries left, try to export the database copy again.
LogMessage ("The last export failed on database " + $dbObj.DatabaseName + ", going back to ToExport state to try again");
LogMessage $check
$dbObj.DatabaseState = ([DatabaseState]::ToExport);
$dbObj.RetryCount++;
return;
}
elseif($global:retryLimit -ile $dbObj.RetryCount -or ($currentTime - $dbObj.OperationStartTime).TotalMinutes -gt $global:waitInMinutes)
{
LogMessage ("Error occurred while exporting " + $dbObj.DatabaseName + ". Deleting the database copy named " + $dbObj.DatabaseCopyName + ".");
# The export id one, set the state to ToDrop either because it failed.
$dbObj.DatabaseState = ([DatabaseState]::ToDrop);
}
elseif(-not $?)
{
# We failed but we haven't hit the retry limit yet so increment RetryCount and return so we try again.
LogMessageLogMessage ("Retrying with database " + $dbObj.DatabaseName);
$dbObj.RetryCount++;
return;
}
}
# This function runs the command to drop the database and sets the state to Finished.
function StartDrop($dbObj)
{
# Start the delete
Remove-AzureSqlDatabase -ServerName $dbObj.ServerName -DatabaseName $dbObj.DatabaseCopyName -Force;
# Set the state to Finished so it gets removed from the array.
$dbObj.DatabaseState = ([DatabaseState]::Finished);
LogMessage ($dbObj.DatabaseCopyName + " dropped")
}
# Runs the "State Machine" so that different databases can progress independently.
function ExportProcess
{
# Get all database objects in the ToCopy state and start the database copy.
$dbsToCopy = $global:dbs | Where-Object DatabaseState -eq ([DatabaseState]::ToCopy);
for($i = 0; $i -lt $dbsToCopy.Count; $i++)
{
LogMessage $dbsToCopy[$i];
StartCopy($dbsToCopy[$i]);
}
# Get all database objects in the Copying state and check on their copy progress.
$dbsCopying = $global:dbs | Where-Object DatabaseState -eq ([DatabaseState]::Copying);
for($i = 0; $i -lt $dbsCopying.Count; $i++)
{
CheckCopy($dbsCopying[$i]);
}
# Get all database objects in the ToExport state and start their export.
$dbsToExport = $global:dbs | Where-Object DatabaseState -eq ([DatabaseState]::ToExport);
for($i = 0; $i -lt $dbsToExport.Count; $i++)
{
LogMessage $dbsToExport[$i];
StartExport($dbsToExport[$i]);
}
# Get all database objects in the Exporting state and check on their export progress.
$dbsExporting = $global:dbs | Where-Object DatabaseState -eq ([DatabaseState]::Exporting);
for($i = 0; $i -lt $dbsExporting.Count; $i++)
{
CheckExport($dbsExporting[$i]);
}
# Get all database objects in the ToDrop state and start their drop.
$dbsToDrop = $global:dbs | Where-Object DatabaseState -eq ([DatabaseState]::ToDrop);
for($i = 0; $i -lt $dbsToDrop.Count; $i++)
{
LogMessage $dbsToDrop[$i];
StartDrop($dbsToDrop[$i]);
}
# Get all database objects in the Finished state and remove them from the array.
$dbsFinished = $global:dbs | Where-Object DatabaseState -eq ([DatabaseState]::Finished);
for($i = 0; $i -lt $dbsFinished.Count; $i++)
{
$global:dbs.Remove($dbsFinished[$i]);
}
}
# Get the certificate to authenticate the subscription
$cert = Get-AutomationCertificate -Name $global:automationCertificateName;
# Set the subscription to use
Set-AzureSubscription -SubscriptionName $global:subName -Certificate $cert -SubscriptionId $global:subID;
Select-AzureSubscription -Current $global:subName;
$currentIndex = 0;
for($currentRun = 0; $currentRun -lt ([math]::Ceiling($databaseServerPairs.Length/$batchingLimit)); $currentRun++)
{
# Loop through all the databses in the $databaseServerPairs array and add corresponding database objects into the array.
for($currentIndex; $currentIndex -lt $global:databaseServerPairs.Length -and $currentIndex -lt ($currentRun*$batchingLimit + $batchingLimit); $currentIndex++)
{
$global:dbs.Add((CreateDatabaseObject $global:databaseServerPairs[$currentIndex].DatabaseName $global:databaseServerPairs[$currentIndex].ServerName))
}
# Continually call ExportProcess until all of the database objects have been removed from the array.
while($global:dbs.Count -gt 0)
{
ExportProcess
}
}
@@ -0,0 +1,23 @@
# The storage key for the storage account you are using.
$storageKey = Get-AutomationVariable -Name "STORAGEKEYVARIABLENAME";
# The name of the storage container you are using.
$storageContainer = "STORAGECONTAINERNAME";
# Set up the storage context for the storage account.
$context = New-AzureStorageContext -StorageAccountName "STORAGEACCOUNTNAME" -StorageAccountKey $storageKey
# Get all of the blobs in the storage account.
$blobs = Get-AzureStorageBlob -Container $storageContainer -Context $context
# Set the number of days that you want the blob to be stored for.
$retentionInDays = 30
foreach($blob in $blobs)
{
# Get the current time to compare to the time that the blob was created.
$currentTime = Get-Date;
# If the blob is more than $retentionInDays old, delete it.
if(($currentTime - $blob.LastModified.DateTime).TotalDays -gt $retentionInDays)
{
echo ("Deleting blob " + $blob.Name)
# Delete the blob.e
Remove-AzureStorageBlob -Container $storageContainer -Context $context -Blob $blob.Name;
}
}
@@ -0,0 +1,53 @@
---
services: azure automation
platforms: azure
author: trgrie-msft
---
# Setting up Auto Export in Azure Automation
Provides the scripts and lists the steps to set up automatically exporting your databases to Azure Storage with Azure Automation.
## Azure Automation Set Up
1. Create and uploade the certificates that you will use to authenticate your connection to azure.
- Run powershell as admin.
- Run the New-SelfSignedCertificate command: New-SelfSignedCertificate -CertStoreLocation cert:\localmachine\my -DnsName <certificateName>
- Create a corresponding pfx certificate by taking the thumbprint of the newly created certificate and running these commands:
- $CertPassword = ConvertTo-SecureString -String <YourPassword> -Force -AsPlainText
- Export-PfxCertificate -Cert cert:\localmachine\my\<thumbprint> -FilePath <PathAndFileName>.pfx -Password $CertPassword
- Upload the .cer file to your subscription [here][https://manage.windowsazure.com/]
- Upload the .pfx file to the certificates under Assets in the automation account that you want to use on Azure. You will use the password you gave in the previous step to authenticate it.
2. Create new a new credentials asset to authenticate your server with.
- Under assets, click on Credentials, and then click on Add a credential.
- Name the credential and give the username and password that you will be logging into the server with.
3. Create a new variable asset to pass the storage key of the Azure storage account you will be using.
- Under assets, click on variables and then Add a variable.
- Give the value of the storage key and you can make it encrypted so that only Azure Automation can read the variable and it won't show the key in plaintext if someone looks at the variable.
4. Set Up Log Analytics (OMS) and Alerts
- If you don't have Log Analytics set up on your Azure account, follow [these][https://azure.microsoft.com/en-us/documentation/articles/automation-manage-send-joblogs-log-analytics/] instructions for setting it up.
5. Set Up Log Analytics Alerts
- To send yourself an email if an error occurs or one of the jobs fails, you need to set up alerts.
- Select your log analytics account that you want to use in the azure portal and click on the OMS Portal box under Management.
- Click on Log Search and enter the queries you want to alert on. These are two that are suggested:
- Category=JobStreams “Error occurred*”
- Category=JobLogs ResultType=Failed
- The first will alert on an the provided script saying an error occurred so you know if something didn't go quite right. The second alerts if the script fails entirely.
## Script Set Up
1. In the AutoExport.ps1 script, here are the values that need to be modified:
- $databaseServerPairs: This is where you put in the names of the databases you want to export along with the name of the server they are on.
- $serverCredentialsDictionary: If you are backing up from multiple servers, you can setup all of the credentials here and look them up by the servers name later.
- $batchingLimit: This tells the script how many databases can be worked on at the same time (basically, the maximum number of database copies that there will be at once).
- $retryLimit: This tells the script how many times it can retry an operation.
- $waitTimeInMinutes: This tells the script how long it can wait for an operation to complete before it fails.
- $storageKeyVariableName: This is the AutomationAccount you created the StorageKey variable under (probably the same one you are running the RunBook under) and -Name is the name of the variable.
- $storageAccountName: This is the name of the storage account you are exporting to.
- $automationCertificateName for Get-AutomationCertificate: This is the name of the certificate you setup to authenticate with Azure.
- $subId: The ID of the subscription you are using. This will be used to tell Azure Automation which subscription to use.
- $subName: The name of the subscription you are using. This will be used to tell Azure Automation which subscription to use.
2. In AutoExportBlobRetention, here are the values that need to be modified:
- -Name for Get-AzureAutomationVariable: This is the AutomationAccount you created the StorageKey variable under (probably the same one you are running the RunBook under) and -Name is the name of the variable.
- $storageContainer: This is the name of the storage container where you will be monitoring the exported blobs.
- $retentionInDays: This is how many days you want to keep the exported blobs stored for before deleting.
@@ -0,0 +1,160 @@
# Solution Quick Start: Elastic Pool Custom Dashboard for Saas
The goal of this Solution Quick Start is to help developers get started using Elastic Pools in a SaaS scenario. Therefore, this quick start focuses on leveraging Elastic Pools to provide a cost-effective, scalable database back-end of a SaaS application, showing how the monitoring of Elastic Pool and constituent databases could be monitored via a custom dashboard that supplements the Azure Portal.
This readme applies to:
- Solution Quick Start Guide Managing Elastic Pools using Custom Dashboard.docx - contains the documentation for the Solution QuickStart
- Contoso ShopKeeper.zip - contains the Visual Studio 2105 solutions for the project
> [AZURE.NOTE] The requirements for building the solution are as follows:
- Visual Studio 2015 Update 1 or later
- Azure Subscription
## About this sample
***Applies to:*** Azure SQL Database<br/>
*** Key features:*** Elastic Pools<br/>
***Workload:*** SaaS workload generator<br/>
***Programming Language:*** ADO.NET, XML, C#, Transact-SQL<br/>
***Authors:*** Zoiner Tejada, Carl Rabeler, Srini Acharya<br/>
***Update history:*** n/a<br/>
## Solution Quick Start Overview
The Solution Quick Start consists of a single Visual Studio 2015 solution with two projects, as follows:
- LoadGeneratorConsole: A console application that creates a configurable load against a specified set of databases.
- MonitoringWeb: A Web App that shows gathering and reporting on telemetry collected from Elastic Pools and Database instances.
### Contents
[Scenario](#scenario)<br/>
[Solution Overview](#solution-overview)<br/>
[Scenario Guidance](#scenario-guidance)<br/>
[Performing Schema Maintenance on Pooled Databases](#schema-maintenance)<br/>
[Monitoring & Alerting](#monitoring-alerting)<br/>
[Database Recovery](#database-recovery)<br/>
[Summary](#summary)<br/>
[Learn More](#learn-more)<br/>
<a name=scenario></a>
## Scenario
Contoso Shopkeeper provides business small and mid-size an easy to use, cost-effective shopping virtual store front and e-commerce solution that merchants can use to sell their products online. ShopKeeper is a multi-tenant Software-as-a-Service (SaaS) application that is entirely hosted in Azure and managed by Contoso on behalf of their merchant customers.
The fundamentals behind the architecture of ShopKeeper are resource sharing amongst tenants (which helps keep costs down for both Contoso and its merchant customers), and isolation between tenants (which aims to guarantee that one merchants code or data is never mixed in with anothers). Take the example below, where a customer is using her Web Browser to shop Fabrikam Fabrics. In the process of placing an order she would be interacting with a Web App that only contains Fabrikam Fabrics code, and the Web App would interact with the database instance that only contains Fabrikam Fabrics data- this is the isolation aspect. The fact that various Web Apps share the resources from an App Service Plan or that multiple SQL Databases instances share resources from an Elastic Pool demonstrates the resource sharing aspect.
<![Architecture1](/media/azure-sql-db-elastic-pools-custom-dashboard-architecture-1.png "1")
<a name=solution-overview></a>
## Solution Overview
The focus of this Solution Quick Start is on leveraging Elastic Pools and understanding how the support the backend for a SaaS application like Contoso ShopKeeper, the design and implementation of the App Services component is considered out of scope.
Since the best way to understand the behavior of Elastic Pools is to experience using them under load, we provide a load generator. The load generator is a console application that targets one or more elastic database instances in an Elastic Pool with a specific write load. You can run multiple instances of the load generator with different settings if you want to create a blended load, e.g., a mix of heavy a light load. In addition, you do not need to target all databases in the pool by the load generator, so you can leave databases you choose without any load.
<![Architecture2](/media/azure-sql-db-elastic-pools-custom-dashboard-architecture-2.png "2")
In this Solution Quick Start, we will walk you thru the implementation of a web app that lets you visualize the load created on the Elastic Pool and the elastic databases in near-real time, which when complete will look similar to the following:
<![Custom Control](/media/azure-sql-db-elastic-pools-custom-dashboard-custom-control-1.png "3")
After that, we will introduce how you would apply a schema change to all the databases in the pool, while the load is running, using an Elastic Job via the Azure Portal.
<![Architecture3](/media/azure-sql-db-elastic-pools-custom-dashboard-architecture-3.png "4")
<a name=scenario-guidance></a>
## Scenario Guidance
Before exploring Elastic Pools “hands-on” by running the load generator and Monitoring Web App in the ShopKeeper scenario, you should review the following guidance.
### Balance costs vs tenant performance
In a multi-tenant scenario, appropriately balancing the cost of resources versus tenant performance is critical. An imbalance in one direction means that too much is being spent on resources and costs are high. An imbalance in the other direction means that tenants experience unacceptably slow performance.
### Freemium Models and Elastic Pools
In most SaaS application scenarios, like ShopKeeper, there is a notion of a freemium subscription model. In this model, the solution as it is sold to end-customers is priced in different tiers, such as free and paid, with the notion that there is a friction free upgrade path from the free subscription (where most customers start out) to the paid subscription.
In the ShopKeeper scenario, for example, assume Contoso has a Free Subscription and a Paid Subscription. Contosos goal for the Free Subscription is to keep the per tenant costs as low as possible because the tenants are not paying for this consumption directly (it might be paid from revenue generated by paying customers, or by other means such as a transaction fees or advertising). The Paid Subscription should still aim for cost-effectiveness, but because tenants are paying for usage in this Subscription, it is likely the per tenant costs can be higher (and provide improved peak performance or storage capacity).
So how does this map to how Contoso might leverage Elastic Pools? The database cost per tenant is effectively the cost of the pool divided by the number of tenant databases in the pool. The number of databases that can be added to any given pool is limited by the pricing tier of the pool. For example, a Standard 200 pool supports up to 400 databases. Currently this tier is priced at $446 USD per month. If Contoso were to fully utilize the pool by adding 400 databases, the cost per tenant would near $1.12 per tenant per month. Similarly, if they used the Basic 200 tier (which currently is priced at $298 USD per month), then the cost per tenant- month would near $0.75.
<![Architecture3](/media/azure-sql-db-elastic-pools-custom-dashboard-pricing.png "5")
Naturally, they might consider using Pools in the Basic Tier for their Free Tier merchants in order to realize the lowest cost per tenant-month for that set of tenants. They might then consider reserving Pools in the Standard Tier for their Paid Tier merchants. Alternately, they may choose to use standardize on just one Pool service tier for both Free and Paid Subscriptions. The goal of getting the lowest cost per tenant month would remain the same.
An important consideration here is that once a Pool is created, the service tier selected cannot be changed. To change the service tier of a Pool amounts to creating a new Pool with the desired tier, removing the databases from the existing Pool and the adding them into the new Pool. While this can be done without any down-time with respect to database access, it is not something you would want to perform on a regular basis on account of the process being time consuming.
### When to Create New Pools
Given Contosos freemium model, the optimal configuration of an Elastic Pool from a per tenant-month cost perspective is to maximize the number of databases it contains, and the least desirable situation is creating a Pool for only a single database, because the Pool has a fixed price regardless of whether there is 1 database or 400 databases within it. As their tenant count grows, at some point they will reach the limit of the number of databases the pool allows—this when they should consider creating a new Pool.
Contoso could also consider moving selected databases into a new pool, especially when a particular database has more consistent demand than others, when a pool is hitting its eDTU capacity limits. Also you may want to split out the databases into separate pools, when multiple databases in the pool show a pattern of consistent spiking at the same time
### When to Adjust eDTUs Allocated to a Pool
There are many reasons why Contoso might consider adjusting the number of eDTUs allocated to a Pool. For example, when they first create a Pool to handle the situation when other existing pools are at capacity, they might create the new Pool with the minimum number of eDTUs to reduce the all-up cost of the Pool and then scale up the number of eDTUs on the Pool as more databases are added.
Another reason Contoso would adjust the number of eDTUs allocated to a pool are if their merchants are encountering seasonal fluctuations or other such fluctuations. It is important to note, that while this capability is supported by Elastic Pools, it is not one that should be used frequently as the scaling up or down of eDTUs takes time to complete—on the order of hours.
### Pool Management Options
The provisioning of new Pools, adjusting the eDTUs assigned to a Pool or the migration of databases between Pools can be accomplished manually using the Azure Portal or it can be automated via C# (using the SQL Database Library for .NET) or PowerShell cmdlets (using Azure PowerShell 1.0 or higher).
> [AZURE.NOTE] Examples of Pool Management: For examples of management with the above options, see https://azure.microsoft.com/en-us/documentation/articles/sql-database-elastic-pool-manage-portal/
<a name=schema-maintenance></a>
## Performing Schema Maintenance on Pooled Databases
The Contoso ShopKeeper solution demonstrates an example of the challenge faces by SaaS solutions with regards to managing schemas. In this Solution Quick Start, it deploys an instance of the AdventureWorks database for each tenant. Therefore, Contoso would have many copies of a database with the same schema, albeit different data. This raises the question, how should Contoso roll out schema updates when required (e.g., because of application updates), without affecting the per tenant data?
Using the Azure Portal, as illustrated in this Solution Quick Start, Contoso can use Elastic Jobs to coordinate the execution of a T-SQL script against all of the databases in an Elastic Pool. The important consideration when taking this approach is that the T-SQL script must be written so it is idempotent. That is, running the script multiple times against a single database does not corrupt the target database or raise errors. The approach shown in this Solution Quick Start is to perform checks that examine if the script has already been run against the target database, and to gracefully complete (instead of executing any changes) if the script has previously completed.
Besides using the Portal, Contoso may consider using PowerShell to control the execution of Elastics Database Jobs. In addition to the automation opportunities this allows for the deployment of updates, using PowerShell has one characteristic that is not present in the Portal: custom groups. With custom groups Contoso can target its T-SQL script to execute on a specific set of databases, instead of all the databases within the Pool.
NOTE: For examples of using PowerShell to create and manage Elastic Database jobs, see https://azure.microsoft.com/en-us/documentation/articles/sql-database-elastic-jobs-overview/
<a name=monitoring-alerting></a>
## Monitoring & Alerting
For Contoso, balancing performance versus cost is critical, and for their ShopKeeper application the cost is directly correlated with the number of Elastic Pools they have allocated. Therefore, they want to monitor their Pools closely so they know when to take actions such as creating new Pools or moving databases between Pools.
This begs the question, how can they setup notifications if their load is overwhelming the pool? In this Solution Quick Start, we demonstrate configuring alerts on the Pool using the Azure Portal. Contoso can configure alerts for metrics such CPU %, eDTU %, sessions %, storage %, workers % which when can be tracked in the portal when they trigger, or they can be used to send an email out to admins.
If the capacity concern has more to do with specific tenant database instances, Contoso can also configure alerts on a per database level, in a similar fashion as they do for Pools, by using the Azure Portal.
For both Pool alerts and per Database alerts, Contoso can configure Web Hooks that will perform an HTTP POST to the endpoint of their choosing when the alert is triggered, in addition to having an email sent out to administrators. The payload of this HTTP POST contains the information about the alert that was configured (e.g., the alert name, description and metric configuration), but also the value that caused the alert to trigger. This enables Contoso to extend their ability to react to alerts by using their own management web app or to send out notifications (e.g. using Azure Notification Services) or SMS text messages (e.g., using Twilio).
When it comes to monitoring the Pool and the databases Contoso can choose to use the Azure Portal as well as T-SQL, as we show in this Solution Quick Start with the Monitoring Web App. They can use the T-SQL options to collect the telemetry from Azure and store it in their own log analytics solution. This would enable them to perform analysis on the telemetry that spans much longer periods of time than that permitted by the retention policy of the data when it is managed by Azure—for example, enabling them to review pool usage over the course of months instead of the 14 days that is maintained by Azure.
<a name=database-recovery></a>
## Database Recovery
By utilizing Elastic Pools, Contoso gets an improved ability to juggle cost versus tenant performance, but does not lose any of the features supporting availability and disaster recovery that are available to SQL Databases outside of a Pool. For example, they can use Point in Time Restore to recover from user error, such as a DBA accidentally dropping the customers table in a tenants database. To accomplish this with minimal down-time, they would ensure Pool to which they will restore has capacity for another database instance, rename the original Database to a temporary name and then restore to new database with same name as original. In this fashion, they could restore the database without having to make any application level changes, such as altering connection strings, because the restored database name would be unchanged.
They also get to benefit from the restore deleted database feature to provide a window of time during which their system could actually delete the database when a merchant cancels, but be able to restore that database should the customer re-join within the retention window. This retention window is controlled by the service tier of the pool: 7 days for Basic, 14 days for Standard and 35 days for Premium.
<a name=summary></a>
## Summary
This Solution Quick Start provides guidance on how to leverage Elastic Pools to support the backend of a SaaS application. In addition, this Solution Quick Start provides a tool to simulate load on elastic databases so that you can understand how the effects of load on a database affect the Elastic Pool. Finally, this solution Quick Start demonstrates how you can build collect your Elastic Pool and database telemetry programmatically so that you can implement a monitoring solution that suites the needs of your application.
<a name=learn-more></a>
## Learn More
- SQL Database Forum on MSDN: https://social.msdn.microsoft.com/Forums/azure/en-US/home?forum=ssdsgetstarted
- Stack Overflow: http://stackoverflow.com/questions/tagged/azure-sql-database
- Azure Documentation on Elastic Pools: https://azure.microsoft.com/en-us/documentation/articles/sql-database-elastic-pool/
@@ -1,11 +1,20 @@
# Elastic Pool Telemetry using PowerShell
# Solution Quick Start: Elastic Pool Telemetry using PowerShell
This sample provides a set of PowerShell scripts for off-loading elastic pool and elastic database telemetry data into a separate telemetry database.
This Solution Quick Start sample provides a set of PowerShell scripts for off-loading elastic pool and elastic database telemetry data into a separate telemetry database.
<!-- Add a diagram if you have it -->
This readme applies to the PowerShell scripts: PoolTelemetryJobRunner.ps1 and PoolTelemetry.ps1.
## About this sample
***Applies to:*** Azure SQL Database<br/>
***Key features:*** Elastic Pools<br/>
***Workload:*** n/a<br/>
***Programming Language:*** PowerShell, Transact-SQL, DAX<br/>
***Authors:*** Carl Rabeler, Srini Acharya<br/>
***Update history:*** n/a<br/>
### Contents
[What do the PowerShell scripts do?](#what-do-the-powershell-scripts-do?)<br/>
@@ -213,7 +222,7 @@ Data can be queried while data collection is in progress.
<<<<<<< HEAD
=======
<a power-bi></a>
<a name=power-bi></a>
## Power BI designer file
@@ -0,0 +1,4 @@
## Windows Containers
Moved here: [Microsoft/mssql-docker](https://github.com/Microsoft/mssql-docker/blob/master/windows/README.md)