次の方法で共有


I/O パフォーマンス分析 (プレビュー) - Azure VM 上の SQL Server

適用対象:Azure VM 上の SQL Server

この記事では、Azure Virtual Machines (VM) 上の SQL Server の I/O パフォーマンスを分析して、仮想マシンとデータ ディスクの制限を超えた結果として発生する問題を見つける方法について説明します。

Note

Azure portal の Azure VM 上の SQL Server の I/O 分析は現在プレビュー段階です。

概要

さまざまなツールを使用すると、SQL Server のパフォーマンスの問題のトラブルシューティングに役立ちますが、Azure VM でこれを効果的に行うには、ホスト レベルと SQL Server インスタンスの両方で何が起こっているかを理解することが重要です。多くの場合、ホスト メトリックと SQL Server ワークロードの関連付けが困難になる可能性があります。 Azure VM 上の SQL Server を使用すると、仮想マシンとデータ ディスクの制限を超えることによる IOPS (1 秒あたりの入力/出力) とスループット調整に起因するパフォーマンスの問題を簡単に特定できます。

問題を示すパフォーマンス メトリックと、それに対処するための潜在的な手順は、Azure portal で入手でき、Azure CLI を使用してクエリを実行できます。

Azure portal の SQL 仮想マシン リソースの [ストレージ] ウィンドウは、次の場合に役立ちます。

メトリックの理解

[I/O 分析] タブでは、Azure メトリックに依存してディスク待機時間、VM またはディスク I/O 調整を識別します。 Azure メトリックは 30 秒ごとにサンプリングされ、1 分ごとに集計されます。

システムは、調整とディスク待機時間を監視します。 一部の調整は想定されており、ディスクの待ち時間がない限り無視されます。 連続する 5 分間に 500 ミリ秒を超えるディスク待ち時間が発生した場合、システムは次の処理を行います。

  • パフォーマンス メトリックをさらに詳しく調べる
  • 調整されたリソースを識別します
  • 考えられる根本原因と軽減手順を提供します

次の表では、問題のある調整の問題を特定するために使用される Azure メトリックについて説明します。

Azure メトリック メトリックの説明 問題のある状態 I/O 調整の結論
ディスク待機時間 (プレビュー) データ ディスクの監視中に IO を完了するための平均時間。 結果はミリ秒単位で示されます。 > 連続する 5 分間で 500 ミリ秒 システムが潜在的な調整をさらに調査するための待機時間の問題 があります。
VM のキャッシュされた IOPS の消費率 仮想マシンのキャッシュあり IOPS の上限に対する完了済みの合計 IOPS で計算された割合。 >= 連続する 5 分間で 95% VM の調整があります。 SQL 仮想マシンで実行されているアプリケーションは、仮想マシンで使用可能な最大キャッシュ IOPS 容量を完全に利用しています。アプリケーションのストレージ要求は、仮想マシンの基になるストレージ構成によって提供されるキャッシュされた IOPS を超えています。
VM のキャッシュされた帯域幅の消費率 仮想マシンのキャッシュあり最大スループットに対する完了済みの合計ディスク スループットで計算された割合。 >= 連続する 5 分間で 95% VM の調整があります。 SQL 仮想マシンで実行されているアプリケーションは、データ転送に使用可能なキャッシュされたディスク帯域幅の最大値を利用しています。アプリケーションのデータ転送要求は、仮想マシンの基になるストレージ構成によって提供されるキャッシュされた帯域幅リソースを超えています。 
VM のキャッシュされていない IOPS の消費率 仮想マシンのキャッシュなし IOPS の上限に対する仮想マシンの完了済みの合計 IOPS で計算された割合。 >= 連続する 5 分間で 95% VM の調整があります。 SQL 仮想マシンで実行されているアプリケーションは、仮想マシンで使用可能なキャッシュされていない最大 IOPS 容量を利用しています。アプリケーションのストレージ要求は、仮想マシンの基になるストレージ構成によって提供されるキャッシュされていない IOPS リソースを超えています。
VM のキャッシュされていない帯域幅の消費率 仮想マシンのプロビジョニングされた最大スループットに対する仮想マシンの完了済みの合計ディスク スループットで計算された割合。 >= 連続する 5 分間で 95% VM の調整があります。 SQL 仮想マシンで実行されているアプリケーションは、データ転送に許容されるキャッシュされていないディスク帯域幅の最大値を利用しています。アプリケーションのデータ転送要求は、仮想マシンの基になるストレージ構成によって提供されるキャッシュされていない帯域幅リソースを超えています。
データ ディスク IOPS の消費率 プロビジョニングされたデータ ディスク IOPS に対する完了済みのデータ ディスク IOPS で計算された割合。 >= 連続する 5 分間で 95% データ ディスクの調整があります。 SQL 仮想マシンで実行されているアプリケーションが、プロビジョニングされたデータ ディスクの IOPS 制限に達しています。アプリケーションのストレージ要求が、選択したディスク構成のパフォーマンス機能を超えています。
データ ディスク帯域幅の消費率 プロビジョニングされたデータ ディスクのスループットに対する完了済みのデータ ディスクのスループットで計算された割合。 >= 連続する 5 分間で 95% データ ディスクの調整があります。 SQL 仮想マシンで実行されているアプリケーションが、プロビジョニングされたデータ ディスクの IOPS 制限に達しています。アプリケーションのストレージ要求が、選択したディスク構成のパフォーマンス機能を超えています。

