SE の雑記

SQL Server の情報をメインに Microsoft 製品の勉強内容を日々投稿

Microsoft Copilot skills in Azure SQL Database を使用したデータベースの稼働状況の調査について

leave a comment

Microsoft Copilot in Azure では、SQL Database 向けのスキルが組み込まれており、Azure ポータルから Copilot で、データベース向けの質問をチャットすることで、SQL Database に特化した回答を得ることができます。

大きなカテゴリとして、次の二つの機能があり、データベースの稼働状況の調査を行う場合には「1.」を活用することになります。

  1. Microsoft Copilot in Azure 統合
  2. 自然言語からの SQL 生成

この機能を活用すると、次の画像のように実際のデータベースの稼働状況に応じた回答が生成されます。

image

この機能がどのようにして動作しているのかが気になったので少し調べてみました。

この機能については Microsoft Copilot in Azure と SQL Database で公開されている記事も参考となります。

データベースからの情報の取得について

Azure SQL データベースの Microsoft Copilot スキル (プレビュー) に関してよく寄せられる質問 に次のように記載されています。

Copilot では、Azure SQL データベース環境のデータをどのように使用しますか?

Copilot では、Azure SQL データベース環境に基づいた応答を生成します。 Copilot からは、ユーザーがアクセスできるリソースにのみアクセスでき、ユーザーが実行するアクセス許可を持つ分析とアクションだけを実行できます。 Copilot では、既存のすべてのアクセス管理と保護が尊重されます。

回答を生成するためにはデータベースから情報を取得する必要がありますが、その情報の取得は Copilot を実行したユーザーに基づいて決定されており、ユーザーが保持している権限以上のデータにはアクセスができないように保護が行われているということになっています。

そのため、Copilot によりデータベースの情報を調査するためには調査用に一定の権限は必要になるのではないでしょうか。

Copilot がデータベースにアクセスする場合、回答の生成中に次のようなメッセージが出力されます。

image

回答の生成中に「Executiong a query on server <論理サーバー名> and <データベース名>. This may take a few moments to complete.」のメッセージが表示されている場合には、SQL Database に対してクエリが実行され、稼働状況の調査が行われていることになります。

このメッセージが出力されなかった場合には、一般的な情報からの回答になっている可能性が高いのではないでしょうか。

Copilot からのクエリの実行状況についてはキャプチャすることができます。私が確認できている範囲では「SQLExternalMonitoring_Copilot」というアプリケーションからの接続については、Copilot が回答を生成するために実行したクエリとなっているようでした。

image

冒頭の添付した画像では サンプル プロンプト に記載されている「Why is the CPU usage high on this database?」を実行して、CPU 使用率を上昇させている要因となるクエリの調査を質問しています。

回答には、要因と考えられるクエリのクエリハッシュが記載されていますが、この情報の取得には実際に SQL Database に対して次のようなクエリが実行されていました。

WITH AggregatedCPU AS ( 
  SELECT 
    '0x'+ CONVERT(VARCHAR(16), CONVERT(VARBINARY(8), q.query_hash), 2) 
      AS query_hash, 
    ROUND( 
      SUM(count_executions * avg_cpu_time / 1000.0), 
      3) AS total_cpu_ms, 
    ROUND( 
      SUM(count_executions * avg_cpu_time / 1000.0)/ SUM(count_executions), 
      3) AS avg_cpu_ms, 
    ROUND(MAX(rs.max_cpu_time / 1000.00), 3) AS max_cpu_ms, 
    ROUND(MAX(max_logical_io_reads), 3) AS max_logical_reads, 
    COUNT(DISTINCT p.plan_id) AS number_of_distinct_plans, 
    COUNT(DISTINCT p.query_id) AS number_of_distinct_query_ids, 
    SUM( 
      CASE 
        WHEN rs.execution_type_desc = 'Aborted' THEN count_executions 
        ELSE 0 
      END) AS aborted_execution_count, 
    SUM( 
      CASE 
        WHEN rs.execution_type_desc = 'Regular' THEN count_executions 
        ELSE 0 
      END) AS regular_execution_count, 
    SUM( 
      CASE 
        WHEN rs.execution_type_desc = 'Exception' THEN count_executions 
        ELSE 0 
      END) AS exception_execution_count, 
    SUM(count_executions) AS total_executions 
  FROM sys.query_store_query_text AS qt 
  INNER JOIN sys.query_store_query AS q  
    ON qt.query_text_id = q.query_text_id 
  INNER JOIN sys.query_store_plan AS p 
    ON q.query_id = p.query_id 
  INNER JOIN sys.query_store_runtime_stats AS rs 
    ON rs.plan_id = p.plan_id 
  INNER JOIN sys.query_store_runtime_stats_interval AS rsi 
    ON rsi.runtime_stats_interval_id = rs.runtime_stats_interval_id 
  WHERE rs.execution_type_desc IN ('Regular', 'Aborted', 'Exception') 
        AND rsi.start_time >= DATEADD(HOUR, -2, GETUTCDATE()) 
  GROUP BY query_hash 
), 
OrderedCPU AS ( 
  SELECT 
    *, 
    ROW_NUMBER() OVER (ORDER BY total_cpu_ms DESC, query_hash ASC) AS RN 
  FROM AggregatedCPU 
) 
SELECT * 
FROM OrderedCPU AS OD 
WHERE OD.RN <= 10 
ORDER BY total_cpu_ms DESC;


