From fa583f42a2ed22f990f9fcac2e88ec4a0d4bc5e8 Mon Sep 17 00:00:00 2001 From: Jon Burchel Date: Tue, 8 Oct 2019 13:04:56 -0500 Subject: [PATCH 1/8] Added AAS AutoScale project --- ...nalysis Services QPU AutoScale ReadMe.docx | Bin 0 -> 27474 bytes AASAutoScale/DeployAASAutoscale.ps1 | 550 ++++++++++++++++++ 2 files changed, 550 insertions(+) create mode 100644 AASAutoScale/Azure Analysis Services QPU AutoScale ReadMe.docx create mode 100644 AASAutoScale/DeployAASAutoscale.ps1 diff --git a/AASAutoScale/Azure Analysis Services QPU AutoScale ReadMe.docx b/AASAutoScale/Azure Analysis Services QPU AutoScale ReadMe.docx new file mode 100644 index 0000000000000000000000000000000000000000..21a791ea5778f3e66ef0bf83b51f3343b410a7ea GIT binary patch literal 27474 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 S$ec;?$q@KqM@YtJul^5_*iyCt literal 0 HcmV?d00001 diff --git a/AASAutoScale/DeployAASAutoscale.ps1 b/AASAutoScale/DeployAASAutoscale.ps1 new file mode 100644 index 0000000..434f211 --- /dev/null +++ b/AASAutoScale/DeployAASAutoscale.ps1 @@ -0,0 +1,550 @@ +# DeployAASAutoScale.ps1 +# Installs or updates Azure Analysis Services QPU AutoScale. + +# Do not manually change tier while AutoScale is configured or unexpected scaling will occur since the alerts need updating by the autoscale runbook. +# Before manually changing tier after AutoScale is configured, remove AutoScale with the -Remove parameter. + +# This script must be executed by an AAD authenticated user who is a server level administrator of the AAS instance and administrator/owner of the Automation Account. +# NOTE: If Active Directory Connect is not setup in AAD to translate Windows accounts to AAD automatically, it is necessary to run Login-AzAccount in the PowerShell command window before running this script. + +[CmdletBinding(DefaultParameterSetName="ConfigParameters")] +param( + [Parameter (Mandatory = $true, HelpMessage = "Subscription containing the Resource Group, AAS instance and Automation Account for AutoScale configuration.", ParameterSetName = "ConfigParameters")] + [Parameter (Mandatory = $true, ParameterSetName = "RemovalParameters")] + [string] $SubscriptionId, + [Parameter (Mandatory = $true, HelpMessage = "The Resource Group of the AAS instance and the Automation Account that will be used to perform AutoScale. These must be in the same resource group.", ParameterSetName = "ConfigParameters")] + [Parameter (Mandatory = $true, ParameterSetName = "RemovalParameters")] + [string] $ResourceGroupName, + + [Parameter (Mandatory = $true, HelpMessage = "Automation Account must import modules Az.Accounts, Az.AnalysisServices, Az.Automation, Az.Monitor, and AzureRM.Insights, and be in the Resource Group specified in the ResourceGroupName parameter.", ParameterSetName = "ConfigParameters")] + [Parameter (Mandatory = $true, ParameterSetName = "RemovalParameters")] + [string] $AutomationAccount, + [Parameter (Mandatory = $true, HelpMessage = "AAS instance to configure AutoScale. The instance must be in the Resource Group provided in the ResourceGroupName paramater.", ParameterSetName = "ConfigParameters")] + [Parameter (Mandatory = $true, ParameterSetName = "RemovalParameters")] + [string] $ASServerName, + + [Parameter (Mandatory = $true, HelpMessage = "The alert interval and window over which QPU usage is monitored for AutoScale alerts.", ParameterSetName = "ConfigParameters")] + [int] $AlertWindowInMins, + + [Parameter (Mandatory = $true, HelpMessage = "Minimum tier for the instance's AutoScale configuration, for example: S0.", ParameterSetName = "ConfigParameters")] + [string] $MinTier, + [Parameter (Mandatory = $true, HelpMessage = "Maximum tier for the instance's AutoScale configuration, for example: S4.", ParameterSetName = "ConfigParameters")] + [string] $MaxTier, + + # Scale-out to add replicas only starts after reaching $MaxTier. + [Parameter (Mandatory = $true, HelpMessage = "Minimum number of read-only replicas to maintain for the AutoScale configuration.", ParameterSetName = "ConfigParameters")] + [int] $MinReplicas, + [Parameter (Mandatory = $true, HelpMessage = "Maximum number of read-only replicas to scale-out for the AutoScale configuration.`nNOTE: Scale-out of additional replicas only starts after reaching MaxTier.", ParameterSetName = "ConfigParameters")] + [int] $MaxReplicas = 0, + + # Even when $SeparateProcessingNodeFromQueryReplicas = $true, if $MinReplicas = 0, then processing cannot be isolated whenever we scale all the way in to 0 replicas. + [Parameter (Mandatory = $false, ParameterSetName = "ConfigParameters")] + [bool] $SeparateProcessingNodeFromQueryReplicas = $true, + + # Scale-In at X% below the lower tier's max, and Scale-Up/Out at X% below the current tier's max. + [Parameter (Mandatory = $false, ParameterSetName = "ConfigParameters")] + [int] $ScaleUpDownOutAtPctDistanceFromTierMax = 10, + # Scale-In at X% below the current tier's limit. Since tier doesn't change with scale-in, the threshold to remove a replica can be higher. + [Parameter (Mandatory = $false, ParameterSetName = "ConfigParameters")] + [int] $ScaleInAtPctDistanceFromTierMax = 25, + + # Uninstall AutoScale for the instance. + [Parameter (Mandatory = $false, ParameterSetName = "RemovalParameters")] + [switch] $Remove = $false, + + # Forces even if instance is not within configured AutoScale limits currently, or an existing alert is already in progress. Could cause non-deterministic results. + [Parameter (Mandatory = $false, ParameterSetName = "ConfigParameters")] + [switch] $Force= $false +) + +function Get-AzAccessToken { + [CmdletBinding()] + param () + + $ErrorActionPreference = 'Stop' + + if(-not (Get-Module Az.Accounts)) { + Import-Module Az.Accounts + } + + $azProfile = [Microsoft.Azure.Commands.Common.Authentication.Abstractions.AzureRmProfileProvider]::Instance.Profile + if (-not $azProfile.Accounts.Count) { + Write-Error 'Could not find a valid AzProfile, please run Connect-AzAccount' + return + } + + $currentAzureContext = Get-AzContext + $profileClient = New-Object Microsoft.Azure.Commands.ResourceManager.Common.RMProfileClient($azProfile) + $token = $profileClient.AcquireAccessToken($currentAzureContext.Tenant.TenantId) + $token.AccessToken +} + +$ErrorActionPreference = "stop" + +cls +Write-Host "`n`n`n`n`n`nConfiguring Azure Analysis Services QPU AutoScale for instance:" -ForegroundColor Green +$PSBoundParameters | Format-Table -HideTableHeaders + +$Token = Get-AzAccessToken +$SKUSObj = Invoke-RestMethod -Uri "https://management.azure.com/subscriptions/$($SubscriptionId)/resourceGroups/$($ResourceGroupName)/providers/Microsoft.AnalysisServices/servers/$($ASServerName)/skus?api-version=2017-08-01" -Headers @{"Authorization" = "Bearer " + $Token } +$SKUS = $SKUSObj.value | Foreach {"$($_.sku.name)"} + +# Get the current SKU to appropriately set initial conditions +$srv = Get-AzAnalysisServicesServer -ResourceGroupName $ResourceGroupName -Name $ASServerName -WarningAction silentlyContinue -ErrorAction Stop +$CurrentSKU = $srv.Sku.Name +$CurrentCapacity = $srv.Sku.Capacity +if ($srv.State -ne "Succeeded") +{ + Write-Host "Server must be active to setup AutoScale. AutoScale will not be configured. Please start the server first." -ForegroundColor Red + exit +} + +if ($Remove) +{ + # Remove everything + Write-Host "-Remove parameter specified. Removing AutoScale." -ForegroundColor Green + Write-Host "Removing metric alerts..." -NoNewline + Remove-AzMetricAlertRuleV2 -Name "AutoScaleDownAlert" -ResourceGroupName $ResourceGroupName -ErrorAction SilentlyContinue -WarningAction Ignore | Out-Null + Remove-AzMetricAlertRuleV2 -Name "AutoScaleUpAlert" -ResourceGroupName $ResourceGroupName -ErrorAction SilentlyContinue -WarningAction Ignore | Out-Null + Write-Host " Success" -ForegroundColor Green + Write-Host "Removing action groups..." -NoNewline + Remove-AzActionGroup -Name "AutoScaleUpActionGroup" -ResourceGroupName $ResourceGroupName -ErrorAction SilentlyContinue -WarningAction Ignore | Out-Null + Remove-AzActionGroup -Name "AutoScaleDownActionGroup" -ResourceGroupName $ResourceGroupName -ErrorAction SilentlyContinue -WarningAction Ignore | Out-Null + Write-Host " Success" -ForegroundColor Green + Write-Host "Removing runbook..." -NoNewline + Remove-AzAutomationVariable -Name "AASAutoScaleSynchronizationStatus" -ResourceGroupName $ResourceGroupName -AutomationAccountName $AutomationAccount -ErrorAction SilentlyContinue -WarningAction Ignore + Remove-AzAutomationRunbook -Name "AASAutoScale-$($ASServerName)" -ResourceGroupName $ResourceGroupName -AutomationAccountName $AutomationAccount -Force -ErrorAction SilentlyContinue -WarningAction Ignore | Out-Null + Write-Host " Success" -ForegroundColor Green + Write-Host "`nAutoScale removed." -ForegroundColor Green + exit +} + +# This gets resused in the runbook script below as well as in the install script so reuse and store as an expression string +$TierMaxesExpression = @' + $TierMaxes = [ordered]@{ + 'S0' = 40; + 'S1' = 100; + 'S2' = 200; + 'S4' = 400; + 'S8' = 320; + 'S9' = 640; + 'S8v2' = 640; + 'S9v2' = 1280; + } +'@ +# We trim down the TierMaxes to only reflect those SKUs that are available. +$TierMaxesExpression = $TierMaxesExpression.Split("`n") | Foreach-Object {if ($SKUS -ccontains $_.Substring($_.IndexOf("'") + 1, 2) -or $_.IndexOf("'") -eq -1) {$_}} +# If S8v2 supported, then remove S8 and S9 from our scaling path unless the instance is currently already on S8 or S9, or Min or Max tiers are explicitly S8 or S9, and then remove v2 versions. +if ($SKUS.Contains("S8v2") -and $CurrentSKU -ne "S8" -and $CurrentSKU -ne "S9" -and ("S8", "S9") -cnotcontains $MinTier -and ("S8", "S9") -cnotcontains $MaxTier) +{ $TierMaxesExpression = $TierMaxesExpression.Split("`n") | Foreach-Object {if ($_.IndexOf("'S8'") -eq -1 -and $_.IndexOf("'S9'") -eq -1) {$_}} | Out-String } +else +{ $TierMaxesExpression = $TierMaxesExpression.Split("`n") | Foreach-Object {if ($_.IndexOf("'S8v2'") -eq -1 -and $_.IndexOf("'S9v2'") -eq -1) {$_}} | Out-String } + +# Invoke the expression here where it is used in this install script too... +Invoke-Expression $TierMaxesExpression + +$MaxReplicasForRegion = 0 +try { Set-AzAnalysisServicesServer -Name quicktest -ResourceGroupName jburchel-AS -ReadonlyReplicaCount 99 -ErrorAction SilentlyContinue } +catch +{ + $err = $Error[0].Exception.Message + $err = $err.Substring($err.IndexOf("The 99 argument is greater than the maximum allowed range of ") + "The 99 argument is greater than the maximum allowed range of ".Length) + $MaxReplicasForRegion = [int]$err.Substring(0, $err.IndexOf(".")) +} + +if ($MaxReplicasForRegion -lt $MaxReplicas) +{ + Write-Host ("Maximum replica count specified would exceed region capacity. AutoScale will not be configured. Specify a -MaxReplicas value less than or equal to the region maximum of " + $MaxReplicasForRegion + ".") -ForegroundColor Red + exit +} + +if (-not $TierMaxes.Contains($MinTier) -or -not $TierMaxes.Contains($MaxTier)) +{ + Write-Host "MinTier and MaxTier parameters must fall within avaible tiers for the instance. AutoScale will not be configured. Available tiers in the current region are:" -ForegroundColor Red + $TierMaxes.Keys | Out-String | Write-Host + exit +} + +$ExistingAlert = Get-AzAutomationVariable -Name "AASAutoScaleSynchronizationStatus" -ResourceGroupName $ResourceGroupName -AutomationAccountName $AutomationAccount -ErrorAction SilentlyContinue +if ($ExistingAlert -ne "Idle" -and $ExistingAlert -ne $null -and $Force -ne $True) +{ + Write-Host "An existing AutoScale alert for this instance is still being processed. AutoScale will not be configured. Please wait until all alerts resolve before updating AutoScale." -ForegroundColor Red + exit +} + +Write-Host "Configuration details:" -ForegroundColor Green +[ordered]@{ + "Minimum tier:" = $MinTier + "Maximum tier:" = $MaxTier + "Minimum replica count:" = $MinReplicas + "Maximum replica count:" = "" + $MaxReplicas + " (adding replicas only after reaching max tier)" + "Isolate processing from replicas:" = $SeparateProcessingNodeFromQueryReplicas + "Alert window/frequency:" = "Checks QPU every " + $AlertWindowInMins + " minute(s), over the prior " + $AlertWindowInMins + " minute(s)." + "Scale-Up threshold:" = "Increase tier when avg QPU exceeds " + $ScaleUpDownOutAtPctDistanceFromTierMax + "% below current tier QPU max." + "Scale-Down threshold:" = "Reduce tier when max QPU remains more than " + $ScaleUpDownOutAtPctDistanceFromTierMax + "% below QPU max of lower tier." + "Scale-Out threshold:" = "Add replica (after max tier) when avg QPU exceeds " + $ScaleUpDownOutAtPctDistanceFromTierMax + "% below current tier QPU max." + "Scale-In threshold:" = "Remove replica when QPU remains more than " + $ScaleInAtPctDistanceFromTierMax + "% below QPU max for current tier." +} | Format-Table -HideTableHeaders -AutoSize + +if ($Force -ne $True -and + ( + $($TierMaxes.Keys).IndexOf($CurrentSKU) -lt $($TierMaxes.Keys).IndexOf($MinTier) -or ` + $($TierMaxes.Keys).IndexOf($CurrentSKU) -gt $($TierMaxes.Keys).IndexOf($MaxTier) -or ` + $CurrentCapacity -gt ($MaxReplicas + 1) -or ` + $CurrentCapacity -lt ($MinReplicas + 1) + ) + ) +{ + Write-Host "Instance's current tier ($($CurrentSKU)) and replica count ($($CurrentCapacity - 1)) exceed the configured AutoScale limits. AutoScale will not be configured.`nInstance must be running within AutoScale configuration's tier/capacity limits to complete this install." -ForegroundColor Red + exit +} + +Write-Progress -Activity "Installing Azure Analysis Services QPU AutoScale" -PercentComplete 0 + +$MinCapacity = $MinReplicas + $SeparateProcessingNodeFromQueryReplicas +if ($MinCapacity -eq 0) { $MinCapacity = 1 } + +# This large string variable that follows is the PS script used for the AutoScale runbook triggered when scaling alerts fire +$PSScript = @' +param( + [Parameter (Mandatory = $true)] + [boolean] $Up, + [Parameter (Mandatory = $false)] + [object] $WebhookData +) + +function GetNextLowerQPUTier +{ + # find the highest QPU available in the tiers below (to avoid scaling down from S9 to S8, which would leave more memory but reduce QPU more than S4) + $HighestLowerTierQPUAvailable = 0 + for ($i = $($TierMaxes.Keys).IndexOf($CurrentSKU) - 1; $i -ge 0; $i--) + { + if ($HighestLowerTierQPUAvailable -lt $TierMaxes[$i]) { $HighestLowerTierQPUAvailable = $TierMaxes[$i] } + } + return ($TierMaxes.GetEnumerator() | Where-Object {$_.Value -eq $HighestLowerTierQPUAvailable}).Key +} + +$ErrorActionPreference = "Stop" + +if ($WebhookData) { $body = ConvertFrom-Json -InputObject $WebhookData.RequestBody } +else +{ + Write-Output "Missing information" + exit +} + +$AlertStage = $body.data.status + +$ResourceGroupName = " +'@ + $ResourceGroupName + @' +" +$AutomationAccount = " +'@ + $AutomationAccount + @' +" +$ASServerName = " +'@ + $ASServerName + @' +" +$SubscriptionId = " +'@ + $SubscriptionId + @' +" +$AlertWindowInMins = +'@ + $AlertWindowInMins + @' + +$Window = New-TimeSpan -Minutes $AlertWindowInMins +$MaxTier = " +'@ + $MaxTier + @' +" +$MinTier = " +'@ + $MinTier + @' +" +$MaxReplicas = +'@ + $MaxReplicas + @' + +$MinReplicas = +'@ + $MinReplicas + @' + +$SeparateProcessingNodeFromQueryReplicas = $ +'@ + $SeparateProcessingNodeFromQueryReplicas + @' + +$MinCapacity = $MinReplicas + $SeparateProcessingNodeFromQueryReplicas +if ($MinCapacity -eq 0) { $MinCapacity = 1 } + +$ScaleUpDownOutAtPctDistanceFromTierMax = +'@ + $ScaleUpDownOutAtPctDistanceFromTierMax + @' + +$ScaleInAtPctDistanceFromTierMax = +'@ + $ScaleInAtPctDistanceFromTierMax + @' + + +'@ + $TierMaxesExpression + @' + + +$servicePrincipalConnection=Get-AutomationConnection -Name "AzureRunAsConnection" +Connect-AzAccount -ServicePrincipal -Tenant $servicePrincipalConnection.TenantId -ApplicationId $servicePrincipalConnection.ApplicationId -CertificateThumbprint $servicePrincipalConnection.CertificateThumbprint | Out-Null + +$Status = Get-AzAutomationVariable -Name "AASAutoScaleSynchronizationStatus" -ResourceGroupName $ResourceGroupName -AutomationAccountName $AutomationAccount + +if (($Status.Value -ne "Idle" -and $AlertStage -ne "Deactivated") -or ($Status.Value -ne "Active" -and $AlertStage -eq "Deactivated")) +{ + Write-Output "Another alert is already in progress so this one is being skipped." + exit +} +if ($AlertStage -eq "Activated") { Set-AzAutomationVariable -Name "AASAutoScaleSynchronizationStatus" -ResourceGroupName $ResourceGroupName -AutomationAccountName $AutomationAccount -Value "Active" -Encrypted $false | Out-Null } +if ($AlertStage -eq "Deactivated") { Set-AzAutomationVariable -Name "AASAutoScaleSynchronizationStatus" -ResourceGroupName $ResourceGroupName -AutomationAccountName $AutomationAccount -Value "Deactivating" -Encrypted $false | Out-Null } + +# Get current SKU and determine new SKU based on that and the $Up parameter +$srv = Get-AzAnalysisServicesServer -ResourceGroupName $ResourceGroupName -Name $ASServerName -WarningAction silentlyContinue -ErrorAction Stop +$CurrentSKU = $NewSKU = $srv.Sku.Name +$CurrentCapacity = $NewCapacity = $srv.Sku.Capacity + +$TargetResourceId = "/subscriptions/" + $SubscriptionId + "/resourceGroups/" + $ResourceGroupName + "/providers/Microsoft.AnalysisServices/servers/" + $ASServerName + +$AutoScaleUpActionGroupId = "/subscriptions/" + $SubscriptionId + "/resourceGroups/" + $ResourceGroupName +"/providers/microsoft.insights/actiongroups/AutoScaleUpActionGroup" +$AutoScaleDownActionGroupId = "/subscriptions/" + $SubscriptionId + "/resourceGroups/" + $ResourceGroupName +"/providers/microsoft.insights/actiongroups/AutoScaleDownActionGroup" +$AutoScaleUpActionGroup = New-AzActionGroup -ActionGroupId $AutoScaleUpActionGroupId -WarningAction silentlyContinue -ErrorAction Stop +$AutoScaleDownActionGroup = New-AzActionGroup -ActionGroupId $AutoScaleDownActionGroupId -WarningAction silentlyContinue -ErrorAction Stop + +# It is necessary to update the alerts when first activated, so they get resolved in the portal. Otherwise they cannot fire again. +if ($AlertStage -eq "Activated") +{ + if ($Up -eq $true) + { + Write-Output "Scale-up alert activated." + if ($CurrentSKU -ne $MaxTier) + { + # find the next higher SKU that _also_ has higher QPU (to avoid scaling up to S8 from S4, which would increase memory but reduce available QPU) + for ($i = $($TierMaxes.Keys).IndexOf($CurrentSKU) + 1; $i -lt $TierMaxes.Count; $i++) + { + # checking the QPU of the next higher tier against current QPU + if ($TierMaxes[$i] -gt $TierMaxes[$CurrentSKU]) + { + $NewSKU = $($TierMaxes.Keys)[$i] + break + } + } + } + + if ($CurrentSKU -eq $MaxTier -and $CurrentCapacity -lt ($MaxReplicas + 1)) { $NewCapacity = $CurrentCapacity + 1 } + + $Dimensions = New-AzMetricAlertRuleV2DimensionSelection -DimensionName "ServerResourceType" -ValuesToInclude "*" -WarningAction silentlyContinue -ErrorAction Stop + + $UpperBoundCriteria = New-AzMetricAlertRuleV2Criteria -MetricName "qpu_metric" -DimensionSelection $Dimensions -TimeAggregation Average -Operator GreaterThan -Threshold 99999 -WarningAction silentlyContinue -ErrorAction Stop + Add-AzMetricAlertRuleV2 -Condition $UpperBoundCriteria -Name "AutoScaleUpAlert" -ResourceGroupName $ResourceGroupName -TargetResourceId $TargetResourceId ` + -ActionGroup $AutoScaleUpActionGroup -WindowSize 00:01:00 -Frequency 00:01:00 -Severity 3 -WarningAction silentlyContinue -ErrorAction Stop | Out-Null + Write-Output "New upper bound set to 99999 just to trigger deactivation of alert. The runbook will be called again on deactivation and new alert values will be set then." + } + else + { + Write-Output "Scale-down alert activated." + if ($CurrentCapacity -gt $MinCapacity) + { + $NewCapacity = $CurrentCapacity - 1 + } + else + { + $NewCapacity = $CurrentCapacity + $NewSKU = GetNextLowerQPUTier + } + + $Dimensions = New-AzMetricAlertRuleV2DimensionSelection -DimensionName "ServerResourceType" -ValuesToInclude "*" -WarningAction silentlyContinue -ErrorAction Stop + + $LowerBoundCriteria = New-AzMetricAlertRuleV2Criteria -MetricName "qpu_metric" -DimensionSelection $Dimensions -TimeAggregation Maximum -Operator LessThan -Threshold 0 -WarningAction silentlyContinue -ErrorAction Stop + Add-AzMetricAlertRuleV2 -Condition $LowerBoundCriteria -Name "AutoScaleDownAlert" -ResourceGroupName $ResourceGroupName -TargetResourceId $TargetResourceId ` + -ActionGroup $AutoScaleDownActionGroup -WindowSize 00:01:00 -Frequency 00:01:00 -Severity 3 -WarningAction silentlyContinue -ErrorAction Stop | Out-Null + Write-Output "New lower bound set to 0 just to trigger deactivation of alert. The runbook will be called again on deactivation and new alert values will be set then." + } + + Write-Output ("Current SKU: `t" + $CurrentSKU) + Write-Output ("Current replica count: `t$($CurrentCapacity - 1)") + Write-Output ("New SKU: `t" + $NewSKU) + Write-Output ("New replica count: `t" + ($NewCapacity - 1)) + + if ($SeparateProcessingNodeFromQueryReplicas -eq $true -and $NewCapacity -gt 1) { $DefaultConnectionMode = "Readonly" } else { $DefaultConnectionMode = "All" } + + # Update tier + Set-AzAnalysisServicesServer -ResourceGroupName $ResourceGroupName -Name $ASServerName -Sku $NewSKU -ReadonlyReplicaCount ($NewCapacity - 1) -DefaultConnectionMode $DefaultConnectionMode -WarningAction silentlyContinue -ErrorAction Stop | Out-Null + Write-Output "Tier updated successfully." +} + +# When the alert is resolved the status is Deactivated, so the alerts can be set to their correct new values. +if ($AlertStage -eq "Deactivated") +{ + if ($Up) { Write-Output "Scale-up alert deactivated. Resetting alert values." } + else { Write-Output "Scale-down alert deactivated. Resetting alert values." } + + if ($CurrentSKU -eq $MaxTier -and $CurrentCapacity -eq ($MaxReplicas + 1)) { $NewQPUUpperBound = 99999 } + else { $NewQPUUpperBound = $($($TierMaxes.GetEnumerator()) | Where-Object { $_.Name -eq $CurrentSKU }).Value * (1 - ($ScaleUpDownOutAtPctDistanceFromTierMax * .01)) } + + if ($CurrentCapacity -gt $MinCapacity) + { + $NewQPULowerBound = (1 - $ScaleInAtPctDistanceFromTierMax * .01) * $($TierMaxes.GetEnumerator() | Where-Object { $_.Name -eq $CurrentSKU }).Value + } + else + { + if ($CurrentSKU -eq $MinTier -and $CurrentCapacity -eq $MinCapacity) { $NewQPULowerBound = 0 } + else + { + $NewSKU = GetNextLowerQPUTier + $NewQPULowerBound = $($($TierMaxes.GetEnumerator()) | Where-Object { $_.Name -eq $NewSKU }).Value * (1 - ($ScaleUpDownOutAtPctDistanceFromTierMax * .01)) + } + } + + Write-Output ("New upper bound: `t" + $NewQPUUpperBound) + Write-Output ("New lower bound: `t" + $NewQPULowerBound) + + if ($SeparateProcessingNodeFromQueryReplicas -and $CurrentCapacity -gt 1) { $DimValues = "QueryPool" } else { $DimValues = "*" } + $Dimensions = New-AzMetricAlertRuleV2DimensionSelection -DimensionName "ServerResourceType" -ValuesToInclude $DimValues -WarningAction silentlyContinue -ErrorAction Stop + + $UpperBoundCriteria = New-AzMetricAlertRuleV2Criteria -MetricName "qpu_metric" -DimensionSelection $Dimensions -TimeAggregation Average -Operator GreaterThan -Threshold $NewQPUUpperBound -WarningAction silentlyContinue -ErrorAction Stop + Add-AzMetricAlertRuleV2 -Condition $UpperBoundCriteria -Name "AutoScaleUpAlert" -ResourceGroupName $ResourceGroupName -TargetResourceId $TargetResourceId ` + -ActionGroup $AutoScaleUpActionGroup -WindowSize $Window -Frequency $Window -Severity 3 -WarningAction silentlyContinue -ErrorAction Stop | Out-Null + + $LowerBoundCriteria = New-AzMetricAlertRuleV2Criteria -MetricName "qpu_metric" -DimensionSelection $Dimensions -TimeAggregation Maximum -Operator LessThan -Threshold $NewQPULowerBound -WarningAction silentlyContinue -ErrorAction Stop + Add-AzMetricAlertRuleV2 -Condition $LowerBoundCriteria -Name "AutoScaleDownAlert" -ResourceGroupName $ResourceGroupName -TargetResourceId $TargetResourceId ` + -ActionGroup $AutoScaleDownActionGroup -WindowSize $Window -Frequency $Window -Severity 3 -WarningAction silentlyContinue -ErrorAction Stop | Out-Null + + Write-Output "Alert values updated succesfully." + + # Update alert status synchronization variable finally so new alerts after this will continue processing. + Set-AzAutomationVariable -Name "AASAutoScaleSynchronizationStatus" -ResourceGroupName $ResourceGroupName -AutomationAccountName $AutomationAccount -Value "Idle" -Encrypted $false | Out-Null +} +'@ + +# Write the Runbook script to file for importing to the runbook, then publish it +Write-Progress -Activity "Installing Azure Analysis Services QPU AutoScale" -PercentComplete 5 -Status "Publishing runbook AASAutoScale-$($ASServerName) to Automation Account $($AutomationAccount)..." +Write-Host "Publishing runbook " -NoNewline +Write-Host "AASAutoScale-$($ASServerName)" -NoNewline -ForegroundColor Green +Write-Host " to Automation Account $($AutomationAccount)..." -NoNewline +Set-Content -Path $env:temp\AutoScale.ps1 -Value $PSScript -ErrorAction Stop +$rb = Import-AzAutomationRunbook -Name "AASAutoScale-$($ASServerName)" -ResourceGroupName $ResourceGroupName -AutomationAccountName $AutomationAccount -Path $env:temp\AutoScale.ps1 -Type PowerShell -Force -WarningAction silentlyContinue -ErrorAction Stop +Remove-Item -Path $env:temp\AutoScale.ps1 +Publish-AzAutomationRunbook -Name "AASAutoScale-$($ASServerName)" -ResourceGroupName $ResourceGroupName -AutomationAccountName $AutomationAccount -WarningAction silentlyContinue -ErrorAction Stop | Out-Null +# Also setup an automation variable used to track when we are processing the alert, to prevent other alerts from starting. +Remove-AzAutomationVariable -Name "AASAutoScaleSynchronizationStatus" -ResourceGroupName $ResourceGroupName -AutomationAccountName $AutomationAccount -ErrorAction SilentlyContinue +New-AzAutomationVariable -Name "AASAutoScaleSynchronizationStatus" -ResourceGroupName $ResourceGroupName -AutomationAccountName $AutomationAccount -Description "Inidicates when an AAS QPU AutoScale alert is being processed, preventing other alerts from scaling until complete." -Value "Idle" -Encrypted $false | Out-Null +Write-Host " Success!" -ForegroundColor Green + +# Create 2 webhooks on the Runbook, one for Up and one for Down +Write-Host "Creating web hooks for scale up and scale down alerts to runbook..." -NoNewline +Write-Progress -Activity "Installing Azure Analysis Services QPU AutoScale" -PercentComplete 40 -Status "Creating web hooks for scale up and scale down alerts to runbook..." +$UpWebhook = New-AzAutomationWebhook -Name "AutoScaleUpWebhook$(Get-Random)" -RunbookName "AASAutoScale-$($ASServerName)" -IsEnabled $true -ExpiryTime "01/01/2029" -Parameters @{"Up"=$true} ` + -AutomationAccountName $AutomationAccount -ResourceGroup $ResourceGroupName -Force -WarningAction silentlyContinue -ErrorAction Stop +$DownWebhook = New-AzAutomationWebhook -Name "AutoScaleDownWebhook$(Get-Random)" -RunbookName "AASAutoScale-$($ASServerName)" -IsEnabled $true -ExpiryTime "01/01/2029" -Parameters @{"Up"=$false} ` + -AutomationAccountName $AutomationAccount -ResourceGroup $ResourceGroupName -Force -WarningAction silentlyContinue -ErrorAction Stop +Write-Host " Success!" -ForegroundColor Green + +# Create action receivers for both web hooks +Write-Host "Creating action receivers to the webhooks..." -NoNewline +Write-Progress -Activity "Installing Azure Analysis Services QPU AutoScale" -PercentComplete 45 -Status "Creating action receivers to the webhooks..." +$UpRuleActionReceiver = New-AzActionGroupReceiver -Name "UpRuleActionReceiver" -WebhookReceiver -ServiceUri $UpWebhook.WebhookURI -WarningAction silentlyContinue -ErrorAction Stop +$DownRuleActionReceiver = New-AzActionGroupReceiver -Name "DownRuleActionReceiver" -WebhookReceiver -ServiceUri $DownWebhook.WebhookURI -WarningAction silentlyContinue -ErrorAction Stop +Write-Host " Success!" -ForegroundColor Green + +# Create action groups for each web hook +Write-Host "Creating action groups " -NoNewline +Write-Host "AutoScaleUpActionGroup" -NoNewline -ForegroundColor Green +Write-Host " and " -NoNewline +Write-Host "AutoScaleDownActionGroup" -NoNewline -ForegroundColor Green +Write-Host " for AS server $($ASServerName)..." -NoNewline +Write-Progress -Activity "Installing Azure Analysis Services QPU AutoScale" -PercentComplete 60 -Status "Creating action groups AutoScaleUpActionGroup and AutoScaleDownActionGroup for AS server $($ASServerName)..." +$AutoScaleUpActionGroup = Set-AzActionGroup -ResourceGroupName $ResourceGroupName -Name "AutoScaleUpActionGroup" -ShortName "AutoUp" -Receiver $UpRuleActionReceiver -WarningAction silentlyContinue -ErrorAction Stop +$AutoScaleDownActionGroup = Set-AzActionGroup -ResourceGroupName $ResourceGroupName -Name "AutoScaleDownActionGroup" -ShortName "AutoDown" -Receiver $DownRuleActionReceiver -WarningAction silentlyContinue -ErrorAction Stop +Write-Host " Success!" -ForegroundColor Green + +Write-Host "Getting current server configuration and details required to create alerts..." -NoNewline +Write-Progress -Activity "Installing Azure Analysis Services QPU AutoScale" -PercentComplete 70 -Status "Getting current server configuration and details required to create alerts..." +# Obtain the action group from Id as necessary (the above ActionGroup returned when they are created is not of the correct type and we have to obtain after creation this way. +$AutoScaleUpActionGroup = New-AzActionGroup -ActionGroupId $AutoScaleUpActionGroup.Id -WarningAction silentlyContinue -ErrorAction Stop +$AutoScaleDownActionGroup = New-AzActionGroup -ActionGroupId $AutoScaleDownActionGroup.Id -WarningAction silentlyContinue -ErrorAction Stop + +# Specify the initial conditions for the alerts +if ($SeparateProcessingNodeFromQueryReplicas -and $CurrentCapacity -gt 1) { $DimValues = "QueryPool" } else { $DimValues = "*" } +$Dimensions = New-AzMetricAlertRuleV2DimensionSelection -DimensionName "ServerResourceType" -ValuesToInclude $DimValues -WarningAction silentlyContinue -ErrorAction Stop +$TargetResourceId = "/subscriptions/" + $SubscriptionId + "/resourceGroups/" + $ResourceGroupName + "/providers/Microsoft.AnalysisServices/servers/" + $ASServerName + +if ($CurrentSKU -eq $MaxTier -and $CurrentCapacity -eq ($MaxReplicas + 1)) { $NewQPUUpperBound = 99999 } +else { $NewQPUUpperBound = $($($TierMaxes.GetEnumerator()) | Where-Object { $_.Name -eq $CurrentSKU }).Value * (1 - ($ScaleUpDownOutAtPctDistanceFromTierMax * .01)) } + +if ($CurrentCapacity -gt $MinCapacity) +{ + $NewQPULowerBound = (1 - $ScaleInAtPctDistanceFromTierMax * .01) * $($TierMaxes.GetEnumerator() | Where-Object { $_.Name -eq $CurrentSKU }).Value +} +else +{ + if ($CurrentSKU -eq $MinTier -and $CurrentCapacity -eq $MinCapacity) { $NewQPULowerBound = 0 } + else + { + $HighestLowerTierQPUAvailable = 0 + for ($i = $($TierMaxes.Keys).IndexOf($CurrentSKU) - 1; $i -ge 0; $i--) + { + if ($HighestLowerTierQPUAvailable -lt $TierMaxes[$i]) { $HighestLowerTierQPUAvailable = $TierMaxes[$i] } + } + $NextLowerSKU = ($TierMaxes.GetEnumerator() | Where-Object {$_.Value -eq $HighestLowerTierQPUAvailable}).Key + $NewQPULowerBound = $($($TierMaxes.GetEnumerator()) | Where-Object { $_.Name -eq $NextLowerSKU }).Value * (1 - ($ScaleUpDownOutAtPctDistanceFromTierMax * .01)) + } +} +Write-Host " Success!" -ForegroundColor Green + +# Create the up/down alert criteria +Write-Host "Creating alert criteria..." -NoNewline +Write-Progress -Activity "Installing Azure Analysis Services QPU AutoScale" -PercentComplete 80 -Status "Creating alert criteria..." +$UpperBoundCriteria = New-AzMetricAlertRuleV2Criteria -MetricName "qpu_metric" -DimensionSelection $Dimensions -TimeAggregation Average -Operator GreaterThan -Threshold $NewQPUUpperBound -WarningAction silentlyContinue -ErrorAction Stop +$LowerBoundCriteria = New-AzMetricAlertRuleV2Criteria -MetricName "qpu_metric" -DimensionSelection $Dimensions -TimeAggregation Maximum -Operator LessThan -Threshold $NewQPULowerBound -WarningAction silentlyContinue -ErrorAction Stop +Write-Host " Success!" -ForegroundColor Green + +$Window = New-TimeSpan -Minutes $AlertWindowInMins + +# And finally add the rules +Write-Host "Adding rules " -NoNewline +Write-Host "AutoScaleUpAlert" -NoNewline -ForegroundColor Green +Write-Host " and " -NoNewline +Write-Host "AutoScaleDownAlert" -NoNewline -ForegroundColor Green +Write-Host " for AS server $($ASServerName)..." -NoNewline +Write-Progress -Activity "Installing Azure Analysis Services QPU AutoScale" -PercentComplete 95 -Status "Adding rules AutoScaleUpAlert and AutoScaleDownAlert for AS server $($ASServerName)..." +Add-AzMetricAlertRuleV2 -Condition $UpperBoundCriteria -Name "AutoScaleUpAlert" -ResourceGroupName $ResourceGroupName -TargetResourceId $TargetResourceId ` + -ActionGroup $AutoScaleUpActionGroup -WindowSize $Window -Frequency $Window -Severity 3 -WarningAction silentlyContinue -ErrorAction Stop ` + -Description "Metric alert rule to automatically scale up Azure AS when average QPU exceeeds threshold for the configured time window." ` + | Out-Null +Add-AzMetricAlertRuleV2 -Condition $LowerBoundCriteria -Name "AutoScaleDownAlert" -ResourceGroupName $ResourceGroupName -TargetResourceId $TargetResourceId ` + -ActionGroup $AutoScaleDownActionGroup -WindowSize $Window -Frequency $Window -Severity 3 -WarningAction silentlyContinue -ErrorAction Stop ` + -Description "Metric alert rule to automatically scale down Azure AS when max QPU is below threshold for the configured time window." ` + | Out-Null +Write-Host " Success!`n" -ForegroundColor Green + +Write-Host ("`nAutoScale configured and active for server " + $ASServerName + "!") -ForegroundColor Green + +if ($CurrentCapacity -gt $MinCapacity) +{ + if ($CurrentCapacity -lt ($MaxReplicas + 1)) { $NextUp = "Server will add a replica when average QPU exceeds " + $NewQPUUpperBound + " over " + $AlertWindowInMins + " minute(s)." } + else { $NextUp = "Server will not scale up or out further from this tier/capacity." } + $NextDown = "Server will remove a replica when maximum QPU does not exceed " + $NewQPULowerBound + " over " + $AlertWindowInMins + " minute(s)." +} +else +{ + if ($CurrentSKU -gt $MinTier) + { + if ($CurrentSKU -eq $MaxTier) + { + if ($CurrentCapacity -lt ($MaxReplicas + 1)) { $NextUp = "Server will add a replica when average QPU exceeds " + $NewQPUUpperBound + " over " + $AlertWindowInMins + " minute(s)." } + else { $NextUp = "Server will not scale up or out further from this tier/capacity." } + } + else { $NextUp = "Server will scale up when average QPU exceeds " + $NewQPUUpperBound + " over " + $AlertWindowInMins + " minute(s)." } + $NextDown = "Server will scale down SKU when maximum QPU does not exceed " + $NewQPULowerBound + " over " + $AlertWindowInMins + " minutes(s)." + } + else + { + $NextDown = "Server will not scale in or down further from this tier/capacity." + if ($CurrentSKU -ne $MaxTier) { $NextUp = "Server will scale up SKU when average QPU exceeds " + $NewQPUUpperBound + " over " + $AlertWindowInMins + " minute(s)." } + elseif ($CurrentCapacity -lt ($MaxReplicas + 1)) { $NextUp = "Server will add a replica when average QPU exceeds " + $NewQPUUpperBound + " over " + $AlertWindowInMins + " minute(s)." } + else { $NextUp = "Server will not scale up or out further from this tier/capacity." } + } +} + +[ordered]@{ + "Current SKU:" = $CurrentSKU + "Replica Count:" = $CurrentCapacity - 1 + "Next up action:" = $NextUp + "Next down action:" = $NextDown +} | Format-Table -HideTableHeaders -AutoSize + +Write-Progress -Activity "Installing Azure Analysis Services QPU AutoScale" -PercentComplete 100 -Completed \ No newline at end of file From 30838ae968441d129c887fc00fb2aa3a1602490e Mon Sep 17 00:00:00 2001 From: Jon Burchel Date: Tue, 8 Oct 2019 13:06:52 -0500 Subject: [PATCH 2/8] Update README.md --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 761fea8..e41e999 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,9 @@ # Analysis Services Git repo for Analysis Services samples and community projects +## [AASAutoScale](ttps://github.com/Microsoft/Analysis-Services/tree/master/AASAutoScale) +A customizeable QPU AutoScale solution for AAS that supports scale up/down as well as in/out + ## [AsPartitionProcessing](https://github.com/Microsoft/Analysis-Services/tree/master/AsPartitionProcessing) Automated partition management of Analysis Services tabular models From d52fefc82218552501cd3754f6030486d98716de Mon Sep 17 00:00:00 2001 From: Jon Burchel Date: Tue, 8 Oct 2019 13:07:11 -0500 Subject: [PATCH 3/8] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e41e999..25fe7e2 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Analysis Services Git repo for Analysis Services samples and community projects -## [AASAutoScale](ttps://github.com/Microsoft/Analysis-Services/tree/master/AASAutoScale) +## [AASAutoScale](https://github.com/Microsoft/Analysis-Services/tree/master/AASAutoScale) A customizeable QPU AutoScale solution for AAS that supports scale up/down as well as in/out ## [AsPartitionProcessing](https://github.com/Microsoft/Analysis-Services/tree/master/AsPartitionProcessing) From 79a93bef55aef4c63024a1ae277a5a8ccf1205fe Mon Sep 17 00:00:00 2001 From: Jon Burchel Date: Tue, 8 Oct 2019 13:10:04 -0500 Subject: [PATCH 4/8] Create Readme.txt --- AASAutoScale/Readme.txt | 72 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 AASAutoScale/Readme.txt diff --git a/AASAutoScale/Readme.txt b/AASAutoScale/Readme.txt new file mode 100644 index 0000000..cfbe40e --- /dev/null +++ b/AASAutoScale/Readme.txt @@ -0,0 +1,72 @@ +Azure Analysis Services QPU AutoScale + +This PowerShell script deploys a customizable QPU AutoScale solution for Azure Analysis Services. + +Requirements + +The script requires the subscription containing the target AAS instance also contain an Azure Automation Account, where AutoScale will be deployed. The Automation Account must import the latest updated modules for Az.Accounts, Az.AnalysisServices, Az.Automation, Az.Monitor, and AzureRM.Insights. + +The script must be executed by an AAD authenticated user who is a server level administrator of the AAS instance and administrator/owner of the Automation Account. + +NOTE: If Active Directory Connect is not setup in AAD to translate Windows accounts to AAD automatically, it is necessary to run Login-AzAccount in the PowerShell command window before running this script. + +The script must be run while the instance is started. Otherwise it will fail with an appropriate error. + +Manual scaling should not be performed while AutoScale is active. However, manually scaling will also not hurt the configuration. The configuration will simply continue to function as configured, and the next time a threshold is encountered within the configured limits, AutoScale will bring the instance back into its configured limits on the next action. + +Behavior + +The AutoScale behavior when reaching the defined upper bound is to first scale up until maximum tier is reached, and thereafter to scale out to the maximum replica count. Likewise, when the lower bound is reached, scaled-out instances are scaled-in first until minimum replica count is reached, and then the instance is scaled down until minimum tier is reached. + +The alerting behavior is triggered when any node used for querying within the current configuration passes the configured QPU thresholds, either to increase resources or decrease. If the processing node is separated from the query nodes, it is ignored for AutoScale purposes. + +Deployment + +To initially deploy AutoScale, just run DeployAASAutoscale.ps1 from a PowerShell command prompt while authenticated as an AAD user as described above in the requirements. Provide values for all the parameters as prompted. + +The required parameters for all operations (deploy/remove) are: + +-SubscriptionID +-ResourceGroupName +-AutomationAccount +-ASServerName + +To remove AutoScale, include the -Remove parameter with the above required parameters. But it is not necessary to remove an existing configuration to update it. Simply run the command with the new desired values to reconfigure. + +When deploying or reconfiguring AutoScale, the following parameters are also required: + +-AlertWindowInMins +-MinTier +-MaxTier +-MinReplicas +-MaxReplicas + +The script checks the region of the specified AAS instance to be sure the specified tier and replica count max/min values are all within the region’s supported limits. If they are not, an error is reported that outputs the available options within the region’s limits. + +The following optional parameters for deployment can be specified, but have default values: + +-SeparateProcessingNodeFromQueryReplicas = $true + +For scaling up or down and adding replicas, -ScaleUpDownOutAtPctDistanceFromTierMax controls the tier QPU threshold percentage. It makes sense to set this threshold quite close to the top of the relevant tier’s range (10 by default is 90% of the max). For up and out, we want to get the most out of our current tier before we pay for a higher tier or another instance. Also, when scaling down, we want to scale down to the cheaper tier as soon as possible once we know we are under the limits for that tier. + +But for scaling IN from a scaled-out scenario with multiple query replicas, then we will want to set a more relaxed threshold, so we have -ScaleInAtPctDistanceFromTierMax (25 by default is only 75% of the max). That’s because a single instance falling below the max for the tier will not typically be enough reason to remove an entire node from the replica count. Instead, we will wait for a more significant lull, and not scale in if that is the next action, until we are a greater distance from the tier maximum. + +Finally, there is an optional -Force parameter, which will prevent the script from failing if the current tier/replica count is outside of the configured limit. Normally this will cause an error to be reported, but if -Force is specified, the deployment will continue. The limits will still be applied as specified, and the next scale event, up or down, will move the instance to the next appropriate selection of tier/replica count within its configuration. If there is an existing alert being processed, or if something failed while a prior alert was being processed, this can also cause failure of the script too, but the -Force parameter will ignore any errors caused by these issues and continue to deploy when specified. + +Infrastructure + +AutoScale deploys the following objects into Azure: +* A runbook called AASAutoScale-, deployed into the specified Automation Account. +* Two web hooks for the runbook, which are used to invoke the script for up and down events. They are named AutoScaleUpWebhookXXXXXXXXX and AutoScaleDownWebhookXXXXXXXXX. +* Two action rules called AutoScaleUpAlert and AutoScaleDownAlert, for the AS server specified. +* Two action groups called AutoScaleUpActionGroup and AutoScaleDownActionGroup, which are used to invoked the webhooks to the runbook, whenever the alerts are fired. + +Calling the script with -Remove deletes all these objects from Azure so there is no cleanup required. + +Monitoring/Debugging + +To monitor AutoScale, you can check a number of places: +* The AAS instance’s own diagnostics and health history +* The Alerts AutoScale creates for the AAS instance, where there is a history of when they were called +* The history for the AASAutoScale- runbook in the Automation Account +The history for the runbook is particularly important. You will see a history of times AutoScale was invoked, and for each, on the output from the runbook you can find the result, including the prior and new configuration indicating the action AutoScale took, and the next action it will take when the threshold max or min values are reached. QPU values here are expressed in hard values given current actual tier settings, rather than their configured AutoScale percentage values. If there is any failure, you will find exception details, etc. here as well. \ No newline at end of file From 613876aaa44f0fbd083aefa5b67e1e440a54d6ce Mon Sep 17 00:00:00 2001 From: Jon Burchel Date: Tue, 8 Oct 2019 13:11:43 -0500 Subject: [PATCH 5/8] Delete Azure Analysis Services QPU AutoScale ReadMe.docx --- ... Analysis Services QPU AutoScale ReadMe.docx | Bin 27474 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 AASAutoScale/Azure Analysis Services QPU AutoScale ReadMe.docx diff --git a/AASAutoScale/Azure Analysis Services QPU AutoScale ReadMe.docx b/AASAutoScale/Azure Analysis Services QPU AutoScale ReadMe.docx deleted file mode 100644 index 21a791ea5778f3e66ef0bf83b51f3343b410a7ea..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 27474 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 S$ec;?$q@KqM@YtJul^5_*iyCt From a1246b059ee8087b4cbb0bd31ebce4b33fa0f401 Mon Sep 17 00:00:00 2001 From: Jon Burchel Date: Tue, 8 Oct 2019 13:12:56 -0500 Subject: [PATCH 6/8] Update Readme.txt --- AASAutoScale/Readme.txt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/AASAutoScale/Readme.txt b/AASAutoScale/Readme.txt index cfbe40e..f10f69f 100644 --- a/AASAutoScale/Readme.txt +++ b/AASAutoScale/Readme.txt @@ -1,4 +1,4 @@ -Azure Analysis Services QPU AutoScale +#Azure Analysis Services QPU AutoScale This PowerShell script deploys a customizable QPU AutoScale solution for Azure Analysis Services. @@ -41,15 +41,15 @@ When deploying or reconfiguring AutoScale, the following parameters are also req -MinReplicas -MaxReplicas -The script checks the region of the specified AAS instance to be sure the specified tier and replica count max/min values are all within the region’s supported limits. If they are not, an error is reported that outputs the available options within the region’s limits. +The script checks the region of the specified AAS instance to be sure the specified tier and replica count max/min values are all within the region’s supported limits. If they are not, an error is reported that outputs the available options within the region’s limits. The following optional parameters for deployment can be specified, but have default values: -SeparateProcessingNodeFromQueryReplicas = $true -For scaling up or down and adding replicas, -ScaleUpDownOutAtPctDistanceFromTierMax controls the tier QPU threshold percentage. It makes sense to set this threshold quite close to the top of the relevant tier’s range (10 by default is 90% of the max). For up and out, we want to get the most out of our current tier before we pay for a higher tier or another instance. Also, when scaling down, we want to scale down to the cheaper tier as soon as possible once we know we are under the limits for that tier. +For scaling up or down and adding replicas, -ScaleUpDownOutAtPctDistanceFromTierMax controls the tier QPU threshold percentage. It makes sense to set this threshold quite close to the top of the relevant tier’s range (10 by default is 90% of the max). For up and out, we want to get the most out of our current tier before we pay for a higher tier or another instance. Also, when scaling down, we want to scale down to the cheaper tier as soon as possible once we know we are under the limits for that tier. -But for scaling IN from a scaled-out scenario with multiple query replicas, then we will want to set a more relaxed threshold, so we have -ScaleInAtPctDistanceFromTierMax (25 by default is only 75% of the max). That’s because a single instance falling below the max for the tier will not typically be enough reason to remove an entire node from the replica count. Instead, we will wait for a more significant lull, and not scale in if that is the next action, until we are a greater distance from the tier maximum. +But for scaling IN from a scaled-out scenario with multiple query replicas, then we will want to set a more relaxed threshold, so we have -ScaleInAtPctDistanceFromTierMax (25 by default is only 75% of the max). That’s because a single instance falling below the max for the tier will not typically be enough reason to remove an entire node from the replica count. Instead, we will wait for a more significant lull, and not scale in if that is the next action, until we are a greater distance from the tier maximum. Finally, there is an optional -Force parameter, which will prevent the script from failing if the current tier/replica count is outside of the configured limit. Normally this will cause an error to be reported, but if -Force is specified, the deployment will continue. The limits will still be applied as specified, and the next scale event, up or down, will move the instance to the next appropriate selection of tier/replica count within its configuration. If there is an existing alert being processed, or if something failed while a prior alert was being processed, this can also cause failure of the script too, but the -Force parameter will ignore any errors caused by these issues and continue to deploy when specified. @@ -66,7 +66,7 @@ Calling the script with -Remove deletes all these objects from Azure so there is Monitoring/Debugging To monitor AutoScale, you can check a number of places: -* The AAS instance’s own diagnostics and health history +* The AAS instance’s own diagnostics and health history * The Alerts AutoScale creates for the AAS instance, where there is a history of when they were called * The history for the AASAutoScale- runbook in the Automation Account -The history for the runbook is particularly important. You will see a history of times AutoScale was invoked, and for each, on the output from the runbook you can find the result, including the prior and new configuration indicating the action AutoScale took, and the next action it will take when the threshold max or min values are reached. QPU values here are expressed in hard values given current actual tier settings, rather than their configured AutoScale percentage values. If there is any failure, you will find exception details, etc. here as well. \ No newline at end of file +The history for the runbook is particularly important. You will see a history of times AutoScale was invoked, and for each, on the output from the runbook you can find the result, including the prior and new configuration indicating the action AutoScale took, and the next action it will take when the threshold max or min values are reached. QPU values here are expressed in hard values given current actual tier settings, rather than their configured AutoScale percentage values. If there is any failure, you will find exception details, etc. here as well. From e7f10ab0c2fc9bbccdf57db5365b410eb2231017 Mon Sep 17 00:00:00 2001 From: Jon Burchel Date: Tue, 8 Oct 2019 13:13:12 -0500 Subject: [PATCH 7/8] Update Readme.txt --- AASAutoScale/Readme.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AASAutoScale/Readme.txt b/AASAutoScale/Readme.txt index f10f69f..d550dff 100644 --- a/AASAutoScale/Readme.txt +++ b/AASAutoScale/Readme.txt @@ -1,4 +1,4 @@ -#Azure Analysis Services QPU AutoScale +Azure Analysis Services QPU AutoScale This PowerShell script deploys a customizable QPU AutoScale solution for Azure Analysis Services. From 21adc38692f1cf8c400ced58301050633def6f79 Mon Sep 17 00:00:00 2001 From: Jon Burchel Date: Tue, 8 Oct 2019 13:24:25 -0500 Subject: [PATCH 8/8] Update Readme.txt --- AASAutoScale/Readme.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/AASAutoScale/Readme.txt b/AASAutoScale/Readme.txt index d550dff..cb0b3e7 100644 --- a/AASAutoScale/Readme.txt +++ b/AASAutoScale/Readme.txt @@ -56,6 +56,7 @@ Finally, there is an optional -Force parameter, which will prevent the script fr Infrastructure AutoScale deploys the following objects into Azure: + * A runbook called AASAutoScale-, deployed into the specified Automation Account. * Two web hooks for the runbook, which are used to invoke the script for up and down events. They are named AutoScaleUpWebhookXXXXXXXXX and AutoScaleDownWebhookXXXXXXXXX. * Two action rules called AutoScaleUpAlert and AutoScaleDownAlert, for the AS server specified. @@ -66,7 +67,9 @@ Calling the script with -Remove deletes all these objects from Azure so there is Monitoring/Debugging To monitor AutoScale, you can check a number of places: + * The AAS instance’s own diagnostics and health history * The Alerts AutoScale creates for the AAS instance, where there is a history of when they were called * The history for the AASAutoScale- runbook in the Automation Account + The history for the runbook is particularly important. You will see a history of times AutoScale was invoked, and for each, on the output from the runbook you can find the result, including the prior and new configuration indicating the action AutoScale took, and the next action it will take when the threshold max or min values are reached. QPU values here are expressed in hard values given current actual tier settings, rather than their configured AutoScale percentage values. If there is any failure, you will find exception details, etc. here as well.