5
5
package akka .projection .scaladsl
6
6
7
7
import java .util .concurrent .ConcurrentHashMap
8
+
8
9
import scala .concurrent .ExecutionContext
9
10
import scala .concurrent .Future
10
11
import scala .concurrent .TimeoutException
11
12
import scala .concurrent .duration .FiniteDuration
13
+
12
14
import akka .Done
13
15
import akka .actor .typed .ActorRef
14
16
import akka .actor .typed .ActorSystem
@@ -20,14 +22,18 @@ import akka.projection.ProjectionBehavior
20
22
import akka .projection .ProjectionId
21
23
import akka .util .JavaDurationConverters ._
22
24
import akka .util .Timeout
23
-
24
25
import java .net .URLEncoder
25
26
import java .nio .charset .StandardCharsets
27
+ import java .time .Instant
28
+
29
+ import akka .projection .scaladsl .ProjectionManagement .UpdateTimestampOffset
26
30
27
31
object ProjectionManagement extends ExtensionId [ProjectionManagement ] {
28
32
def createExtension (system : ActorSystem [_]): ProjectionManagement = new ProjectionManagement (system)
29
33
30
34
def get (system : ActorSystem [_]): ProjectionManagement = apply(system)
35
+
36
+ final case class UpdateTimestampOffset (persistenceId : String , seqNr : Long , timestamp : Instant )
31
37
}
32
38
33
39
class ProjectionManagement (system : ActorSystem [_]) extends Extension {
@@ -100,6 +106,24 @@ class ProjectionManagement(system: ActorSystem[_]) extends Extension {
100
106
retry(() => askSetOffset())
101
107
}
102
108
109
+ /**
110
+ * Update the stored `TimestampOffset` for the `projectionId` and restart the `Projection`.
111
+ * This can be useful if the projection was stuck with errors on a specific offset and should skip
112
+ * that offset and continue with next.
113
+ *
114
+ * Another use case is to populate the offset store with know starting points when enabling change events
115
+ * for Durable State. In that case the offset sequence number should be set to current Durable State
116
+ * revision minus 1.
117
+ */
118
+ def updateTimestampOffset (projectionId : ProjectionId , updates : Set [UpdateTimestampOffset ]): Future [Done ] = {
119
+ def askSetTimestampOffset (): Future [Done ] = {
120
+ topic(projectionId.name)
121
+ .ask(replyTo => Topic .Publish (SetTimestampOffset (projectionId, updates, replyTo)))
122
+ }
123
+
124
+ retry(() => askSetTimestampOffset())
125
+ }
126
+
103
127
private def retry [T ](operation : () => Future [T ]): Future [T ] = {
104
128
def attempt (remaining : Int ): Future [T ] = {
105
129
operation().recoverWith {
0 commit comments