Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: delete events for snapshotWhen/shouldSnapshot predicate #32231

Merged

Conversation

sebastian-alfers
Copy link
Contributor

@sebastian-alfers sebastian-alfers commented Nov 13, 2023

javadsl/EventSourcedBehavior.shouldSnapshot and scaladsl/EventSourcedBehavior.snapshotWhen both got deprecated and should be used via .withRetention(RetentionCriteria.shouldSnapshot(predicate).

Disablers the combined usage of retention and predicate based api. This means (like before) snapshot deletion can not be triggered when using the predicate base api.

Fixes #29685.

Copy link
Member

@patriknw patriknw left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looking very good

@sebastian-alfers
Copy link
Contributor Author

sebastian-alfers commented Nov 14, 2023

Since it seems that the direction of this PR is about right, I am going to add docs, mima ect now.

@sebastian-alfers
Copy link
Contributor Author

@patriknw I think this is ready to be reviewed.

Copy link
Member

@patriknw patriknw left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looking good

*
* @return `true` if events should be deleted after `shouldSnapshot` evaluates to true
*/
def deleteEventsOnSnapshot: Boolean = false
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the Java API this is a bit weirder because it doesn't have context except for the API-docs. I think a user could easily end up missing the shouldSnapshot and just override just this expecting that it will delete events on snapshot.

public RetentionCriteria retentionCriteria() {
  return RetentionCriteria.snapshotEvery(100, 2);
}
public bool deleteEventsOnSnapshot() {
  return true;
}

I think that we need to either make either delete flag being true work or try to make this name more clear about being only for predicate based snapshot

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Initially the name of this method was deleteEventsOnShouldSnapshot which got changed into the current name.

I think because of the current implementation we should separate the two flags and their respective impact.

The main problem here is the api of def shouldSnapshot(@unused state: State, @unused event: Event, @unused sequenceNr: Long): Boolean which should be evolved to a proper type, similar to withRetention. Maybe this is the way to go by adding @deprecated to the api and introducing a new method+type for this? This should then also be made for the scala api.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, tricky to get it right without overdoing it. A type to return also means users has to figure out how to construct that, but maybe that is worth it because it makes what the flag refers to obvious.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe naming it back to deleteEventsOnShouldSnapshot @patriknw? ;-)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Taking a step back, it's kind of weird to have the snapshot predicate and snapshot via RetentionCriteria as two separate things. We could add the snapshotWhen as another RetentionCriteria, and use the same approach for withDeleteEventsOnSnapshot.

Maybe that would loose the possibility to use both snapshotEvery(numberOfEvents) and snapshotWhen(predicate), but that is probably an unusual combination anyway.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A user could always do the every N as a part of their predicate, by looking at seqnr, if they want both.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the idea of evolving the predicate api. But is a snapshotWhen predicate actually something that belongs to a RetentionCriteria? To me retention refers to the fact if a snapshot should be stored, and if so how many of them. I think we need a more composable api:

  • define when a snapshot is triggered (predicate and/or numberOfEvents)
  • if triggered, what is the retention of snapshots?
  • delete events after a snapshot is triggered

To me this are three different properties that may be composed as needed (with some validation). But this probably goes into "not overdoing it".

For now I will change this PR and put snapshotWhen into RetentionCriteria to move it forward, we can see?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's fine that we add some more for when to snapshot in RetentionCriteria. That is anyway the normal way to define that snapshots should be enabled.

Originally I think retention referred to deletion of events, but we didn't allow deletion unless there are snapshots. Yeah, maybe not perfect name, but that is what it is.

For now I will change this PR and put snapshotWhen into RetentionCriteria to move it forward

Agreed

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you agree that the new snapshotWhen should also receive a keepNSnapshots param, like def snapshotEvery(numberOfEvents: Int, keepNSnapshots: Int): SnapshotCountRetentionCriteria?

If not, after this pr, it will not be possible anymore to delete snapshots when using the (new) predicate based api because we remove option to combine snapshotEvery and predicate ways to define snapshot triggering.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or we rely on only-one-snapshot to clean up snapshots?

Copy link
Member

@johanandren johanandren left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added some notes but I guess this is in active dev so maybe you let us know when you want it reviewed again

@sebastian-alfers sebastian-alfers marked this pull request as draft December 5, 2023 16:56
Copy link
Member

@patriknw patriknw left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looking good

