April 10, 2015

JAX-RS Kryo Message Body Writer & Reader performance

In this blog post I'm going to return to one of my previous post about JAX-RS Message Body Providers using Kryo serialization framework. Today I'll show you performance statistics and compare it with some of Jersey build-in providers.

Comparing to ...

I have chosen to compare my Kryo provider with:
  • JAXB RI providers to read from and write to XML format.
  • Jackson providers to read from and write to JSON format.
  • Custom provider that reads and writes test entity (Person class) by itself. (Note: Used format is really simple and implementation is Person specific, uses artificial MIKME type application/person.)
  • Just plain text provider to read and write String only. It uses Jersey build-in provider.
Test applications are very similar to mine Jersey Kryo module and are part of Jersey source code:
The Kryo provider has been introduced in previous blog post. I have done one change since then. Performance of Kryo provider was several times improved using KryoPool:


@Consumes("application/x-kryo")
@Produces("application/x-kryo")
public class KryoMessageBodyProvider implements MessageBodyWriter<Object>,
                MessageBodyReader<Object> {

    private final KryoPool kryoPool;

    public KryoMessageBodyProvider() {
        final KryoFactory kryoFactory = new KryoFactory() {
            public Kryo create() {
                final Kryo kryo = new Kryo();
                // configure kryo instance, customize settings
                return kryo;
            }
        };
        kryoPool = new KryoPool.Builder(kryoFactory).softReferences().build();
    }

    //
    // MessageBodyWriter
    //

    @Override
    public long getSize(final Object object, final Class<?> type, final Type genericType,
                        final Annotation[] annotations, final MediaType mediaType) {
        return -1;
    }

    @Override
    public boolean isWriteable(final Class<?> type, final Type genericType,
                               final Annotation[] annotations, final MediaType mediaType) {
        return true;
    }

    @Override
    public void writeTo(final Object object, final Class<?> type, final Type genericType,
                        final Annotation[] annotations, final MediaType mediaType,
                        final MultivaluedMap<String, Object> httpHeaders, 
                        final OutputStream entityStream)
            throws IOException, WebApplicationException {
        final ByteArrayOutputStream baos = new ByteArrayOutputStream();

        final Output output = new Output(baos);
        kryoPool.run(new KryoCallback() {
            public Object execute(Kryo kryo) {
                kryo.writeObject(output, object);
                return null;
            }
        });
        output.close();

        entityStream.write(baos.toByteArray());
    }

    //
    // MessageBodyReader
    //

    @Override
    public boolean isReadable(final Class<?> type, final Type genericType,
                              final Annotation[] annotations, final MediaType mediaType) {
        return true;
    }

    @Override
    public Object readFrom(final Class<Object> type, final Type genericType,
                           final Annotation[] annotations, final MediaType mediaType,
                           final MultivaluedMap<String, String> httpHeaders,
                           final InputStream entityStream) 
                throws IOException, WebApplicationException {
        try {
            final Input input = new Input(entityStream);
            final Object object = kryoPool.run(new KryoCallback() {
                public Object execute(Kryo kryo) {
                    return kryo.readObject(input, type);
                }
            });
            input.close();

            return object;
        } catch (Exception e) {
            throw new WebApplicationException(e);
        }
    }

}

Test environment

Each test application was executed using Grizzly HTTP Server, no servlet container, on 4 core VM. And load has been produced from another 2 client VMs using wrk - a HTTP benchmarking tool. GET, POST and PUT HTTP methods was measured. It is necessary to say each test works just with one instance of Person entity. It means data load is small. Test's focus is on throughput measurement. 

Test results

Following table shows number of requests per second for each tested provider and mentioned HTTP method:

GETPOSTPUT
XML (JAXB RI)217051131612080
JSON (Jackson)220971859020116
Kryo215331837121304
Custom215431888420442
TEXT (String)218161769519882


Conclusion

Please note the Kryo provider is not in production quality. It just demonstrates how simple it is to enhance JAX-RS runtime to support new entity media type. However it is naive implementation of message body provider it provides very good performance. Kryo also support class registration and compression features that can also affect performance.

And finally, test applications work just with one instance of entity (Person). I guess Kryo could show it's power while working with more complex objects or with collection of them. And Kryo compression feature would be very strong in such case. Maybe I could prepare such test in couple of next months. ;-)