SELECT TOP 10 
  req.session_id, 
  req.status, 
  req.start_time, 
  ROUND(req.cpu_time, 3) AS 'cpu_time_ms', 
  '0x'+ CONVERT(VARCHAR(16), CONVERT(VARBINARY(8), req.query_hash), 2) AS query_hash, 
  req.logical_reads, 
  req.dop,s.login_name, 
  s.host_name, 
  s.program_name, 
  OBJECT_NAME(st.objectid, st.dbid) AS 'object_name', 
  REPLACE( 
    REPLACE( 
      SUBSTRING( 
        st.text, 
        (req.statement_start_offset / 2) + 1, 
        (CASE req.statement_end_offset 
           WHEN -1 THEN DATALENGTH(st.text) 
           ELSE req.statement_end_offset 
         END - req.statement_start_offset) / 2 + 1), 
      CHAR(10), ' '), 
    CHAR(13), ' ') AS statement_text 
FROM sys.dm_exec_requests AS req 
INNER JOIN sys.dm_exec_sessions AS s 
  ON req.session_id = s.session_id 
CROSS APPLY sys.dm_exec_sql_text(req.sql_handle) AS st 
WHERE req.session_id <> @@SPID 
ORDER BY req.cpu_time DESC;

 

実行されているクエリとしては次のような内容となります。

  • クエリストアから直近 2 時間で合計 CPU 使用時間の高いクエリの TOP 10 を取得
  • sys.dm_exec_requests から、実行中のクエリで CPU 使用時間の高いクエリの TOP 10 を取得

どちらも CPU 使用率が高い場合に使用することがある可能性の高い汎用的なクエリですね。

現状、調査用のクエリを動的に生成しているというわけではなく、質問に対応したクエリが確認できた場合にはそれを実行して回答に含めるというような動作をしているような雰囲気がありました。

「15:30~15:45 で CPU 使用時間の高かった原因を調査してください」というような、時間を指定したクエリに対しても「直近 2 時間」というような固定的な期間でクエリの実行が行われているようでした。

「現在問題が発生している直近の時間で質問」をした場合には様々な回答を得られそうですが「過去の時間の状態を基準にして調査を行ってもらいたい」というような場合は、Copilot に頼るのは難しそうな気がしました。

すべてのサンプルプロンプトの調査はまだ行えていないのですが、いくつかのサンプルスクリプトで実行されるクエリについては Gist に載せていますので興味のある方は参照してみてください。

「専門の DBA がいないが直近の状態で調査を行う必要がある」場合には、Copilot を使用してみるのは良いかと思います。

しかし、専門的な分析や過去に遡って解析が必要な場合には、現状 Copilot に頼るのは難しく、この観点では DBA のスキルは活かしていくことができるのではないでしょうか。(Database Watcher と Copilot が連携するようになると過去データについても Azure で保持されているデータで分析ができるようになりますので、そうなると Copilot で過去の状態の分析も任せることができるかもしれませんね)

Share

Written by Masayuki.Ozawa

8月 19th, 2024 at 9:41 am

Posted in Azure,SQL Database

Tagged with ,

Leave a Reply