Saya memiliki Database Azure SQL yang mendukung aplikasi .NET Core API. Menjelajahi laporan ikhtisar kinerja di Portal Azure menunjukkan bahwa sebagian besar beban (penggunaan DTU) pada server database saya berasal dari CPU, dan satu permintaan khusus:
Seperti yang dapat kita lihat, kueri 3780 bertanggung jawab untuk hampir semua penggunaan CPU di server.
Ini agak masuk akal, karena permintaan 3780 (lihat di bawah) pada dasarnya adalah inti dari aplikasi dan sering dipanggil oleh pengguna. Ini juga permintaan yang agak rumit dengan banyak gabungan yang diperlukan untuk mendapatkan set data yang diperlukan. Kueri berasal dari sproc yang akhirnya tampak seperti ini:
-- @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
)
)
Jika Anda peduli, sumber lengkap untuk database ini dapat ditemukan di GitHub di sini . Sumber dari kueri di atas:
- 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
Saya telah menghabiskan beberapa waktu pada permintaan ini selama berbulan-bulan menyesuaikan rencana eksekusi sebaik yang saya tahu, berakhir dengan keadaan saat ini. Pertanyaan dengan rencana eksekusi ini sangat cepat di jutaan baris (<1 detik), tetapi seperti disebutkan di atas, semakin memakan CPU server semakin banyak seiring dengan bertambahnya ukuran aplikasi.
Saya telah melampirkan rencana permintaan aktual di bawah ini (tidak yakin ada cara lain untuk membagikannya di sini di pertukaran tumpukan), yang menunjukkan eksekusi sproc dalam produksi terhadap dataset yang dikembalikan sebesar ~ 400 hasil.
Beberapa poin yang saya cari klarifikasi tentang:
Indeks Cari
[IX_Cipher_UserId_Type_IncludeAll]
membutuhkan 57% dari total biaya rencana. Pemahaman saya tentang rencana ini adalah bahwa biaya ini terkait dengan IO, yang membuat sejak tabel Cipher berisi jutaan catatan. Namun, laporan kinerja Azure SQL menunjukkan kepada saya bahwa masalah saya berasal dari CPU pada permintaan ini, bukan IO, jadi saya tidak yakin apakah ini benar-benar masalah atau tidak. Plus itu sudah melakukan pencarian indeks di sini, jadi saya tidak begitu yakin ada ruang untuk perbaikan.Operasi Pencocokan Hash dari semua gabungan tampaknya menjadi yang menunjukkan penggunaan CPU yang signifikan dalam paket (saya pikir?), Tapi saya tidak begitu yakin bagaimana ini bisa dibuat lebih baik. Sifat kompleks dari bagaimana saya perlu mendapatkan data mengharuskan banyak bergabung di beberapa tabel. Saya sudah melakukan hubungan singkat dengan banyak dari gabungan ini jika memungkinkan (berdasarkan hasil dari gabungan sebelumnya) di
ON
klausa mereka .
Unduh paket eksekusi lengkap di sini: https://www.dropbox.com/s/lua1awsc0uz1lo9/CipherDetails_ReadByUserId.sqlplan?dl=0
Saya merasa seperti saya bisa mendapatkan kinerja CPU yang lebih baik dari permintaan ini, tetapi saya berada pada tahap di mana saya tidak yakin bagaimana untuk melanjutkan penyempurnaan rencana eksekusi lebih jauh. Apa optimasi lain yang bisa dilakukan untuk mengurangi beban CPU? Operasi apa dalam rencana eksekusi yang merupakan pelanggar terburuk penggunaan CPU?
UNION ALL
(satu untukC.[UserId] = @UserId
dan satu untukC.[UserId] IS NULL AND ...
). Ini mengurangi set gabungan hasil dan menghilangkan kebutuhan untuk hash cocok sama sekali (sekarang melakukan loop bersarang pada set gabungan kecil). Permintaan sekarang jauh lebih baik pada CPU. Terima kasih!Jawaban wiki komunitas :
Anda dapat mencoba membaginya menjadi dua pertanyaan dan
UNION ALL
mengembalikannya menjadi satu.WHERE
Klausa Anda terjadi pada akhirnya, tetapi jika Anda membaginya menjadi:C.[UserId] = @UserId
C.[UserId] IS NULL AND OU.[Status] = 2 AND O.[Enabled] = 1
... masing-masing mungkin mendapatkan rencana yang cukup baik untuk membuatnya bernilai sementara.
Jika setiap kueri menerapkan predikat awal dalam paket, Anda tidak perlu bergabung dengan begitu banyak baris yang pada akhirnya disaring.
sumber