Merge branch 'master' into feature-active-active-event-sourcing

This commit is contained in:
Patrik Nordwall 2020-09-02 15:46:06 +02:00
commit 7bf12721c1
48 changed files with 935 additions and 688 deletions

View file

@ -27,30 +27,30 @@ There are two parts of Akka that need careful consideration when performing an r
1. Serialization format of persisted events and snapshots. New nodes must be able to read old data, and
during the update old nodes must be able to read data stored by new nodes.
There are many more application specific aspects for serialization changes during rolling upgrades to consider.
There are many more application specific aspects for serialization changes during rolling updates to consider.
For example based on the use case and requirements, whether to allow dropped messages or tear down the TCP connection when the manifest is unknown.
When some message loss during a rolling upgrade is acceptable versus a full shutdown and restart, assuming the application recovers afterwards
When some message loss during a rolling update is acceptable versus a full shutdown and restart, assuming the application recovers afterwards
* If a `java.io.NotSerializableException` is thrown in `fromBinary` this is treated as a transient problem, the issue logged and the message is dropped
* If other exceptions are thrown it can be an indication of corrupt bytes from the underlying transport, and the connection is broken
For more zero-impact rolling upgrades, it is important to consider a strategy for serialization that can be evolved.
One approach to retiring a serializer without downtime is described in @ref:[two rolling upgrade steps to switch to the new serializer](../serialization.md#rolling-upgrades).
For more zero-impact rolling updates, it is important to consider a strategy for serialization that can be evolved.
One approach to retiring a serializer without downtime is described in @ref:[two rolling update steps to switch to the new serializer](../serialization.md#rolling-updates).
Additionally you can find advice on @ref:[Persistence - Schema Evolution](../persistence-schema-evolution.md) which also applies to remote messages when deploying with rolling updates.
## Cluster Sharding
During a rolling upgrade, sharded entities receiving traffic may be moved during @ref:[shard rebalancing](../typed/cluster-sharding-concepts.md#shard-rebalancing),
During a rolling update, sharded entities receiving traffic may be moved during @ref:[shard rebalancing](../typed/cluster-sharding-concepts.md#shard-rebalancing),
to an old or new node in the cluster, based on the pluggable allocation strategy and settings.
When an old node is stopped the shards that were running on it are moved to one of the
other old nodes remaining in the cluster. The `ShardCoordinator` is itself a cluster singleton.
To minimize downtime of the shard coordinator, see the strategies about @ref[ClusterSingleton](#cluster-singleton) rolling upgrades below.
To minimize downtime of the shard coordinator, see the strategies about @ref[ClusterSingleton](#cluster-singleton) rolling updates below.
A few specific changes to sharding configuration require @ref:[a full cluster restart](#cluster-sharding-configuration-change).
## Cluster Singleton
Cluster singletons are always running on the oldest node. To avoid moving cluster singletons more than necessary during a rolling upgrade,
it is recommended to upgrade the oldest node last. This way cluster singletons are only moved once during a full rolling upgrade.
Cluster singletons are always running on the oldest node. To avoid moving cluster singletons more than necessary during a rolling update,
it is recommended to upgrade the oldest node last. This way cluster singletons are only moved once during a full rolling update.
Otherwise, in the worst case cluster singletons may be migrated from node to node which requires coordination and initialization
overhead several times.
@ -160,5 +160,5 @@ Rolling update is not supported when @ref:[changing the remoting transport](../r
### Migrating from Classic Sharding to Typed Sharding
If you have been using classic sharding it is possible to do a rolling upgrade to typed sharding using a 3 step procedure.
If you have been using classic sharding it is possible to do a rolling update to typed sharding using a 3 step procedure.
The steps along with example commits are detailed in [this sample PR](https://github.com/akka/akka-samples/pull/110)

View file

@ -5,9 +5,11 @@
Akka offers tiny helpers for use with @scala[@scaladoc[Future](scala.concurrent.Future)s]@java[@javadoc[CompletionStage](java.util.concurrent.CompletionStage)]. These are part of Akka's core module:
@@dependency[sbt,Maven,Gradle] {
symbol1=AkkaVersion
value1="$akka.version$"
group="com.typesafe.akka"
artifact="akka-actor_$scala.binary_version$"
version="$akka.version$"
artifact="akka-actor_$scala.binary.version$"
version=AkkaVersion
}
## After

View file

@ -224,7 +224,7 @@ needs to have an associated code which indicates if it is a window or aisle seat
Adding fields is the most common change you'll need to apply to your messages so make sure the serialization format
you picked for your payloads can handle it apropriately, i.e. such changes should be *binary compatible*.
This is achieved using the right serializer toolkit. In the following examples we will be using protobuf.
See also @ref:[how to add fields with Jackson](serialization-jackson.md#add-field).
See also @ref:[how to add fields with Jackson](serialization-jackson.md#add-optional-field).
While being able to read messages with missing fields is half of the solution, you also need to deal with the missing
values somehow. This is usually modeled as some kind of default value, or by representing the field as an @scala[`Option[T]`]@java[`Optional<T>`]

View file

@ -19,7 +19,9 @@ reading this migration guide and testing your application thoroughly is recommen
Rolling updates are possible without shutting down all nodes of the Akka Cluster, but will require
configuration adjustments as described in the @ref:[Remoting](#remoting) section of this migration
guide.
guide. Due to the @ref:[changed serialization of the Cluster messages in Akka 2.6.2](rolling-update.md#2-6-2-clustermessageserializer-manifests-change)
a rolling update from 2.5.x must first be made to Akka 2.6.2 and then a second rolling update can change to Akka 2.6.3
or later.
## Scala 2.11 no longer supported

View file

@ -92,7 +92,7 @@ This means that a rolling update will have to go through at least one of 2.6.2,
Issue: [#28918](https://github.com/akka/akka/issues/28918). JacksonCborSerializer was using plain JSON format
instead of CBOR.
If you have `jackson-cbor` in your `serialization-bindings` a rolling upgrade will have to go through 2.6.5 when
If you have `jackson-cbor` in your `serialization-bindings` a rolling update will have to go through 2.6.5 when
upgrading to 2.6.5 or higher.
In Akka 2.6.5 the `jackson-cbor` binding will still serialize to JSON format to support rolling update from 2.6.4.

View file

@ -164,11 +164,20 @@ when using polymorphic types.
### ADT with trait and case object
In Scala it's common to use a sealed trait and case objects to represent enums. If the values are case classes
It's common in Scala to use a sealed trait and case objects to represent enums. If the values are case classes
the `@JsonSubTypes` annotation as described above works, but if the values are case objects it will not.
The annotation requires a `Class` and there is no way to define that in an annotation for a `case object`.
This can be solved by implementing a custom serialization for the enums. Annotate the `trait` with
The easiest workaround is to define the case objects as case class without any field.
Alternatively, you can define an intermediate trait for the case object and a custom deserializer for it. The example below builds on the previous `Animal` sample by adding a fictitious, single instance, new animal, an `Unicorn`.
Scala
: @@snip [SerializationDocSpec.scala](/akka-serialization-jackson/src/test/scala/doc/akka/serialization/jackson/SerializationDocSpec.scala) { #polymorphism-case-object }
The case object `Unicorn` can't be used in a `@JsonSubTypes` annotation, but its trait can. When serializing the case object we need to know which type tag to use, hence the `@JsonTypeName` annotation on the object. When deserializing, Jackson will only know about the trait variant therefore we need a custom deserializer that returns the case object.
On the other hand, if the ADT only has case objects, you can solve it by implementing a custom serialization for the enums. Annotate the `trait` with
`@JsonSerialize` and `@JsonDeserialize` and implement the serialization with `StdSerializer` and
`StdDeserializer`.
@ -205,7 +214,7 @@ We will look at a few scenarios of how the classes may be evolved.
Removing a field can be done without any migration code. The Jackson serializer will ignore properties that does
not exist in the class.
### Add Field
### Add Optional Field
Adding an optional field can be done without any migration code. The default value will be @scala[None]@java[`Optional.empty`].
@ -226,6 +235,8 @@ Scala
Java
: @@snip [ItemAdded.java](/akka-serialization-jackson/src/test/java/jdoc/akka/serialization/jackson/v2a/ItemAdded.java) { #add-optional }
### Add Mandatory Field
Let's say we want to have a mandatory `discount` property without default value instead:
Scala
@ -361,6 +372,63 @@ binding, but it should still be possible to deserialize old data with Jackson.
It's a list of class names or prefixes of class names.
## Rolling updates
When doing a rolling update, for a period of time there are two different binaries running in production. If the schema
has evolved requiring a new schema version, the data serialized by the new binary will be unreadable from the old
binary. This situation causes transient errors on the processes running the old binary. This service degradation is
usually fine since the rolling update will eventually complete and all old processes will be replaced with the new
binary. To avoid this service degradation you can also use forward-one support in your schema evolutions.
To complete a no-degradation rolling update, you need to make two deployments. First, deploy a new binary which can read
the new schema but still uses the old schema. Then, deploy a second binary which serializes data using the new schema
and drops the downcasting code from the migration.
Let's take, for example, the case above where we [renamed a field](#rename-field).
The starting schema is:
Scala
: @@snip [ItemAdded.java](/akka-serialization-jackson/src/test/scala/doc/akka/serialization/jackson/v1/ItemAdded.scala) { #add-optional }
Java
: @@snip [ItemAdded.java](/akka-serialization-jackson/src/test/java/jdoc/akka/serialization/jackson/v1/ItemAdded.java) { #add-optional }
In a first deployment, we still don't make any change to the event class:
Scala
: @@snip [ItemAdded.scala](/akka-serialization-jackson/src/test/scala/doc/akka/serialization/jackson/v1/ItemAdded.scala) { #forward-one-rename }
Java
: @@snip [ItemAdded.java](/akka-serialization-jackson/src/test/java/jdoc/akka/serialization/jackson/v1/ItemAdded.java) { #forward-one-rename }
but we introduce a migration that can read the newer schema which is versioned `2`:
Scala
: @@snip [ItemAddedMigration.scala](/akka-serialization-jackson/src/test/scala/doc/akka/serialization/jackson/v1withv2/ItemAddedMigration.scala) { #forward-one-rename }
Java
: @@snip [ItemAddedMigration.java](/akka-serialization-jackson/src/test/java/jdoc/akka/serialization/jackson/v1withv2/ItemAddedMigration.java) { #forward-one-rename }
Once all running nodes have the new migration code which can read version `2` of `ItemAdded` we can proceed with the
second step. So, we deploy the updated event:
Scala
: @@snip [ItemAdded.scala](/akka-serialization-jackson/src/test/scala/doc/akka/serialization/jackson/v2c/ItemAdded.scala) { #rename }
Java
: @@snip [ItemAdded.java](/akka-serialization-jackson/src/test/java/jdoc/akka/serialization/jackson/v2c/ItemAdded.java) { #rename }
and the final migration code which no longer needs forward-compatibility code:
Scala
: @@snip [ItemAddedMigration.scala](/akka-serialization-jackson/src/test/scala/doc/akka/serialization/jackson/v2c/ItemAddedMigration.scala) { #rename }
Java
: @@snip [ItemAddedMigration.java](/akka-serialization-jackson/src/test/java/jdoc/akka/serialization/jackson/v2c/ItemAddedMigration.java) { #rename }
## Jackson Modules
The following Jackson modules are enabled by default:

View file

@ -180,7 +180,7 @@ should be serialized by it.
It's recommended to throw `IllegalArgumentException` or `java.io.NotSerializableException` in
`fromBinary` if the manifest is unknown. This makes it possible to introduce new message types and
send them to nodes that don't know about them. This is typically needed when performing
rolling upgrades, i.e. running a cluster with mixed versions for a while.
rolling updates, i.e. running a cluster with mixed versions for a while.
Those exceptions are treated as a transient problem in the classic remoting
layer. The problem will be logged and the message dropped. Other exceptions will tear down
the TCP connection because it can be an indication of corrupt bytes from the underlying
@ -252,24 +252,24 @@ akka.actor.warn-about-java-serializer-usage = off
It is not safe to mix major Scala versions when using the Java serialization as Scala does not guarantee compatibility
and this could lead to very surprising errors.
## Rolling upgrades
## Rolling updates
A serialized remote message (or persistent event) consists of serializer-id, the manifest, and the binary payload.
When deserializing it is only looking at the serializer-id to pick which `Serializer` to use for `fromBinary`.
The message class (the bindings) is not used for deserialization. The manifest is only used within the
`Serializer` to decide how to deserialize the payload, so one `Serializer` can handle many classes.
That means that it is possible to change serialization for a message by performing two rolling upgrade steps to
That means that it is possible to change serialization for a message by performing two rolling update steps to
switch to the new serializer.
1. Add the `Serializer` class and define it in `akka.actor.serializers` config section, but not in
`akka.actor.serialization-bindings`. Perform a rolling upgrade for this change. This means that the
`akka.actor.serialization-bindings`. Perform a rolling update for this change. This means that the
serializer class exists on all nodes and is registered, but it is still not used for serializing any
messages. That is important because during the rolling upgrade the old nodes still don't know about
messages. That is important because during the rolling update the old nodes still don't know about
the new serializer and would not be able to deserialize messages with that format.
1. The second change is to register that the serializer is to be used for certain classes by defining
those in the `akka.actor.serialization-bindings` config section. Perform a rolling upgrade for this
those in the `akka.actor.serialization-bindings` config section. Perform a rolling update for this
change. This means that new nodes will use the new serializer when sending messages and old nodes will
be able to deserialize the new format. Old nodes will continue to use the old serializer when sending
messages and new nodes will be able to deserialize the old format.

View file

@ -140,7 +140,7 @@ The reason for only using a limited number of nodes is to keep the number of con
centers low. The same nodes are also used for the gossip protocol when disseminating the membership
information across data centers. Within a data center all nodes are involved in gossip and failure detection.
This influences how rolling upgrades should be performed. Don't stop all of the oldest that are used for gossip
This influences how rolling updates should be performed. Don't stop all of the oldest that are used for gossip
at the same time. Stop one or a few at a time so that new nodes can take over the responsibility.
It's best to leave the oldest nodes until last.

View file

@ -293,7 +293,7 @@ Cluster Sharding uses its own Distributed Data `Replicator` per node.
If using roles with sharding there is one `Replicator` per role, which enables a subset of
all nodes for some entity types and another subset for other entity types. Each replicator has a name
that contains the node role and therefore the role configuration must be the same on all nodes in the
cluster, for example you can't change the roles when performing a rolling upgrade.
cluster, for example you can't change the roles when performing a rolling update.
Changing roles requires @ref:[a full cluster restart](../additional/rolling-updates.md#cluster-sharding-configuration-change).
The `akka.cluster.sharding.distributed-data` config section configures the settings for Distributed Data.
@ -413,7 +413,7 @@ akka.persistence.cassandra.journal {
}
```
Once you have migrated you cannot go back to the old persistence store, a rolling upgrade is therefore not possible.
Once you have migrated you cannot go back to the old persistence store, a rolling update is therefore not possible.
When @ref:[Distributed Data mode](#distributed-data-mode) is used the identifiers of the entities are
stored in @ref:[Durable Storage](distributed-data.md#durable-storage) of Distributed Data. You may want to change the

View file

@ -173,7 +173,7 @@ avoid blocking APIs. The following solution explains how to handle blocking
operations properly.
Note that the same hints apply to managing blocking operations anywhere in Akka,
including Streams, Http and other reactive libraries built on top of it.
including Streams, HTTP and other reactive libraries built on top of it.
@@@

View file

@ -139,7 +139,7 @@ public class LambdaPersistencePluginDocTest {
public MyJournalSpecTest() {
super(
ConfigFactory.parseString(
"persistence.journal.plugin = "
"akka.persistence.journal.plugin = "
+ "\"akka.persistence.journal.leveldb-shared\""));
}