@@ -15,21 +15,43 @@ import akka.actor.testkit.typed.scaladsl.ScalaTestWithActorTestKit
15
15
import akka .actor .typed .ActorSystem
16
16
import akka .persistence .query .NoOffset
17
17
import akka .persistence .query .PersistenceQuery
18
+ import akka .persistence .query .TimestampOffset
18
19
import akka .persistence .r2dbc .TestActors
19
20
import akka .persistence .r2dbc .TestActors .Persister .Persist
20
21
import akka .persistence .r2dbc .TestConfig
21
22
import akka .persistence .r2dbc .TestData
22
23
import akka .persistence .r2dbc .TestDbLifecycle
24
+ import akka .persistence .r2dbc .internal .EnvelopeOrigin
23
25
import akka .persistence .r2dbc .query .scaladsl .R2dbcReadJournal
24
26
import akka .stream .scaladsl .Sink
27
+ import akka .stream .scaladsl .Source
28
+ import com .typesafe .config .ConfigFactory
25
29
import org .scalatest .wordspec .AnyWordSpecLike
26
30
31
+ object EventsBySlicePerfSpec {
32
+ private val config = ConfigFactory
33
+ .parseString("""
34
+ akka.persistence.r2dbc.journal.publish-events = on
35
+ akka.persistence.r2dbc.query {
36
+ backtracking.enabled = on
37
+ refresh-interval = 3s
38
+ #buffer-size = 100
39
+ }
40
+ # to measure lag latency more accurately
41
+ akka.persistence.r2dbc.use-app-timestamp = true
42
+ """ )
43
+ .withFallback(TestConfig .config)
44
+
45
+ private final case class PidSeqNr (pid : String , seqNr : Long )
46
+ }
47
+
27
48
class EventsBySlicePerfSpec
28
- extends ScalaTestWithActorTestKit (TestConfig .backtrackingDisabledConfig.withFallback( TestConfig . config) )
49
+ extends ScalaTestWithActorTestKit (EventsBySlicePerfSpec . config)
29
50
with AnyWordSpecLike
30
51
with TestDbLifecycle
31
- with TestData
32
- with LogCapturing {
52
+ with LogCapturing
53
+ with TestData {
54
+ import EventsBySlicePerfSpec .PidSeqNr
33
55
34
56
override def typedSystem : ActorSystem [_] = system
35
57
@@ -39,11 +61,12 @@ class EventsBySlicePerfSpec
39
61
40
62
" retrieve from several slices" in {
41
63
// increase these properties for "real" testing
64
+ // also, remove LogCapturing and change logback log levels for "real" testing
42
65
val numberOfPersisters = 30
43
66
val numberOfEvents = 5
44
67
val writeConcurrency = 10
45
68
val numberOfSliceRanges = 4
46
- val iterations = 3
69
+ val iterations = 2
47
70
val totalNumberOfEvents = numberOfPersisters * numberOfEvents
48
71
49
72
val entityType = nextEntityType()
@@ -83,22 +106,98 @@ class EventsBySlicePerfSpec
83
106
val counts : Seq [Future [Int ]] = ranges.map { range =>
84
107
query
85
108
.currentEventsBySlices[String ](entityType, range.min, range.max, NoOffset )
86
- .runWith(Sink .fold(0 ) { case (acc, _) =>
87
- if (acc > 0 && acc % 100 == 0 )
88
- println(s " # $iteration Reading [ $acc] events from slices [ ${range.min}- ${range.max}] " +
89
- s " took [ ${(System .nanoTime() - t1) / 1000 / 1000 }] ms " )
90
- acc + 1
109
+ .runWith(Sink .fold(0 ) { case (acc, env) =>
110
+ if (EnvelopeOrigin .fromQuery(env)) {
111
+ if (acc > 0 && acc % 100 == 0 )
112
+ println(s " # $iteration Reading [ $acc] events from slices [ ${range.min}- ${range.max}] " +
113
+ s " took [ ${(System .nanoTime() - t1) / 1000 / 1000 }] ms " )
114
+ acc + 1
115
+ } else {
116
+ acc
117
+ }
91
118
})
92
119
}
93
120
implicit val ec : ExecutionContext = testKit.system.executionContext
94
121
val total = Await .result(Future .sequence(counts).map(_.sum), 30 .seconds)
95
122
total shouldBe totalNumberOfEvents
96
123
println(
97
- s " # $iteration Reading all [ $totalNumberOfEvents] events from [ ${ranges.size}] eventsBySlices " +
124
+ s " # $iteration Reading all [ $totalNumberOfEvents] events from [ ${ranges.size}] slices with currentEventsBySlices " +
98
125
s " took [ ${(System .nanoTime() - t1) / 1000 / 1000 }] ms " )
99
126
}
100
127
}
101
128
129
+ " write and read concurrently" in {
130
+ // increase these properties for "real" testing
131
+ // also, remove LogCapturing and change logback log levels for "real" testing
132
+ val numberOfEventsPerWriter = 20
133
+ val writeConcurrency = 10
134
+ val writeRps = 300
135
+ val iterations = 2
136
+ val totalNumberOfEvents = writeConcurrency * numberOfEventsPerWriter
137
+ val verbosePrintLag = false
138
+
139
+ implicit val ec : ExecutionContext = testKit.system.executionContext
140
+
141
+ val entityType = nextEntityType()
142
+ val persistenceIds = (1 to writeConcurrency).map(_ => nextPid(entityType)).toVector
143
+
144
+ (1 to iterations).foreach { iteration =>
145
+ val t0 = System .nanoTime()
146
+ val writeProbe = createTestProbe[Done ]()
147
+ val persisters = persistenceIds.map(pid => testKit.spawn(TestActors .Persister (pid)))
148
+ Source (1 to numberOfEventsPerWriter)
149
+ .mapConcat(n => persisters.map(ref => ref -> n))
150
+ .throttle(writeRps / 10 , 100 .millis)
151
+ .map { case (ref, n) =>
152
+ ref ! Persist (s " e- $n" )
153
+ }
154
+ .runWith(Sink .ignore)
155
+ .foreach { _ =>
156
+ // stop them at the end
157
+ persisters.foreach(_ ! TestActors .Persister .Stop (writeProbe.ref))
158
+ }
159
+
160
+ val done : Future [Done ] =
161
+ query
162
+ .eventsBySlices[String ](entityType, 0 , persistenceExt.numberOfSlices - 1 , NoOffset )
163
+ .scan(Set .empty[PidSeqNr ]) { case (acc, env) =>
164
+ val newAcc = acc + PidSeqNr (env.persistenceId, env.sequenceNr)
165
+
166
+ if (verbosePrintLag) {
167
+ val duplicate = if (newAcc.size == acc.size) " (duplicate)" else " "
168
+ val lagMillis = System .currentTimeMillis() - env.timestamp
169
+ val delayed =
170
+ (EnvelopeOrigin .fromPubSub(env) && lagMillis > 50 ) ||
171
+ (EnvelopeOrigin .fromQuery(
172
+ env) && lagMillis > r2dbcSettings.querySettings.refreshInterval.toMillis + 300 ) ||
173
+ (EnvelopeOrigin .fromPubSub(
174
+ env) && lagMillis > r2dbcSettings.querySettings.backtrackingWindow.toMillis / 2 + 300 )
175
+ if (delayed)
176
+ println(
177
+ s " # received ${newAcc.size}$duplicate from ${env.source}: ${env.persistenceId} seqNr ${env.sequenceNr}, lag $lagMillis ms " )
178
+ }
179
+
180
+ if (newAcc.size != acc.size && (newAcc.size % 100 == 0 ))
181
+ println(s " # $iteration Reading [ ${newAcc.size}] events " +
182
+ s " took [ ${(System .nanoTime() - t0) / 1000 / 1000 }] ms " )
183
+ newAcc
184
+
185
+ }
186
+ .takeWhile(_.size < totalNumberOfEvents)
187
+ .runWith(Sink .ignore)
188
+
189
+ writeProbe.receiveMessages(persisters.size, (totalNumberOfEvents / writeRps).seconds + 10 .seconds)
190
+ println(
191
+ s " # $iteration Persisting all [ $totalNumberOfEvents] events from [ ${persistenceIds.size}] persistent " +
192
+ s " actors took [ ${(System .nanoTime() - t0) / 1000 / 1000 }] ms " )
193
+
194
+ Await .result(done, 30 .seconds)
195
+ println(
196
+ s " # $iteration Reading all [ $totalNumberOfEvents] events with eventsBySlices " +
197
+ s " took [ ${(System .nanoTime() - t0) / 1000 / 1000 }] ms " )
198
+ }
199
+ }
200
+
102
201
}
103
202
104
203
}
0 commit comments