Improving Performance with the Kryo Serializer
The Kryo serializer and the Community Edition Serialization API let you serialize or deserialize objects into a byte array. These serializers decouple Mule and its extensions from the actual serialization mechanism, thus enabling configuration of the mechanism to use or the creation of a custom serializer. The Kryo serializer replaces plain old Java serialization, in which Java classes implement java.io.Serializable
or java.io.Externalizable
to store objects in files, or to replicate classes through a Mule cluster.
Community Edition Serialization API - The open source Serialization API is available in GitHub in the ObjectSerializer.java interface.
Mule relies on serialization when apps read from or write to persistent object stores, for a VM or JMS queue, or to an object from a file. Serialization also occurs when an object distributes through a Mule cluster. The use of Kryo and Community Edition serializers greatly improve functionality and performance over plain Java serialization.
Note: The batch module requires Kryo serialization.
Kryo provides:
-
Kryo is much faster than Java serialization for:
-
Persistent or clustered object stores
-
Persistent or distributed VM queues
-
JMS transport
-
-
Support for a wider range on Java types. Kryo is not bounded by most of the limitations that Java serialization imposes like requiring to implement the Serializable interface, having a default constructor, etc.
-
Support for compression so that you can deflate or use GZip compression algorithms.
About Serializer Tasks
You can develop projects with the Kryo and Community Edition serializers to:
-
Create a custom serializer using the Serialization API (CE).
-
Configure the Kryo Serializer (EE only).
The Kryo serializer and the Community Edition Serialization API:
-
Are thread safe.
-
Passes an OutputStream when serializing and streaming.
-
Allows an InputStream as an input source.
-
You can specify which classloader to use, the default is the current classloader.
-
When deserializing, if the obtained object implements the DeserializationPostInitialisable interface, the serializer is responsible for properly initializing the object before returning it.
About How Kryo Improves Performance
The following illustrations from the MuleSoft performance team compare the transactions per second (TPS) of persistent and distributed VM queues and ObjectStore using the plain old Java serializer versus various Kryo settings.
The previous charts indicate that Kryo without compression is significantly faster than the plain old Java serializer in all cases. However, the compression mode only provides an actual improvement on the high availability (HA) cases.
For the compression to be worthy, the amount of time the CPU spends compressing and decompressing has to be significantly lower than the amount of I/O time saved by reducing the payload size. Because network operations are typically slower than disk operations and because HA clustering requires node replication, which translates to more traffic), only in the HA case the compression paid off.
This is not a universal constant. You might be running Mule on machines with slower disks or higher I/O demands in which compression might be worthy on any case. Also, these tests were performed with 1 MB payloads, but the larger the data stream, the more worthy becomes the compression.
About Performance Results
The following are the performance results:
Test | VM Persistent | OS Persistent | VM HA | OS HA |
---|---|---|---|---|
Kryo |
64.71% |
6.64% |
21.09% |
24.79% |
Kryo + Deflate |
11.84% |
-11.01% |
63.77% |
77.13% |
Kryo + GZip |
8.53% |
-8.69% |
13.93% |
23.96% |
The conclusions from table are that:
-
You can get up to a 77.13% improvement in performance when using distributed ObjectStores, 63.77% when using distributed VM queues and 64.71% when using local persistent VM queues.
-
Although local object stores don’t show much improvement. They are actually slower when using compression. There’s no use case in which you don’t get some level of gain when using Kryo.
Performance results are a guideline rather than an absolute fact. Depending on your app, environment, payload size, etc., the actual output may vary.
To Configure a Serializer
The Mule configuration tag allows you to configure the default ObjectSerializer for a Mule app:
<mule>
<spring:beans>
<spring:bean id = "customSerializer" class = "com.my.CustomSerializer" />
</spring:beans>
<configuration defaultObjectSerializer-ref = "customSerializer" />
</mule>
If no defaultObjectSerializer is configured, then the default implementation uses plain old Java serialization. From a user perspective, there’s no change unless you set a different serializer.
To Configure Kryo Serialization
In Enterprise Edition Mule provides an implementation of ObjectSerializer which relies on the Kryo framework.
The Kryo namespace lets you configure this serializer inside your Mule app without needing to define a custom Spring bean. This configuration example sets the default serializer to a Kryo based one:
<mule>
<kryo:serializer name = "kryo" />
<configuration defaultObjectSerializer-ref = "kryo" />
</mule>
Additionally, you can also include the compressionMode
XML attribute to configure compression:
<kryo:serializer name = "noCompression" compressionMode = "NONE" />
<kryo:serializer name = "deflate" compressionMode = "DEFLATE" />
<kryo:serializer name = "gzip" compressionMode = "GZIP" />
To Configure Custom Serialization
You can obtain an ObjectSerializer inside Java code through dependency injection.
The following code example shows how to get a currently configured default ObjectSerializer:
public class MyClass {
@Inject
@DefaultObjectSerializer
private ObjectSerializer objectSerializer;
}
If you want a specific named serializer (whether it’s the default or not), you can access the serializer by name:
public class MyClass {
@Inject
@Named ("mySerializer")
private ObjectSerializer objectSerializer;
}
Finally, although dependency injection is preferred, you can always look up a serializer from the muleContext
:
// Returns the default object serializer
muleContext.getObjectSerializer();
// Returns a named object serializer
muleContext.getRegistry().get("mySerializer");
About Limitations and Considerations
-
Clean slate updates
Serializers are not interoperable nor interchangeable. That means that if you decide to change the serializer your app uses, you need to make sure that all messages in VM and JMS queues have been consumed and that those queues are empty by the time the new serializer kicks in. This is because Kryo serializer won’t be able to read datagrams written by the Java serializer and vice-versa. The same thing applies to persistent ObjectStores. Reading an entry generated with a different serializer does not work.
-
Shared VM Connector updates
Mule treats domains as a way to share resources between apps. For example, you can define a VM connector on a domain to allow inter-app communication through VM message queues. However, serializers can only be configured at an app level, they cannot be configured in a domain. If two apps A and B communicate with each other through a VM connector defined on a domain to which both belong, Mule lets app A serialize using Java and app B using Kryo. Whenever an app writes to an endpoint that uses the shared connector, a message is not serialized with the app’s serializer but with the serializer that the VM connector uses. This may mean that Kryo is not used.
-
Local Persistent ObjectStore
A local persistent ObjectStore shows less improvement because of high contention on the ObjectStore implementation that absorbs all the gain.
-
No JMS Improvement Chart
Per the JMS API, queues don’t work with raw payload objects. Instead, you have to provide an instance of the javax.jms.Message class. The broker client is then responsible for serializing it, not Mule. Therefore, the impact of Kryo in such an scenario is minimum. The only performance gain of using Kryo with JMS is that Mule serializes the Mule session and puts it as a header in Base64 format. Serializing the Mule session with Kryo can give you up to 10% performance speed, but this is just as an example use case because the big part of the serialization is up to the JMS broker instead of Mule.
-
Kryo as the default serializer
Although Kryo is capable of serializing objects that don’t implement the serializable interface, setting Kryo as the default serializer doesn’t mean that components such as the VM transport, ObjectSerializer, or a cluster are able to handle objects that don’t implement such an interface. That’s because even though Kryo can deal with those objects, the Java APIs for those components still expect instances of serializable in their method signatures.
-
POJO serialization fails
Plain old Java serialization fails with an object that implements the Serializable interface. However if serialization contains another object that doesn’t implement the Serializable interface, Kryo is likely (but not guaranteed) to succeed. A typical case is a POJO containing an
org.apache.xerces.jaxp.datatype.XMLGregorianCalendarImpl
that is in use in NetSuite and Microsoft Dynamics connectors.