Reformat / cleanup
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
using System.Text;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Configs;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
using ZB.MOM.WW.CBDD.Bson;
|
||||
using ZB.MOM.WW.CBDD.Core.Collections;
|
||||
using ZB.MOM.WW.CBDD.Core.Storage;
|
||||
@@ -15,21 +15,22 @@ namespace ZB.MOM.WW.CBDD.Tests.Benchmark;
|
||||
[JsonExporterAttribute.Full]
|
||||
public class CompactionBenchmarks
|
||||
{
|
||||
private readonly List<ObjectId> _insertedIds = [];
|
||||
private DocumentCollection<Person> _collection = null!;
|
||||
|
||||
private string _dbPath = string.Empty;
|
||||
private StorageEngine _storage = null!;
|
||||
private BenchmarkTransactionHolder _transactionHolder = null!;
|
||||
private string _walPath = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the number of documents used per benchmark iteration.
|
||||
/// Gets or sets the number of documents used per benchmark iteration.
|
||||
/// </summary>
|
||||
[Params(2_000)]
|
||||
public int DocumentCount { get; set; }
|
||||
|
||||
private string _dbPath = string.Empty;
|
||||
private string _walPath = string.Empty;
|
||||
private StorageEngine _storage = null!;
|
||||
private BenchmarkTransactionHolder _transactionHolder = null!;
|
||||
private DocumentCollection<Person> _collection = null!;
|
||||
private List<ObjectId> _insertedIds = [];
|
||||
|
||||
/// <summary>
|
||||
/// Prepares benchmark state and seed data for each iteration.
|
||||
/// Prepares benchmark state and seed data for each iteration.
|
||||
/// </summary>
|
||||
[IterationSetup]
|
||||
public void Setup()
|
||||
@@ -53,17 +54,14 @@ public class CompactionBenchmarks
|
||||
_transactionHolder.CommitAndReset();
|
||||
_storage.Checkpoint();
|
||||
|
||||
for (var i = _insertedIds.Count - 1; i >= _insertedIds.Count / 3; i--)
|
||||
{
|
||||
_collection.Delete(_insertedIds[i]);
|
||||
}
|
||||
for (int i = _insertedIds.Count - 1; i >= _insertedIds.Count / 3; i--) _collection.Delete(_insertedIds[i]);
|
||||
|
||||
_transactionHolder.CommitAndReset();
|
||||
_storage.Checkpoint();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cleans up benchmark resources and temporary files after each iteration.
|
||||
/// Cleans up benchmark resources and temporary files after each iteration.
|
||||
/// </summary>
|
||||
[IterationCleanup]
|
||||
public void Cleanup()
|
||||
@@ -76,7 +74,7 @@ public class CompactionBenchmarks
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Benchmarks reclaimed file bytes reported by offline compaction.
|
||||
/// Benchmarks reclaimed file bytes reported by offline compaction.
|
||||
/// </summary>
|
||||
/// <returns>The reclaimed file byte count.</returns>
|
||||
[Benchmark(Baseline = true)]
|
||||
@@ -95,7 +93,7 @@ public class CompactionBenchmarks
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Benchmarks tail bytes truncated by offline compaction.
|
||||
/// Benchmarks tail bytes truncated by offline compaction.
|
||||
/// </summary>
|
||||
/// <returns>The truncated tail byte count.</returns>
|
||||
[Benchmark]
|
||||
@@ -135,7 +133,7 @@ public class CompactionBenchmarks
|
||||
|
||||
private static string BuildPayload(int seed)
|
||||
{
|
||||
var builder = new System.Text.StringBuilder(2500);
|
||||
var builder = new StringBuilder(2500);
|
||||
for (var i = 0; i < 80; i++)
|
||||
{
|
||||
builder.Append("compact-");
|
||||
@@ -147,4 +145,4 @@ public class CompactionBenchmarks
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
using System.IO.Compression;
|
||||
using System.Text;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Configs;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
using System.IO.Compression;
|
||||
using ZB.MOM.WW.CBDD.Bson;
|
||||
using ZB.MOM.WW.CBDD.Core.Collections;
|
||||
using ZB.MOM.WW.CBDD.Core.Compression;
|
||||
@@ -19,36 +19,36 @@ public class CompressionBenchmarks
|
||||
{
|
||||
private const int SeedCount = 300;
|
||||
private const int WorkloadCount = 100;
|
||||
private DocumentCollection<Person> _collection = null!;
|
||||
|
||||
private string _dbPath = string.Empty;
|
||||
|
||||
private Person[] _insertBatch = Array.Empty<Person>();
|
||||
private ObjectId[] _seedIds = Array.Empty<ObjectId>();
|
||||
private StorageEngine _storage = null!;
|
||||
private BenchmarkTransactionHolder _transactionHolder = null!;
|
||||
private string _walPath = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether compression is enabled for the benchmark run.
|
||||
/// Gets or sets whether compression is enabled for the benchmark run.
|
||||
/// </summary>
|
||||
[Params(false, true)]
|
||||
public bool EnableCompression { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the compression codec for the benchmark run.
|
||||
/// Gets or sets the compression codec for the benchmark run.
|
||||
/// </summary>
|
||||
[Params(CompressionCodec.Brotli, CompressionCodec.Deflate)]
|
||||
public CompressionCodec Codec { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the compression level for the benchmark run.
|
||||
/// Gets or sets the compression level for the benchmark run.
|
||||
/// </summary>
|
||||
[Params(CompressionLevel.Fastest, CompressionLevel.Optimal)]
|
||||
public CompressionLevel Level { get; set; }
|
||||
|
||||
private string _dbPath = string.Empty;
|
||||
private string _walPath = string.Empty;
|
||||
private StorageEngine _storage = null!;
|
||||
private BenchmarkTransactionHolder _transactionHolder = null!;
|
||||
private DocumentCollection<Person> _collection = null!;
|
||||
|
||||
private Person[] _insertBatch = Array.Empty<Person>();
|
||||
private ObjectId[] _seedIds = Array.Empty<ObjectId>();
|
||||
|
||||
/// <summary>
|
||||
/// Prepares benchmark storage and seed data for each iteration.
|
||||
/// Prepares benchmark storage and seed data for each iteration.
|
||||
/// </summary>
|
||||
[IterationSetup]
|
||||
public void Setup()
|
||||
@@ -73,19 +73,19 @@ public class CompressionBenchmarks
|
||||
_seedIds = new ObjectId[SeedCount];
|
||||
for (var i = 0; i < SeedCount; i++)
|
||||
{
|
||||
var doc = CreatePerson(i, includeLargeBio: true);
|
||||
var doc = CreatePerson(i, true);
|
||||
_seedIds[i] = _collection.Insert(doc);
|
||||
}
|
||||
|
||||
_transactionHolder.CommitAndReset();
|
||||
|
||||
_insertBatch = Enumerable.Range(SeedCount, WorkloadCount)
|
||||
.Select(i => CreatePerson(i, includeLargeBio: true))
|
||||
.Select(i => CreatePerson(i, true))
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cleans up benchmark resources for each iteration.
|
||||
/// Cleans up benchmark resources for each iteration.
|
||||
/// </summary>
|
||||
[IterationCleanup]
|
||||
public void Cleanup()
|
||||
@@ -98,7 +98,7 @@ public class CompressionBenchmarks
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Benchmarks insert workload performance.
|
||||
/// Benchmarks insert workload performance.
|
||||
/// </summary>
|
||||
[Benchmark(Baseline = true)]
|
||||
[BenchmarkCategory("Compression_InsertUpdateRead")]
|
||||
@@ -109,7 +109,7 @@ public class CompressionBenchmarks
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Benchmarks update workload performance.
|
||||
/// Benchmarks update workload performance.
|
||||
/// </summary>
|
||||
[Benchmark]
|
||||
[BenchmarkCategory("Compression_InsertUpdateRead")]
|
||||
@@ -131,7 +131,7 @@ public class CompressionBenchmarks
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Benchmarks read workload performance.
|
||||
/// Benchmarks read workload performance.
|
||||
/// </summary>
|
||||
[Benchmark]
|
||||
[BenchmarkCategory("Compression_InsertUpdateRead")]
|
||||
@@ -141,10 +141,7 @@ public class CompressionBenchmarks
|
||||
for (var i = 0; i < WorkloadCount; i++)
|
||||
{
|
||||
var person = _collection.FindById(_seedIds[i]);
|
||||
if (person != null)
|
||||
{
|
||||
checksum += person.Age;
|
||||
}
|
||||
if (person != null) checksum += person.Age;
|
||||
}
|
||||
|
||||
_transactionHolder.CommitAndReset();
|
||||
@@ -158,7 +155,7 @@ public class CompressionBenchmarks
|
||||
Id = ObjectId.NewObjectId(),
|
||||
FirstName = $"First_{i}",
|
||||
LastName = $"Last_{i}",
|
||||
Age = 20 + (i % 50),
|
||||
Age = 20 + i % 50,
|
||||
Bio = includeLargeBio ? BuildBio(i) : $"bio-{i}",
|
||||
CreatedAt = DateTime.UnixEpoch.AddMinutes(i),
|
||||
Balance = 100 + i,
|
||||
@@ -183,7 +180,7 @@ public class CompressionBenchmarks
|
||||
|
||||
private static string BuildBio(int seed)
|
||||
{
|
||||
var builder = new System.Text.StringBuilder(4500);
|
||||
var builder = new StringBuilder(4500);
|
||||
for (var i = 0; i < 150; i++)
|
||||
{
|
||||
builder.Append("bio-");
|
||||
@@ -195,4 +192,4 @@ public class CompressionBenchmarks
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,21 +1,21 @@
|
||||
using ZB.MOM.WW.CBDD.Bson;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace ZB.MOM.WW.CBDD.Tests.Benchmark;
|
||||
|
||||
public class Address
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the Street.
|
||||
/// Gets or sets the Street.
|
||||
/// </summary>
|
||||
public string Street { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the City.
|
||||
/// Gets or sets the City.
|
||||
/// </summary>
|
||||
public string City { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the ZipCode.
|
||||
/// Gets or sets the ZipCode.
|
||||
/// </summary>
|
||||
public string ZipCode { get; set; } = string.Empty;
|
||||
}
|
||||
@@ -23,19 +23,22 @@ public class Address
|
||||
public class WorkHistory
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the CompanyName.
|
||||
/// Gets or sets the CompanyName.
|
||||
/// </summary>
|
||||
public string CompanyName { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Title.
|
||||
/// Gets or sets the Title.
|
||||
/// </summary>
|
||||
public string Title { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the DurationYears.
|
||||
/// Gets or sets the DurationYears.
|
||||
/// </summary>
|
||||
public int DurationYears { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Tags.
|
||||
/// Gets or sets the Tags.
|
||||
/// </summary>
|
||||
public List<string> Tags { get; set; } = new();
|
||||
}
|
||||
@@ -43,41 +46,48 @@ public class WorkHistory
|
||||
public class Person
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the Id.
|
||||
/// Gets or sets the Id.
|
||||
/// </summary>
|
||||
public ObjectId Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the FirstName.
|
||||
/// Gets or sets the FirstName.
|
||||
/// </summary>
|
||||
public string FirstName { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the LastName.
|
||||
/// Gets or sets the LastName.
|
||||
/// </summary>
|
||||
public string LastName { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Age.
|
||||
/// Gets or sets the Age.
|
||||
/// </summary>
|
||||
public int Age { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Bio.
|
||||
/// Gets or sets the Bio.
|
||||
/// </summary>
|
||||
public string? Bio { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the CreatedAt.
|
||||
/// Gets or sets the CreatedAt.
|
||||
/// </summary>
|
||||
public DateTime CreatedAt { get; set; }
|
||||
|
||||
// Complex fields
|
||||
/// <summary>
|
||||
/// Gets or sets the Balance.
|
||||
/// Gets or sets the Balance.
|
||||
/// </summary>
|
||||
public decimal Balance { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the HomeAddress.
|
||||
/// Gets or sets the HomeAddress.
|
||||
/// </summary>
|
||||
public Address HomeAddress { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the EmploymentHistory.
|
||||
/// Gets or sets the EmploymentHistory.
|
||||
/// </summary>
|
||||
public List<WorkHistory> EmploymentHistory { get; set; } = new();
|
||||
}
|
||||
}
|
||||
@@ -1,26 +1,30 @@
|
||||
using ZB.MOM.WW.CBDD.Bson;
|
||||
using ZB.MOM.WW.CBDD.Core.Collections;
|
||||
using System.Buffers;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace ZB.MOM.WW.CBDD.Tests.Benchmark;
|
||||
|
||||
public class PersonMapper : ObjectIdMapperBase<Person>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override string CollectionName => "people";
|
||||
|
||||
/// <inheritdoc />
|
||||
public override ObjectId GetId(Person entity) => entity.Id;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void SetId(Person entity, ObjectId id) => entity.Id = id;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override int Serialize(Person entity, BsonSpanWriter writer)
|
||||
{
|
||||
var sizePos = writer.BeginDocument();
|
||||
|
||||
public class PersonMapper : ObjectIdMapperBase<Person>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override string CollectionName => "people";
|
||||
|
||||
/// <inheritdoc />
|
||||
public override ObjectId GetId(Person entity)
|
||||
{
|
||||
return entity.Id;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void SetId(Person entity, ObjectId id)
|
||||
{
|
||||
entity.Id = id;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override int Serialize(Person entity, BsonSpanWriter writer)
|
||||
{
|
||||
int sizePos = writer.BeginDocument();
|
||||
|
||||
writer.WriteObjectId("_id", entity.Id);
|
||||
writer.WriteString("firstname", entity.FirstName);
|
||||
writer.WriteString("lastname", entity.LastName);
|
||||
@@ -30,111 +34,119 @@ public class PersonMapper : ObjectIdMapperBase<Person>
|
||||
else
|
||||
writer.WriteNull("bio");
|
||||
|
||||
writer.WriteInt64("createdat", entity.CreatedAt.Ticks);
|
||||
|
||||
// Complex fields
|
||||
writer.WriteDouble("balance", (double)entity.Balance);
|
||||
|
||||
// Nested Object: Address
|
||||
var addrPos = writer.BeginDocument("homeaddress");
|
||||
writer.WriteInt64("createdat", entity.CreatedAt.Ticks);
|
||||
|
||||
// Complex fields
|
||||
writer.WriteDouble("balance", (double)entity.Balance);
|
||||
|
||||
// Nested Object: Address
|
||||
int addrPos = writer.BeginDocument("homeaddress");
|
||||
writer.WriteString("street", entity.HomeAddress.Street);
|
||||
writer.WriteString("city", entity.HomeAddress.City);
|
||||
writer.WriteString("zipcode", entity.HomeAddress.ZipCode);
|
||||
writer.EndDocument(addrPos);
|
||||
|
||||
// Collection: EmploymentHistory
|
||||
var histPos = writer.BeginArray("employmenthistory");
|
||||
for (int i = 0; i < entity.EmploymentHistory.Count; i++)
|
||||
writer.EndDocument(addrPos);
|
||||
|
||||
// Collection: EmploymentHistory
|
||||
int histPos = writer.BeginArray("employmenthistory");
|
||||
for (var i = 0; i < entity.EmploymentHistory.Count; i++)
|
||||
{
|
||||
var item = entity.EmploymentHistory[i];
|
||||
// Array elements are keys "0", "1", "2"...
|
||||
var itemPos = writer.BeginDocument(i.ToString());
|
||||
|
||||
int itemPos = writer.BeginDocument(i.ToString());
|
||||
|
||||
writer.WriteString("companyname", item.CompanyName);
|
||||
writer.WriteString("title", item.Title);
|
||||
writer.WriteInt32("durationyears", item.DurationYears);
|
||||
|
||||
// Nested Collection: Tags
|
||||
var tagsPos = writer.BeginArray("tags");
|
||||
for (int j = 0; j < item.Tags.Count; j++)
|
||||
{
|
||||
writer.WriteString(j.ToString(), item.Tags[j]);
|
||||
}
|
||||
writer.EndArray(tagsPos);
|
||||
|
||||
writer.WriteInt32("durationyears", item.DurationYears);
|
||||
|
||||
// Nested Collection: Tags
|
||||
int tagsPos = writer.BeginArray("tags");
|
||||
for (var j = 0; j < item.Tags.Count; j++) writer.WriteString(j.ToString(), item.Tags[j]);
|
||||
writer.EndArray(tagsPos);
|
||||
|
||||
writer.EndDocument(itemPos);
|
||||
}
|
||||
writer.EndArray(histPos);
|
||||
|
||||
writer.EndDocument(sizePos);
|
||||
|
||||
|
||||
writer.EndArray(histPos);
|
||||
|
||||
writer.EndDocument(sizePos);
|
||||
|
||||
return writer.Position;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Person Deserialize(BsonSpanReader reader)
|
||||
{
|
||||
var person = new Person();
|
||||
|
||||
reader.ReadDocumentSize();
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Person Deserialize(BsonSpanReader reader)
|
||||
{
|
||||
var person = new Person();
|
||||
|
||||
reader.ReadDocumentSize();
|
||||
|
||||
while (reader.Remaining > 0)
|
||||
{
|
||||
var type = reader.ReadBsonType();
|
||||
if (type == BsonType.EndOfDocument)
|
||||
break;
|
||||
|
||||
var name = reader.ReadElementHeader();
|
||||
|
||||
break;
|
||||
|
||||
string name = reader.ReadElementHeader();
|
||||
|
||||
switch (name)
|
||||
{
|
||||
case "_id": person.Id = reader.ReadObjectId(); break;
|
||||
case "firstname": person.FirstName = reader.ReadString(); break;
|
||||
case "lastname": person.LastName = reader.ReadString(); break;
|
||||
case "age": person.Age = reader.ReadInt32(); break;
|
||||
case "bio":
|
||||
case "bio":
|
||||
if (type == BsonType.Null) person.Bio = null;
|
||||
else person.Bio = reader.ReadString();
|
||||
else person.Bio = reader.ReadString();
|
||||
break;
|
||||
case "createdat": person.CreatedAt = new DateTime(reader.ReadInt64()); break;
|
||||
case "balance": person.Balance = (decimal)reader.ReadDouble(); break;
|
||||
|
||||
case "balance": person.Balance = (decimal)reader.ReadDouble(); break;
|
||||
|
||||
case "homeaddress":
|
||||
reader.ReadDocumentSize(); // Enter document
|
||||
while (reader.Remaining > 0)
|
||||
{
|
||||
var addrType = reader.ReadBsonType();
|
||||
if (addrType == BsonType.EndOfDocument) break;
|
||||
var addrName = reader.ReadElementHeader();
|
||||
|
||||
// We assume strict schema for benchmark speed, but should handle skipping
|
||||
string addrName = reader.ReadElementHeader();
|
||||
|
||||
// We assume strict schema for benchmark speed, but should handle skipping
|
||||
if (addrName == "street") person.HomeAddress.Street = reader.ReadString();
|
||||
else if (addrName == "city") person.HomeAddress.City = reader.ReadString();
|
||||
else if (addrName == "zipcode") person.HomeAddress.ZipCode = reader.ReadString();
|
||||
else reader.SkipValue(addrType);
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
break;
|
||||
|
||||
case "employmenthistory":
|
||||
reader.ReadDocumentSize(); // Enter Array
|
||||
while (reader.Remaining > 0)
|
||||
{
|
||||
var arrType = reader.ReadBsonType();
|
||||
if (arrType == BsonType.EndOfDocument) break;
|
||||
reader.ReadElementHeader(); // Array index "0", "1"... ignore
|
||||
|
||||
// Read WorkHistory item
|
||||
reader.ReadElementHeader(); // Array index "0", "1"... ignore
|
||||
|
||||
// Read WorkHistory item
|
||||
var workItem = new WorkHistory();
|
||||
reader.ReadDocumentSize(); // Enter Item Document
|
||||
while (reader.Remaining > 0)
|
||||
{
|
||||
var itemType = reader.ReadBsonType();
|
||||
if (itemType == BsonType.EndOfDocument) break;
|
||||
var itemName = reader.ReadElementHeader();
|
||||
|
||||
if (itemName == "companyname") workItem.CompanyName = reader.ReadString();
|
||||
else if (itemName == "title") workItem.Title = reader.ReadString();
|
||||
else if (itemName == "durationyears") workItem.DurationYears = reader.ReadInt32();
|
||||
string itemName = reader.ReadElementHeader();
|
||||
|
||||
if (itemName == "companyname")
|
||||
{
|
||||
workItem.CompanyName = reader.ReadString();
|
||||
}
|
||||
else if (itemName == "title")
|
||||
{
|
||||
workItem.Title = reader.ReadString();
|
||||
}
|
||||
else if (itemName == "durationyears")
|
||||
{
|
||||
workItem.DurationYears = reader.ReadInt32();
|
||||
}
|
||||
else if (itemName == "tags")
|
||||
{
|
||||
reader.ReadDocumentSize(); // Enter Tags Array
|
||||
@@ -149,18 +161,23 @@ public class PersonMapper : ObjectIdMapperBase<Person>
|
||||
reader.SkipValue(tagType);
|
||||
}
|
||||
}
|
||||
else reader.SkipValue(itemType);
|
||||
else
|
||||
{
|
||||
reader.SkipValue(itemType);
|
||||
}
|
||||
}
|
||||
|
||||
person.EmploymentHistory.Add(workItem);
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
reader.SkipValue(type);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return person;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
using ZB.MOM.WW.CBDD.Core;
|
||||
using ZB.MOM.WW.CBDD.Core.Storage;
|
||||
using ZB.MOM.WW.CBDD.Core.Transactions;
|
||||
|
||||
@@ -11,7 +10,7 @@ internal sealed class BenchmarkTransactionHolder : ITransactionHolder, IDisposab
|
||||
private ITransaction? _currentTransaction;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BenchmarkTransactionHolder"/> class.
|
||||
/// Initializes a new instance of the <see cref="BenchmarkTransactionHolder" /> class.
|
||||
/// </summary>
|
||||
/// <param name="storage">The storage engine used to create transactions.</param>
|
||||
public BenchmarkTransactionHolder(StorageEngine storage)
|
||||
@@ -20,7 +19,15 @@ internal sealed class BenchmarkTransactionHolder : ITransactionHolder, IDisposab
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current active transaction or starts a new one.
|
||||
/// Disposes this holder and rolls back any outstanding transaction.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
RollbackAndReset();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current active transaction or starts a new one.
|
||||
/// </summary>
|
||||
/// <returns>The current active transaction.</returns>
|
||||
public ITransaction GetCurrentTransactionOrStart()
|
||||
@@ -28,16 +35,14 @@ internal sealed class BenchmarkTransactionHolder : ITransactionHolder, IDisposab
|
||||
lock (_sync)
|
||||
{
|
||||
if (_currentTransaction == null || _currentTransaction.State != TransactionState.Active)
|
||||
{
|
||||
_currentTransaction = _storage.BeginTransaction();
|
||||
}
|
||||
|
||||
return _currentTransaction;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current active transaction or starts a new one asynchronously.
|
||||
/// Gets the current active transaction or starts a new one asynchronously.
|
||||
/// </summary>
|
||||
/// <returns>A task that returns the current active transaction.</returns>
|
||||
public Task<ITransaction> GetCurrentTransactionOrStartAsync()
|
||||
@@ -46,22 +51,17 @@ internal sealed class BenchmarkTransactionHolder : ITransactionHolder, IDisposab
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Commits the current transaction when active and clears the holder.
|
||||
/// Commits the current transaction when active and clears the holder.
|
||||
/// </summary>
|
||||
public void CommitAndReset()
|
||||
{
|
||||
lock (_sync)
|
||||
{
|
||||
if (_currentTransaction == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (_currentTransaction == null) return;
|
||||
|
||||
if (_currentTransaction.State == TransactionState.Active ||
|
||||
_currentTransaction.State == TransactionState.Preparing)
|
||||
{
|
||||
_currentTransaction.Commit();
|
||||
}
|
||||
|
||||
_currentTransaction.Dispose();
|
||||
_currentTransaction = null;
|
||||
@@ -69,33 +69,20 @@ internal sealed class BenchmarkTransactionHolder : ITransactionHolder, IDisposab
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rolls back the current transaction when active and clears the holder.
|
||||
/// Rolls back the current transaction when active and clears the holder.
|
||||
/// </summary>
|
||||
public void RollbackAndReset()
|
||||
{
|
||||
lock (_sync)
|
||||
{
|
||||
if (_currentTransaction == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (_currentTransaction == null) return;
|
||||
|
||||
if (_currentTransaction.State == TransactionState.Active ||
|
||||
_currentTransaction.State == TransactionState.Preparing)
|
||||
{
|
||||
_currentTransaction.Rollback();
|
||||
}
|
||||
|
||||
_currentTransaction.Dispose();
|
||||
_currentTransaction = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disposes this holder and rolls back any outstanding transaction.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
RollbackAndReset();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Serilog;
|
||||
using ILogger = Microsoft.Extensions.Logging.ILogger;
|
||||
|
||||
namespace ZB.MOM.WW.CBDD.Tests.Benchmark;
|
||||
|
||||
@@ -8,16 +9,16 @@ internal static class Logging
|
||||
private static readonly Lazy<ILoggerFactory> LoggerFactoryInstance = new(CreateFactory);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the shared logger factory for benchmarks.
|
||||
/// Gets the shared logger factory for benchmarks.
|
||||
/// </summary>
|
||||
public static ILoggerFactory LoggerFactory => LoggerFactoryInstance.Value;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a logger for the specified category type.
|
||||
/// Creates a logger for the specified category type.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The logger category type.</typeparam>
|
||||
/// <returns>A logger for <typeparamref name="T"/>.</returns>
|
||||
public static Microsoft.Extensions.Logging.ILogger CreateLogger<T>()
|
||||
/// <returns>A logger for <typeparamref name="T" />.</returns>
|
||||
public static ILogger CreateLogger<T>()
|
||||
{
|
||||
return LoggerFactory.CreateLogger<T>();
|
||||
}
|
||||
@@ -32,7 +33,7 @@ internal static class Logging
|
||||
return Microsoft.Extensions.Logging.LoggerFactory.Create(builder =>
|
||||
{
|
||||
builder.ClearProviders();
|
||||
builder.AddSerilog(serilogLogger, dispose: true);
|
||||
builder.AddSerilog(serilogLogger, true);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,17 +3,17 @@ using BenchmarkDotNet.Configs;
|
||||
using BenchmarkDotNet.Exporters;
|
||||
using BenchmarkDotNet.Reports;
|
||||
using BenchmarkDotNet.Running;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Perfolizer.Horology;
|
||||
using Serilog.Context;
|
||||
|
||||
namespace ZB.MOM.WW.CBDD.Tests.Benchmark;
|
||||
|
||||
class Program
|
||||
internal class Program
|
||||
{
|
||||
static void Main(string[] args)
|
||||
private static void Main(string[] args)
|
||||
{
|
||||
var logger = Logging.CreateLogger<Program>();
|
||||
var mode = args.Length > 0 ? args[0].Trim().ToLowerInvariant() : string.Empty;
|
||||
string mode = args.Length > 0 ? args[0].Trim().ToLowerInvariant() : string.Empty;
|
||||
|
||||
if (mode == "manual")
|
||||
{
|
||||
@@ -84,6 +84,6 @@ class Program
|
||||
.AddExporter(HtmlExporter.Default)
|
||||
.WithSummaryStyle(SummaryStyle.Default
|
||||
.WithRatioStyle(RatioStyle.Trend)
|
||||
.WithTimeUnit(Perfolizer.Horology.TimeUnit.Microsecond));
|
||||
.WithTimeUnit(TimeUnit.Microsecond));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,13 @@
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Configs;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
using ZB.MOM.WW.CBDD.Bson;
|
||||
using ZB.MOM.WW.CBDD.Core;
|
||||
using ZB.MOM.WW.CBDD.Core.Collections;
|
||||
using ZB.MOM.WW.CBDD.Core.Storage;
|
||||
using ZB.MOM.WW.CBDD.Core.Transactions;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Serilog.Context;
|
||||
using System.IO;
|
||||
using ZB.MOM.WW.CBDD.Bson;
|
||||
using ZB.MOM.WW.CBDD.Core.Collections;
|
||||
using ZB.MOM.WW.CBDD.Core.Storage;
|
||||
|
||||
namespace ZB.MOM.WW.CBDD.Tests.Benchmark;
|
||||
|
||||
|
||||
[InProcess]
|
||||
[MemoryDiagnoser]
|
||||
[GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)]
|
||||
@@ -23,33 +18,30 @@ public class InsertBenchmarks
|
||||
private const int BatchSize = 1000;
|
||||
private static readonly ILogger Logger = Logging.CreateLogger<InsertBenchmarks>();
|
||||
|
||||
private Person[] _batchData = Array.Empty<Person>();
|
||||
private DocumentCollection<Person>? _collection;
|
||||
|
||||
private string _docDbPath = "";
|
||||
private string _docDbWalPath = "";
|
||||
private Person? _singlePerson;
|
||||
|
||||
private StorageEngine? _storage = null;
|
||||
private BenchmarkTransactionHolder? _transactionHolder = null;
|
||||
private DocumentCollection<Person>? _collection = null;
|
||||
|
||||
private Person[] _batchData = Array.Empty<Person>();
|
||||
private Person? _singlePerson = null;
|
||||
private StorageEngine? _storage;
|
||||
private BenchmarkTransactionHolder? _transactionHolder;
|
||||
|
||||
/// <summary>
|
||||
/// Tests setup.
|
||||
/// Tests setup.
|
||||
/// </summary>
|
||||
[GlobalSetup]
|
||||
public void Setup()
|
||||
{
|
||||
var temp = AppContext.BaseDirectory;
|
||||
string temp = AppContext.BaseDirectory;
|
||||
var id = Guid.NewGuid().ToString("N");
|
||||
_docDbPath = Path.Combine(temp, $"bench_docdb_{id}.db");
|
||||
_docDbWalPath = Path.ChangeExtension(_docDbPath, ".wal");
|
||||
|
||||
_singlePerson = CreatePerson(0);
|
||||
_batchData = new Person[BatchSize];
|
||||
for (int i = 0; i < BatchSize; i++)
|
||||
{
|
||||
_batchData[i] = CreatePerson(i);
|
||||
}
|
||||
for (var i = 0; i < BatchSize; i++) _batchData[i] = CreatePerson(i);
|
||||
}
|
||||
|
||||
private Person CreatePerson(int i)
|
||||
@@ -59,7 +51,7 @@ public class InsertBenchmarks
|
||||
Id = ObjectId.NewObjectId(),
|
||||
FirstName = $"First_{i}",
|
||||
LastName = $"Last_{i}",
|
||||
Age = 20 + (i % 50),
|
||||
Age = 20 + i % 50,
|
||||
Bio = null, // Removed large payload to focus on structure
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
Balance = 1000.50m * (i + 1),
|
||||
@@ -72,8 +64,7 @@ public class InsertBenchmarks
|
||||
};
|
||||
|
||||
// Add 10 work history items to stress structure traversal
|
||||
for (int j = 0; j < 10; j++)
|
||||
{
|
||||
for (var j = 0; j < 10; j++)
|
||||
p.EmploymentHistory.Add(new WorkHistory
|
||||
{
|
||||
CompanyName = $"TechCorp_{i}_{j}",
|
||||
@@ -81,13 +72,12 @@ public class InsertBenchmarks
|
||||
DurationYears = j,
|
||||
Tags = new List<string> { "C#", "BSON", "Performance", "Database", "Complex" }
|
||||
});
|
||||
}
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests iteration setup.
|
||||
/// Tests iteration setup.
|
||||
/// </summary>
|
||||
[IterationSetup]
|
||||
public void IterationSetup()
|
||||
@@ -98,7 +88,7 @@ public class InsertBenchmarks
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests cleanup.
|
||||
/// Tests cleanup.
|
||||
/// </summary>
|
||||
[IterationCleanup]
|
||||
public void Cleanup()
|
||||
@@ -111,7 +101,7 @@ public class InsertBenchmarks
|
||||
_storage?.Dispose();
|
||||
_storage = null;
|
||||
|
||||
System.Threading.Thread.Sleep(100);
|
||||
Thread.Sleep(100);
|
||||
|
||||
if (File.Exists(_docDbPath)) File.Delete(_docDbPath);
|
||||
if (File.Exists(_docDbWalPath)) File.Delete(_docDbWalPath);
|
||||
@@ -125,7 +115,7 @@ public class InsertBenchmarks
|
||||
// --- Benchmarks ---
|
||||
|
||||
/// <summary>
|
||||
/// Tests document db insert single.
|
||||
/// Tests document db insert single.
|
||||
/// </summary>
|
||||
[Benchmark(Baseline = true, Description = "CBDD Single Insert")]
|
||||
[BenchmarkCategory("Insert_Single")]
|
||||
@@ -136,7 +126,7 @@ public class InsertBenchmarks
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests document db insert batch.
|
||||
/// Tests document db insert batch.
|
||||
/// </summary>
|
||||
[Benchmark(Description = "CBDD Batch Insert (1000 items, 1 Txn)")]
|
||||
[BenchmarkCategory("Insert_Batch")]
|
||||
@@ -145,4 +135,4 @@ public class InsertBenchmarks
|
||||
_collection?.InsertBulk(_batchData);
|
||||
_transactionHolder?.CommitAndReset();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,8 @@
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Configs;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
using ZB.MOM.WW.CBDD.Bson;
|
||||
using ZB.MOM.WW.CBDD.Core;
|
||||
using ZB.MOM.WW.CBDD.Core.Collections;
|
||||
using ZB.MOM.WW.CBDD.Core.Storage;
|
||||
using ZB.MOM.WW.CBDD.Core.Transactions;
|
||||
using System.IO;
|
||||
|
||||
namespace ZB.MOM.WW.CBDD.Tests.Benchmark;
|
||||
|
||||
@@ -19,24 +15,24 @@ namespace ZB.MOM.WW.CBDD.Tests.Benchmark;
|
||||
public class ReadBenchmarks
|
||||
{
|
||||
private const int DocCount = 1000;
|
||||
private DocumentCollection<Person> _collection = null!;
|
||||
|
||||
private string _docDbPath = null!;
|
||||
private string _docDbWalPath = null!;
|
||||
|
||||
private StorageEngine _storage = null!;
|
||||
private BenchmarkTransactionHolder _transactionHolder = null!;
|
||||
private DocumentCollection<Person> _collection = null!;
|
||||
|
||||
private ObjectId[] _ids = null!;
|
||||
|
||||
private StorageEngine _storage = null!;
|
||||
private ObjectId _targetId;
|
||||
private BenchmarkTransactionHolder _transactionHolder = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Tests setup.
|
||||
/// Tests setup.
|
||||
/// </summary>
|
||||
[GlobalSetup]
|
||||
public void Setup()
|
||||
{
|
||||
var temp = AppContext.BaseDirectory;
|
||||
string temp = AppContext.BaseDirectory;
|
||||
var id = Guid.NewGuid().ToString("N");
|
||||
_docDbPath = Path.Combine(temp, $"bench_read_docdb_{id}.db");
|
||||
_docDbWalPath = Path.ChangeExtension(_docDbPath, ".wal");
|
||||
@@ -49,18 +45,19 @@ public class ReadBenchmarks
|
||||
_collection = new DocumentCollection<Person>(_storage, _transactionHolder, new PersonMapper());
|
||||
|
||||
_ids = new ObjectId[DocCount];
|
||||
for (int i = 0; i < DocCount; i++)
|
||||
for (var i = 0; i < DocCount; i++)
|
||||
{
|
||||
var p = CreatePerson(i);
|
||||
_ids[i] = _collection.Insert(p);
|
||||
}
|
||||
|
||||
_transactionHolder.CommitAndReset();
|
||||
|
||||
_targetId = _ids[DocCount / 2];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests cleanup.
|
||||
/// Tests cleanup.
|
||||
/// </summary>
|
||||
[GlobalCleanup]
|
||||
public void Cleanup()
|
||||
@@ -79,7 +76,7 @@ public class ReadBenchmarks
|
||||
Id = ObjectId.NewObjectId(),
|
||||
FirstName = $"First_{i}",
|
||||
LastName = $"Last_{i}",
|
||||
Age = 20 + (i % 50),
|
||||
Age = 20 + i % 50,
|
||||
Bio = null,
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
Balance = 1000.50m * (i + 1),
|
||||
@@ -92,8 +89,7 @@ public class ReadBenchmarks
|
||||
};
|
||||
|
||||
// Add 10 work history items
|
||||
for (int j = 0; j < 10; j++)
|
||||
{
|
||||
for (var j = 0; j < 10; j++)
|
||||
p.EmploymentHistory.Add(new WorkHistory
|
||||
{
|
||||
CompanyName = $"TechCorp_{i}_{j}",
|
||||
@@ -101,13 +97,12 @@ public class ReadBenchmarks
|
||||
DurationYears = j,
|
||||
Tags = new List<string> { "C#", "BSON", "Performance", "Database", "Complex" }
|
||||
});
|
||||
}
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests document db find by id.
|
||||
/// Tests document db find by id.
|
||||
/// </summary>
|
||||
[Benchmark(Baseline = true, Description = "CBDD FindById")]
|
||||
[BenchmarkCategory("Read_Single")]
|
||||
@@ -115,4 +110,4 @@ public class ReadBenchmarks
|
||||
{
|
||||
return _collection.FindById(_targetId);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Text.Json;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Configs;
|
||||
using ZB.MOM.WW.CBDD.Bson;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace ZB.MOM.WW.CBDD.Tests.Benchmark;
|
||||
|
||||
@@ -13,32 +14,37 @@ namespace ZB.MOM.WW.CBDD.Tests.Benchmark;
|
||||
public class SerializationBenchmarks
|
||||
{
|
||||
private const int BatchSize = 10000;
|
||||
private Person _person = null!;
|
||||
private List<Person> _people = null!;
|
||||
private PersonMapper _mapper = new PersonMapper();
|
||||
|
||||
private static readonly ConcurrentDictionary<string, ushort> _keyMap = new(StringComparer.OrdinalIgnoreCase);
|
||||
private static readonly ConcurrentDictionary<ushort, string> _keys = new();
|
||||
|
||||
private readonly List<byte[]> _bsonDataList = new();
|
||||
private readonly List<byte[]> _jsonDataList = new();
|
||||
private readonly PersonMapper _mapper = new();
|
||||
private byte[] _bsonData = Array.Empty<byte>();
|
||||
private byte[] _jsonData = Array.Empty<byte>();
|
||||
|
||||
private List<byte[]> _bsonDataList = new();
|
||||
private List<byte[]> _jsonDataList = new();
|
||||
|
||||
private byte[] _jsonData = Array.Empty<byte>();
|
||||
private List<Person> _people = null!;
|
||||
private Person _person = null!;
|
||||
|
||||
private byte[] _serializeBuffer = Array.Empty<byte>();
|
||||
|
||||
private static readonly System.Collections.Concurrent.ConcurrentDictionary<string, ushort> _keyMap = new(StringComparer.OrdinalIgnoreCase);
|
||||
private static readonly System.Collections.Concurrent.ConcurrentDictionary<ushort, string> _keys = new();
|
||||
|
||||
static SerializationBenchmarks()
|
||||
static SerializationBenchmarks()
|
||||
{
|
||||
ushort id = 1;
|
||||
string[] initialKeys = { "_id", "firstname", "lastname", "age", "bio", "createdat", "balance", "homeaddress", "street", "city", "zipcode", "employmenthistory", "companyname", "title", "durationyears", "tags" };
|
||||
foreach (var key in initialKeys)
|
||||
string[] initialKeys =
|
||||
{
|
||||
"_id", "firstname", "lastname", "age", "bio", "createdat", "balance", "homeaddress", "street", "city",
|
||||
"zipcode", "employmenthistory", "companyname", "title", "durationyears", "tags"
|
||||
};
|
||||
foreach (string key in initialKeys)
|
||||
{
|
||||
_keyMap[key] = id;
|
||||
_keys[id] = key;
|
||||
id++;
|
||||
}
|
||||
|
||||
// Add some indices for arrays
|
||||
for (int i = 0; i < 100; i++)
|
||||
for (var i = 0; i < 100; i++)
|
||||
{
|
||||
var s = i.ToString();
|
||||
_keyMap[s] = id;
|
||||
@@ -47,26 +53,23 @@ public class SerializationBenchmarks
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prepares benchmark data for serialization and deserialization scenarios.
|
||||
/// </summary>
|
||||
[GlobalSetup]
|
||||
public void Setup()
|
||||
/// <summary>
|
||||
/// Prepares benchmark data for serialization and deserialization scenarios.
|
||||
/// </summary>
|
||||
[GlobalSetup]
|
||||
public void Setup()
|
||||
{
|
||||
_person = CreatePerson(0);
|
||||
_people = new List<Person>(BatchSize);
|
||||
for (int i = 0; i < BatchSize; i++)
|
||||
{
|
||||
_people.Add(CreatePerson(i));
|
||||
}
|
||||
|
||||
// Pre-allocate buffer for BSON serialization
|
||||
for (var i = 0; i < BatchSize; i++) _people.Add(CreatePerson(i));
|
||||
|
||||
// Pre-allocate buffer for BSON serialization
|
||||
_serializeBuffer = new byte[8192];
|
||||
|
||||
var writer = new BsonSpanWriter(_serializeBuffer, _keyMap);
|
||||
|
||||
// Single item data
|
||||
var len = _mapper.Serialize(_person, writer);
|
||||
int len = _mapper.Serialize(_person, writer);
|
||||
_bsonData = _serializeBuffer.AsSpan(0, len).ToArray();
|
||||
_jsonData = JsonSerializer.SerializeToUtf8Bytes(_person);
|
||||
|
||||
@@ -87,10 +90,10 @@ public class SerializationBenchmarks
|
||||
FirstName = $"First_{i}",
|
||||
LastName = $"Last_{i}",
|
||||
Age = 25,
|
||||
Bio = null,
|
||||
Bio = null,
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
Balance = 1000.50m,
|
||||
HomeAddress = new Address
|
||||
HomeAddress = new Address
|
||||
{
|
||||
Street = $"{i} Main St",
|
||||
City = "Tech City",
|
||||
@@ -98,8 +101,7 @@ public class SerializationBenchmarks
|
||||
}
|
||||
};
|
||||
|
||||
for (int j = 0; j < 10; j++)
|
||||
{
|
||||
for (var j = 0; j < 10; j++)
|
||||
p.EmploymentHistory.Add(new WorkHistory
|
||||
{
|
||||
CompanyName = $"TechCorp_{i}_{j}",
|
||||
@@ -107,58 +109,57 @@ public class SerializationBenchmarks
|
||||
DurationYears = j,
|
||||
Tags = new List<string> { "C#", "BSON", "Performance", "Database", "Complex" }
|
||||
});
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Benchmarks BSON serialization for a single document.
|
||||
/// </summary>
|
||||
[Benchmark(Description = "Serialize Single (BSON)")]
|
||||
[BenchmarkCategory("Single")]
|
||||
public void Serialize_Bson()
|
||||
/// <summary>
|
||||
/// Benchmarks BSON serialization for a single document.
|
||||
/// </summary>
|
||||
[Benchmark(Description = "Serialize Single (BSON)")]
|
||||
[BenchmarkCategory("Single")]
|
||||
public void Serialize_Bson()
|
||||
{
|
||||
var writer = new BsonSpanWriter(_serializeBuffer, _keyMap);
|
||||
_mapper.Serialize(_person, writer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Benchmarks JSON serialization for a single document.
|
||||
/// </summary>
|
||||
[Benchmark(Description = "Serialize Single (JSON)")]
|
||||
[BenchmarkCategory("Single")]
|
||||
public void Serialize_Json()
|
||||
/// <summary>
|
||||
/// Benchmarks JSON serialization for a single document.
|
||||
/// </summary>
|
||||
[Benchmark(Description = "Serialize Single (JSON)")]
|
||||
[BenchmarkCategory("Single")]
|
||||
public void Serialize_Json()
|
||||
{
|
||||
JsonSerializer.SerializeToUtf8Bytes(_person);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Benchmarks BSON deserialization for a single document.
|
||||
/// </summary>
|
||||
[Benchmark(Description = "Deserialize Single (BSON)")]
|
||||
[BenchmarkCategory("Single")]
|
||||
public Person Deserialize_Bson()
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Benchmarks BSON deserialization for a single document.
|
||||
/// </summary>
|
||||
[Benchmark(Description = "Deserialize Single (BSON)")]
|
||||
[BenchmarkCategory("Single")]
|
||||
public Person Deserialize_Bson()
|
||||
{
|
||||
var reader = new BsonSpanReader(_bsonData, _keys);
|
||||
return _mapper.Deserialize(reader);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Benchmarks JSON deserialization for a single document.
|
||||
/// </summary>
|
||||
[Benchmark(Description = "Deserialize Single (JSON)")]
|
||||
[BenchmarkCategory("Single")]
|
||||
public Person? Deserialize_Json()
|
||||
/// <summary>
|
||||
/// Benchmarks JSON deserialization for a single document.
|
||||
/// </summary>
|
||||
[Benchmark(Description = "Deserialize Single (JSON)")]
|
||||
[BenchmarkCategory("Single")]
|
||||
public Person? Deserialize_Json()
|
||||
{
|
||||
return JsonSerializer.Deserialize<Person>(_jsonData);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Benchmarks BSON serialization for a list of documents.
|
||||
/// </summary>
|
||||
[Benchmark(Description = "Serialize List 10k (BSON loop)")]
|
||||
[BenchmarkCategory("Batch")]
|
||||
public void Serialize_List_Bson()
|
||||
/// <summary>
|
||||
/// Benchmarks BSON serialization for a list of documents.
|
||||
/// </summary>
|
||||
[Benchmark(Description = "Serialize List 10k (BSON loop)")]
|
||||
[BenchmarkCategory("Batch")]
|
||||
public void Serialize_List_Bson()
|
||||
{
|
||||
foreach (var p in _people)
|
||||
{
|
||||
@@ -167,43 +168,37 @@ public class SerializationBenchmarks
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Benchmarks JSON serialization for a list of documents.
|
||||
/// </summary>
|
||||
[Benchmark(Description = "Serialize List 10k (JSON loop)")]
|
||||
[BenchmarkCategory("Batch")]
|
||||
public void Serialize_List_Json()
|
||||
/// <summary>
|
||||
/// Benchmarks JSON serialization for a list of documents.
|
||||
/// </summary>
|
||||
[Benchmark(Description = "Serialize List 10k (JSON loop)")]
|
||||
[BenchmarkCategory("Batch")]
|
||||
public void Serialize_List_Json()
|
||||
{
|
||||
foreach (var p in _people)
|
||||
{
|
||||
JsonSerializer.SerializeToUtf8Bytes(p);
|
||||
}
|
||||
foreach (var p in _people) JsonSerializer.SerializeToUtf8Bytes(p);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Benchmarks BSON deserialization for a list of documents.
|
||||
/// </summary>
|
||||
[Benchmark(Description = "Deserialize List 10k (BSON loop)")]
|
||||
[BenchmarkCategory("Batch")]
|
||||
public void Deserialize_List_Bson()
|
||||
/// <summary>
|
||||
/// Benchmarks BSON deserialization for a list of documents.
|
||||
/// </summary>
|
||||
[Benchmark(Description = "Deserialize List 10k (BSON loop)")]
|
||||
[BenchmarkCategory("Batch")]
|
||||
public void Deserialize_List_Bson()
|
||||
{
|
||||
foreach (var data in _bsonDataList)
|
||||
foreach (byte[] data in _bsonDataList)
|
||||
{
|
||||
var reader = new BsonSpanReader(data, _keys);
|
||||
_mapper.Deserialize(reader);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Benchmarks JSON deserialization for a list of documents.
|
||||
/// </summary>
|
||||
[Benchmark(Description = "Deserialize List 10k (JSON loop)")]
|
||||
[BenchmarkCategory("Batch")]
|
||||
public void Deserialize_List_Json()
|
||||
/// <summary>
|
||||
/// Benchmarks JSON deserialization for a list of documents.
|
||||
/// </summary>
|
||||
[Benchmark(Description = "Deserialize List 10k (JSON loop)")]
|
||||
[BenchmarkCategory("Batch")]
|
||||
public void Deserialize_List_Json()
|
||||
{
|
||||
foreach (var data in _jsonDataList)
|
||||
{
|
||||
JsonSerializer.Deserialize<Person>(data);
|
||||
}
|
||||
foreach (byte[] data in _jsonDataList) JsonSerializer.Deserialize<Person>(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Diagnostics;
|
||||
using System.IO.Compression;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Serilog.Context;
|
||||
using ZB.MOM.WW.CBDD.Bson;
|
||||
@@ -10,47 +11,47 @@ namespace ZB.MOM.WW.CBDD.Tests.Benchmark;
|
||||
|
||||
internal static class DatabaseSizeBenchmark
|
||||
{
|
||||
private const int BatchSize = 50_000;
|
||||
private const int ProgressInterval = 1_000_000;
|
||||
private static readonly int[] TargetCounts = [10_000, 1_000_000, 10_000_000];
|
||||
|
||||
private static readonly CompressionOptions CompressedBrotliFast = new()
|
||||
{
|
||||
EnableCompression = true,
|
||||
MinSizeBytes = 256,
|
||||
MinSavingsPercent = 0,
|
||||
Codec = CompressionCodec.Brotli,
|
||||
Level = System.IO.Compression.CompressionLevel.Fastest
|
||||
Level = CompressionLevel.Fastest
|
||||
};
|
||||
|
||||
private static readonly Scenario[] Scenarios =
|
||||
[
|
||||
// Separate compression set (no compaction)
|
||||
new(
|
||||
Set: "compression",
|
||||
Name: "CompressionOnly-Uncompressed",
|
||||
CompressionOptions: CompressionOptions.Default,
|
||||
RunCompaction: false),
|
||||
"compression",
|
||||
"CompressionOnly-Uncompressed",
|
||||
CompressionOptions.Default,
|
||||
false),
|
||||
new(
|
||||
Set: "compression",
|
||||
Name: "CompressionOnly-Compressed-BrotliFast",
|
||||
CompressionOptions: CompressedBrotliFast,
|
||||
RunCompaction: false),
|
||||
"compression",
|
||||
"CompressionOnly-Compressed-BrotliFast",
|
||||
CompressedBrotliFast,
|
||||
false),
|
||||
// Separate compaction set (compaction enabled)
|
||||
new(
|
||||
Set: "compaction",
|
||||
Name: "Compaction-Uncompressed",
|
||||
CompressionOptions: CompressionOptions.Default,
|
||||
RunCompaction: true),
|
||||
"compaction",
|
||||
"Compaction-Uncompressed",
|
||||
CompressionOptions.Default,
|
||||
true),
|
||||
new(
|
||||
Set: "compaction",
|
||||
Name: "Compaction-Compressed-BrotliFast",
|
||||
CompressionOptions: CompressedBrotliFast,
|
||||
RunCompaction: true)
|
||||
"compaction",
|
||||
"Compaction-Compressed-BrotliFast",
|
||||
CompressedBrotliFast,
|
||||
true)
|
||||
];
|
||||
|
||||
private const int BatchSize = 50_000;
|
||||
private const int ProgressInterval = 1_000_000;
|
||||
|
||||
/// <summary>
|
||||
/// Tests run.
|
||||
/// Tests run.
|
||||
/// </summary>
|
||||
/// <param name="logger">Logger for benchmark progress and results.</param>
|
||||
public static void Run(ILogger logger)
|
||||
@@ -62,109 +63,101 @@ internal static class DatabaseSizeBenchmark
|
||||
logger.LogInformation("Scenarios: {Scenarios}", string.Join(", ", Scenarios.Select(x => $"{x.Set}:{x.Name}")));
|
||||
logger.LogInformation("Batch size: {BatchSize:N0}", BatchSize);
|
||||
|
||||
foreach (var targetCount in TargetCounts)
|
||||
foreach (int targetCount in TargetCounts)
|
||||
foreach (var scenario in Scenarios)
|
||||
{
|
||||
foreach (var scenario in Scenarios)
|
||||
string dbPath = Path.Combine(Path.GetTempPath(),
|
||||
$"cbdd_size_{scenario.Name}_{targetCount}_{Guid.NewGuid():N}.db");
|
||||
string walPath = Path.ChangeExtension(dbPath, ".wal");
|
||||
using var _ = LogContext.PushProperty("TargetCount", targetCount);
|
||||
using var __ = LogContext.PushProperty("Scenario", scenario.Name);
|
||||
using var ___ = LogContext.PushProperty("ScenarioSet", scenario.Set);
|
||||
|
||||
logger.LogInformation(
|
||||
"Starting {Set} scenario {Scenario} for target {TargetCount:N0} docs",
|
||||
scenario.Set,
|
||||
scenario.Name,
|
||||
targetCount);
|
||||
|
||||
var insertStopwatch = Stopwatch.StartNew();
|
||||
CompressionStats compressionStats = default;
|
||||
CompactionStats compactionStats = new();
|
||||
long preCompactDbBytes;
|
||||
long preCompactWalBytes;
|
||||
long postCompactDbBytes;
|
||||
long postCompactWalBytes;
|
||||
|
||||
using (var storage = new StorageEngine(dbPath, PageFileConfig.Default, scenario.CompressionOptions))
|
||||
using (var transactionHolder = new BenchmarkTransactionHolder(storage))
|
||||
{
|
||||
var dbPath = Path.Combine(Path.GetTempPath(), $"cbdd_size_{scenario.Name}_{targetCount}_{Guid.NewGuid():N}.db");
|
||||
var walPath = Path.ChangeExtension(dbPath, ".wal");
|
||||
using var _ = LogContext.PushProperty("TargetCount", targetCount);
|
||||
using var __ = LogContext.PushProperty("Scenario", scenario.Name);
|
||||
using var ___ = LogContext.PushProperty("ScenarioSet", scenario.Set);
|
||||
var collection = new DocumentCollection<SizeBenchmarkDocument>(
|
||||
storage,
|
||||
transactionHolder,
|
||||
new SizeBenchmarkDocumentMapper());
|
||||
|
||||
logger.LogInformation(
|
||||
"Starting {Set} scenario {Scenario} for target {TargetCount:N0} docs",
|
||||
scenario.Set,
|
||||
scenario.Name,
|
||||
targetCount);
|
||||
|
||||
var insertStopwatch = Stopwatch.StartNew();
|
||||
CompressionStats compressionStats = default;
|
||||
CompactionStats compactionStats = new();
|
||||
long preCompactDbBytes;
|
||||
long preCompactWalBytes;
|
||||
long postCompactDbBytes;
|
||||
long postCompactWalBytes;
|
||||
|
||||
using (var storage = new StorageEngine(dbPath, PageFileConfig.Default, scenario.CompressionOptions))
|
||||
using (var transactionHolder = new BenchmarkTransactionHolder(storage))
|
||||
var inserted = 0;
|
||||
while (inserted < targetCount)
|
||||
{
|
||||
var collection = new DocumentCollection<SizeBenchmarkDocument>(
|
||||
storage,
|
||||
transactionHolder,
|
||||
new SizeBenchmarkDocumentMapper());
|
||||
int currentBatchSize = Math.Min(BatchSize, targetCount - inserted);
|
||||
var documents = new SizeBenchmarkDocument[currentBatchSize];
|
||||
int baseValue = inserted;
|
||||
|
||||
var inserted = 0;
|
||||
while (inserted < targetCount)
|
||||
{
|
||||
var currentBatchSize = Math.Min(BatchSize, targetCount - inserted);
|
||||
var documents = new SizeBenchmarkDocument[currentBatchSize];
|
||||
var baseValue = inserted;
|
||||
for (var i = 0; i < currentBatchSize; i++) documents[i] = CreateDocument(baseValue + i);
|
||||
|
||||
for (var i = 0; i < currentBatchSize; i++)
|
||||
{
|
||||
documents[i] = CreateDocument(baseValue + i);
|
||||
}
|
||||
collection.InsertBulk(documents);
|
||||
transactionHolder.CommitAndReset();
|
||||
|
||||
collection.InsertBulk(documents);
|
||||
transactionHolder.CommitAndReset();
|
||||
|
||||
inserted += currentBatchSize;
|
||||
if (inserted == targetCount || inserted % ProgressInterval == 0)
|
||||
{
|
||||
logger.LogInformation("Inserted {Inserted:N0}/{TargetCount:N0}", inserted, targetCount);
|
||||
}
|
||||
}
|
||||
|
||||
insertStopwatch.Stop();
|
||||
preCompactDbBytes = File.Exists(dbPath) ? new FileInfo(dbPath).Length : 0;
|
||||
preCompactWalBytes = File.Exists(walPath) ? new FileInfo(walPath).Length : 0;
|
||||
|
||||
if (scenario.RunCompaction)
|
||||
{
|
||||
compactionStats = storage.Compact(new CompactionOptions
|
||||
{
|
||||
EnableTailTruncation = true,
|
||||
DefragmentSlottedPages = true,
|
||||
NormalizeFreeList = true
|
||||
});
|
||||
}
|
||||
|
||||
postCompactDbBytes = File.Exists(dbPath) ? new FileInfo(dbPath).Length : 0;
|
||||
postCompactWalBytes = File.Exists(walPath) ? new FileInfo(walPath).Length : 0;
|
||||
compressionStats = storage.GetCompressionStats();
|
||||
inserted += currentBatchSize;
|
||||
if (inserted == targetCount || inserted % ProgressInterval == 0)
|
||||
logger.LogInformation("Inserted {Inserted:N0}/{TargetCount:N0}", inserted, targetCount);
|
||||
}
|
||||
|
||||
var result = new SizeResult(
|
||||
scenario.Set,
|
||||
scenario.Name,
|
||||
scenario.RunCompaction,
|
||||
targetCount,
|
||||
insertStopwatch.Elapsed,
|
||||
preCompactDbBytes,
|
||||
preCompactWalBytes,
|
||||
postCompactDbBytes,
|
||||
postCompactWalBytes,
|
||||
compactionStats,
|
||||
compressionStats);
|
||||
results.Add(result);
|
||||
insertStopwatch.Stop();
|
||||
preCompactDbBytes = File.Exists(dbPath) ? new FileInfo(dbPath).Length : 0;
|
||||
preCompactWalBytes = File.Exists(walPath) ? new FileInfo(walPath).Length : 0;
|
||||
|
||||
logger.LogInformation(
|
||||
"Completed {Set}:{Scenario} {TargetCount:N0} docs in {Elapsed}. pre={PreTotal}, post={PostTotal}, shrink={Shrink}, compactApplied={CompactionApplied}, compactReclaim={CompactReclaim}, compRatio={CompRatio}",
|
||||
scenario.Set,
|
||||
scenario.Name,
|
||||
targetCount,
|
||||
insertStopwatch.Elapsed,
|
||||
FormatBytes(result.PreCompactTotalBytes),
|
||||
FormatBytes(result.PostCompactTotalBytes),
|
||||
FormatBytes(result.ShrinkBytes),
|
||||
scenario.RunCompaction,
|
||||
FormatBytes(result.CompactionStats.ReclaimedFileBytes),
|
||||
result.CompressionRatioText);
|
||||
if (scenario.RunCompaction)
|
||||
compactionStats = storage.Compact(new CompactionOptions
|
||||
{
|
||||
EnableTailTruncation = true,
|
||||
DefragmentSlottedPages = true,
|
||||
NormalizeFreeList = true
|
||||
});
|
||||
|
||||
TryDelete(dbPath);
|
||||
TryDelete(walPath);
|
||||
postCompactDbBytes = File.Exists(dbPath) ? new FileInfo(dbPath).Length : 0;
|
||||
postCompactWalBytes = File.Exists(walPath) ? new FileInfo(walPath).Length : 0;
|
||||
compressionStats = storage.GetCompressionStats();
|
||||
}
|
||||
|
||||
var result = new SizeResult(
|
||||
scenario.Set,
|
||||
scenario.Name,
|
||||
scenario.RunCompaction,
|
||||
targetCount,
|
||||
insertStopwatch.Elapsed,
|
||||
preCompactDbBytes,
|
||||
preCompactWalBytes,
|
||||
postCompactDbBytes,
|
||||
postCompactWalBytes,
|
||||
compactionStats,
|
||||
compressionStats);
|
||||
results.Add(result);
|
||||
|
||||
logger.LogInformation(
|
||||
"Completed {Set}:{Scenario} {TargetCount:N0} docs in {Elapsed}. pre={PreTotal}, post={PostTotal}, shrink={Shrink}, compactApplied={CompactionApplied}, compactReclaim={CompactReclaim}, compRatio={CompRatio}",
|
||||
scenario.Set,
|
||||
scenario.Name,
|
||||
targetCount,
|
||||
insertStopwatch.Elapsed,
|
||||
FormatBytes(result.PreCompactTotalBytes),
|
||||
FormatBytes(result.PostCompactTotalBytes),
|
||||
FormatBytes(result.ShrinkBytes),
|
||||
scenario.RunCompaction,
|
||||
FormatBytes(result.CompactionStats.ReclaimedFileBytes),
|
||||
result.CompressionRatioText);
|
||||
|
||||
TryDelete(dbPath);
|
||||
TryDelete(walPath);
|
||||
}
|
||||
|
||||
logger.LogInformation("=== Size Benchmark Summary ===");
|
||||
@@ -172,7 +165,6 @@ internal static class DatabaseSizeBenchmark
|
||||
.OrderBy(x => x.Set)
|
||||
.ThenBy(x => x.TargetCount)
|
||||
.ThenBy(x => x.Scenario))
|
||||
{
|
||||
logger.LogInformation(
|
||||
"{Set,-11} | {Scenario,-38} | {Count,12:N0} docs | insert={Elapsed,12} | pre={Pre,12} | post={Post,12} | shrink={Shrink,12} | compact={CompactBytes,12} | ratio={Ratio}",
|
||||
result.Set,
|
||||
@@ -184,7 +176,6 @@ internal static class DatabaseSizeBenchmark
|
||||
FormatBytes(result.ShrinkBytes),
|
||||
FormatBytes(result.CompactionStats.ReclaimedFileBytes),
|
||||
result.CompressionRatioText);
|
||||
}
|
||||
|
||||
WriteSummaryCsv(results, logger);
|
||||
}
|
||||
@@ -201,10 +192,7 @@ internal static class DatabaseSizeBenchmark
|
||||
|
||||
private static void TryDelete(string path)
|
||||
{
|
||||
if (File.Exists(path))
|
||||
{
|
||||
File.Delete(path);
|
||||
}
|
||||
if (File.Exists(path)) File.Delete(path);
|
||||
}
|
||||
|
||||
private static string FormatBytes(long bytes)
|
||||
@@ -224,9 +212,9 @@ internal static class DatabaseSizeBenchmark
|
||||
|
||||
private static void WriteSummaryCsv(IEnumerable<SizeResult> results, ILogger logger)
|
||||
{
|
||||
var outputDirectory = Path.Combine(Directory.GetCurrentDirectory(), "BenchmarkDotNet.Artifacts", "results");
|
||||
string outputDirectory = Path.Combine(Directory.GetCurrentDirectory(), "BenchmarkDotNet.Artifacts", "results");
|
||||
Directory.CreateDirectory(outputDirectory);
|
||||
var outputPath = Path.Combine(outputDirectory, "DatabaseSizeBenchmark-results.csv");
|
||||
string outputPath = Path.Combine(outputDirectory, "DatabaseSizeBenchmark-results.csv");
|
||||
|
||||
var lines = new List<string>
|
||||
{
|
||||
@@ -234,7 +222,6 @@ internal static class DatabaseSizeBenchmark
|
||||
};
|
||||
|
||||
foreach (var result in results.OrderBy(x => x.Set).ThenBy(x => x.TargetCount).ThenBy(x => x.Scenario))
|
||||
{
|
||||
lines.Add(string.Join(",",
|
||||
result.Set,
|
||||
result.Scenario,
|
||||
@@ -246,7 +233,6 @@ internal static class DatabaseSizeBenchmark
|
||||
result.ShrinkBytes.ToString(),
|
||||
result.CompactionStats.ReclaimedFileBytes.ToString(),
|
||||
result.CompressionRatioText));
|
||||
}
|
||||
|
||||
File.WriteAllLines(outputPath, lines);
|
||||
logger.LogInformation("Database size summary CSV written to {OutputPath}", outputPath);
|
||||
@@ -268,20 +254,22 @@ internal static class DatabaseSizeBenchmark
|
||||
CompressionStats CompressionStats)
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the pre compact total bytes.
|
||||
/// Gets or sets the pre compact total bytes.
|
||||
/// </summary>
|
||||
public long PreCompactTotalBytes => PreCompactDbBytes + PreCompactWalBytes;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the post compact total bytes.
|
||||
/// Gets or sets the post compact total bytes.
|
||||
/// </summary>
|
||||
public long PostCompactTotalBytes => PostCompactDbBytes + PostCompactWalBytes;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the shrink bytes.
|
||||
/// Gets or sets the shrink bytes.
|
||||
/// </summary>
|
||||
public long ShrinkBytes => PreCompactTotalBytes - PostCompactTotalBytes;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the compression ratio text.
|
||||
/// Gets or sets the compression ratio text.
|
||||
/// </summary>
|
||||
public string CompressionRatioText =>
|
||||
CompressionStats.BytesAfterCompression > 0
|
||||
@@ -292,15 +280,17 @@ internal static class DatabaseSizeBenchmark
|
||||
private sealed class SizeBenchmarkDocument
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the id.
|
||||
/// Gets or sets the id.
|
||||
/// </summary>
|
||||
public ObjectId Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the value.
|
||||
/// Gets or sets the value.
|
||||
/// </summary>
|
||||
public int Value { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the name.
|
||||
/// Gets or sets the name.
|
||||
/// </summary>
|
||||
public string Name { get; set; } = string.Empty;
|
||||
}
|
||||
@@ -311,15 +301,21 @@ internal static class DatabaseSizeBenchmark
|
||||
public override string CollectionName => "size_documents";
|
||||
|
||||
/// <inheritdoc />
|
||||
public override ObjectId GetId(SizeBenchmarkDocument entity) => entity.Id;
|
||||
public override ObjectId GetId(SizeBenchmarkDocument entity)
|
||||
{
|
||||
return entity.Id;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void SetId(SizeBenchmarkDocument entity, ObjectId id) => entity.Id = id;
|
||||
public override void SetId(SizeBenchmarkDocument entity, ObjectId id)
|
||||
{
|
||||
entity.Id = id;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override int Serialize(SizeBenchmarkDocument entity, BsonSpanWriter writer)
|
||||
{
|
||||
var sizePos = writer.BeginDocument();
|
||||
int sizePos = writer.BeginDocument();
|
||||
writer.WriteObjectId("_id", entity.Id);
|
||||
writer.WriteInt32("value", entity.Value);
|
||||
writer.WriteString("name", entity.Name);
|
||||
@@ -336,12 +332,9 @@ internal static class DatabaseSizeBenchmark
|
||||
while (reader.Remaining > 0)
|
||||
{
|
||||
var bsonType = reader.ReadBsonType();
|
||||
if (bsonType == BsonType.EndOfDocument)
|
||||
{
|
||||
break;
|
||||
}
|
||||
if (bsonType == BsonType.EndOfDocument) break;
|
||||
|
||||
var name = reader.ReadElementHeader();
|
||||
string name = reader.ReadElementHeader();
|
||||
switch (name)
|
||||
{
|
||||
case "_id":
|
||||
@@ -362,4 +355,4 @@ internal static class DatabaseSizeBenchmark
|
||||
return document;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Serilog.Context;
|
||||
@@ -8,7 +7,7 @@ namespace ZB.MOM.WW.CBDD.Tests.Benchmark;
|
||||
|
||||
public class ManualBenchmark
|
||||
{
|
||||
private static StringBuilder _log = new();
|
||||
private static readonly StringBuilder _log = new();
|
||||
|
||||
private static void Log(ILogger logger, string message = "")
|
||||
{
|
||||
@@ -16,11 +15,11 @@ public class ManualBenchmark
|
||||
_log.AppendLine(message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests run.
|
||||
/// </summary>
|
||||
/// <param name="logger">Logger for benchmark progress and results.</param>
|
||||
public static void Run(ILogger logger)
|
||||
/// <summary>
|
||||
/// Tests run.
|
||||
/// </summary>
|
||||
/// <param name="logger">Logger for benchmark progress and results.</param>
|
||||
public static void Run(ILogger logger)
|
||||
{
|
||||
using var _ = LogContext.PushProperty("Benchmark", nameof(ManualBenchmark));
|
||||
_log.Clear();
|
||||
@@ -60,10 +59,7 @@ public class ManualBenchmark
|
||||
try
|
||||
{
|
||||
var sw = Stopwatch.StartNew();
|
||||
for (int i = 0; i < 1000; i++)
|
||||
{
|
||||
readBench.DocumentDb_FindById();
|
||||
}
|
||||
for (var i = 0; i < 1000; i++) readBench.DocumentDb_FindById();
|
||||
sw.Stop();
|
||||
readByIdMs = sw.ElapsedMilliseconds;
|
||||
Log(logger, $" CBDD FindById x1000: {readByIdMs} ms ({(double)readByIdMs / 1000:F3} ms/op)");
|
||||
@@ -101,14 +97,11 @@ public class ManualBenchmark
|
||||
Log(logger, $"FindById x1000: {readByIdMs} ms");
|
||||
Log(logger, $"Single Insert: {singleInsertMs} ms");
|
||||
|
||||
var artifactsDir = Path.Combine(AppContext.BaseDirectory, "BenchmarkDotNet.Artifacts", "results");
|
||||
if (!Directory.Exists(artifactsDir))
|
||||
{
|
||||
Directory.CreateDirectory(artifactsDir);
|
||||
}
|
||||
string artifactsDir = Path.Combine(AppContext.BaseDirectory, "BenchmarkDotNet.Artifacts", "results");
|
||||
if (!Directory.Exists(artifactsDir)) Directory.CreateDirectory(artifactsDir);
|
||||
|
||||
var filePath = Path.Combine(artifactsDir, "manual_report.txt");
|
||||
string filePath = Path.Combine(artifactsDir, "manual_report.txt");
|
||||
File.WriteAllText(filePath, _log.ToString());
|
||||
logger.LogInformation("Report saved to: {FilePath}", filePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
using System.Text;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Configs;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
using ZB.MOM.WW.CBDD.Bson;
|
||||
using ZB.MOM.WW.CBDD.Core.Collections;
|
||||
using ZB.MOM.WW.CBDD.Core.Compression;
|
||||
@@ -16,28 +16,29 @@ namespace ZB.MOM.WW.CBDD.Tests.Benchmark;
|
||||
[JsonExporterAttribute.Full]
|
||||
public class MixedWorkloadBenchmarks
|
||||
{
|
||||
private readonly List<ObjectId> _activeIds = [];
|
||||
private DocumentCollection<Person> _collection = null!;
|
||||
|
||||
private string _dbPath = string.Empty;
|
||||
private int _nextValueSeed;
|
||||
private StorageEngine _storage = null!;
|
||||
private BenchmarkTransactionHolder _transactionHolder = null!;
|
||||
private string _walPath = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether periodic online compaction is enabled.
|
||||
/// Gets or sets whether periodic online compaction is enabled.
|
||||
/// </summary>
|
||||
[Params(false, true)]
|
||||
public bool PeriodicCompaction { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the number of operations per benchmark iteration.
|
||||
/// Gets or sets the number of operations per benchmark iteration.
|
||||
/// </summary>
|
||||
[Params(800)]
|
||||
public int Operations { get; set; }
|
||||
|
||||
private string _dbPath = string.Empty;
|
||||
private string _walPath = string.Empty;
|
||||
private StorageEngine _storage = null!;
|
||||
private BenchmarkTransactionHolder _transactionHolder = null!;
|
||||
private DocumentCollection<Person> _collection = null!;
|
||||
private readonly List<ObjectId> _activeIds = [];
|
||||
private int _nextValueSeed;
|
||||
|
||||
/// <summary>
|
||||
/// Prepares benchmark storage and seed data for each iteration.
|
||||
/// Prepares benchmark storage and seed data for each iteration.
|
||||
/// </summary>
|
||||
[IterationSetup]
|
||||
public void Setup()
|
||||
@@ -71,7 +72,7 @@ public class MixedWorkloadBenchmarks
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cleans up benchmark resources for each iteration.
|
||||
/// Cleans up benchmark resources for each iteration.
|
||||
/// </summary>
|
||||
[IterationCleanup]
|
||||
public void Cleanup()
|
||||
@@ -84,7 +85,7 @@ public class MixedWorkloadBenchmarks
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Benchmarks a mixed insert/update/delete workload.
|
||||
/// Benchmarks a mixed insert/update/delete workload.
|
||||
/// </summary>
|
||||
[Benchmark(Baseline = true)]
|
||||
[BenchmarkCategory("MixedWorkload")]
|
||||
@@ -94,7 +95,7 @@ public class MixedWorkloadBenchmarks
|
||||
|
||||
for (var i = 1; i <= Operations; i++)
|
||||
{
|
||||
var mode = i % 5;
|
||||
int mode = i % 5;
|
||||
if (mode is 0 or 1)
|
||||
{
|
||||
var id = _collection.Insert(CreatePerson(_nextValueSeed++));
|
||||
@@ -104,7 +105,7 @@ public class MixedWorkloadBenchmarks
|
||||
{
|
||||
if (_activeIds.Count > 0)
|
||||
{
|
||||
var idx = random.Next(_activeIds.Count);
|
||||
int idx = random.Next(_activeIds.Count);
|
||||
var id = _activeIds[idx];
|
||||
var current = _collection.FindById(id);
|
||||
if (current != null)
|
||||
@@ -119,20 +120,16 @@ public class MixedWorkloadBenchmarks
|
||||
{
|
||||
if (_activeIds.Count > 100)
|
||||
{
|
||||
var idx = random.Next(_activeIds.Count);
|
||||
int idx = random.Next(_activeIds.Count);
|
||||
var id = _activeIds[idx];
|
||||
_collection.Delete(id);
|
||||
_activeIds.RemoveAt(idx);
|
||||
}
|
||||
}
|
||||
|
||||
if (i % 50 == 0)
|
||||
{
|
||||
_transactionHolder.CommitAndReset();
|
||||
}
|
||||
if (i % 50 == 0) _transactionHolder.CommitAndReset();
|
||||
|
||||
if (PeriodicCompaction && i % 200 == 0)
|
||||
{
|
||||
_storage.RunOnlineCompactionPass(new CompactionOptions
|
||||
{
|
||||
OnlineMode = true,
|
||||
@@ -141,7 +138,6 @@ public class MixedWorkloadBenchmarks
|
||||
MaxOnlineDuration = TimeSpan.FromMilliseconds(120),
|
||||
EnableTailTruncation = true
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
_transactionHolder.CommitAndReset();
|
||||
@@ -155,7 +151,7 @@ public class MixedWorkloadBenchmarks
|
||||
Id = ObjectId.NewObjectId(),
|
||||
FirstName = $"First_{seed}",
|
||||
LastName = $"Last_{seed}",
|
||||
Age = 18 + (seed % 60),
|
||||
Age = 18 + seed % 60,
|
||||
Bio = BuildPayload(seed),
|
||||
CreatedAt = DateTime.UnixEpoch.AddSeconds(seed),
|
||||
Balance = seed,
|
||||
@@ -170,7 +166,7 @@ public class MixedWorkloadBenchmarks
|
||||
|
||||
private static string BuildPayload(int seed)
|
||||
{
|
||||
var builder = new System.Text.StringBuilder(1800);
|
||||
var builder = new StringBuilder(1800);
|
||||
for (var i = 0; i < 64; i++)
|
||||
{
|
||||
builder.Append("mixed-");
|
||||
@@ -182,4 +178,4 @@ public class MixedWorkloadBenchmarks
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.IO.Compression;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using ZB.MOM.WW.CBDD.Bson;
|
||||
@@ -14,21 +15,21 @@ internal static class PerformanceGateSmoke
|
||||
private const int CompressionDocumentCount = 1_500;
|
||||
|
||||
/// <summary>
|
||||
/// Runs the performance gate smoke probes and writes a report.
|
||||
/// Runs the performance gate smoke probes and writes a report.
|
||||
/// </summary>
|
||||
/// <param name="logger">The logger.</param>
|
||||
public static void Run(ILogger logger)
|
||||
{
|
||||
var compaction = RunCompactionProbe();
|
||||
var compressionOff = RunCompressionGcProbe(enableCompression: false);
|
||||
var compressionOn = RunCompressionGcProbe(enableCompression: true);
|
||||
var compressionOff = RunCompressionGcProbe(false);
|
||||
var compressionOn = RunCompressionGcProbe(true);
|
||||
|
||||
var report = new PerformanceGateReport(
|
||||
DateTimeOffset.UtcNow,
|
||||
compaction,
|
||||
compressionOff,
|
||||
compressionOn);
|
||||
var reportPath = WriteReport(report);
|
||||
string reportPath = WriteReport(report);
|
||||
|
||||
logger.LogInformation("Performance gate smoke report written to {ReportPath}", reportPath);
|
||||
|
||||
@@ -52,8 +53,8 @@ internal static class PerformanceGateSmoke
|
||||
|
||||
private static CompactionProbeResult RunCompactionProbe()
|
||||
{
|
||||
var dbPath = NewDbPath("gate_compaction");
|
||||
var walPath = Path.ChangeExtension(dbPath, ".wal");
|
||||
string dbPath = NewDbPath("gate_compaction");
|
||||
string walPath = Path.ChangeExtension(dbPath, ".wal");
|
||||
|
||||
try
|
||||
{
|
||||
@@ -62,18 +63,12 @@ internal static class PerformanceGateSmoke
|
||||
var collection = new DocumentCollection<Person>(storage, transactionHolder, new PersonMapper());
|
||||
|
||||
var ids = new List<ObjectId>(CompactionDocumentCount);
|
||||
for (var i = 0; i < CompactionDocumentCount; i++)
|
||||
{
|
||||
ids.Add(collection.Insert(CreatePerson(i, includeLargeBio: true)));
|
||||
}
|
||||
for (var i = 0; i < CompactionDocumentCount; i++) ids.Add(collection.Insert(CreatePerson(i, true)));
|
||||
|
||||
transactionHolder.CommitAndReset();
|
||||
storage.Checkpoint();
|
||||
|
||||
for (var i = 0; i < ids.Count; i += 3)
|
||||
{
|
||||
collection.Delete(ids[i]);
|
||||
}
|
||||
for (var i = 0; i < ids.Count; i += 3) collection.Delete(ids[i]);
|
||||
|
||||
for (var i = 0; i < ids.Count; i += 5)
|
||||
{
|
||||
@@ -117,8 +112,8 @@ internal static class PerformanceGateSmoke
|
||||
|
||||
private static CompressionGcProbeResult RunCompressionGcProbe(bool enableCompression)
|
||||
{
|
||||
var dbPath = NewDbPath(enableCompression ? "gate_gc_on" : "gate_gc_off");
|
||||
var walPath = Path.ChangeExtension(dbPath, ".wal");
|
||||
string dbPath = NewDbPath(enableCompression ? "gate_gc_on" : "gate_gc_off");
|
||||
string walPath = Path.ChangeExtension(dbPath, ".wal");
|
||||
var compressionOptions = enableCompression
|
||||
? new CompressionOptions
|
||||
{
|
||||
@@ -140,16 +135,13 @@ internal static class PerformanceGateSmoke
|
||||
GC.WaitForPendingFinalizers();
|
||||
GC.Collect();
|
||||
|
||||
var g0Before = GC.CollectionCount(0);
|
||||
var g1Before = GC.CollectionCount(1);
|
||||
var g2Before = GC.CollectionCount(2);
|
||||
var allocBefore = GC.GetTotalAllocatedBytes(true);
|
||||
int g0Before = GC.CollectionCount(0);
|
||||
int g1Before = GC.CollectionCount(1);
|
||||
int g2Before = GC.CollectionCount(2);
|
||||
long allocBefore = GC.GetTotalAllocatedBytes(true);
|
||||
|
||||
var ids = new ObjectId[CompressionDocumentCount];
|
||||
for (var i = 0; i < CompressionDocumentCount; i++)
|
||||
{
|
||||
ids[i] = collection.Insert(CreatePerson(i, includeLargeBio: true));
|
||||
}
|
||||
for (var i = 0; i < CompressionDocumentCount; i++) ids[i] = collection.Insert(CreatePerson(i, true));
|
||||
|
||||
transactionHolder.CommitAndReset();
|
||||
|
||||
@@ -166,17 +158,17 @@ internal static class PerformanceGateSmoke
|
||||
|
||||
transactionHolder.CommitAndReset();
|
||||
|
||||
var readCount = collection.FindAll().Count();
|
||||
int readCount = collection.FindAll().Count();
|
||||
transactionHolder.CommitAndReset();
|
||||
|
||||
GC.Collect();
|
||||
GC.WaitForPendingFinalizers();
|
||||
GC.Collect();
|
||||
|
||||
var g0After = GC.CollectionCount(0);
|
||||
var g1After = GC.CollectionCount(1);
|
||||
var g2After = GC.CollectionCount(2);
|
||||
var allocAfter = GC.GetTotalAllocatedBytes(true);
|
||||
int g0After = GC.CollectionCount(0);
|
||||
int g1After = GC.CollectionCount(1);
|
||||
int g2After = GC.CollectionCount(2);
|
||||
long allocAfter = GC.GetTotalAllocatedBytes(true);
|
||||
|
||||
return new CompressionGcProbeResult(
|
||||
enableCompression,
|
||||
@@ -198,11 +190,11 @@ internal static class PerformanceGateSmoke
|
||||
|
||||
private static string WriteReport(PerformanceGateReport report)
|
||||
{
|
||||
var outputDirectory = Path.Combine(Directory.GetCurrentDirectory(), "BenchmarkDotNet.Artifacts", "results");
|
||||
string outputDirectory = Path.Combine(Directory.GetCurrentDirectory(), "BenchmarkDotNet.Artifacts", "results");
|
||||
Directory.CreateDirectory(outputDirectory);
|
||||
|
||||
var reportPath = Path.Combine(outputDirectory, "PerformanceGateSmoke-report.json");
|
||||
var json = JsonSerializer.Serialize(report, new JsonSerializerOptions { WriteIndented = true });
|
||||
string reportPath = Path.Combine(outputDirectory, "PerformanceGateSmoke-report.json");
|
||||
string json = JsonSerializer.Serialize(report, new JsonSerializerOptions { WriteIndented = true });
|
||||
File.WriteAllText(reportPath, json);
|
||||
return reportPath;
|
||||
}
|
||||
@@ -214,7 +206,7 @@ internal static class PerformanceGateSmoke
|
||||
Id = ObjectId.NewObjectId(),
|
||||
FirstName = $"First_{i}",
|
||||
LastName = $"Last_{i}",
|
||||
Age = 20 + (i % 50),
|
||||
Age = 20 + i % 50,
|
||||
Bio = includeLargeBio ? BuildBio(i) : $"bio-{i}",
|
||||
CreatedAt = DateTime.UnixEpoch.AddMinutes(i),
|
||||
Balance = 100 + i,
|
||||
@@ -239,7 +231,7 @@ internal static class PerformanceGateSmoke
|
||||
|
||||
private static string BuildBio(int seed)
|
||||
{
|
||||
var builder = new System.Text.StringBuilder(4500);
|
||||
var builder = new StringBuilder(4500);
|
||||
for (var i = 0; i < 150; i++)
|
||||
{
|
||||
builder.Append("bio-");
|
||||
@@ -253,14 +245,13 @@ internal static class PerformanceGateSmoke
|
||||
}
|
||||
|
||||
private static string NewDbPath(string prefix)
|
||||
=> Path.Combine(Path.GetTempPath(), $"{prefix}_{Guid.NewGuid():N}.db");
|
||||
{
|
||||
return Path.Combine(Path.GetTempPath(), $"{prefix}_{Guid.NewGuid():N}.db");
|
||||
}
|
||||
|
||||
private static void TryDelete(string path)
|
||||
{
|
||||
if (File.Exists(path))
|
||||
{
|
||||
File.Delete(path);
|
||||
}
|
||||
if (File.Exists(path)) File.Delete(path);
|
||||
}
|
||||
|
||||
private sealed record PerformanceGateReport(
|
||||
@@ -284,4 +275,4 @@ internal static class PerformanceGateSmoke
|
||||
int Gen1Delta,
|
||||
int Gen2Delta,
|
||||
long AllocatedBytesDelta);
|
||||
}
|
||||
}
|
||||
@@ -1,25 +1,25 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<AssemblyName>ZB.MOM.WW.CBDD.Tests.Benchmark</AssemblyName>
|
||||
<RootNamespace>ZB.MOM.WW.CBDD.Tests.Benchmark</RootNamespace>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<AssemblyName>ZB.MOM.WW.CBDD.Tests.Benchmark</AssemblyName>
|
||||
<RootNamespace>ZB.MOM.WW.CBDD.Tests.Benchmark</RootNamespace>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BenchmarkDotNet" Version="0.15.8" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="9.0.10" />
|
||||
<PackageReference Include="Serilog" Version="4.2.0" />
|
||||
<PackageReference Include="Serilog.Extensions.Logging" Version="8.0.0" />
|
||||
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BenchmarkDotNet" Version="0.15.8"/>
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="9.0.10"/>
|
||||
<PackageReference Include="Serilog" Version="4.2.0"/>
|
||||
<PackageReference Include="Serilog.Extensions.Logging" Version="8.0.0"/>
|
||||
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\CBDD.Core\ZB.MOM.WW.CBDD.Core.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\CBDD.Core\ZB.MOM.WW.CBDD.Core.csproj"/>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
Reference in New Issue
Block a user