I/O 分析結果

過去 24 時間のパフォーマンス メトリックの分析に基づいて、I/O 分析では次のように判断されます。

  • 調整なし
  • VM レベルの I/O 調整
  • ディスク レベルの I/O 調整

I/O 調整の問題なし

パフォーマンスの問題が発生してもディスクの待機時間がない場合、パフォーマンスの問題は I/O 調整の問題が原因ではありません。 他の領域を調査する必要があります。 Azure VM 上の SQL Server のベスト プラクティス チェックリストを使用して、システムが効率的に構成されていることを確認したり、SQL Server のパフォーマンスをトラブルシューティングするための便利なリンクを見つけたりすることができます。 SQL ベスト プラクティス アセスメントを有効にすると、SQL Server VM の推奨事項の完全な一覧が表示されます。

VM レベルの I/O 調整の問題

Azure Virtual Machines はクラウドベースのコンピューティング リソースであり、さまざまなワークロードのさまざまなシリーズとサイズで提供され、それぞれ異なる機能とパフォーマンス特性を備えています。 SQL Server ワークロードの場合、一般に、SQL Server ワークロードに推奨されるシリーズは、Ebdsv5、M、Mv2 シリーズなどのメモリ最適化シリーズです。

VM のサイズによって、SQL Server インスタンスで使用できる vCPU、メモリ、ストレージの数が決まります。 ストレージと比較すると、お客様が仮想マシンのサイズを変更し、アプリケーション リソースのニーズに基づいて VM をスケールアップおよびスケールダウンすることは比較的簡単です。 IOPS とスループットが VM レベルで調整される可能性があるため、パフォーマンスのニーズとワークロードのコストに基づいて適切な VM サイズを選択します。

Azure に移行する場合は、Data Migration AssistantSKU おすすめなどのツールを使用して、現在の SQL Server の構成と使用状況を分析し、Azure のワークロードに最適な VM サイズを提案できます。

次の Azure メトリックを使用して、VM によって課される制限を超えないようにワークロードが調整されていることを判断します。

  • [VM Cached IOPS Consumed Percentage](VM のキャッシュされた IOPS の消費率)
  • VM のキャッシュされた帯域幅の消費率
  • [VM Uncached IOPS Consumed Percentage](VM のキャッシュされていない IOPS の消費率)
  • VM のキャッシュされていない帯域幅の消費率

VM の調整に関する重要なポイントを次に示します。

  • VM シリーズ内の仮想マシンのサイズを変更することで、メモリ、仮想コア、スループット、IOPS を増やすことができます。
  • データ ディスクの数がターゲット VM サイズの最大データ ディスク制限を超えるレベルに VM サイズを減らすことはできません。
  • 調整パターンを決定することが重要です。 たとえば、ワークロードのチューニングによって調整の急増が頻繁に発生する可能性があります。一方、持続的なスパイクは、基になるストレージがワークロードを処理できないことを示している可能性があります。

ディスク レベルの I/O 調整の問題

SQL 仮想マシンのお客様にとって、ストレージは、仮想マシンのサイズを変更するよりも難しいため、最適化されたパフォーマンスのために適切に構成する最も重要な側面です。 たとえば、プレミアム SSD ディスクの IOPS またはスループットを向上させるために変更を加える場合は、新しい記憶域プールを作成する必要があります。 そのため、デプロイ後のパフォーマンスの問題を回避するために、計画フェーズ中に価格とパフォーマンスの両方のストレージ構成を最適化することが重要です。

次の Azure メトリックを使用して、ディスクによって課される制限を超えないようにワークロードが調整されていることを確認します。

  • [Data Disk IOPS Consumed Percentage](データ ディスク IOPS の消費率)

  • データ ディスク帯域幅の消費率 ディスク レベルの I/O 調整に関する次の重要なポイントを考慮してください。

  • データ ディスクは、SQL Server のパフォーマンスにとって重要です。 データ ディスクに SQL Server データ (.mdf) ファイルとログ (.df) ファイルを配置することをお勧めします。

  • データ ディスク レベルで調整を行う場合は、読み取りキャッシュを有効にします (使用可能な場合)。

[Data Disk IOPS Consumed Percentage](データ ディスク IOPS の消費率)

データ ディスクの IOPS 消費率メトリックは、ディスク レベルでの IOPS 消費量を測定します。 一般に、高 IOPS のニーズは、高トランザクションの OLTP ベースのアプリケーションとワークロードに関連付けられています。   次のシナリオまたは条件は、データ ディスクの IOPS の制限を超える可能性があります。

  • 高トランザクション ワークロード (IOPS): 頻繁な読み取りと書き込みの操作を伴う大量のデータベース トランザクションをアプリケーションが処理している場合、割り当てられた IOPS をすぐに使用できます。 
  • 非効率的なクエリ: SQL クエリまたはデータ取得操作が最適化されていないと、I/O アクティビティが過剰になり、予想よりも多くの IOPS が消費される可能性があります。 
  • 同時ユーザー: 複数のユーザーまたはセッションがデータベースに同時にアクセスし、I/O 要求を生成している場合、累積効果によって IOPS の制限に達する可能性があります。 
  • 競合するリソース: 基になる物理インフラストラクチャが他のテナントまたはワークロードと頻繁に共有されている場合、仮想マシンで使用可能な IOPS に影響を与える可能性があります。 
  • 一時的なスパイク: バッチ処理やデータ移行などのワークロードの一時的な急増により、割り当てられた IOPS を超える I/O 需要が急激に増加する可能性があります。 
  • 小さいディスク サイズ: プロビジョニングされたデータ ディスク サイズが比較的小さい場合、IOPS 容量が制限される可能性があります。 個々の小さいディスクの IOPS 制限は低く、アプリケーションの要求がこの制限を超えると、"Data Disk IOPS Consumed Percentage" は 100% に達します。 
  • ディスクの種類が不十分: I/O 集中型アプリケーションに対して低パフォーマンスのディスクの種類 (Standard HDD など) を選択すると、IOPS の制限が発生する可能性があります。 
  • 最適化されていないディスク ストライプ サイズ: ストレージ構成がワークロード用に最適化されていない場合、最適でない IOPS パフォーマンスにつながる可能性があります。 

データ ディスクの IOPS 制限を超えないようにするには、次の手順を検討してください。

  • 不要な I/O 操作を最小限に抑えるために、SQL クエリとデータベース設計を最適化します。 
  • アプリケーションの IOPS 要件に一致する適切なディスクの種類 (Standard SSD または プレミアム SSD) を選択します。 
  • ディスク サイズを大きくして、使用可能な IOPS 容量を増やします。 
  • RAID 構成を使用して、複数のデータ ディスクに I/O を分散します。 

データ ディスク帯域幅の消費率

Azure のデータ ディスク帯域幅消費率メトリックは、ディスク レベルでの帯域幅使用率を測定します。 一般に、高スループットのニーズは、データ ウェアハウス、データ マート、レポート、ETL、およびその他のデータ分析ワークロードに関連しています。

次のシナリオまたは条件は、データ ディスクの帯域幅の制限を超える可能性があります。

  • 大規模なデータ転送: ディスクと SQL データベース間のアプリケーション データ転送が頻繁に行われると、使用可能なデータ ディスク帯域幅がすぐに消費される可能性があります。 
  • 一括データの読み込み: 一括データの挿入、更新、またはインポートに関連付けられているディスク転送アクティビティは、高帯域幅の消費につながる可能性があります。 
  • データ ウェアハウスまたは分析: 大量のデータ ウェアハウス、分析処理、またはレポートを含むアプリケーションでは、大量のデータ移動が発生し、帯域幅の制限を超える可能性があります。
  • 高いデータ冗長性テクノロジ/レプリケーション: 関連するデータ コピーでは、ディスク ベースのレプリケーション、データミラー、またはその他の冗長性メカニズムが使用され、帯域幅の飽和に寄与する可能性があります。 
  • データのバックアップと復元: 頻繁なデータ バックアップ、スナップショット、または復元プロセスでは、大量のデータ ディスク帯域幅が消費される可能性があります。 
  • 並列クエリの実行: 大規模なデータ スキャンまたは結合を伴う並列クエリは、帯域幅の使用率につながる大量のデータ移動につながる可能性があります。 
  • ネットワーク トラフィックの昇格: 仮想マシンと他のリソース間のデータ転送などの高いネットワーク アクティビティは、データ ディスクの帯域幅の可用性に間接的に影響する可能性があります。 
  • ディスクの種類が不十分: データ転送要件が高いアプリケーションに対してパフォーマンスの低いディスクの種類を選択すると、帯域幅の制限を超える可能性があります。 
  • 同時データ集中型操作: 複数の同時プロセスまたはセッションが同じディスク上のデータにアクセスして転送すると、帯域幅の上限に達する可能性があります。 
  • 最適化されていないクエリまたは ETL プロセス: 最適化されていない SQL クエリまたは抽出、変換、読み込み (ETL) プロセスは、過剰なデータ移動を引き起こす可能性があります。その結果、帯域幅が過剰に消費される可能性があります。 

データ ディスクの帯域幅制限を超えないようにするには、次の手順を検討してください。

  • データ転送操作を最適化して、不要なデータ移動を最小限に抑えます。 
  • プレミアム SSD や プレミアム SSD v2 など、より高い帯域幅容量を提供するパフォーマンスの高いディスクの種類を使用することを検討してください。
  • パーティション分割やシャーディングなどの手法を使用して、複数のディスクにデータを分散します。
  • クエリとデータ処理を最適化して並列化し、データ移動を減らします。
  • 圧縮と効率的なデータ ストレージ メカニズムを使用して、転送されるデータの量を減らします。
  • パフォーマンス メトリックを監視し、必要に応じてストレージ構成をスケールアップします。 プレミアム SSD v2 を使用すると、必要に応じて IOPS とスループットを必要に応じてスケーリングできます。
  • パフォーマンス メトリックを定期的に監視および分析して、IOPS の制限の原因を特定し、SQL 仮想マシンのストレージ パフォーマンスを最適化するための適切なアクションを実行することが重要です。

ヒント

パフォーマンス メトリックの定期的な監視、データ転送操作のチューニング、ディスク構成の最適化により、SQL 仮想マシンのデータ ディスクのパフォーマンスがメイン制限を超えることなく最適になります。

適切に構成されていないストレージ システムはパフォーマンスの問題につながる可能性があるため、Azure portal の [ストレージ] ウィンドウを使用して、SQL ベスト プラクティス アセスメント規則のディスク固有のサブセットを実行して、Azure VM 上の SQL Server に関するストレージ構成の問題を特定できます。 SQL のベスト プラクティス機能は、SQL Assessment API に基づいています。

