1
0
mirror of https://github.com/bitwarden/server synced 2026-01-31 00:33:17 +00:00

[PM-25138] Reduce db locking when creating default collections (#6308)

* Use single method for default collection creation

* Use GenerateComb to create sequential guids

* Pre-sort data for SqlBulkCopy

* Add SqlBulkCopy options per dbops recommendations
This commit is contained in:
Thomas Rittson
2025-09-18 14:50:36 +10:00
committed by GitHub
parent e46365ac20
commit 780400fcf9
9 changed files with 172 additions and 33 deletions

View File

@@ -1,6 +1,7 @@
using System.Data;
using Bit.Core.Entities;
using Bit.Core.Vault.Entities;
using Bit.Infrastructure.Dapper.Utilities;
using Microsoft.Data.SqlClient;
namespace Bit.Infrastructure.Dapper.AdminConsole.Helpers;
@@ -8,11 +9,25 @@ namespace Bit.Infrastructure.Dapper.AdminConsole.Helpers;
public static class BulkResourceCreationService
{
private const string _defaultErrorMessage = "Must have at least one record for bulk creation.";
public static async Task CreateCollectionsUsersAsync(SqlConnection connection, SqlTransaction transaction, IEnumerable<CollectionUser> collectionUsers, string errorMessage = _defaultErrorMessage)
public static async Task CreateCollectionsUsersAsync(SqlConnection connection, SqlTransaction transaction,
IEnumerable<CollectionUser> collectionUsers, string errorMessage = _defaultErrorMessage)
{
// Offload some work from SQL Server by pre-sorting before insert.
// This lets us use the SqlBulkCopy.ColumnOrderHints to improve performance and reduce deadlocks.
var sortedCollectionUsers = collectionUsers
.OrderBySqlGuid(cu => cu.CollectionId)
.ThenBySqlGuid(cu => cu.OrganizationUserId)
.ToList();
using var bulkCopy = new SqlBulkCopy(connection, SqlBulkCopyOptions.KeepIdentity, transaction);
bulkCopy.DestinationTableName = "[dbo].[CollectionUser]";
var dataTable = BuildCollectionsUsersTable(bulkCopy, collectionUsers, errorMessage);
bulkCopy.BatchSize = 500;
bulkCopy.BulkCopyTimeout = 120;
bulkCopy.EnableStreaming = true;
bulkCopy.ColumnOrderHints.Add("CollectionId", SortOrder.Ascending);
bulkCopy.ColumnOrderHints.Add("OrganizationUserId", SortOrder.Ascending);
var dataTable = BuildCollectionsUsersTable(bulkCopy, sortedCollectionUsers, errorMessage);
await bulkCopy.WriteToServerAsync(dataTable);
}
@@ -96,11 +111,21 @@ public static class BulkResourceCreationService
return table;
}
public static async Task CreateCollectionsAsync(SqlConnection connection, SqlTransaction transaction, IEnumerable<Collection> collections, string errorMessage = _defaultErrorMessage)
public static async Task CreateCollectionsAsync(SqlConnection connection, SqlTransaction transaction,
IEnumerable<Collection> collections, string errorMessage = _defaultErrorMessage)
{
// Offload some work from SQL Server by pre-sorting before insert.
// This lets us use the SqlBulkCopy.ColumnOrderHints to improve performance and reduce deadlocks.
var sortedCollections = collections.OrderBySqlGuid(c => c.Id).ToList();
using var bulkCopy = new SqlBulkCopy(connection, SqlBulkCopyOptions.KeepIdentity, transaction);
bulkCopy.DestinationTableName = "[dbo].[Collection]";
var dataTable = BuildCollectionsTable(bulkCopy, collections, errorMessage);
bulkCopy.BatchSize = 500;
bulkCopy.BulkCopyTimeout = 120;
bulkCopy.EnableStreaming = true;
bulkCopy.ColumnOrderHints.Add("Id", SortOrder.Ascending);
var dataTable = BuildCollectionsTable(bulkCopy, sortedCollections, errorMessage);
await bulkCopy.WriteToServerAsync(dataTable);
}