Skip to content

Commit c839119

Browse files
committed
feat: add simulation engine
1 parent 6c1a407 commit c839119

37 files changed

+6558
-37
lines changed

.scalafix.conf

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
OrganizeImports {
2+
groups = [
3+
"re:javax?\\."
4+
"scala."
5+
"*"
6+
]
7+
}

README.md

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ This project tests event sourced actors events that are then read by a projectio
55
It is currently support the following alternative Akka Persistence and Akka Projection setup:
66

77
* R2DBC (Postgres) as the event sourcing event store and the projection using R2DBC (Postgres)
8-
* Cassandra as the event sourcing event store and the projection using JDBC (Postgres)
8+
* Cassandra as the event sourcing event store and the projection using JDBC (Postgres)
99
* JDBC (Postgres) as the event sourcing event store and the projection using JDBC (Postgres)
1010

1111
## Running a test locally
@@ -48,12 +48,16 @@ Start the application:
4848
sbt "run 2551"
4949
```
5050

51+
### Run simulations
52+
53+
See [simulation/README.md] for more detail on running simulations. Or for simple test runs, see below.
54+
5155
### Start test run
5256

5357
Start a test run:
5458

5559
```shell
56-
curl -X POST --data '{"name":"","nrActors":1000, "messagesPerActor": 100, "concurrentActors": 100, "bytesPerEvent": 100, "timeout": 60000}' --header "content-type: application/json" http://127.0.0.1:8051/test
60+
curl -X POST --data '{"nrActors":1000, "messagesPerActor": 100, "concurrentActors": 100, "bytesPerEvent": 100, "timeout": 60000}' --header "content-type: application/json" http://127.0.0.1:8051/test
5761
```
5862

5963
the params are:
@@ -73,7 +77,7 @@ the expected event total is the `nrActors` * `messagesPeractor` * `${event-proce
7377
Multiple projections can be run to increase the load on the tagging infrastructure while not overloading the normal event log.
7478
Each projection gets its own tag for the same reason. A real production application would have different projections use the same tag.
7579

76-
The test checks that every message makes it into the projection. these are stored in the `events` table. Duplicated
80+
The test checks that every message makes it into the projection. these are stored in the `events` table. Duplicated
7781
are detected with a primary key.
7882

7983
To inspect the database:
@@ -137,15 +141,15 @@ Typically, multiple nodes are required to re-create issues as while one node is
137141

138142
### cinnamon
139143

140-
the application exposes persistence metrics via cinnamon and prometheus. the cinnamon prometheus sandbox can be used to
144+
the application exposes persistence metrics via cinnamon and prometheus. the cinnamon prometheus sandbox can be used to
141145
view the metrics in grafana.
142146

143147
## failure scenarios
144148

145149
### projection restart
146150

147151
A known edge case is that a projection is restarted and delayed events from before the offset are then missed.
148-
This should only happen in when multiple nodes are writing events as delayed event should still be written in offset
152+
This should only happen in when multiple nodes are writing events as delayed event should still be written in offset
149153
order.
150154

151155
## Deployment to eks/gke
@@ -176,7 +180,7 @@ this will create:
176180
- install the metrics server into the eks cluster (requried by the operator)
177181
- configure security groups to allow communication
178182

179-
the outputs printed at the end of `terraform apply` give all the information needed to configure the `kubernetes/deployment.yaml`
183+
the outputs printed at the end of `terraform apply` give all the information needed to configure the `kubernetes/deployment.yaml`
180184

181185
```
182186
db_endpoint = "projection-testing.cgrtpi2lqrw8.us-east-2.rds.amazonaws.com:5432"

build.sbt

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
val AkkaVersion = "2.10.0"
2-
val AkkaHttpVersion = "10.7.0"
3-
val AkkaProjectionVersion = "1.6.7"
4-
val AkkaManagementVersion = "1.6.0"
5-
val AkkaPersistenceR2dbcVersion = "1.3.1"
6-
val AkkaPersistenceDynamoDBVersion = "2.0.5"
7-
val AkkaPersistenceJdbcVersion = "5.5.0"
8-
val AkkaPersistenceCassandraVersion = "1.3.0"
1+
val AkkaVersion = "2.10.5"
2+
val AkkaHttpVersion = "10.7.1"
3+
val AkkaProjectionVersion = "1.6.13"
4+
val AkkaManagementVersion = "1.6.2"
5+
val AkkaPersistenceR2dbcVersion = "1.3.7"
6+
val AkkaPersistenceDynamoDBVersion = "2.0.6"
7+
val AkkaPersistenceJdbcVersion = "5.5.2"
8+
val AkkaPersistenceCassandraVersion = "1.3.2"
99

1010
ThisBuild / dynverSeparator := "-"
1111

@@ -59,6 +59,8 @@ lazy val `akka-projection-testing` = project
5959
"ch.qos.logback" % "logback-classic" % "1.5.12",
6060
"org.postgresql" % "postgresql" % "42.7.4",
6161
"org.hdrhistogram" % "HdrHistogram" % "2.2.2",
62+
"org.apache.commons" % "commons-rng-simple" % "1.6",
63+
"org.apache.commons" % "commons-statistics-distribution" % "1.1",
6264
"com.typesafe.akka" %% "akka-actor-testkit-typed" % AkkaVersion % Test,
6365
"com.typesafe.akka" %% "akka-persistence-testkit" % AkkaVersion % Test,
6466
"com.typesafe.akka" %% "akka-stream-testkit" % AkkaVersion % Test,

project/plugins.sbt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
addSbtPlugin("com.lightbend.cinnamon" % "sbt-cinnamon" % "2.21.2")
1+
addSbtPlugin("com.lightbend.cinnamon" % "sbt-cinnamon" % "2.21.3-20250408-0269a7e")
22
addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.5.2")
33
addSbtPlugin("de.heikoseeberger" % "sbt-header" % "5.10.0")
44
addSbtPlugin("com.github.sbt" % "sbt-native-packager" % "1.10.4")

simulation/README.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Simulation
2+
3+
A simulation engine can be used to generate more randomised projection tests.
4+
5+
See [../README.md] for more detail on running the projection testing application generally.
6+
7+
Examples for simulation definitions can be found in [examples].
8+
9+
A JSON schema is available in [schema/run-simulation.json].
10+
11+
With the projection testing application running locally with a database, a simple simulation can be run with:
12+
13+
```sh
14+
curl -X POST http://localhost:8051/simulation/run \
15+
-H "Content-Type: application/json" \
16+
-d @simulation/examples/simple.json
17+
```

simulation/examples/multi.json

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
{
2+
"$schema": "../schema/run-simulation.json",
3+
"simulation": {
4+
"name": "Multi-stage test",
5+
"description": "Simulates a warm-up, peak load, and cool-down phase.",
6+
"stages": [
7+
{
8+
"name": "Warm-up",
9+
"duration": "3m",
10+
"generators": [
11+
{
12+
"entityId": {
13+
"entity": {
14+
"count": "10k"
15+
}
16+
},
17+
"activity": {
18+
"frequency": "5/s",
19+
"duration": {
20+
"distribution": "exponential",
21+
"mean": "5s",
22+
"min": "1s",
23+
"max": "15s"
24+
},
25+
"event": {
26+
"frequency": "2/s",
27+
"dataSize": "256B"
28+
}
29+
},
30+
"random": {
31+
"seed": 1001
32+
}
33+
}
34+
]
35+
},
36+
{
37+
"name": "Peak load",
38+
"duration": "5m",
39+
"generators": [
40+
{
41+
"entityId": {
42+
"entity": {
43+
"count": "10k"
44+
},
45+
"sliceDistribution": {
46+
"type": "zipf",
47+
"exponent": 1.1,
48+
"shuffled": true
49+
}
50+
},
51+
"activity": {
52+
"frequency": {
53+
"rate": {
54+
"function": "sinusoidal",
55+
"base": "20/s",
56+
"amplitude": "10/s",
57+
"period": "30s"
58+
}
59+
},
60+
"duration": {
61+
"distribution": "log-normal",
62+
"median": "8s",
63+
"p95": "20s",
64+
"min": "2s",
65+
"max": "30s"
66+
},
67+
"event": {
68+
"frequency": "5/s",
69+
"dataSize": {
70+
"distribution": "weibull",
71+
"shape": 1.5,
72+
"scale": "1kB",
73+
"min": "100B",
74+
"max": "5kB"
75+
}
76+
}
77+
},
78+
"random": {
79+
"seed": 2002
80+
}
81+
}
82+
]
83+
},
84+
{
85+
"name": "Cool-down",
86+
"duration": "2m",
87+
"delay": "10s",
88+
"generators": [
89+
{
90+
"entityId": {
91+
"entity": {
92+
"count": "10k"
93+
}
94+
},
95+
"activity": {
96+
"frequency": {
97+
"rate": {
98+
"function": "linear",
99+
"initial": "10/s",
100+
"target": "1/s"
101+
}
102+
},
103+
"duration": "3s",
104+
"event": {
105+
"frequency": "1/s",
106+
"dataSize": "128B"
107+
}
108+
},
109+
"random": {
110+
"seed": 3003
111+
}
112+
}
113+
]
114+
}
115+
],
116+
"engine": {
117+
"tick": "100ms",
118+
"parallelism": 8,
119+
"ackPersists": false
120+
}
121+
}
122+
}

simulation/examples/simple.json

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
{
2+
"$schema": "../schema/run-simulation.json",
3+
"simulation": {
4+
"name": "Simple",
5+
"description": "Simple simulation with uniform distribution over entities.",
6+
"stages": [
7+
{
8+
"duration": "1 minute",
9+
"generators": [
10+
{
11+
"entityId": {
12+
"entity": {
13+
"count": "10k"
14+
}
15+
},
16+
"activity": {
17+
"frequency": "10 / second",
18+
"duration": "10 seconds",
19+
"event": {
20+
"frequency": "1 / second",
21+
"dataSize": "100 B"
22+
}
23+
}
24+
}
25+
]
26+
}
27+
]
28+
}
29+
}

0 commit comments

Comments
 (0)