From 4ecb8ecaf04594b3312a44d801423f515ea445b3 Mon Sep 17 00:00:00 2001 From: Tom Pantelis Date: Mon, 3 Dec 2018 12:59:43 -0500 Subject: [PATCH] Add specific serializer for SimpleReplicatedLogEntry JFR shows a lot of re-allocations of the backing byte [] when serializing SimpleReplicatedLogEntry. We can pretty closely estimate the total serialized size with some reasonable padding since the Payload is already serialized. Introduce a specific akka serializer for this. Jira: CONTROLLER-1872 Change-Id: I94ac9528657119b5bab60dc2f37eef98f18ca1b0 Signed-off-by: Tom Pantelis --- .../persisted/SimpleReplicatedLogEntry.java | 9 +++ .../SimpleReplicatedLogEntrySerializer.java | 73 +++++++++++++++++++ ...impleReplicatedLogEntrySerializerTest.java | 49 +++++++++++++ .../main/resources/initial/factory-akka.conf | 2 + 4 files changed, 133 insertions(+) create mode 100644 opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/persisted/SimpleReplicatedLogEntrySerializer.java create mode 100644 opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/persisted/SimpleReplicatedLogEntrySerializerTest.java diff --git a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/persisted/SimpleReplicatedLogEntry.java b/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/persisted/SimpleReplicatedLogEntry.java index 85fa51e93f..14ce5420d2 100644 --- a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/persisted/SimpleReplicatedLogEntry.java +++ b/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/persisted/SimpleReplicatedLogEntry.java @@ -39,6 +39,11 @@ public final class SimpleReplicatedLogEntry implements ReplicatedLogEntry, Seria this.replicatedLogEntry = replicatedLogEntry; } + static int estimatedSerializedSize(ReplicatedLogEntry replicatedLogEntry) { + return 8 /* index */ + 8 /* term */ + replicatedLogEntry.getData().size() + + 400 /* estimated extra padding for class info */; + } + @Override public void writeExternal(final ObjectOutput out) throws IOException { out.writeLong(replicatedLogEntry.getIndex()); @@ -110,6 +115,10 @@ public final class SimpleReplicatedLogEntry implements ReplicatedLogEntry, Seria return new Proxy(this); } + public int estimatedSerializedSize() { + return Proxy.estimatedSerializedSize(this); + } + @Override public int hashCode() { final int prime = 31; diff --git a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/persisted/SimpleReplicatedLogEntrySerializer.java b/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/persisted/SimpleReplicatedLogEntrySerializer.java new file mode 100644 index 0000000000..ca6e6dff30 --- /dev/null +++ b/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/persisted/SimpleReplicatedLogEntrySerializer.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2018 Red Hat, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.cluster.raft.persisted; + +import static com.google.common.base.Preconditions.checkArgument; +import static java.util.Objects.requireNonNull; + +import akka.actor.ExtendedActorSystem; +import akka.serialization.JSerializer; +import akka.util.ClassLoaderObjectInputStream; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import org.apache.commons.lang3.SerializationUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Specialized serializer for {@link SimpleReplicatedLogEntry} that optimizes serialization. + * + * @author Thomas Pantelis + */ +public class SimpleReplicatedLogEntrySerializer extends JSerializer { + private static final Logger LOG = LoggerFactory.getLogger(SimpleReplicatedLogEntrySerializer.class); + + private final ExtendedActorSystem system; + + public SimpleReplicatedLogEntrySerializer(final ExtendedActorSystem system) { + this.system = requireNonNull(system); + } + + @Override + public int identifier() { + return 97439500; + } + + @Override + public boolean includeManifest() { + return false; + } + + @Override + public byte[] toBinary(Object obj) { + checkArgument(obj instanceof SimpleReplicatedLogEntry, "Unsupported object type %s", obj.getClass()); + + SimpleReplicatedLogEntry replicatedLogEntry = (SimpleReplicatedLogEntry)obj; + final int estimatedSerializedSize = replicatedLogEntry.estimatedSerializedSize(); + + final ByteArrayOutputStream bos = new ByteArrayOutputStream(estimatedSerializedSize); + SerializationUtils.serialize(replicatedLogEntry, bos); + final byte[] bytes = bos.toByteArray(); + + LOG.debug("Estimated serialized size {}, data size {} for payload: {}. Actual serialized size: {}", + estimatedSerializedSize, replicatedLogEntry.getData().size(), replicatedLogEntry.getData(), bytes.length); + + return bytes; + } + + @Override + public Object fromBinaryJava(byte[] bytes, Class manifest) { + try (ClassLoaderObjectInputStream is = new ClassLoaderObjectInputStream(system.dynamicAccess().classLoader(), + new ByteArrayInputStream(bytes))) { + return is.readObject(); + } catch (IOException | ClassNotFoundException e) { + throw new IllegalStateException("Failed to deserialize object", e); + } + } +} diff --git a/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/persisted/SimpleReplicatedLogEntrySerializerTest.java b/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/persisted/SimpleReplicatedLogEntrySerializerTest.java new file mode 100644 index 0000000000..be48c06f4a --- /dev/null +++ b/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/persisted/SimpleReplicatedLogEntrySerializerTest.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2018 Red Hat, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.cluster.raft.persisted; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import akka.actor.ExtendedActorSystem; +import akka.testkit.javadsl.TestKit; +import java.io.NotSerializableException; +import org.junit.Test; +import org.opendaylight.controller.cluster.raft.MockRaftActorContext; + +/** + * Unit tests for SimpleReplicatedLogEntrySerializer. + * + * @author Thomas Pantelis + */ +public class SimpleReplicatedLogEntrySerializerTest { + + @Test + public void testToAndFromBinary() throws NotSerializableException { + SimpleReplicatedLogEntry expected = new SimpleReplicatedLogEntry(0, 1, + new MockRaftActorContext.MockPayload("A")); + + final ExtendedActorSystem system = (ExtendedActorSystem) ExtendedActorSystem.create("test"); + final Object deserialized; + try { + final SimpleReplicatedLogEntrySerializer serializer = new SimpleReplicatedLogEntrySerializer(system); + final byte[] bytes = serializer.toBinary(expected); + deserialized = serializer.fromBinary(bytes, SimpleReplicatedLogEntry.class); + } finally { + TestKit.shutdownActorSystem(system); + } + + assertNotNull("fromBinary returned null", deserialized); + assertEquals("fromBinary return type", SimpleReplicatedLogEntry.class, deserialized.getClass()); + + SimpleReplicatedLogEntry actual = (SimpleReplicatedLogEntry)deserialized; + assertEquals("getTerm", expected.getTerm(), actual.getTerm()); + assertEquals("getIndex", expected.getIndex(), actual.getIndex()); + assertEquals("getData", expected.getData(), actual.getData()); + } +} diff --git a/opendaylight/md-sal/sal-clustering-config/src/main/resources/initial/factory-akka.conf b/opendaylight/md-sal/sal-clustering-config/src/main/resources/initial/factory-akka.conf index 490bd87626..7b86f1c572 100644 --- a/opendaylight/md-sal/sal-clustering-config/src/main/resources/initial/factory-akka.conf +++ b/opendaylight/md-sal/sal-clustering-config/src/main/resources/initial/factory-akka.conf @@ -63,11 +63,13 @@ odl-cluster-data { java = "akka.serialization.JavaSerializer" proto = "akka.remote.serialization.ProtobufSerializer" readylocal = "org.opendaylight.controller.cluster.datastore.messages.ReadyLocalTransactionSerializer" + simpleReplicatedLogEntry = "org.opendaylight.controller.cluster.raft.persisted.SimpleReplicatedLogEntrySerializer" } serialization-bindings { "com.google.protobuf.Message" = proto "org.opendaylight.controller.cluster.datastore.messages.ReadyLocalTransaction" = readylocal + "org.opendaylight.controller.cluster.raft.persisted.SimpleReplicatedLogEntry" = simpleReplicatedLogEntry } default-dispatcher { -- 2.36.6