Implement checkpoint modes with docs/tests and reorganize project file layout
All checks were successful
NuGet Publish / build-and-pack (push) Successful in 46s
NuGet Publish / publish-to-gitea (push) Successful in 53s

This commit is contained in:
Joseph Doherty
2026-02-21 07:56:36 -05:00
parent 3ffd468c79
commit 4c6aaa5a3f
96 changed files with 744 additions and 249 deletions

View File

@@ -0,0 +1,101 @@
using ZB.MOM.WW.CBDD.Core;
using ZB.MOM.WW.CBDD.Core.Storage;
using ZB.MOM.WW.CBDD.Core.Transactions;
namespace ZB.MOM.WW.CBDD.Tests.Benchmark;
internal sealed class BenchmarkTransactionHolder : ITransactionHolder, IDisposable
{
private readonly StorageEngine _storage;
private readonly object _sync = new();
private ITransaction? _currentTransaction;
/// <summary>
/// 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)
{
_storage = storage ?? throw new ArgumentNullException(nameof(storage));
}
/// <summary>
/// Gets the current active transaction or starts a new one.
/// </summary>
/// <returns>The current active transaction.</returns>
public ITransaction GetCurrentTransactionOrStart()
{
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.
/// </summary>
/// <returns>A task that returns the current active transaction.</returns>
public Task<ITransaction> GetCurrentTransactionOrStartAsync()
{
return Task.FromResult(GetCurrentTransactionOrStart());
}
/// <summary>
/// Commits the current transaction when active and clears the holder.
/// </summary>
public void CommitAndReset()
{
lock (_sync)
{
if (_currentTransaction == null)
{
return;
}
if (_currentTransaction.State == TransactionState.Active ||
_currentTransaction.State == TransactionState.Preparing)
{
_currentTransaction.Commit();
}
_currentTransaction.Dispose();
_currentTransaction = null;
}
}
/// <summary>
/// Rolls back the current transaction when active and clears the holder.
/// </summary>
public void RollbackAndReset()
{
lock (_sync)
{
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();
}
}

View File

@@ -0,0 +1,38 @@
using Microsoft.Extensions.Logging;
using Serilog;
namespace ZB.MOM.WW.CBDD.Tests.Benchmark;
internal static class Logging
{
private static readonly Lazy<ILoggerFactory> LoggerFactoryInstance = new(CreateFactory);
/// <summary>
/// Gets the shared logger factory for benchmarks.
/// </summary>
public static ILoggerFactory LoggerFactory => LoggerFactoryInstance.Value;
/// <summary>
/// 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>()
{
return LoggerFactory.CreateLogger<T>();
}
private static ILoggerFactory CreateFactory()
{
var serilogLogger = new LoggerConfiguration()
.Enrich.FromLogContext()
.WriteTo.Console()
.CreateLogger();
return Microsoft.Extensions.Logging.LoggerFactory.Create(builder =>
{
builder.ClearProviders();
builder.AddSerilog(serilogLogger, dispose: true);
});
}
}

View File

@@ -0,0 +1,89 @@
using BenchmarkDotNet.Columns;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Exporters;
using BenchmarkDotNet.Reports;
using BenchmarkDotNet.Running;
using Microsoft.Extensions.Logging;
using Serilog.Context;
namespace ZB.MOM.WW.CBDD.Tests.Benchmark;
class Program
{
static void Main(string[] args)
{
var logger = Logging.CreateLogger<Program>();
var mode = args.Length > 0 ? args[0].Trim().ToLowerInvariant() : string.Empty;
if (mode == "manual")
{
using var _ = LogContext.PushProperty("Mode", "Manual");
ManualBenchmark.Run(logger);
return;
}
if (mode == "size")
{
using var _ = LogContext.PushProperty("Mode", "SizeBenchmark");
DatabaseSizeBenchmark.Run(logger);
return;
}
if (mode == "compression")
{
using var _ = LogContext.PushProperty("Mode", "CompressionBenchmarks");
BenchmarkRunner.Run<CompressionBenchmarks>(CreateConfig());
return;
}
if (mode == "compaction")
{
using var _ = LogContext.PushProperty("Mode", "CompactionBenchmarks");
BenchmarkRunner.Run<CompactionBenchmarks>(CreateConfig());
return;
}
if (mode == "mixed")
{
using var _ = LogContext.PushProperty("Mode", "MixedWorkloadBenchmarks");
BenchmarkRunner.Run<MixedWorkloadBenchmarks>(CreateConfig());
return;
}
if (mode == "gate")
{
using var _ = LogContext.PushProperty("Mode", "PerformanceGateSmoke");
PerformanceGateSmoke.Run(logger);
return;
}
if (mode == "all")
{
using var _ = LogContext.PushProperty("Mode", "AllBenchmarks");
var config = CreateConfig();
BenchmarkRunner.Run<InsertBenchmarks>(config);
BenchmarkRunner.Run<ReadBenchmarks>(config);
BenchmarkRunner.Run<SerializationBenchmarks>(config);
BenchmarkRunner.Run<CompressionBenchmarks>(config);
BenchmarkRunner.Run<CompactionBenchmarks>(config);
BenchmarkRunner.Run<MixedWorkloadBenchmarks>(config);
return;
}
using var __ = LogContext.PushProperty("Mode", "BenchmarkDotNet");
var defaultConfig = CreateConfig();
BenchmarkRunner.Run<InsertBenchmarks>(defaultConfig);
BenchmarkRunner.Run<ReadBenchmarks>(defaultConfig);
BenchmarkRunner.Run<SerializationBenchmarks>(defaultConfig);
}
private static IConfig CreateConfig()
{
return DefaultConfig.Instance
.AddExporter(HtmlExporter.Default)
.WithSummaryStyle(SummaryStyle.Default
.WithRatioStyle(RatioStyle.Trend)
.WithTimeUnit(Perfolizer.Horology.TimeUnit.Microsecond));
}
}