using System.Diagnostics; using System.Globalization; using System.Reflection; using System.Text.RegularExpressions; using Microsoft.FSharp.Core; using System.Text.Json; using JsonSerializer = System.Text.Json.JsonSerializer; using LiteDB; using Microsoft.Data.Sqlite; using SoloDatabase; using MongoDB.Driver; using MongoDB.Driver.GridFS; using MongoDB.Driver.Core.Events; using MongoDB.Bson; using MB = MongoDB.Bson; public sealed class Event { public long Id { get; set; } public string System { get; set; } public string Subsystem { get; set; } public string MachineName { get; set; } public string AggregateType { get; set; } public string AggId { get; set; } public string Username { get; set; } public string Type { get; set; } public long UnixTimestamp { get; set; } public string Body { get; set; } public string Tags { get; set; } } sealed record Reads(Func Count, Func> Page, Func Point, Func> Groups); abstract class Store : IDisposable { public abstract Reads Prepare(); public abstract void CompoundIndex(); public abstract long StorageBytes(); public abstract void Insert(List data); public abstract void Index(); public abstract long Count(int shape); public abstract List Page(int offset); public abstract Event Point(long id); public abstract List AllDocuments(); public abstract int Update(string body); public abstract Dictionary Groups(); public abstract void WriteFile(int id, byte[] bytes); public abstract byte[] ReadFile(int id); public abstract byte[] ReadFileAt(int id,int offset,bool optimized); public abstract void EditFile(int id,int offset,byte[] patch,int length); public abstract byte[] ReadWholeFile(int id); public abstract void IndexTags(); public abstract void DropTagIndex(); public abstract object Plans(); public abstract object Settings(); public abstract void Dispose(); public virtual void DropFixture() {} } sealed class SoloStore : Store { readonly SoloDB db; readonly ISoloDBCollection col; public SoloStore(string path) { db = new SoloDB(path); col = db.GetCollection(); } public override void Insert(List data) => col.InsertBatch(data); public override void CompoundIndex() {col.EnsureIndex(x=>new ValueTuple(x.MachineName,x.AggId));db.Optimize();} public override long StorageBytes() {using var c=col.GetInternalConnection();using var cmd=c.CreateCommand();cmd.CommandText="SELECT page_count * page_size FROM pragma_page_count(), pragma_page_size()";return Convert.ToInt64(cmd.ExecuteScalar());} public override void Index() { col.EnsureIndex(x=>x.System); db.Optimize(); col.EnsureIndex(x=>x.MachineName); db.Optimize(); col.EnsureIndex(x=>x.AggId); db.Optimize(); col.EnsureIndex(x=>x.UnixTimestamp); db.Optimize(); col.EnsureIndex(x=>x.Type); db.Optimize(); col.EnsureIndex(x=>x.Username); db.Optimize(); } public override long Count(int shape) => shape switch { 0=>col.LongCount(), 1=>col.LongCount(x=>x.System.StartsWith("ui console")), 2=>col.LongCount(x=>x.MachineName.StartsWith("NOD") && x.AggId.StartsWith("AB")), 3=>col.LongCount(x=>x.Tags=="NOK"), _=>throw new ArgumentException() }; IQueryable Query(int offset) => col.Where(x=>x.System.StartsWith("ui console")).OrderByDescending(x=>x.UnixTimestamp).Skip(offset).Take(100); public override List Page(int offset)=>Query(offset).ToList(); public override Reads Prepare() { var all=col.Compile(q=>q.LongCount()); var prefix=col.Compile((IQueryable q,string value)=>q.LongCount(x=>x.System.StartsWith(value))); var two=col.Compile((IQueryable q,string machine,string aggregate)=>q.LongCount(x=>x.MachineName.StartsWith(machine)&&x.AggId.StartsWith(aggregate))); var tag=col.Compile((IQueryable q,string value)=>q.LongCount(x=>x.Tags==value)); var page=col.Compile((IQueryable q,string value,int offset)=>q.Where(x=>x.System.StartsWith(value)).OrderByDescending(x=>x.UnixTimestamp).Skip(offset).Take(100)); var point=col.Compile((IQueryable q,long id)=>q.Single(x=>x.Id==id)); var groups=col.Compile(q=>q.GroupBy(x=>x.Username.Substring(0,1)).Select(g=>new {g.Key,N=g.LongCount()})); return new Reads(shape=>shape switch {0=>all(),1=>prefix("ui console"),2=>two("NOD","AB"),3=>tag("NOK"),_=>throw new ArgumentException()}, offset=>page("ui console",offset).ToList(),point,()=>groups().ToDictionary(x=>x.Key,x=>x.N)); } public override Event Point(long id)=>col.GetById(id); public override List AllDocuments()=>col.OrderBy(x=>x.Id).ToList(); public override int Update(string body)=>col.UpdateMany(x=>x.Id<=20000 && (x.System.StartsWith("ui") || x.Type=="Error") && x.MachineName.StartsWith("NOD") && x.AggId.StartsWith("AB") && x.Username.Length>3,x=>x.Body.Set(body)); public override Dictionary Groups()=>col.GroupBy(x=>x.Username.Substring(0,1)).Select(g=>new {g.Key,N=g.LongCount()}).ToDictionary(x=>x.Key,x=>x.N); public override void WriteFile(int id,byte[] bytes) { db.FileSystem.WriteAt($"/bench/{id}.bin",0,bytes); using var stream=db.FileSystem.OpenAt($"/bench/{id}.bin"); stream.SetLength(bytes.Length); } public override byte[] ReadFile(int id)=>db.FileSystem.ReadAt($"/bench/{id}.bin",0,1024); public override byte[] ReadFileAt(int id,int offset,bool optimized) { if(!optimized)return db.FileSystem.ReadAt($"/bench/{id}.bin",offset,1024); using var stream=db.FileSystem.OpenAt($"/bench/{id}.bin");stream.Position=offset; var bytes=new byte[1024];stream.ReadExactly(bytes.AsSpan());return bytes; } public override void EditFile(int id,int offset,byte[] patch,int length) { if(patch.Length>0)db.FileSystem.WriteAt($"/bench/{id}.bin",offset,patch); using(var stream=db.FileSystem.OpenAt($"/bench/{id}.bin"))stream.SetLength(length); } public override byte[] ReadWholeFile(int id) { using var stream=db.FileSystem.OpenAt($"/bench/{id}.bin"); var bytes=new byte[checked((int)stream.Length)];stream.ReadExactly(bytes);return bytes; } public override void DropTagIndex()=>col.DropIndexIfExists(x=>x.Tags); public override void IndexTags() { col.EnsureIndex(x=>x.Tags); db.Optimize(); } public override object Plans() { var property=typeof(SoloDB).Assembly.GetType("SoloDatabase.SQLiteToolsParams")?.GetProperty("sqlBoundTraceCallback",BindingFlags.Static|BindingFlags.Public|BindingFlags.NonPublic); if(property==null) throw new Exception("Pinned SoloDB diagnostic hook unavailable"); var records=new List(); var prepared=Prepare(); var operations=new List<(string name,Action run)>(); foreach(int shape in Enumerable.Range(0,4)) { int selected=shape;operations.Add(("Count"+shape+".ordinary",()=>Count(selected))); operations.Add(("Count"+shape+".optimized",()=>prepared.Count(selected))); } foreach(int offset in new[]{0,5000}) { int skip=offset;operations.Add(("Page"+offset+".ordinary",()=>Page(skip))); operations.Add(("Page"+offset+".optimized",()=>prepared.Page(skip))); } operations.Add(("Point.ordinary",()=>Point(1)));operations.Add(("Point.optimized",()=>prepared.Point(1))); operations.Add(("GroupBy.ordinary",()=>Groups()));operations.Add(("GroupBy.optimized",()=>prepared.Groups())); foreach(var (name,run) in operations) { var statements=new List<(string sql,KeyValuePair[] parameters)>(); var callback=new Action>>((sql,ps)=>statements.Add((sql,ps.ToArray()))); property.SetValue(null,FSharpValueOption>>>.NewValueSome(callback)); try { run(); } finally {property.SetValue(null,default(FSharpValueOption>>>));} using var conn=col.GetInternalConnection(); foreach(var statement in statements.Where(s=>s.sql.TrimStart().StartsWith("SELECT",StringComparison.OrdinalIgnoreCase)||s.sql.TrimStart().StartsWith("WITH",StringComparison.OrdinalIgnoreCase))) { using var cmd=conn.CreateCommand();cmd.CommandText="EXPLAIN QUERY PLAN "+statement.sql; foreach(var pair in statement.parameters)cmd.Parameters.AddWithValue(pair.Key,pair.Value??DBNull.Value); using var reader=cmd.ExecuteReader();var plan=new List();while(reader.Read())plan.Add(reader.GetString(3)); records.Add(new{name,statement.sql,bindings=statement.parameters,plan}); } } return records; } public override object Settings() { using var c=col.GetInternalConnection(); return SqlDiagnostics.ReadSettings(c); } public override void Dispose()=>db.Dispose(); } sealed class LiteStore : Store { readonly LiteDatabase db; readonly string path; readonly ILiteCollection col; public LiteStore(string path) { this.path=path; db=new LiteDatabase(new ConnectionString {Filename=path,Collation=new LiteDB.Collation(127,CompareOptions.Ordinal)},new BsonMapper {EmptyStringToNull=false,TrimWhitespace=false}); col=db.GetCollection("events"); } public override void Insert(List data) { db.BeginTrans(); try { col.InsertBulk(data); db.Commit(); } catch { db.Rollback(); throw; } } public override void Index() { col.EnsureIndex(x=>x.System); col.EnsureIndex(x=>x.MachineName); col.EnsureIndex(x=>x.AggId); col.EnsureIndex(x=>x.UnixTimestamp); col.EnsureIndex(x=>x.Type); col.EnsureIndex(x=>x.Username); } public override void CompoundIndex()=>col.EnsureIndex("machine_aggregate","{machine:$.MachineName,aggregate:$.AggId}"); public override long StorageBytes() {db.Checkpoint();return new FileInfo(path).Length;} static BsonExpression Single()=>LiteDB.Query.StartsWith("System","ui console"); public override long Count(int shape)=>shape switch { 0=>col.LongCount(),1=>col.LongCount(x=>x.System.StartsWith("ui console")), 2=>col.LongCount(x=>x.MachineName.StartsWith("NOD")&&x.AggId.StartsWith("AB")), 3=>col.LongCount(x=>x.Tags=="NOK"),_=>throw new ArgumentException() }; ILiteQueryableResult Query(int offset)=>col.Query().Where(x=>x.System.StartsWith("ui console")).OrderByDescending(x=>x.UnixTimestamp).Offset(offset).Limit(100); public override List Page(int offset)=>Query(offset).ToList(); public override Reads Prepare() { var all=col.Query().Select("{n:COUNT(*._id)}"); var prefix=col.Query().Where(x=>x.System.StartsWith("ui console")).Select("{n:COUNT(*.System)}"); var two=col.Query().Where(x=>x.MachineName.StartsWith("NOD")&&x.AggId.StartsWith("AB")).Select("{n:COUNT(*._id)}"); var tag=col.Query().Where(x=>x.Tags=="NOK").Select("{n:COUNT(*.Tags)}"); var page=col.Query().Where(x=>x.System.StartsWith("ui console")).OrderByDescending(x=>x.UnixTimestamp).Limit(100); var groups=col.Query().GroupBy("SUBSTRING(Username,0,1)").Select("{key:@key,n:COUNT(*)}"); return new Reads(shape=>(shape switch {0=>all,1=>prefix,2=>two,3=>tag,_=>throw new ArgumentException()}).Single()["n"].AsInt64, offset=>page.Offset(offset).ToList(),id=>col.FindOne(LiteDB.Query.EQ("_id",id)), ()=>groups.ToList().ToDictionary(x=>x["key"].AsString,x=>x["n"].AsInt64)); } public override Event Point(long id)=>col.FindById(id); public override List AllDocuments()=>col.Query().OrderBy(x=>x.Id).ToList(); public override int Update(string body)=>col.UpdateMany(x=>new Event {Body=body},x=>x.Id<=20000 && (x.System.StartsWith("ui") || x.Type=="Error") && x.MachineName.StartsWith("NOD") && x.AggId.StartsWith("AB") && x.Username.Length>3); public override Dictionary Groups()=>col.Query().GroupBy("SUBSTRING(Username,0,1)").Select("{key:@key,n:COUNT(*)}").ToList().ToDictionary(x=>x["key"].AsString,x=>x["n"].AsInt64); public override void WriteFile(int id,byte[] bytes) { using var s=new MemoryStream(bytes,false); db.FileStorage.Upload($"bench/{id}.bin","blob.bin",s); } public override byte[] ReadFile(int id) { using var s=db.FileStorage.OpenRead($"bench/{id}.bin"); var b=new byte[1024]; s.ReadExactly(b); return b; } public override byte[] ReadFileAt(int id,int offset,bool optimized) { using var stream=db.FileStorage.OpenRead($"bench/{id}.bin");stream.Position=offset; var bytes=new byte[1024];if(optimized)stream.ReadExactly(bytes.AsSpan());else stream.ReadExactly(bytes);return bytes; } public override void EditFile(int id,int offset,byte[] patch,int length) { var bytes=new byte[length]; using(var stream=db.FileStorage.OpenRead($"bench/{id}.bin")) stream.ReadExactly(bytes.AsSpan(0,checked((int)Math.Min(stream.Length,length)))); patch.AsSpan(0,Math.Min(patch.Length,Math.Max(0,length-offset))).CopyTo(bytes.AsSpan(Math.Min(offset,length))); WriteFile(id,bytes); } public override byte[] ReadWholeFile(int id) { using var stream=db.FileStorage.OpenRead($"bench/{id}.bin"); var bytes=new byte[checked((int)stream.Length)];stream.ReadExactly(bytes);return bytes; } public override void DropTagIndex()=>col.DropIndex("Tags"); public override void IndexTags()=>col.EnsureIndex(x=>x.Tags); public override object Plans() { var plans=new List(); foreach(int shape in Enumerable.Range(0,4))foreach(bool optimized in new[]{false,true}) { var query=col.Query(); if(shape==1)query=query.Where(x=>x.System.StartsWith("ui console")); if(shape==2)query=query.Where(x=>x.MachineName.StartsWith("NOD")&&x.AggId.StartsWith("AB")); if(shape==3)query=query.Where(x=>x.Tags=="NOK"); string select=optimized&&shape==1?"{n:COUNT(*.System)}":optimized&&shape==3?"{n:COUNT(*.Tags)}":"{n:COUNT(*._id)}"; plans.Add(new {name="Count"+shape+(optimized?".optimized":".ordinary"),projection=select,plan=query.Select(select).GetPlan().ToString()}); } foreach(int offset in new[]{0,5000})plans.Add(new {name="Page"+offset,plan=Query(offset).GetPlan().ToString()}); plans.Add(new {name="Point",plan=col.Query().Where(LiteDB.Query.EQ("_id",1)).Limit(1).GetPlan().ToString()}); plans.Add(new {name="GroupBy",plan=col.Query().GroupBy("SUBSTRING(Username,0,1)").Select("{key:@key,n:COUNT(*)}").GetPlan().ToString()}); return plans; } public override object Settings()=>new {Collation=db.Collation.ToString(),Version=typeof(LiteDatabase).Assembly.GetName().Version.ToString(),Connection="Direct",EmptyStringToNull=false,TrimWhitespace=false,Files="Native chunk transactions"}; public object CoveredGroupingProbe() { try { var rows=col.Query().GroupBy("SUBSTRING(Username,0,1)").Select("{key:@key,n:COUNT(*.Username)}").ToList(); return new {outcome="completed",rows=rows.Select(r=>r.ToString()).ToArray()}; } catch(LiteException ex) {return new {outcome="exception",type=ex.GetType().FullName,ex.Message};} } public object StreamFeatures() {using var s=db.FileStorage.OpenRead("bench/0.bin");return new{s.CanRead,s.CanWrite,s.CanSeek};} public void MultiKeyCheck() { var c=db.GetCollection("array_feature"); c.Insert(new LiteDB.BsonDocument { ["_id"]=1,["Tags"]=new LiteDB.BsonArray {"NOK","other"} }); c.Insert(new LiteDB.BsonDocument { ["_id"]=2,["Tags"]=new LiteDB.BsonArray {"other"} }); c.EnsureIndex("tags","$.Tags[*]"); if(c.Count("$.Tags ANY = 'NOK'")!=1) throw new Exception("multikey feature check"); db.DropCollection("array_feature"); } public override void Dispose() { db.Checkpoint(); db.Dispose(); } } static class SqlDiagnostics { public static object ReadSettings(SqliteConnection c) { object Scalar(string sql) {using var cmd=c.CreateCommand();cmd.CommandText=sql;return cmd.ExecuteScalar();} return new {SQLite=Scalar("SELECT sqlite_version()"),Journal=Scalar("PRAGMA journal_mode"),Synchronous=Scalar("PRAGMA synchronous"),CacheSize=Scalar("PRAGMA cache_size")}; } public static void Exec(SqliteConnection c,string sql) {using var cmd=c.CreateCommand();cmd.CommandText=sql;cmd.ExecuteNonQuery();} } sealed class MongoStore : Store { readonly MongoClient client; List captured; readonly IMongoDatabase db; readonly IMongoCollection col; readonly GridFSBucket files; readonly HashSet writtenFiles=new(); static readonly FilterDefinitionBuilder F=Builders.Filter; public MongoStore(string path) { var settings=MongoClientSettings.FromConnectionString(Environment.GetEnvironmentVariable("BENCH_MONGO_URI")??"mongodb://127.0.0.1:27028/?replicaSet=benchmark"); settings.ClusterConfigurator=cluster=>cluster.Subscribe(command=> { if(captured!=null && (command.CommandName=="find"||command.CommandName=="aggregate"))captured.Add(MB.BsonDocument.Parse(command.Command.ToJson())); }); client=new MongoClient(settings); string name="blog_"+Path.GetFileName(Path.GetDirectoryName(path)).Replace('-','_')+"_"+Path.GetFileNameWithoutExtension(path).Replace('-','_'); db=client.GetDatabase(name).WithWriteConcern(WriteConcern.WMajority.With(journal:true)).WithReadPreference(ReadPreference.Primary); if(!db.ListCollectionNames().ToList().Contains("events"))db.CreateCollection("events"); col=db.GetCollection("events").WithReadConcern(ReadConcern.Snapshot); files=new GridFSBucket(db); db.RunCommand(new MB.BsonDocument("ping",1)); } static readonly TransactionOptions Transactions=new(readConcern:ReadConcern.Snapshot,readPreference:ReadPreference.Primary,writeConcern:WriteConcern.WMajority.With(journal:true)); public override void Insert(List data) { using var session=client.StartSession(); session.WithTransaction((tx,_)=>{col.InsertMany(tx,data);return true;},Transactions); } public override void Index()=>col.Indexes.CreateMany(new[]{"System","MachineName","AggId","UnixTimestamp","Type","Username"}.Select(field=>new CreateIndexModel(Builders.IndexKeys.Ascending(field)))); static FilterDefinition Filter(int shape)=>shape switch { 0=>F.Empty,1=>F.Regex(x=>x.System,new MB.BsonRegularExpression("^"+Regex.Escape("ui console"))), 2=>F.Regex(x=>x.MachineName,new MB.BsonRegularExpression("^"+Regex.Escape("NOD"))) & F.Regex(x=>x.AggId,new MB.BsonRegularExpression("^"+Regex.Escape("AB"))), 3=>F.Eq(x=>x.Tags,"NOK"),_=>throw new ArgumentException()}; public override long Count(int shape)=>col.CountDocuments(Filter(shape)); public override List Page(int offset)=>col.Find(Filter(1)).SortByDescending(x=>x.UnixTimestamp).Skip(offset).Limit(100).ToList(); public override Reads Prepare() { var filters=Enumerable.Range(0,4).Select(Filter).ToArray(); var counts=new[] {new CountOptions {Hint="_id_"},new CountOptions {Hint="System_1"},new CountOptions {Hint="MachineName_1"},new CountOptions()}; var sort=Builders.Sort.Descending(x=>x.UnixTimestamp); var options=new FindOptions {Hint="UnixTimestamp_1"}; var group=MB.BsonDocument.Parse("{$group:{_id:{$substrCP:['$Username',0,1]},n:{$sum:1}}}"); return new Reads(shape=>col.CountDocuments(filters[shape],counts[shape]), offset=>col.Find(filters[1],options).Sort(sort).Skip(offset).Limit(100).ToList(), id=>col.Find(F.Eq(x=>x.Id,id),new FindOptions {Hint="_id_"}).Single(), ()=>col.Aggregate(new[]{group},new AggregateOptions {Hint="Username_1"}).ToList().ToDictionary(x=>x["_id"].AsString,x=>x["n"].ToInt64())); } public override Event Point(long id)=>col.Find(F.Eq(x=>x.Id,id)).Single(); public override List AllDocuments()=>col.Find(F.Empty).SortBy(x=>x.Id).ToList(); public override int Update(string body) { using var session=client.StartSession(); return session.WithTransaction((tx,_)=>checked((int)col.UpdateMany(tx,F.Where(x=>x.Id<=20000 && (x.System.StartsWith("ui") || x.Type=="Error") && x.MachineName.StartsWith("NOD") && x.AggId.StartsWith("AB") && x.Username.Length>3),Builders.Update.Set(x=>x.Body,body)).ModifiedCount),Transactions); } public override Dictionary Groups() { var group=MB.BsonDocument.Parse("{$group:{_id:{$substrCP:['$Username',0,1]},n:{$sum:1}}}"); return col.Aggregate(new[]{group}).ToList().ToDictionary(x=>x["_id"].AsString,x=>x["n"].ToInt64()); } public override void WriteFile(int id,byte[] bytes) { if(writtenFiles.Contains(id))files.Delete(id); using var input=new MemoryStream(bytes,false);files.UploadFromStream(id,$"{id}.bin",input);writtenFiles.Add(id); } public override byte[] ReadFile(int id) {using var stream=files.OpenDownloadStream(id);var bytes=new byte[1024];stream.ReadExactly(bytes);return bytes;} public override byte[] ReadFileAt(int id,int offset,bool optimized) { using var stream=files.OpenDownloadStream(id,new GridFSDownloadOptions {Seekable=true});stream.Position=offset; var bytes=new byte[1024];if(optimized)stream.ReadExactly(bytes.AsSpan());else stream.ReadExactly(bytes);return bytes; } public override void EditFile(int id,int offset,byte[] patch,int length) { var bytes=new byte[length]; using(var stream=files.OpenDownloadStream(id)) stream.ReadExactly(bytes.AsSpan(0,checked((int)Math.Min(stream.Length,length)))); patch.AsSpan(0,Math.Min(patch.Length,Math.Max(0,length-offset))).CopyTo(bytes.AsSpan(Math.Min(offset,length))); WriteFile(id,bytes); } public override byte[] ReadWholeFile(int id) { using var stream=files.OpenDownloadStream(id); var bytes=new byte[checked((int)stream.Length)];stream.ReadExactly(bytes);return bytes; } public override void DropTagIndex()=>col.Indexes.DropOne("Tags_1"); public override void IndexTags()=>col.Indexes.CreateOne(new CreateIndexModel(Builders.IndexKeys.Ascending(x=>x.Tags))); public override void CompoundIndex()=>col.Indexes.CreateOne(new CreateIndexModel(Builders.IndexKeys.Ascending(x=>x.MachineName).Ascending(x=>x.AggId))); public long OptimizedCount(int shape)=>col.CountDocuments(Filter(shape),new CountOptions {Hint=shape switch {0=>"_id_",1=>"System_1",2=>"MachineName_1_AggId_1",3=>"Tags_1",_=>throw new ArgumentException()}}); MB.BsonDocument Render(FilterDefinition filter)=>filter.Render(new RenderArgs(col.DocumentSerializer,col.Settings.SerializerRegistry)); public override object Plans() { var prepared=Prepare();var results=new List(); var operations=new List<(string name,Action run)>(); foreach(int shape in Enumerable.Range(0,4)) { int selected=shape;operations.Add(("Count"+shape+".ordinary",()=>Count(selected))); operations.Add(("Count"+shape+".optimized",()=>prepared.Count(selected))); } foreach(int offset in new[]{0,5000}) { int skip=offset;operations.Add(("Page"+offset+".ordinary",()=>Page(skip))); operations.Add(("Page"+offset+".optimized",()=>prepared.Page(skip))); } operations.Add(("Point.ordinary",()=>Point(1)));operations.Add(("Point.optimized",()=>prepared.Point(1))); operations.Add(("GroupBy.ordinary",()=>Groups()));operations.Add(("GroupBy.optimized",()=>prepared.Groups())); foreach(var (name,run) in operations) { captured=new();List commands; try {run();commands=captured;}finally {captured=null;} foreach(var command in commands) { var explain=command.DeepClone().AsBsonDocument; foreach(var field in new[]{"$db","lsid","$clusterTime","readConcern"})explain.Remove(field); results.Add(new {name,command=command.ToJson(),plan=Explain(explain)}); } } return results; } string Explain(MB.BsonDocument cmd)=>db.RunCommand(new MB.BsonDocument {{"explain",cmd},{"verbosity","executionStats"}}).ToJson(); public object CompoundPlan()=>Explain(new MB.BsonDocument {{"aggregate","events"},{"pipeline",new MB.BsonArray {new MB.BsonDocument("$match",Render(Filter(2))),new MB.BsonDocument("$count","n")}},{"cursor",new MB.BsonDocument()},{"hint","MachineName_1_AggId_1"}}); public object StreamFeatures() {using var s=files.OpenDownloadStream(0);return new{s.CanRead,s.CanWrite,s.CanSeek};} public override long StorageBytes()=>db.RunCommand(new MB.BsonDocument {{"dbStats",1},{"scale",1}})["indexSize"].ToInt64(); public object Storage()=>db.RunCommand(new MB.BsonDocument {{"dbStats",1},{"scale",1}}).ToJson(); public override object Settings()=>new {Server=db.RunCommand(new MB.BsonDocument("buildInfo",1))["version"].AsString,Driver=typeof(MongoClient).Assembly.GetName().Version.ToString(),WriteConcern="w:majority,j:true",ReadConcern="snapshot",ReadPreference="primary",WriteTransactions="one committed transaction per insert batch or bulk update",Deployment="single-node replica set, localhost bubblewrap",Storage=db.RunCommand(new MB.BsonDocument("serverStatus",1))["storageEngine"].ToJson()}; public void VerifyTransactions() { Insert(new List{new Event {Id=1,Body="original",System="ui console",MachineName="NOD",AggId="AB",Username="aser"},new Event {Id=2,Body="original",System="ui console",MachineName="NOD",AggId="AB",Username="aser"}}); using(var session=client.StartSession()) { session.StartTransaction(Transactions); col.UpdateMany(session,F.Empty,Builders.Update.Set(x=>x.Body,"uncommitted")); col.InsertOne(session,new Event {Id=3,Body="uncommitted"}); if(col.CountDocuments(session,F.Empty)!=3)throw new Exception("transaction does not see own insert"); if(Count(0)!=2 || Point(1).Body!="original")throw new Exception("dirty read outside transaction"); session.AbortTransaction(); } if(Count(0)!=2 || Point(1).Body!="original" || Point(2).Body!="original")throw new Exception("rollback failed"); if(Update("committed")!=2 || Point(1).Body!="committed" || Point(2).Body!="committed")throw new Exception("commit failed"); using(var session=client.StartSession()) { session.StartTransaction(Transactions); if(col.Find(session,F.Eq(x=>x.Id,1)).Single().Body!="committed")throw new Exception("initial snapshot"); Update("later"); if(col.Find(session,F.Eq(x=>x.Id,1)).Single().Body!="committed")throw new Exception("snapshot changed"); session.CommitTransaction(); } if(Point(1).Body!="later")throw new Exception("fresh read failed"); Console.WriteLine("PASS: journaled majority transactions, dirty-read exclusion, insert/update rollback, committed updates, stable snapshot and fresh subsequent reads"); Console.WriteLine(JsonSerializer.Serialize(Settings())); } public static void InitializeReplicaSet() { using var direct=new MongoClient("mongodb://127.0.0.1:27028/?directConnection=true"); var admin=direct.GetDatabase("admin"); admin.RunCommand(MB.BsonDocument.Parse("{replSetInitiate:{_id:'benchmark',members:[{_id:0,host:'127.0.0.1:27028'}]}}")); var deadline=DateTime.UtcNow.AddSeconds(60); while(DateTime.UtcNow(new MB.BsonDocument("hello",1)); if(hello.GetValue("isWritablePrimary",false).AsBoolean) {Console.WriteLine("Replica set ready");return;} Thread.Sleep(100); } throw new TimeoutException("Replica set did not elect a primary"); } public override void DropFixture()=>client.DropDatabase(db.DatabaseNamespace.DatabaseName); public override void Dispose()=>client.Dispose(); } static class Program { static readonly JsonSerializerOptions Json=new(){WriteIndented=true}; static readonly List Measurements=new(); static int Iters; static HashSet SameApiOperations=new(); static void ClassifyApis(string engine) => SameApiOperations = engine switch { "lite" => new(){"Count0","Page0","Page5000","Sequential10Pages","Random10Pages","GroupBy"}, "mongo" => new(){"Count3","IndexedTagCount"}, _ => new() }; static void Check(bool ok,string message) {if(!ok)throw new Exception(message);} static void Measure(string name,Func action,Action validate,Action setup=null) { setup?.Invoke();validate(action());var samples=new List();var times=new List(); for(int k=0;k(string name,Func ordinary,Func optimized,Action validate) { if(SameApiOperations.Contains(name)) { validate(optimized()); Measure(name+".ordinary",ordinary,validate); return; } validate(ordinary());validate(optimized()); var samples=new List<(double ms,long bytes)>[2] {new(),new()}; for(int k=0;kx.ms).Order().ToArray(); string label=name+(variant==0?".ordinary":".optimized"); Measurements.Add(new {name=label,medianMs=times[times.Length/2],samples=samples[variant].Select(x=>new {x.ms,managedBytes=x.bytes}).ToArray()}); Console.WriteLine($"{label}: {times[times.Length/2]:F3} ms"); } } static List Data(int count) { var rng=new Random(20260916);var result=new List(); string[] systems={"ui console","ui server","db portal","batch","gateway"}, machines={"NOD","NOD-2","LAB1","LAB2","BOX7"}, aggregates={"AB","ABX","ORD","INV"}, tags={"tag-a","tag-b","NOK","","tag-c"}; for(int n=0;ns switch {0=>true,1=>e.System.StartsWith("ui console",StringComparison.Ordinal),2=>e.MachineName.StartsWith("NOD",StringComparison.Ordinal)&&e.AggId.StartsWith("AB",StringComparison.Ordinal),3=>e.Tags=="NOK",_=>false}; static void EqualEvents(List actual,List expected)=>Check(JsonSerializer.Serialize(actual)==JsonSerializer.Serialize(expected),"full event values/order differ"); static void Features(string directory) { Check(!Directory.Exists(directory),"Use a fresh feature directory");Directory.CreateDirectory(directory); object soloFeatures; using(var db=new SoloDB(Path.Combine(directory,"files.db"))) { var fs=db.FileSystem; fs.WriteAt("/sparse.bin",0,new byte[]{1,2,3}); fs.WriteAt("/sparse.bin",1,new byte[]{9}); fs.WriteAt("/sparse.bin",1048576,new byte[]{7}); Check(fs.ReadAt("/sparse.bin",0,3).SequenceEqual(new byte[]{1,9,3}),"offset write preserved neighbors"); Check(fs.ReadAt("/sparse.bin",1048560,17).SequenceEqual(Enumerable.Repeat((byte)0,16).Append((byte)7)),"zero-filled gap"); using(var file=fs.OpenAt("/sparse.bin")) {file.SetLength(2097152);Check(file.Length==2097152,"grow");} Check(fs.ReadAt("/sparse.bin",2097136,16).All(x=>x==0),"extended zeros"); using(var file=fs.OpenAt("/sparse.bin")) {file.SetLength(2);Check(file.Length==2,"shrink");} Check(fs.ReadAt("/sparse.bin",0,100).SequenceEqual(new byte[]{1,9}),"shrink preserved prefix"); soloFeatures=new {offsetEdit=true,zeroFilledGap=true,grow=true,shrink=true}; } object liteGrouping,liteStream,liteClose=null; var lite=new LiteStore(Path.Combine(directory,"lite.db")); { lite.Insert(Data(200000));lite.Index(); Check(lite.Groups().Values.Sum()==200000,"ordinary grouping"); lite.WriteFile(0,new byte[65536]);liteStream=lite.StreamFeatures(); liteGrouping=lite.CoveredGroupingProbe(); } try {lite.Dispose();} catch(ObjectDisposedException ex) {liteClose=new {type=ex.GetType().FullName,ex.Message};} object mongoStream; using(var mongo=new MongoStore(Path.Combine(directory,"mongo.db"))) {mongo.WriteFile(0,new byte[65536]);mongoStream=mongo.StreamFeatures();} File.WriteAllText(Path.Combine(directory,"features.json"),JsonSerializer.Serialize(new{soloFeatures,liteStream,liteGrouping,liteClose,mongoStream},Json)); Console.WriteLine("PASS file behavior and grouping reproduction"); } static void Tuning(string engine,string directory,int rows) { Check(!Directory.Exists(directory),"Use fresh tuning directory");Directory.CreateDirectory(directory);Iters=3; var data=Data(rows);var path=Path.Combine(directory,"tuning.db"); using Store store=engine switch {"solo"=>new SoloStore(path),"lite"=>new LiteStore(path),"mongo"=>new MongoStore(path),_=>throw new ArgumentException()}; store.Insert(data);store.Index();long expected=data.LongCount(e=>Match(e,2)); var before=store.Prepare();Pair("SingleIndexesCount2",()=>store.Count(2),()=>before.Count(2),n=>Check(n==expected,"tuning count")); var beforePlans=store.Plans();long bytesBefore=store.StorageBytes(); string failure=null;var watch=Stopwatch.StartNew();try {store.CompoundIndex();}catch(Exception ex){failure=ex.GetType().FullName+": "+ex.Message;}watch.Stop(); long bytesAfter=store.StorageBytes();var after=store.Prepare(); Pair("CompoundIndexCount2",()=>store.Count(2),()=>store is MongoStore mongo?mongo.OptimizedCount(2):after.Count(2),n=>Check(n==expected,"compound count")); var afterPlans=store.Plans();object hintedPlan=store is MongoStore mongoPlan?mongoPlan.CompoundPlan():null; File.WriteAllText(Path.Combine(directory,"tuning.json"),JsonSerializer.Serialize(new {engine,rows,failure,indexBuildMs=watch.Elapsed.TotalMilliseconds,bytesBefore,bytesAfter,storageMetric=engine=="mongo"?"dbStats indexSize":engine=="solo"?"pager allocated bytes":"checkpointed database file bytes",measurements=Measurements,beforePlans,afterPlans,hintedPlan},Json)); Console.WriteLine("PASS tuning "+engine); } static void VerifyExisting(string engine,string directory,int rows) { var path=Path.Combine(directory,"insert-3.db"); using Store store=engine switch {"solo"=>new SoloStore(path),"lite"=>new LiteStore(path),"mongo"=>new MongoStore(path),_=>throw new ArgumentException()}; var data=Data(rows); using var report=JsonDocument.Parse(File.ReadAllText(Path.Combine(directory,"results.json"))); string finalBody=report.RootElement.TryGetProperty("finalUpdateBody",out var body) ? body.GetString() : (report.RootElement.GetProperty("iterations").GetInt32()%2==0?"updated-a":"updated-b"); foreach(var x in data.Where(x=>x.Id<=20000 && (x.System.StartsWith("ui") || x.Type=="Error") && x.MachineName.StartsWith("NOD") && x.AggId.StartsWith("AB") && x.Username.Length>3))x.Body=finalBody; EqualEvents(store.AllDocuments(),data); var indexedPlans=store.Plans();store.DropTagIndex();var commonPlans=store.Plans();store.IndexTags(); File.WriteAllText(Path.Combine(directory,"verified-plans.json"),JsonSerializer.Serialize(new {indexedPlans,commonPlans},Json)); Console.WriteLine("PASS every document and property after complex update, including all selected and untouched rows"); } static void VerifyRetained(string engine,string directory) { Check(!Directory.Exists(directory),"Use fresh proof directory");Directory.CreateDirectory(directory); var path=Path.Combine(directory,"proof.db"); using Store store=engine switch {"solo"=>new SoloStore(path),"lite"=>new LiteStore(path),"mongo"=>new MongoStore(path),_=>throw new ArgumentException()}; store.Index();var retained=store.Prepare(); foreach(int shape in Enumerable.Range(0,4))Check(retained.Count(shape)==0,"empty retained count"); var data=Data(1000);store.Insert(data); foreach(int shape in Enumerable.Range(0,4))Check(retained.Count(shape)==data.LongCount(x=>Match(x,shape)),"fresh retained count"); EqualEvents(retained.Page(0),data.Where(x=>Match(x,1)).OrderByDescending(x=>x.UnixTimestamp).Take(100).ToList()); Check(retained.Groups().Values.Sum()==data.Count,"fresh groups"); Console.WriteLine("PASS empty and fresh retained counts, pages and grouping on the benchmark model"); } const int FileEditScale=100; static readonly (string name,int offset,int patchBytes,int length)[] FileEditCases={ ("FileEditRange",4096*FileEditScale,1024*FileEditScale,65536*FileEditScale), ("FileExtendGap",1048576*FileEditScale,1024*FileEditScale,1049600*FileEditScale), ("FileGrow",0,0,131072*FileEditScale), ("FileTruncate",0,0,32768*FileEditScale) }; static void MeasureFileEdits(Store store) { var blob=new byte[65536*FileEditScale];new Random(5).NextBytes(blob); foreach(var edit in FileEditCases) { var patch=new byte[edit.patchBytes];Array.Fill(patch,(byte)7); var oracle=new byte[edit.length];blob.AsSpan(0,Math.Min(blob.Length,oracle.Length)).CopyTo(oracle); patch.CopyTo(oracle,edit.offset); Measure(edit.name,()=>{store.EditFile(999,edit.offset,patch,edit.length);return true;}, _=>Check(store.ReadWholeFile(999).SequenceEqual(oracle),"file edit bytes"), ()=>{store.WriteFile(999,blob);Check(store.ReadWholeFile(999).SequenceEqual(blob),"file reset bytes");}); } } static void FileEdits(string engine,string directory,int iterations) { Check(iterations>0,"Iterations must be positive"); Check(!Directory.Exists(directory),"Use a fresh file-edit directory");Directory.CreateDirectory(directory);Iters=iterations; var path=Path.Combine(directory,"files.db"); using Store store=engine switch {"solo"=>new SoloStore(path),"lite"=>new LiteStore(path),"mongo"=>new MongoStore(path),_=>throw new ArgumentException(engine)}; MeasureFileEdits(store); var report=new {engine,iterations,scale=FileEditScale,initialFileBytes=65536*FileEditScale, operations=FileEditCases.Select(x=>new{x.name,offsetBytes=x.offset,x.patchBytes,lengthBytes=x.length}), runtime=Environment.Version.ToString(),settings=store.Settings(),measurements=Measurements, correctness="complete initial and resulting file bytes verified for warm-up and every measured edit"}; File.WriteAllText(Path.Combine(directory,"file-edits.json"),JsonSerializer.Serialize(report,Json)); Console.WriteLine("PASS file edits "+engine); } public static void Main(string[] args) { if(args.Length>=3 && args[0]=="file-edits") {FileEdits(args[1],Path.GetFullPath(args[2]),args.Length>3?int.Parse(args[3]):3);return;} if(args.Length==3 && args[0]=="verify-retained") {VerifyRetained(args[1],Path.GetFullPath(args[2]));return;} if(args.Length==4 && args[0]=="verify-existing") {VerifyExisting(args[1],Path.GetFullPath(args[2]),int.Parse(args[3]));return;} if(args.Length==4 && args[0]=="tuning") {Tuning(args[1],Path.GetFullPath(args[2]),int.Parse(args[3]));return;} if(args.Length==1 && args[0]=="mongo-init") {MongoStore.InitializeReplicaSet();return;} if(args.Length==2 && args[0]=="mongo-transaction-check") { Check(!Directory.Exists(args[1]),"Use a fresh check directory");Directory.CreateDirectory(args[1]); using var transactionProbe=new MongoStore(Path.Combine(args[1],"transactions.db"));transactionProbe.VerifyTransactions();return; } if(args.Length==2 && args[0]=="features") {Features(Path.GetFullPath(args[1]));return;} Check(args.Length>=2,"Usage: engine fresh-output-directory [rows=200000] [iterations=3]"); string engine=args[0],dir=Path.GetFullPath(args[1]);ClassifyApis(engine);int count=args.Length>2?int.Parse(args[2]):200000;Iters=args.Length>3?int.Parse(args[3]):3; Check(!Directory.Exists(dir),"Use a new output directory; no existing database is deleted");Directory.CreateDirectory(dir); var data=Data(count);var bodyChars=new {min=data.Min(e=>e.Body.Length),max=data.Max(e=>e.Body.Length)};Store current=null;var inserts=new List();string path=""; Store Open(string p)=>engine switch {"solo"=>new SoloStore(p),"lite"=>new LiteStore(p),"mongo"=>new MongoStore(p),_=>throw new ArgumentException(engine)}; // Every insert starts with a fresh database. Startup is outside insert timing. for(int k=-1;k=0)inserts.Add(new{ms=sw.Elapsed.TotalMilliseconds,managedBytes=bytes}); if(kdata.LongCount(e=>Match(e,s))).ToArray(); var preparation=Stopwatch.StartNew();var retained=current.Prepare();preparation.Stop(); var compile=new {ms=preparation.Elapsed.TotalMilliseconds}; Check(retained.Count(0)==count,"prepared all count"); foreach(int shape in Enumerable.Range(0,4))Pair("Count"+shape,()=>current.Count(shape),()=>retained.Count(shape),n=>Check(n==expected[shape],"count oracle")); List ExpectedPage(int offset)=>data.Where(e=>Match(e,1)).OrderByDescending(e=>e.UnixTimestamp).Skip(offset).Take(100).ToList(); foreach(int offset in new[]{0,5000}) { var page=ExpectedPage(offset); Pair("Page"+offset,()=>current.Page(offset),()=>retained.Page(offset),p=>EqualEvents(p,page)); } var sequential=Enumerable.Range(0,10).Select(i=>i*100).ToArray(); var random=new Random(20260916);var shuffled=Enumerable.Range(0,10).Select(_=>random.Next(0,Math.Max(1,(int)expected[1]/100))*100).ToArray(); foreach(var sequence in new[]{(name:"Sequential10Pages",offsets:sequential),(name:"Random10Pages",offsets:shuffled)}) { var oracle=sequence.offsets.SelectMany(ExpectedPage).ToList(); Pair(sequence.name,()=>sequence.offsets.SelectMany(current.Page).ToList(),()=>sequence.offsets.SelectMany(retained.Page).ToList(),p=>EqualEvents(p,oracle)); } Pair("Point1000",()=>Enumerable.Range(1,Math.Min(1000,count)).Select(i=>current.Point(i)).ToList(), ()=>Enumerable.Range(1,Math.Min(1000,count)).Select(i=>retained.Point(i)).ToList(),p=>EqualEvents(p,data.Take(1000).ToList())); var expectedGroups=data.GroupBy(e=>e.Username.Substring(0,1)).ToDictionary(g=>g.Key,g=>g.LongCount()); Pair("GroupBy",current.Groups,retained.Groups,g=>Check(g.OrderBy(x=>x.Key).SequenceEqual(expectedGroups.OrderBy(x=>x.Key)),"group oracle")); // Each worker owns its mutable native builder; preparation is outside timing. var workers=Enumerable.Range(0,4).Select(_=>current.Prepare()).ToArray(); long[] Concurrent(bool optimized) { var tasks=Enumerable.Range(0,4).Select(worker=>Task.Run(()=> {long n=0;for(int i=0;i<50;i++)n+=optimized?workers[worker].Count(1):current.Count(1);return n;})).ToArray(); Task.WaitAll(tasks);return tasks.Select(t=>t.Result).ToArray(); } Pair("Concurrent4x50",()=>Concurrent(false),()=>Concurrent(true),ns=>Check(ns.All(n=>n==expected[1]*50),"concurrent counts")); var blob=new byte[65536];new Random(5).NextBytes(blob); Measure("Write200Files",()=>{for(int i=0;i<200;i++)current.WriteFile(i,blob);return 200;},_=>{}); Measure("Read200x1KB",()=>Enumerable.Range(0,200).Select(current.ReadFile).ToArray(),bs=>Check(bs.All(b=>b.SequenceEqual(blob.Take(1024))),"file prefix bytes")); var fileOffsets=Enumerable.Range(0,200).Select(i=>(i*7919)%(65536-1024)).ToArray(); Pair("RandomRead200x1KB",()=>fileOffsets.Select((offset,id)=>current.ReadFileAt(id,offset,false)).ToArray(), ()=>fileOffsets.Select((offset,id)=>current.ReadFileAt(id,offset,true)).ToArray(), blocks=>Check(blocks.Select((block,id)=>block.SequenceEqual(blob.Skip(fileOffsets[id]).Take(1024))).All(x=>x),"random file bytes")); MeasureFileEdits(current); // Alternate equal-length values on the same predicate-selected documents. int updateRound=0;string updated=""; var updateRows=data.Where(x=>x.Id<=20000 && (x.System.StartsWith("ui") || x.Type=="Error") && x.MachineName.StartsWith("NOD") && x.AggId.StartsWith("AB") && x.Username.Length>3).ToArray(); Measure("ComplexNativeUpdate",()=> {updated=(updateRound++%2==0)?"updated-a":"updated-b";return current.Update(updated);},n=>Check(n==updateRows.Length,"update count")); foreach(var e in updateRows)e.Body=updated; EqualEvents(current.AllDocuments(),data); var plans=current.Plans();current.IndexTags(); retained=current.Prepare(); Pair("IndexedTagCount",()=>current.Count(3),()=>retained.Count(3),n=>Check(n==expected[3],"indexed tag oracle")); if(current is LiteStore lite)lite.MultiKeyCheck(); current.Dispose(); long? fileBytes=File.Exists(path)?new FileInfo(path).Length:null; var report=new{engine,rows=count,iterations=Iters,sameApiOperations=SameApiOperations,finalUpdateBody=updated,indexMs,runtime=Environment.Version.ToString(),logicalCpus=Environment.ProcessorCount,settings,bodyChars,inserts,compile,measurements=Measurements,plans,closedMainFileBytes=fileBytes,correctness="all measured counts, full page fields/order, point results, groups, updates and file prefixes verified"}; File.WriteAllText(Path.Combine(dir,"results.json"),JsonSerializer.Serialize(report,Json));Console.WriteLine("PASS "+engine); } }