.NET Core APIアプリを強化するAzure SQLデータベースがあります。Azure Portalでパフォーマンス概要レポートを参照すると、データベースサーバーの負荷(DTU使用量)の大部分がCPUからのものであり、具体的には1つのクエリが原因であることがわかります。
ご覧のように、クエリ3780は、サーバーのほぼすべてのCPU使用率の原因です。
クエリ3780(下記参照)は基本的にアプリケーションの核心であり、ユーザーから頻繁に呼び出されるため、これは多少意味があります。また、必要な適切なデータセットを取得するために必要な多くの結合を伴う、かなり複雑なクエリでもあります。クエリは、次のようなsprocから取得されます。
-- @UserId UNIQUEIDENTIFIER
SELECT
C.[Id],
C.[UserId],
C.[OrganizationId],
C.[Type],
C.[Data],
C.[Attachments],
C.[CreationDate],
C.[RevisionDate],
CASE
WHEN
@UserId IS NULL
OR C.[Favorites] IS NULL
OR JSON_VALUE(C.[Favorites], CONCAT('$."', @UserId, '"')) IS NULL
THEN 0
ELSE 1
END [Favorite],
CASE
WHEN
@UserId IS NULL
OR C.[Folders] IS NULL
THEN NULL
ELSE TRY_CONVERT(UNIQUEIDENTIFIER, JSON_VALUE(C.[Folders], CONCAT('$."', @UserId, '"')))
END [FolderId],
CASE
WHEN C.[UserId] IS NOT NULL OR OU.[AccessAll] = 1 OR CU.[ReadOnly] = 0 OR G.[AccessAll] = 1 OR CG.[ReadOnly] = 0 THEN 1
ELSE 0
END [Edit],
CASE
WHEN C.[UserId] IS NULL AND O.[UseTotp] = 1 THEN 1
ELSE 0
END [OrganizationUseTotp]
FROM
[dbo].[Cipher] C
LEFT JOIN
[dbo].[Organization] O ON C.[UserId] IS NULL AND O.[Id] = C.[OrganizationId]
LEFT JOIN
[dbo].[OrganizationUser] OU ON OU.[OrganizationId] = O.[Id] AND OU.[UserId] = @UserId
LEFT JOIN
[dbo].[CollectionCipher] CC ON C.[UserId] IS NULL AND OU.[AccessAll] = 0 AND CC.[CipherId] = C.[Id]
LEFT JOIN
[dbo].[CollectionUser] CU ON CU.[CollectionId] = CC.[CollectionId] AND CU.[OrganizationUserId] = OU.[Id]
LEFT JOIN
[dbo].[GroupUser] GU ON C.[UserId] IS NULL AND CU.[CollectionId] IS NULL AND OU.[AccessAll] = 0 AND GU.[OrganizationUserId] = OU.[Id]
LEFT JOIN
[dbo].[Group] G ON G.[Id] = GU.[GroupId]
LEFT JOIN
[dbo].[CollectionGroup] CG ON G.[AccessAll] = 0 AND CG.[CollectionId] = CC.[CollectionId] AND CG.[GroupId] = GU.[GroupId]
WHERE
C.[UserId] = @UserId
OR (
C.[UserId] IS NULL
AND OU.[Status] = 2
AND O.[Enabled] = 1
AND (
OU.[AccessAll] = 1
OR CU.[CollectionId] IS NOT NULL
OR G.[AccessAll] = 1
OR CG.[CollectionId] IS NOT NULL
)
)
気になれば、このデータベースの完全なソースはGitHubこちらにあります。上記のクエリのソース:
- https://github.com/bitwarden/core/blob/master/src/Sql/dbo/Stored%20Procedures/CipherDetails_ReadByUserId.sql
- https://github.com/bitwarden/core/blob/master/src/Sql/dbo/Functions/UserCipherDetails.sql
- https://github.com/bitwarden/core/blob/master/src/Sql/dbo/Functions/CipherDetails.sql
私はこのクエリに数か月かけていくつかの時間を費やして実行計画を調整しましたが、それが現在の状態に至るまでの最良の方法です。この実行プランのクエリは、数百万行(<1秒)にわたって高速ですが、前述のように、アプリケーションのサイズが大きくなるにつれて、サーバーのCPUをどんどん消費しています。
以下の実際のクエリプランを添付しました(ここでスタック交換でそれを共有する他の方法がわからない)。これは、返された約400件のデータセットに対して本番環境でのsprocの実行を示しています。
明確化を求めているいくつかのポイント:
Index Seek on
[IX_Cipher_UserId_Type_IncludeAll]
は、計画の総コストの57%を占めます。プランについての私の理解は、このコストはIOに関連していると理解しています。これは、Cipherテーブルに数百万のレコードが含まれているためです。ただし、Azure SQLパフォーマンスレポートでは、この問題はIOではなく、このクエリのCPUに起因することが示されているため、これが実際に問題であるかどうかはわかりません。さらに、ここではすでにインデックスシークを行っているため、改善の余地があるかどうかは本当にわかりません。すべての結合からのハッシュマッチ操作は、計画でかなりのCPU使用率を示しているように見えます(私はそうでしょうか)。データを取得する方法が複雑であるため、複数のテーブルにまたがる多くの結合が必要になります。これらの結合の多くは、可能であれば(前の結合の結果に基づいて)
ON
節で短絡しています。
ここで完全な実行計画をダウンロード:https : //www.dropbox.com/s/lua1awsc0uz1lo9/CipherDetails_ReadByUserId.sqlplan?dl=0
このクエリからより良いCPUパフォーマンスを得ることができるように感じますが、実行プランのチューニングをさらに進める方法がわからない段階にあります。CPUの負荷を減らすために、他にどのような最適化を行う必要がありますか?実行計画のどの操作がCPU使用率の最悪の違反者ですか?
UNION ALL
(1 つはとC.[UserId] = @UserId
1つC.[UserId] IS NULL AND ...
)でsprocを書き直すことができました。これにより、結合結果セットが減り、ハッシュ一致の必要性が完全になくなりました(小さな結合セットでネストされたループを実行するようになりました)。クエリはCPUではるかに優れています。ありがとうございました!