GitHub で推奨事項の完全な一覧を表示できます。 GitHub の規則の ID 列をフィルター処理すると、Azure portal の SQL 仮想マシン リソースの [ストレージ] ウィンドウの [I/O 構成のベスト プラクティス] タブで検証される SQL VM ディスク構成規則を確認できます。

  • AzSqlVmSize
  • AzDataDiskCache
  • AzDataDiskStriping
  • AzDataOnDataDisks
  • AzDbDefaultLocation
  • AzDiskColumnCount
  • AzErrorLogLocation
  • AzPremSsdDataFiles
  • AzTempDbFileLocation
  • AzTranLogDiskCache
  • NtfsBlockSizeNotFormatted
  • LockedPagesInMemory

[I/O 関連のベスト プラクティス] タブで、[評価の実行] を使用して構成の評価を開始します。完了するまでに数分かかります (データベースとオブジェクトが多数ある場合を除く)。 または、利用可能な最新の結果のタイムスタンプが表示される場合は、最新の結果をフェッチを使用して、以前の評価の結果を確認できます。

PowerShell を使用して I/O を分析する

I/O 分析 PowerShell スクリプトを使用して、SQL Server VM の IO パフォーマンスを分析することもできます。

# Enter parameters
$subscriptionId = Read-Host "<Subscription ID>"
$resourceGroup = Read-Host "<Resource Group>"
$vmName = Read-Host "<Virtual machine name>"

# Set resource details
$resourceType = "Microsoft.Compute/virtualMachines"
$resourceId = "/subscriptions/$subscriptionId/resourceGroups/$resourceGroup/providers/$resourceType/$vmName"

# Get Azure access token
$accessToken = az account get-access-token --query accessToken -o tsv

# Invoke Azure Monitor Metrics API
function Get-Metrics {
    [CmdletBinding()]
    param (
        [string]$accessToken,
        [string]$resourceId,
        [string]$metricNames,
        [string]$apiVersion = "2023-10-01"
    )
    try {
        $startTime = (Get-Date).AddHours(-24).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ssZ')
        $endTime = (Get-Date).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ssZ')
        $timespan = "$startTime/$endTime"
        Write-Verbose "Evaluating timespan: $timespan"
        $uri = "https://management.azure.com$resourceId/providers/Microsoft.Insights/metrics?api-version=$apiVersion&metricnames=$metricNames&aggregation=maximum&interval=PT1M&timespan=$timespan"
        $headers = @{ "Authorization" = "Bearer $accessToken"; "Content-Type" = "application/json" }
        
        $response = Invoke-RestMethod -Uri $uri -Headers $headers -Method Get
        if ($response) {
            Write-Verbose "API response successfully retrieved."
            return $response
        } else {
            Write-Error "No response from API."
        }
    } catch {
        Write-Error "Error retrieving metrics: $_"
    }
}

# Check if data disk latency violates thresholds
function Check-Latency {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [Object]$metrics,

        [Parameter()]
        [int]$latencyThreshold = 500,

        [Parameter()]
        [int]$consecutiveCount = 5
    )
    $violationTimes = @()
    foreach ($metric in $metrics.value) {
        if ($metric.name.value -eq "Data Disk Latency") {
            $count = 0
            foreach ($dataPoint in $metric.timeseries[0].data) {
                if ($dataPoint.maximum -gt $latencyThreshold) {
                    $count++
                    if ($count -ge $consecutiveCount) {
                        $violationTimes += $dataPoint.timeStamp
                        $count = 0  # Reset count after recording a violation
                    }
                } else {
                    $count = 0  # Reset count if the sequence is broken
                }
            }
        }
    }
    if ($violationTimes.Count -gt 0) {
        Write-Verbose "Latency violations detected."
        return @{ "Flag" = $true; "Times" = $violationTimes }
    } else {
        Write-Verbose "No latency violations detected."
        return @{ "Flag" = $false }
    }
}

# Check metrics other than latency to evaluate for throttling
function Check-OtherMetricsThrottled {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [Object]$metrics,

        [Parameter()]
        [int]$PercentageThreshold = 90,

        [Parameter()]
        [int]$consecutiveCount = 5
    )
    $violatedMetrics = @()
    foreach ($metric in $metrics.value) {
        $count = 0
        foreach ($dataPoint in $metric.timeseries[0].data) {
            if ($dataPoint.maximum -gt $PercentageThreshold) {
                $count++
                if ($count -ge $consecutiveCount) {
                    $violatedMetrics += @{ "Metric" = $metric.name.localizedValue; "Time" = $dataPoint.timeStamp; "Value" = $dataPoint.maximum }
                    break
                }
            } else {
                $count = 0
            }
        }
    }
    if ($violatedMetrics.Count -gt 0) {
        Write-Verbose "Other metrics violations detected."
    } else {
        Write-Verbose "No other metrics violations detected."
    }
    return $violatedMetrics
}

# Compare times for latency & other throttled metrics. Logs the volations with values & timestamps
function CompareTimes {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [Hashtable]$latencyResult,
        
        [Parameter(Mandatory = $true)]
        [Array]$otherMetrics
    )
    foreach ($metric in $otherMetrics) {
        $otherDateTime = [DateTime]$metric["Time"]
        $isWithinFiveMinutes = $false
        $closestLatencyTime = $null
        $closestTimeDifference = [int]::MaxValue

        foreach ($latencyTime in $latencyResult.Times) {
            $latencyDateTime = [DateTime]$latencyTime
            $timeDifference = [Math]::Abs(($otherDateTime - $latencyDateTime).TotalMinutes)
            
            if ($timeDifference -le 5) {
                $isWithinFiveMinutes = $true
                if ($timeDifference -lt $closestTimeDifference) {
                    $closestTimeDifference = $timeDifference
                    $closestLatencyTime = $latencyTime
                }
            }
        }

        if ($isWithinFiveMinutes) {
            if ($otherDateTime -lt $closestLatencyTime) {
                Write-Host "`n $($metric["Metric"]) limit was hit before latency spiked at $closestLatencyTime with value $($metric["Value"]). `n"
            } else {
                Write-Host "`n $($metric["Metric"]) hit its limit with value $($metric["Value"]) at $($metric["Time"])."
                Write-Host "Latency spiked at $closestLatencyTime before $($metric["Metric"]) hit its limit `n"
            }
        } else {
            Write-Host "`n Metric: $($metric["Metric"]) exceeded its threshold with a value of $($metric["Value"]) at $($metric["Time"]), but this was not within 5 minutes of any latency spikes."
        }
    }
}

# Prompt user for latency threshold
$latencyThreshold = Read-Host "Enter Latency Threshold (default is 500)"
if (-not [int]::TryParse($latencyThreshold, [ref]0)) {
    $latencyThreshold = 500 # Use default if invalid input
    Write-Host "No valid input provided. Using Default 500ms for disk latency threshold"
}

# Execute main logic
$latencyMetrics = Get-Metrics -accessToken $accessToken -resourceId $resourceId -metricNames "Data Disk Latency"
$latencyResult = Check-Latency -metrics $latencyMetrics -latencyThreshold $latencyThreshold

if ($latencyResult.Flag) {
    
    # If latency is flagged, check for other metrics. If there is no disk latency, machine is likely not throttled but only at high consumption
    Write-Verbose "Checking the following metrics: Data Disk Bandwidth Consumed Percentage,Data Disk IOPS Consumed Percentage,VM Cached Bandwidth Consumed Percentage,VM Cached IOPS Consumed Percentage,VM Uncached Bandwidth Consumed Percentage,VM Uncached IOPS Consumed Percentage"
    
    $DiskVMMetrics = Get-Metrics -accessToken $accessToken -resourceId $resourceId -metricNames "Data Disk Bandwidth Consumed Percentage,Data Disk IOPS Consumed Percentage,VM Cached Bandwidth Consumed Percentage,VM Cached IOPS Consumed Percentage,VM Uncached Bandwidth Consumed Percentage,VM Uncached IOPS Consumed Percentage"
    
    $additionalMetrics = Check-OtherMetricsThrottled -metrics $DiskVMMetrics
    
    if ($additionalMetrics.Count -gt 0) {
        CompareTimes $latencyResult $additionalMetrics
    } else {
        Write-Host "No metrics violations detected besides latency."
    }
} else {
    Write-Host "No latency issues detected."
}

次のステップ