case _ => false
}
.withRetention(RetentionCriteria.snapshotEvery(numberOfEvents = 3, keepNSnapshots = 2))
.withRetention(RetentionCriteria.snapshotEvery(numberOfEvents = 2, keepNSnapshots = 2))
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This had to change because now we do not allow anymore to combine snapshotWhen and snapshotEvery

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For it to be equivalent with the existing it would be something like snapshotWhen((_, event, currentSeqNr) => event == SnapshotMade || currentSeqNr % 3 == 0)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting, this means we will loose the keepNSnapshots = 2 of the current api usage.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure it is important to keep it snapshotting at the exact same points here though, so maybe fine.

@sebastian-alfers sebastian-alfers marked this pull request as ready for review December 10, 2023 13:27
akka-docs/src/main/paradox/typed/persistence-snapshot.md Outdated Show resolved Hide resolved
case _ => false
}
.withRetention(RetentionCriteria.snapshotEvery(numberOfEvents = 3, keepNSnapshots = 2))
.withRetention(RetentionCriteria.snapshotEvery(numberOfEvents = 2, keepNSnapshots = 2))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For it to be equivalent with the existing it would be something like snapshotWhen((_, event, currentSeqNr) => event == SnapshotMade || currentSeqNr % 3 == 0)

@@ -51,9 +54,9 @@ object CounterSpec {
(counter, event) => {
eventProbe.foreach(_ ! event)
counter.applyOperation(event)
}).snapshotWhen { (_, _, seqNr) =>
}).withRetention(RetentionCriteria.snapshotWhen[Any, Any] { (_, _, seqNr) =>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The explicit type params are needed now, even with the explicit types on EventSourcedBehavior.apply above?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. When I remove [Any, Any] I do get:

akka-persistence-typed-tests/src/test/scala/akka/persistence/typed/crdt/CounterSpec.scala:57:65: missing parameter type
[error]           }).withRetention(RetentionCriteria.snapshotWhen { (_, _, seqNr) =>

pid,
snapshotSignalProbe = Some(snapshotSignalProbe.ref),
eventSignalProbe = Some(eventProbe.ref))
.withRetention(RetentionCriteria.snapshotWhen[State, Event]((_, _, _) => true).withDeleteEventsOnSnapshot)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here we have explicit types on the returned ESB, the compiler doesn't infer those?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same error as above:

akka-persistence-typed-tests/src/test/scala/akka/persistence/typed/scaladsl/EventSourcedBehaviorRetentionSpec.scala:141:60: missing parameter type
[error]             .withRetention(RetentionCriteria.snapshotWhen((_, _, _) => true).withDeleteEventsOnSnapshot)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm, this is unfortunate. There are no types on the RetentionCriteria base trait, ofc. Therefore the types of the snapshotWhen are unrelated to the types of the EventSourcedBehavior. For example this would be wrong, but compiles fine:

.withRetention(RetentionCriteria.snapshotWhen[String, Int]((_, _, _) => true).withDeleteEventsOnSnapshot)

*
* Can not be combined with the `snapshotEvery` api.
*/
def snapshotWhen[State, Event](predicate: (State, Event, Long) => Boolean): SnapshotPredicateCriteria =
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, now I see why the types cannot be inferred, they aren't tied to anything except the lambda. Hmmm, that's annoying but I don't see an obvious solution, other than introducing type params to RetentionCriteria or pulling the factory methods up to EventSourcedBehavior and neither of those seem like a great idea.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@patriknw any other ideas how we could get this to align, note that it is not only cumbersome but also somewhat unsafe because with the current PR API you can create a SnapshotPredicate for other event and state types than your actual ESB uses.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, I think this is a stopper for placing the predicate function in the RetentionCriteria. We can't really add type parameters to RetentionCritiera in a source compatible way.

Sorry, but I think we are back to the old approach. If we see that the SnapshotPredicateCriteria simplifies the internal implementation we could keep that as an internal implementation, but maybe we should just revert back to where we were before we suggested the RetentionCriteria?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I reverted this PR to the old approach.

@Override // override retentionCriteria in EventSourcedBehavior
public RetentionCriteria retentionCriteria() {
return RetentionCriteria
.snapshotWhen((state, event, currentSequenceNr) -> event instanceof BookingCompleted)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So here the types end up being (Object, Object, Long)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All three seem Object as this is what intellij infers me as the type. I assume that its only scala that is more picky in having the requirement that all types resolvable in an anonymous lambda.

Co-authored-by: Johan Andrén <johan@markatta.com>
@sebastian-alfers
Copy link
Contributor Author

Resolved the merge conflicts with main, so ready to be re-reviewed I guess.

Copy link
Member

@johanandren johanandren left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thanks for exploring the alternative API idea even if it turned out to be a dead end!

@johanandren johanandren merged commit e49b39e into akka:main Feb 20, 2024
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add support for event deletion on snapshot predicate
3 participants