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; using ZB.MOM.WW.CBDD.Core.Storage; namespace ZB.MOM.WW.CBDD.Tests.Benchmark; [SimpleJob] [InProcess] [MemoryDiagnoser] [GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)] [HtmlExporter] [JsonExporterAttribute.Full] public class MixedWorkloadBenchmarks { [Params(false, true)] public bool PeriodicCompaction { get; set; } [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 _collection = null!; private readonly List _activeIds = []; private int _nextValueSeed; [IterationSetup] public void Setup() { var id = Guid.NewGuid().ToString("N"); _dbPath = Path.Combine(AppContext.BaseDirectory, $"bench_mixed_{id}.db"); _walPath = Path.ChangeExtension(_dbPath, ".wal"); var options = new CompressionOptions { EnableCompression = true, MinSizeBytes = 256, MinSavingsPercent = 0, Codec = CompressionCodec.Brotli }; _storage = new StorageEngine(_dbPath, PageFileConfig.Default, options); _transactionHolder = new BenchmarkTransactionHolder(_storage); _collection = new DocumentCollection(_storage, _transactionHolder, new PersonMapper()); _activeIds.Clear(); _nextValueSeed = 0; for (var i = 0; i < 300; i++) { var idValue = _collection.Insert(CreatePerson(_nextValueSeed++)); _activeIds.Add(idValue); } _transactionHolder.CommitAndReset(); } [IterationCleanup] public void Cleanup() { _transactionHolder?.Dispose(); _storage?.Dispose(); if (File.Exists(_dbPath)) File.Delete(_dbPath); if (File.Exists(_walPath)) File.Delete(_walPath); } [Benchmark(Baseline = true)] [BenchmarkCategory("MixedWorkload")] public int InsertUpdateDeleteMix() { var random = new Random(12345); for (var i = 1; i <= Operations; i++) { var mode = i % 5; if (mode is 0 or 1) { var id = _collection.Insert(CreatePerson(_nextValueSeed++)); _activeIds.Add(id); } else if (mode is 2 or 3) { if (_activeIds.Count > 0) { var idx = random.Next(_activeIds.Count); var id = _activeIds[idx]; var current = _collection.FindById(id); if (current != null) { current.Age += 1; current.Bio = BuildPayload(_nextValueSeed++); _collection.Update(current); } } } else { if (_activeIds.Count > 100) { var idx = random.Next(_activeIds.Count); var id = _activeIds[idx]; _collection.Delete(id); _activeIds.RemoveAt(idx); } } if (i % 50 == 0) { _transactionHolder.CommitAndReset(); } if (PeriodicCompaction && i % 200 == 0) { _storage.RunOnlineCompactionPass(new CompactionOptions { OnlineMode = true, OnlineBatchPageLimit = 8, OnlineBatchDelay = TimeSpan.FromMilliseconds(1), MaxOnlineDuration = TimeSpan.FromMilliseconds(120), EnableTailTruncation = true }); } } _transactionHolder.CommitAndReset(); return _collection.Count(); } private static Person CreatePerson(int seed) { return new Person { Id = ObjectId.NewObjectId(), FirstName = $"First_{seed}", LastName = $"Last_{seed}", Age = 18 + (seed % 60), Bio = BuildPayload(seed), CreatedAt = DateTime.UnixEpoch.AddSeconds(seed), Balance = seed, HomeAddress = new Address { Street = $"{seed} Mixed Ave", City = "Workload City", ZipCode = "10101" } }; } private static string BuildPayload(int seed) { var builder = new System.Text.StringBuilder(1800); for (var i = 0; i < 64; i++) { builder.Append("mixed-"); builder.Append(seed.ToString("D6")); builder.Append('-'); builder.Append(i.ToString("D3")); builder.Append('|'); } return builder.ToString(); } }