-
Notifications
You must be signed in to change notification settings - Fork 1k
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
Fix broken object type serializer in QueryExecutor #6223
Fix broken object type serializer in QueryExecutor #6223
Conversation
await using var cmd = new SqliteCommand(sql, conn); | ||
var record = await cmd.ExecuteReaderAsync(); | ||
await record.ReadAsync(); | ||
record[0].Should().Be(9999); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This fails because the payload is being serialized using the JSON serializer (1) and not MySerializer (9999)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you add a comment explaining what this test actually tests - like, what use case is it here to assert? Just need a snippet at the top explaining this.
Nice work @Arkatufus - yep, that will do it. So what can be done to safely change this default, given that this behavior is somewhat destructive but has also been in-place for years? |
To be honest, no idea... this is breaking change anyway we approach it. |
https://github.com/akkadotnet/akka.net/blame/dev/src/contrib/persistence/Akka.Persistence.Sql.Common/Journal/QueryExecutor.cs#L815 <-- so it looks like for at least the past 5 years, we've been recording both the manifest and the serializer id in their own columns in all SQL persistence implementations. I think this means we can safely work around this default to some extent, but it will require making some backwards compatibility tests for the journal itself. Basically, we need to try loading the explicit serializer by its ID first during reads - and on writes we should just use whatever the default cc @ismaelhamed |
So what would a backwards compat spec look like? Using SQLite as an example:
|
@@ -780,7 +780,7 @@ protected DbCommand GetCommand(DbConnection connection, string sql) | |||
protected virtual void WriteEvent(DbCommand command, IPersistentRepresentation e, IImmutableSet<string> tags) | |||
{ | |||
|
|||
var serializer = Serialization.FindSerializerForType(e.Payload.GetType(), Configuration.DefaultSerializer); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
@@ -846,7 +846,7 @@ protected virtual IPersistentRepresentation ReadEvent(DbDataReader reader) | |||
{ | |||
// Support old writes that did not set the serializer id | |||
var type = Type.GetType(manifest, true); | |||
var deserializer = Serialization.FindSerializerForType(type, Configuration.DefaultSerializer); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
so we'd need to keep this configuration setting here for the rare cases where the data is really old and never had a serialzier ID in-place - we should revert this change specifically.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TL;DR; this is probably not the hotpath for reads on 99.999999% of events in Akka.Persistence.Sql
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This Do we have a backward compat spec around this specific condition? My concern is that if someone had a different default serializer defined, will we be using the correct one here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Exactly my question
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So we'll need to find which versions of Akka.Persistence didn't include a serializerId.
It looks like we first started adding serializerIds to Akka.Persistence.Sql.Common back in 1.3.1: https://github.com/akkadotnet/akka.net/releases/tag/v1.3.1
So, we should create a Sqlite database that runs on version 1.3.0 or earlier, writes some data, commit the binary to this repo, and then replay it using the latest development version of Akka.Persistence.Sql.Common - if we can still recover that data with this change, we're in good shape.
Don't forget this also applies to the So, what happens now for users that have set a custom serializer for persistence that is different from the default serializer?
|
New messages will be serialized using your |
I may be missing something, but with my example above, does it mean that messages that were being persisted with |
I misread your comment, yes, that would mean that messages that used to be persisted using |
This is a "breaking" change for those people that sets their |
OK, looking at the history of why this setting is introduced in the first place, I'm in two minds about this. Seems like removing this feature would introduce a bigger problem than it would solve. |
…rkatufus/akka.net into Persistence.Sql_CustomSerializer_repro
This is clear for me now, thanks @Arkatufus. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall looks good, but we do need to harden the backwards compat test and add some additional comments for future committers to understand the significance of these tests - most of this backwards compatibility data is very "inside baseball."
@@ -234,6 +234,7 @@ public abstract class BatchingSqlJournalSetup | |||
/// <summary> | |||
/// The default serializer used when not type override matching is found | |||
/// </summary> | |||
[Obsolete(message: "This property should never be used for writes, use the default `System.Object` serializer instead")] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
@@ -1325,7 +1326,7 @@ private async Task<WriteMessagesResult> HandleWriteMessages(WriteMessages req, T | |||
protected virtual void WriteEvent(TCommand command, IPersistentRepresentation persistent, string tags = "") | |||
{ | |||
var payloadType = persistent.Payload.GetType(); | |||
var serializer = _serialization.FindSerializerForType(payloadType, Setup.DefaultSerializer); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM - this is really the key fix here.
@@ -846,7 +846,7 @@ protected virtual IPersistentRepresentation ReadEvent(DbDataReader reader) | |||
{ | |||
// Support old writes that did not set the serializer id | |||
var type = Type.GetType(manifest, true); | |||
var deserializer = Serialization.FindSerializerForType(type, Configuration.DefaultSerializer); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So we'll need to find which versions of Akka.Persistence didn't include a serializerId.
It looks like we first started adding serializerIds to Akka.Persistence.Sql.Common back in 1.3.1: https://github.com/akkadotnet/akka.net/releases/tag/v1.3.1
So, we should create a Sqlite database that runs on version 1.3.0 or earlier, writes some data, commit the binary to this repo, and then replay it using the latest development version of Akka.Persistence.Sql.Common - if we can still recover that data with this change, we're in good shape.
@@ -224,22 +224,5 @@ protected override async Task DeleteAsync(string persistenceId, SnapshotSelectio | |||
await QueryExecutor.DeleteBatchAsync(connection, nestedCancellationTokenSource.Token, persistenceId, criteria.MaxSequenceNr, criteria.MaxTimeStamp); | |||
} | |||
} | |||
|
|||
private SnapshotEntry ToSnapshotEntry(SnapshotMetadata metadata, object snapshot) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why was this removed?
await using var cmd = new SqliteCommand(sql, conn); | ||
var record = await cmd.ExecuteReaderAsync(); | ||
await record.ReadAsync(); | ||
record[0].Should().Be(9999); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you add a comment explaining what this test actually tests - like, what use case is it here to assert? Just need a snippet at the top explaining this.
} | ||
|
||
[Fact(DisplayName = "Persistence.Sql should be able to read legacy data")] | ||
public void LegacyDataTest() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So to make this cover the issue referenced here: https://github.com/akkadotnet/akka.net/pull/6223/files#r1012961321 - we just need even older data, correct?
Also, you'll need to add an entry to the upgrade advisories page for this change too @Arkatufus |
Closing this issue, moving the changes to |
Fix for akkadotnet/Akka.Hosting#127
Fix #6389
Changes
Issue
Any non-serializer mapped objects being serialized by
Akka.Persistence.Sql.Common.Journal.QueryExecutor
will use the JSON serializer and not the object type serializer.Problem is with this line:
akka.net/src/contrib/persistence/Akka.Persistence.Sql.Common/Journal/QueryExecutor.cs
Line 783 in 39a8f9e
which takes its value from the very obscure and very rarely used HOCON override here:
akka.net/src/core/Akka.Persistence/persistence.conf
Line 119 in 39a8f9e
Workaround
Override the HOCON setting:
or declare the default non-mapped object serializer in the journal/snapshot settings: