-
-
Notifications
You must be signed in to change notification settings - Fork 448
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #623 from eventflow/fix-index-fragmentation-for-comb
Fix: Index fragmentation using COMB GUIDs in MSSQL
- Loading branch information
Showing
7 changed files
with
302 additions
and
20 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
20 changes: 20 additions & 0 deletions
20
Source/EventFlow.MsSql.Tests/Extensions/MsSqlDatabaseExtensions.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using Dapper; | ||
using EventFlow.TestHelpers.MsSql; | ||
|
||
namespace EventFlow.MsSql.Tests.Extensions | ||
{ | ||
public static class MsSqlDatabaseExtensions | ||
{ | ||
public static IReadOnlyCollection<T> Query<T>(this IMsSqlDatabase database, string sql) | ||
{ | ||
return database.WithConnection<IReadOnlyCollection<T>>(c => c.Query<T>(sql).ToList()); | ||
} | ||
|
||
public static int Execute(this IMsSqlDatabase database, string sql, object param) | ||
{ | ||
return database.WithConnection(c => c.Execute(sql, param)); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
211 changes: 211 additions & 0 deletions
211
Source/EventFlow.MsSql.Tests/IntegrationTests/IdentityIndexFragmentationTests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,211 @@ | ||
using System; | ||
using System.Linq; | ||
using System.Threading; | ||
using EventFlow.Core; | ||
using EventFlow.Extensions; | ||
using EventFlow.MsSql.Tests.Extensions; | ||
using EventFlow.TestHelpers; | ||
using EventFlow.TestHelpers.MsSql; | ||
using FluentAssertions; | ||
using NUnit.Framework; | ||
|
||
// ReSharper disable StringLiteralTypo | ||
|
||
namespace EventFlow.MsSql.Tests.IntegrationTests | ||
{ | ||
[Category(Categories.Integration)] | ||
public class IdentityIndexFragmentationTests : Test | ||
{ | ||
private const int ROWS = 10000; | ||
private IMsSqlDatabase _testDatabase; | ||
|
||
private class MagicId : Identity<MagicId> | ||
{ | ||
public MagicId(string value) : base(value) | ||
{ | ||
} | ||
} | ||
|
||
[Test] | ||
public void VerifyIdentityHasThereLittleFragmentationUsingString() | ||
{ | ||
// Act | ||
InsertRows(() => MagicId.NewComb().Value, ROWS, "IndexFragmentationString"); | ||
|
||
// Assert | ||
var fragmentation = GetIndexFragmentation("IndexFragmentationString"); | ||
fragmentation.Should().BeLessThan(10); | ||
} | ||
|
||
|
||
[Test] | ||
public void SanityIntLowFragmentationStoredInGuid() | ||
{ | ||
// Arrange | ||
var i = 0; | ||
|
||
// Act | ||
InsertRows(() => | ||
{ | ||
Interlocked.Increment(ref i); | ||
return $"{i,5}"; | ||
}, | ||
ROWS, | ||
"IndexFragmentationString"); | ||
|
||
// Assert | ||
var fragmentation = GetIndexFragmentation("IndexFragmentationString"); | ||
fragmentation.Should().BeLessThan(10); | ||
} | ||
|
||
[Test] | ||
public void SanityIntAsHexLowFragmentationStoredInGuid() | ||
{ | ||
// Arrange | ||
var i = 0; | ||
|
||
// Act | ||
InsertRows(() => | ||
{ | ||
Interlocked.Increment(ref i); | ||
return $"{i,5:X}"; | ||
}, | ||
ROWS, | ||
"IndexFragmentationString"); | ||
|
||
// Assert | ||
var fragmentation = GetIndexFragmentation("IndexFragmentationString"); | ||
fragmentation.Should().BeLessThan(10); | ||
} | ||
|
||
|
||
[Test] | ||
public void SanityCombYieldsLowFragmentationStoredInGuid() | ||
{ | ||
// Act | ||
InsertRows(GuidFactories.Comb.Create, ROWS, "IndexFragmentationGuid"); | ||
|
||
// Assert | ||
var fragmentation = GetIndexFragmentation("IndexFragmentationGuid"); | ||
fragmentation.Should().BeLessThan(10); | ||
} | ||
|
||
[Test] | ||
public void SanityCombYieldsHighFragmentationStoredInString() | ||
{ | ||
// Act | ||
InsertRows(() => GuidFactories.Comb.Create().ToString("N"), ROWS, "IndexFragmentationString"); | ||
|
||
// Assert | ||
var fragmentation = GetIndexFragmentation("IndexFragmentationString"); | ||
fragmentation.Should().BeGreaterThan(90); | ||
} | ||
|
||
[Test] | ||
public void SanityGuidIdentityYieldsHighFragmentationStoredInString() | ||
{ | ||
// Act | ||
InsertRows(() => MagicId.New.Value, ROWS, "IndexFragmentationString"); | ||
|
||
// Assert | ||
var fragmentation = GetIndexFragmentation("IndexFragmentationString"); | ||
fragmentation.Should().BeGreaterThan(30); // closer to 100 in reality | ||
} | ||
|
||
[Test] | ||
public void SanityGuidIdentityYieldsHighFragmentationStoredInGuid() | ||
{ | ||
// Act | ||
InsertRows(() => MagicId.New.GetGuid(), ROWS, "IndexFragmentationGuid"); | ||
|
||
// Assert | ||
var fragmentation = GetIndexFragmentation("IndexFragmentationGuid"); | ||
fragmentation.Should().BeGreaterThan(30); // closer to 100 in reality | ||
} | ||
|
||
public void InsertRows<T>(Func<T> generator, int count, string table) | ||
{ | ||
var ids = Enumerable.Range(0, count) | ||
.Select(_ => generator()) | ||
.ToList(); | ||
|
||
foreach (var id in ids.Take(20)) | ||
{ | ||
Console.WriteLine(id); | ||
} | ||
|
||
foreach (var id in ids) | ||
{ | ||
_testDatabase.Execute($"INSERT INTO {table} (Id) VALUES (@Id)", new { Id = id }); | ||
} | ||
} | ||
|
||
private double GetIndexFragmentation(string table) | ||
{ | ||
const string sql = @" | ||
SELECT dbschemas.[name] as 'schema', | ||
dbtables.[name] as 'table', | ||
dbindexes.[name] as 'index', | ||
indexstats.avg_fragmentation_in_percent AS 'fragmentation', | ||
indexstats.page_count AS 'pageCount', | ||
index_level AS 'level' | ||
FROM sys.dm_db_index_physical_stats (DB_ID(), NULL, NULL, NULL, 'DETAILED') AS indexstats | ||
INNER JOIN sys.tables dbtables on dbtables.[object_id] = indexstats.[object_id] | ||
INNER JOIN sys.schemas dbschemas on dbtables.[schema_id] = dbschemas.[schema_id] | ||
INNER JOIN sys.indexes AS dbindexes ON dbindexes.[object_id] = indexstats.[object_id] | ||
AND indexstats.index_id = dbindexes.index_id | ||
WHERE indexstats.database_id = DB_ID() | ||
ORDER BY dbschemas.[name],dbtables.[name],dbindexes.[name],index_level desc | ||
"; | ||
|
||
var rows = _testDatabase.Query<IndexFragmentationDetails>(sql) | ||
.Where(r => string.Equals(table, r.Table, StringComparison.OrdinalIgnoreCase)) | ||
.OrderBy(r => r.Level) | ||
.ToList(); | ||
|
||
return rows.First().Fragmentation; | ||
} | ||
|
||
[SetUp] | ||
public void SetUp() | ||
{ | ||
_testDatabase = MsSqlHelpz.CreateDatabase("index_fragmentation"); | ||
_testDatabase.Execute("CREATE TABLE IndexFragmentationString (Id nvarchar(250) PRIMARY KEY)"); | ||
_testDatabase.Execute("CREATE TABLE IndexFragmentationGuid (Id uniqueidentifier PRIMARY KEY)"); | ||
} | ||
|
||
[TearDown] | ||
public void TearDown() | ||
{ | ||
_testDatabase.DisposeSafe("DROP test database"); | ||
} | ||
|
||
private class IndexFragmentationDetails | ||
{ | ||
public IndexFragmentationDetails( | ||
string schema, | ||
string table, | ||
string index, | ||
double fragmentation, | ||
long pageCount, | ||
byte level) | ||
{ | ||
Schema = schema; | ||
Table = table; | ||
Index = index; | ||
Fragmentation = fragmentation; | ||
PageCount = pageCount; | ||
Level = level; | ||
} | ||
|
||
public string Schema { get; } | ||
public string Table { get; } | ||
public string Index { get; } | ||
|
||
public double Fragmentation { get; } | ||
public long PageCount { get; } | ||
|
||
public byte Level { get; } | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters