@@ -130,10 +130,8 @@ final class DynamoDBReadJournal(system: ExtendedActorSystem, config: Config, cfg
130130 correlationId : Option [String ]): BySliceQuery [SerializedJournalItem , EventEnvelope [Event ]] = {
131131 val createEnvelope : (TimestampOffset , SerializedJournalItem ) => EventEnvelope [Event ] = createEventEnvelope
132132 val extractOffset = (env : EventEnvelope [Event ]) => env.offset.asInstanceOf [TimestampOffset ]
133- val createHeartbeat = (timestamp : Instant ) =>
134- Some (createEventEnvelopeHeartbeat[Event ](entityType, slice, timestamp, correlationId))
135133
136- new BySliceQuery (queryDao, createEnvelope, extractOffset, createHeartbeat, clock, settings, log)
134+ new BySliceQuery (queryDao, createEnvelope, extractOffset, clock, settings, log)
137135 }
138136
139137 private def snapshotsBySlice [Snapshot , Event ](
@@ -145,10 +143,8 @@ final class DynamoDBReadJournal(system: ExtendedActorSystem, config: Config, cfg
145143 (offset, row) => createEnvelopeFromSnapshot(row, offset, transformSnapshot)
146144
147145 val extractOffset : EventEnvelope [Event ] => TimestampOffset = env => env.offset.asInstanceOf [TimestampOffset ]
148- val createHeartbeat = (timestamp : Instant ) =>
149- Some (createEventEnvelopeHeartbeat[Event ](entityType, slice, timestamp, correlationId))
150146
151- new BySliceQuery (snapshotDao, createEnvelope, extractOffset, createHeartbeat, clock, settings, log)
147+ new BySliceQuery (snapshotDao, createEnvelope, extractOffset, clock, settings, log)
152148 }
153149
154150 private def createEnvelopeFromSnapshot [Snapshot , Event ](
@@ -324,8 +320,20 @@ final class DynamoDBReadJournal(system: ExtendedActorSystem, config: Config, cfg
324320
325321 val dbSource = bySliceQueries.head.mergeAll(bySliceQueries.tail, eagerComplete = false )
326322 if (settings.journalPublishEvents) {
323+ val initialHeartbeats = (minSlice to maxSlice).flatMap { slice =>
324+ sliceStartOffset(slice, offset) match {
325+ case t : TimestampOffset =>
326+ Some (
327+ createEventEnvelopeHeartbeat[Event ](
328+ entityType,
329+ slice,
330+ t.timestamp.minus(JDuration .ofMillis(settings.querySettings.backtrackingWindow.toMillis)),
331+ correlationId))
332+ case _ => None
333+ }
334+ }
327335 val pubSubSource = eventsBySlicesPubSubSource[Event ](entityType, minSlice, maxSlice)
328- mergeDbAndPubSubSources(dbSource, pubSubSource, correlationId)
336+ mergeDbAndPubSubSources(dbSource, pubSubSource, initialHeartbeats, correlationId)
329337 } else
330338 dbSource
331339 }
@@ -480,8 +488,20 @@ final class DynamoDBReadJournal(system: ExtendedActorSystem, config: Config, cfg
480488
481489 val dbSource = bySliceQueries.head.mergeAll(bySliceQueries.tail, eagerComplete = false )
482490 if (settings.journalPublishEvents) {
491+ val initialHeartbeats = (minSlice to maxSlice).flatMap { slice =>
492+ sliceStartOffset(slice, offset) match {
493+ case t : TimestampOffset =>
494+ Some (
495+ createEventEnvelopeHeartbeat[Event ](
496+ entityType,
497+ slice,
498+ t.timestamp.minus(JDuration .ofMillis(settings.querySettings.backtrackingWindow.toMillis)),
499+ correlationId))
500+ case _ => None
501+ }
502+ }
483503 val pubSubSource = eventsBySlicesPubSubSource[Event ](entityType, minSlice, maxSlice)
484- mergeDbAndPubSubSources(dbSource, pubSubSource, correlationId)
504+ mergeDbAndPubSubSources(dbSource, pubSubSource, initialHeartbeats, correlationId)
485505 } else
486506 dbSource
487507 }
@@ -554,15 +574,24 @@ final class DynamoDBReadJournal(system: ExtendedActorSystem, config: Config, cfg
554574 private def mergeDbAndPubSubSources [Event , Snapshot ](
555575 dbSource : Source [EventEnvelope [Event ], NotUsed ],
556576 pubSubSource : Source [EventEnvelope [Event ], NotUsed ],
577+ initialHeartbeats : Iterable [EventEnvelope [Event ]],
557578 correlationId : Option [String ]) = {
558- dbSource
559- .mergePrioritized(pubSubSource, leftPriority = 1 , rightPriority = 10 )
560- .via(
561- skipPubSubTooFarAhead(
562- settings.querySettings.backtrackingEnabled,
563- JDuration .ofMillis(settings.querySettings.backtrackingWindow.toMillis),
564- correlationId))
565- .via(deduplicate(settings.querySettings.deduplicateCapacity))
579+ val dbMergedWithPubsub =
580+ dbSource
581+ .mergePrioritized(pubSubSource, leftPriority = 1 , rightPriority = 10 )
582+
583+ val base =
584+ if (settings.querySettings.backtrackingEnabled) {
585+ Source
586+ .fromIterator(() => initialHeartbeats.iterator)
587+ .concat(dbMergedWithPubsub)
588+ .via(
589+ handlePubSubTooFarAhead(
590+ JDuration .ofMillis(settings.querySettings.backtrackingWindow.toMillis),
591+ correlationId))
592+ } else dbMergedWithPubsub
593+
594+ base.via(deduplicate(settings.querySettings.deduplicateCapacity))
566595 }
567596
568597 /**
@@ -605,77 +634,85 @@ final class DynamoDBReadJournal(system: ExtendedActorSystem, config: Config, cfg
605634 }
606635 }
607636
608- /**
609- * INTERNAL API
610- */
611- @ InternalApi private [akka] def skipPubSubTooFarAhead [Event ](
612- enabled : Boolean ,
613- maxAheadOfBacktracking : JDuration ,
637+ /** INTERNAL API */
638+ @ nowarn(" msg=eventMetadata in class EventEnvelope is deprecated" )
639+ @ InternalApi private [akka] def handlePubSubTooFarAhead [Event ](
640+ backtrackingWindow : JDuration ,
614641 correlationId : Option [String ]): Flow [EventEnvelope [Event ], EventEnvelope [Event ], NotUsed ] = {
615642 def correlationIdLogText = CorrelationId .toLogText(correlationId)
616- if (! enabled)
617- Flow [EventEnvelope [Event ]]
618- else
619- Flow [EventEnvelope [Event ]]
620- .statefulMapConcat(() => {
621- // track backtracking offset per slice
622- var latestBacktrackingPerSlice = Map .empty[Int , Instant ]
623- def latestBacktracking (slice : Int ): Instant = latestBacktrackingPerSlice.get(slice) match {
624- case Some (instant) => instant
625- case None => Instant .EPOCH
626- }
627- env => {
628- val slice = persistenceExt.sliceForPersistenceId(env.persistenceId)
629- env.offset match {
630- case t : TimestampOffset =>
631- if (EnvelopeOrigin .fromQuery(env)) {
632- if (log.isDebugEnabled()) {
633- val l = latestBacktracking(slice)
634- if (l.isAfter(t.timestamp))
635- log.debug(
636- " event from query for persistenceId [{}] seqNr [{}] " +
637- s " timestamp [{}]{} was before last event from backtracking or heartbeat [{}]. " ,
638- env.persistenceId,
639- env.sequenceNr,
640- t.timestamp,
641- correlationIdLogText,
642- l)
643- }
644643
645- env :: Nil
646- } else {
647- if (EnvelopeOrigin .fromBacktracking(env)) {
644+ Flow [EventEnvelope [Event ]]
645+ .statefulMapConcat(() => {
646+ // track backtracking offsets per slice
647+ var latestBacktrackingPerSlice = Map .empty[Int , Instant ]
648+ def latestBacktracking (slice : Int ): Instant = latestBacktrackingPerSlice.get(slice) match {
649+ case Some (instant) => instant
650+ case None => Instant .EPOCH
651+ }
652+
653+ env => {
654+ val slice = persistenceExt.sliceForPersistenceId(env.persistenceId)
655+ val l = latestBacktracking(slice)
656+ env.offset match {
657+ case t : TimestampOffset =>
658+ if (EnvelopeOrigin .fromQuery(env)) {
659+ if (log.isDebugEnabled() && l.isAfter(t.timestamp)) {
660+ log.debug(
661+ " event from query for persistenceId [{}] seqNr [{}] " +
662+ s " timestamp [{}] was before last event from backtracking or heartbeat [{}]. " ,
663+ env.persistenceId,
664+ env.sequenceNr,
665+ t.timestamp,
666+ l)
667+ }
668+ env :: Nil
669+ } else {
670+ if (EnvelopeOrigin .fromBacktracking(env)) {
671+ if (l.isBefore(t.timestamp)) {
648672 latestBacktrackingPerSlice = latestBacktrackingPerSlice.updated(slice, t.timestamp)
649- env :: Nil
650- } else if (EnvelopeOrigin .fromHeartbeat(env)) {
673+ }
674+ env :: Nil
675+ } else if (EnvelopeOrigin .fromHeartbeat(env)) {
676+ if (l.isBefore(t.timestamp)) {
651677 latestBacktrackingPerSlice = latestBacktrackingPerSlice.updated(slice, t.timestamp)
652- Nil // always drop heartbeats
653- } else if (EnvelopeOrigin .fromPubSub(env) && latestBacktracking(slice) == Instant .EPOCH ) {
678+ }
679+ Nil // heartbeats are an internal implementation and must never leak out
680+ } else if (EnvelopeOrigin .fromPubSub(env)) {
681+ if (l == Instant .EPOCH ) {
654682 log.trace(
655683 " Dropping pubsub event for persistenceId [{}] seqNr [{}]{} because no event from backtracking yet." ,
656684 env.persistenceId,
657685 env.sequenceNr,
658686 correlationIdLogText)
659687 Nil
660- } else if (EnvelopeOrigin .fromPubSub(env) && JDuration
661- .between(latestBacktracking(slice), t. timestamp)
662- .compareTo(maxAheadOfBacktracking) > 0 ) {
663- // drop from pubsub when too far ahead from backtracking
664- log.debug (
665- " Dropping pubsub event for persistenceId [{}] seqNr [{}]{} because too far ahead of backtracking. " ,
688+ } else if (JDuration .between(l, t.timestamp).compareTo(backtrackingWindow) > 0 ) {
689+ // far ahead of backtracking, so adjust the offset (the timestamp in the envelope is unaffected )
690+ // so as to prevent the offset from being moved too far ahead
691+ val offset = TimestampOffset (l.plus(backtrackingWindow), t.readTimestamp, t.seen)
692+ val emittedEnv = new EventEnvelope (
693+ offset ,
666694 env.persistenceId,
667695 env.sequenceNr,
668- correlationIdLogText)
669- Nil
696+ env.eventOption,
697+ env.timestamp,
698+ env.eventMetadata,
699+ env.entityType,
700+ env.slice,
701+ env.filtered,
702+ env.source,
703+ env.tags)
704+ emittedEnv :: Nil
670705 } else {
671706 env :: Nil
672707 }
708+ } else {
709+ env :: Nil
673710 }
674- case _ =>
675- env :: Nil
676- }
711+ }
712+ case _ => env :: Nil
677713 }
678- })
714+ }
715+ })
679716 }
680717
681718 // EventTimestampQuery
0 commit comments