Skip to content

Commit 0d59f13

Browse files
Added benchmarks (#41)
1 parent ae93a37 commit 0d59f13

34 files changed

+640
-204
lines changed

.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@ _site/
5151
[Tt]est[Rr]esult*/
5252
[Bb]uild[Ll]og.*
5353

54+
# Benchmark Results
55+
BenchmarkDotNet.Artifacts/
56+
5457
# NUNIT
5558
*.VisualState.xml
5659
TestResult.xml

Akka.Persistence.EventStore.sln

+17
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Akka.Persistence.EventStore
3939
EndProject
4040
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Akka.Persistence.EventStore.Hosting.Tests", "src\Akka.Persistence.EventStore.Hosting.Tests\Akka.Persistence.EventStore.Hosting.Tests.csproj", "{8D28138D-C51A-491A-B5EA-028D725B6442}"
4141
EndProject
42+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Benchmarks", "Benchmarks", "{6DCB3F60-66B1-44BE-AA32-C7CA4B11563D}"
43+
EndProject
44+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Akka.Persistence.EventStore.Benchmarks", "src\Akka.Persistence.EventStore.Benchmarks\Akka.Persistence.EventStore.Benchmarks.csproj", "{EF1D827E-2B2B-4BA0-8733-D54CACDEE69F}"
45+
EndProject
4246
Global
4347
GlobalSection(SolutionConfigurationPlatforms) = preSolution
4448
Debug|Any CPU = Debug|Any CPU
@@ -97,6 +101,18 @@ Global
97101
{8D28138D-C51A-491A-B5EA-028D725B6442}.Release|x64.Build.0 = Release|Any CPU
98102
{8D28138D-C51A-491A-B5EA-028D725B6442}.Release|x86.ActiveCfg = Release|Any CPU
99103
{8D28138D-C51A-491A-B5EA-028D725B6442}.Release|x86.Build.0 = Release|Any CPU
104+
{EF1D827E-2B2B-4BA0-8733-D54CACDEE69F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
105+
{EF1D827E-2B2B-4BA0-8733-D54CACDEE69F}.Debug|Any CPU.Build.0 = Debug|Any CPU
106+
{EF1D827E-2B2B-4BA0-8733-D54CACDEE69F}.Debug|x64.ActiveCfg = Debug|Any CPU
107+
{EF1D827E-2B2B-4BA0-8733-D54CACDEE69F}.Debug|x64.Build.0 = Debug|Any CPU
108+
{EF1D827E-2B2B-4BA0-8733-D54CACDEE69F}.Debug|x86.ActiveCfg = Debug|Any CPU
109+
{EF1D827E-2B2B-4BA0-8733-D54CACDEE69F}.Debug|x86.Build.0 = Debug|Any CPU
110+
{EF1D827E-2B2B-4BA0-8733-D54CACDEE69F}.Release|Any CPU.ActiveCfg = Release|Any CPU
111+
{EF1D827E-2B2B-4BA0-8733-D54CACDEE69F}.Release|Any CPU.Build.0 = Release|Any CPU
112+
{EF1D827E-2B2B-4BA0-8733-D54CACDEE69F}.Release|x64.ActiveCfg = Release|Any CPU
113+
{EF1D827E-2B2B-4BA0-8733-D54CACDEE69F}.Release|x64.Build.0 = Release|Any CPU
114+
{EF1D827E-2B2B-4BA0-8733-D54CACDEE69F}.Release|x86.ActiveCfg = Release|Any CPU
115+
{EF1D827E-2B2B-4BA0-8733-D54CACDEE69F}.Release|x86.Build.0 = Release|Any CPU
100116
EndGlobalSection
101117
GlobalSection(SolutionProperties) = preSolution
102118
HideSolutionNode = FALSE
@@ -108,5 +124,6 @@ Global
108124
{96349315-C6BE-4888-B81A-9A46EF7CE685} = {F4AC94E7-D5F3-4B85-9810-A8BF02441883}
109125
{DF2C9C02-9F0D-4FC8-8F72-234FD68FC918} = {F4AC94E7-D5F3-4B85-9810-A8BF02441883}
110126
{6017AE31-4718-413B-983E-EAF9D4B465C9} = {DF2C9C02-9F0D-4FC8-8F72-234FD68FC918}
127+
{EF1D827E-2B2B-4BA0-8733-D54CACDEE69F} = {6DCB3F60-66B1-44BE-AA32-C7CA4B11563D}
111128
EndGlobalSection
112129
EndGlobal

Directory.Packages.props

+1
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
</PackageVersion>
3333
<PackageVersion Include="FluentAssertions" Version="6.12.0" />
3434
<PackageVersion Include="Docker.DotNet" Version="3.125.15" />
35+
<PackageVersion Include="BenchmarkDotNet" Version="0.13.12" />
3536
</ItemGroup>
3637
<ItemGroup>
3738
<GlobalPackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0" PrivateAssets="all" />
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<OutputType>Exe</OutputType>
5+
<TargetFramework>net8.0</TargetFramework>
6+
<ImplicitUsings>enable</ImplicitUsings>
7+
<Nullable>enable</Nullable>
8+
</PropertyGroup>
9+
10+
<ItemGroup>
11+
<ProjectReference Include="..\Akka.Persistence.EventStore.Tests\Akka.Persistence.EventStore.Tests.csproj" />
12+
<ProjectReference Include="..\Akka.Persistence.EventStore\Akka.Persistence.EventStore.csproj" />
13+
</ItemGroup>
14+
15+
<ItemGroup>
16+
<PackageReference Include="BenchmarkDotNet" />
17+
<PackageReference Include="FluentAssertions" />
18+
</ItemGroup>
19+
20+
<ItemGroup>
21+
<None Update="benchmark.conf">
22+
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
23+
</None>
24+
</ItemGroup>
25+
26+
</Project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
namespace Akka.Persistence.EventStore.Benchmarks;
2+
3+
internal static class Const
4+
{
5+
public const int TotalMessages = 3000000;
6+
public const int TagLowerBound = 2 * (TotalMessages / 3);
7+
public const string Tag10 = "Tag1";
8+
public const string Tag100 = "Tag2";
9+
public const string Tag1000 = "Tag3";
10+
public const string Tag10000 = "Tag4";
11+
public const int Tag10UpperBound = TagLowerBound + 10;
12+
public const int Tag100UpperBound = TagLowerBound + 100;
13+
public const int Tag1000UpperBound = TagLowerBound + 1000;
14+
public const int Tag10000UpperBound = TagLowerBound + 10000;
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
using Akka.Actor;
2+
using Akka.Configuration;
3+
using Akka.Persistence.EventStore.Tests;
4+
using FluentAssertions.Extensions;
5+
6+
namespace Akka.Persistence.EventStore.Benchmarks;
7+
8+
public static class EventStoreBenchmarkFixture
9+
{
10+
private static EventStoreContainer? _eventStoreContainer;
11+
12+
public static async Task<ActorSystem> CreateActorSystem(string name, Config? extraConfig = null)
13+
{
14+
var config = ConfigurationFactory.ParseString(await File.ReadAllTextAsync("benchmark.conf"))
15+
.WithFallback(extraConfig ?? "")
16+
.WithFallback(Persistence.DefaultConfig())
17+
.WithFallback(EventStorePersistence.DefaultConfiguration);
18+
19+
return ActorSystem.Create(name, config);
20+
}
21+
22+
public static async Task Initialize()
23+
{
24+
_eventStoreContainer = new EventStoreContainer();
25+
await _eventStoreContainer.InitializeAsync();
26+
27+
await File.WriteAllTextAsync(
28+
"benchmark.conf",
29+
$$"""
30+
akka.persistence.journal {
31+
plugin = akka.persistence.journal.eventstore
32+
eventstore {
33+
connection-string = "{{_eventStoreContainer.ConnectionString}}"
34+
35+
event-adapters {
36+
event-tagger = "{{typeof(EventTagger).AssemblyQualifiedName}}"
37+
}
38+
event-adapter-bindings {
39+
"System.Int32" = event-tagger
40+
}
41+
}
42+
}
43+
44+
akka.persistence.query.journal.eventstore {
45+
write-plugin = akka.persistence.journal.eventstore
46+
}
47+
""");
48+
49+
var sys = await CreateActorSystem("Initializer", """
50+
akka.persistence.journal {
51+
eventstore {
52+
auto-initialize = true
53+
}
54+
}
55+
""");
56+
57+
var initializer = sys.ActorOf(Props.Create(() => new InitializeDbActor()), "INITIALIZER");
58+
59+
await initializer.Ask<InitializeDbActor.Initialized>(
60+
InitializeDbActor.Initialize.Instance,
61+
20.Minutes());
62+
}
63+
64+
public static async Task Dispose()
65+
{
66+
if (_eventStoreContainer is not null)
67+
await _eventStoreContainer.DisposeAsync();
68+
}
69+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
using Akka.Actor;
2+
using Akka.Persistence.EventStore.Query;
3+
using Akka.Persistence.Query;
4+
using Akka.Streams;
5+
using BenchmarkDotNet.Attributes;
6+
using FluentAssertions;
7+
8+
namespace Akka.Persistence.EventStore.Benchmarks;
9+
10+
[Config(typeof(MicroBenchmarkConfig))]
11+
public class EventStoreTagBenchmark
12+
{
13+
private IMaterializer? _materializer;
14+
private IReadJournal? _readJournal;
15+
16+
private ActorSystem? _sys;
17+
18+
[GlobalSetup]
19+
public async Task Setup()
20+
{
21+
_sys = await EventStoreBenchmarkFixture.CreateActorSystem("system");
22+
_materializer = _sys.Materializer();
23+
_readJournal = _sys.ReadJournalFor<EventStoreReadJournal>("akka.persistence.query.journal.eventstore");
24+
}
25+
26+
[GlobalCleanup]
27+
public async Task Cleanup()
28+
{
29+
if (_sys is not null)
30+
await _sys.Terminate();
31+
}
32+
33+
[Benchmark]
34+
public async Task QueryByTag10()
35+
{
36+
var events = new List<EventEnvelope>();
37+
var source = ((ICurrentEventsByTagQuery)_readJournal!).CurrentEventsByTag(Const.Tag10, NoOffset.Instance);
38+
await source.RunForeach(
39+
msg => { events.Add(msg); },
40+
_materializer);
41+
events.Select(e => e.SequenceNr).Should().BeEquivalentTo(Enumerable.Range(2000001, 10));
42+
}
43+
44+
[Benchmark]
45+
public async Task QueryByTag100()
46+
{
47+
var events = new List<EventEnvelope>();
48+
var source = ((ICurrentEventsByTagQuery)_readJournal!).CurrentEventsByTag(Const.Tag100, NoOffset.Instance);
49+
await source.RunForeach(
50+
msg => { events.Add(msg); },
51+
_materializer);
52+
events.Select(e => e.SequenceNr).Should().BeEquivalentTo(Enumerable.Range(2000001, 100));
53+
}
54+
55+
[Benchmark]
56+
public async Task QueryByTag1000()
57+
{
58+
var events = new List<EventEnvelope>();
59+
var source = ((ICurrentEventsByTagQuery)_readJournal!).CurrentEventsByTag(Const.Tag1000, NoOffset.Instance);
60+
await source.RunForeach(
61+
msg => { events.Add(msg); },
62+
_materializer);
63+
events.Select(e => e.SequenceNr).Should().BeEquivalentTo(Enumerable.Range(2000001, 1000));
64+
}
65+
66+
[Benchmark]
67+
public async Task QueryByTag10000()
68+
{
69+
var events = new List<EventEnvelope>();
70+
var source = ((ICurrentEventsByTagQuery)_readJournal!).CurrentEventsByTag(Const.Tag10000, NoOffset.Instance);
71+
await source.RunForeach(
72+
msg => { events.Add(msg); },
73+
_materializer);
74+
events.Select(e => e.SequenceNr).Should().BeEquivalentTo(Enumerable.Range(2000001, 10000));
75+
}
76+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
using Akka.Actor;
2+
using BenchmarkDotNet.Attributes;
3+
4+
namespace Akka.Persistence.EventStore.Benchmarks;
5+
6+
[Config(typeof(MicroBenchmarkConfig))]
7+
public class EventStoreWriteBenchmark
8+
{
9+
private ActorSystem? _sys;
10+
11+
[GlobalSetup]
12+
public async Task Setup()
13+
{
14+
_sys = await EventStoreBenchmarkFixture.CreateActorSystem("system");
15+
}
16+
17+
[GlobalCleanup]
18+
public async Task Cleanup()
19+
{
20+
if (_sys is not null)
21+
await _sys.Terminate();
22+
}
23+
24+
[Benchmark]
25+
public async Task Write10Events()
26+
{
27+
var writeEventsActor = _sys!.ActorOf(Props.Create(() => new WriteEventsActor(Guid.NewGuid().ToString())));
28+
29+
for (var i = 0; i < 10; i++)
30+
{
31+
await writeEventsActor.Ask<WriteEventsActor.Responses.WriteEventsResponse>(
32+
new WriteEventsActor.Commands.WriteEvents(1));
33+
}
34+
}
35+
36+
[Benchmark]
37+
public async Task Write100Events()
38+
{
39+
var writeEventsActor = _sys!.ActorOf(Props.Create(() => new WriteEventsActor(Guid.NewGuid().ToString())));
40+
41+
for (var i = 0; i < 100; i++)
42+
{
43+
await writeEventsActor.Ask<WriteEventsActor.Responses.WriteEventsResponse>(
44+
new WriteEventsActor.Commands.WriteEvents(1));
45+
}
46+
}
47+
48+
[Benchmark]
49+
public async Task Write10EventsBatched()
50+
{
51+
var writeEventsActor = _sys!.ActorOf(Props.Create(() => new WriteEventsActor(Guid.NewGuid().ToString())));
52+
53+
await writeEventsActor.Ask<WriteEventsActor.Responses.WriteEventsResponse>(
54+
new WriteEventsActor.Commands.WriteEvents(10));
55+
}
56+
57+
[Benchmark]
58+
public async Task Write100EventsBatched()
59+
{
60+
var writeEventsActor = _sys!.ActorOf(Props.Create(() => new WriteEventsActor(Guid.NewGuid().ToString())));
61+
62+
await writeEventsActor.Ask<WriteEventsActor.Responses.WriteEventsResponse>(
63+
new WriteEventsActor.Commands.WriteEvents(100));
64+
}
65+
66+
private class WriteEventsActor : ReceivePersistentActor
67+
{
68+
public static class Commands
69+
{
70+
public record WriteEvents(int NumberOfEvents);
71+
}
72+
73+
public static class Responses
74+
{
75+
public record WriteEventsResponse;
76+
}
77+
78+
public override string PersistenceId { get; }
79+
80+
public WriteEventsActor(string id)
81+
{
82+
PersistenceId = id;
83+
84+
Command<Commands.WriteEvents>(cmd =>
85+
{
86+
var events = Enumerable.Range(1, cmd.NumberOfEvents).Select(_ => Guid.NewGuid());
87+
88+
PersistAll(events, _ => { });
89+
90+
DeferAsync("done", _ =>
91+
{
92+
Sender.Tell(new Responses.WriteEventsResponse());
93+
});
94+
});
95+
}
96+
}
97+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
using Akka.Persistence.Journal;
2+
3+
namespace Akka.Persistence.EventStore.Benchmarks;
4+
5+
public sealed class EventTagger : IWriteEventAdapter
6+
{
7+
public string Manifest(object evt) => string.Empty;
8+
9+
public object ToJournal(object evt)
10+
{
11+
if (evt is not int i)
12+
return evt;
13+
14+
return i switch
15+
{
16+
<= Const.TagLowerBound => evt,
17+
<= Const.Tag10UpperBound => new Tagged(evt, new[] { Const.Tag10, Const.Tag100, Const.Tag1000, Const.Tag10000 }),
18+
<= Const.Tag100UpperBound => new Tagged(evt, new[] { Const.Tag100, Const.Tag1000, Const.Tag10000 }),
19+
<= Const.Tag1000UpperBound => new Tagged(evt, new[] { Const.Tag1000, Const.Tag10000 }),
20+
<= Const.Tag10000UpperBound => new Tagged(evt, new[] { Const.Tag10000 }),
21+
_ => evt,
22+
};
23+
}
24+
}

0 commit comments

Comments
 (0)