From ed91964e9fde4a6e723368c409be56f9ff16d679 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Mon, 12 Mar 2018 10:14:14 +0100 Subject: [PATCH] Use BitSet for slot storage #715 Lettuce now uses BitSet to represent internally cluster slots assigned to a cluster node. The slot storage is allocated lazily and nodes without a slot (non-slot masters, slave nodes) won't allocate any memory for the slot storage. This change reduces the memory usage from the previously used ArrayList with initially 16 elements backed by an Object[]. A node with all slots assigned required about 325.000 bytes of memory. Using a BitSet stores only bitwise whether a slot is assigned or not. The storage requires a fixed amount of memory (about 2184 bytes) for each node independent of how many slots are occupied. A cluster of 150 nodes reached the break-even between the previous and current pattern if at least a single slot is used on a particular node. Clusters with more nodes will have always a higher memory consumption if at least a single slot is occupied. --- .../models/partitions/RedisClusterNode.java | 153 +++++++++++------- .../core/cluster/RedisClusterClientTest.java | 18 +-- .../partitions/RedisClusterNodeTest.java | 27 +++- .../models/slots/ClusterSlotsParserTest.java | 18 +-- .../cluster/topology/TopologyRefreshTest.java | 41 ++--- .../cluster/models/partitions/JmhMain.java | 54 +++++++ .../partitions/RedisClusterNodeBenchmark.java | 60 +++++++ 7 files changed, 274 insertions(+), 97 deletions(-) create mode 100644 src/test/jmh/io/lettuce/core/cluster/models/partitions/JmhMain.java create mode 100644 src/test/jmh/io/lettuce/core/cluster/models/partitions/RedisClusterNodeBenchmark.java diff --git a/src/main/java/io/lettuce/core/cluster/models/partitions/RedisClusterNode.java b/src/main/java/io/lettuce/core/cluster/models/partitions/RedisClusterNode.java index dc78f2eda7..e6e05d331b 100644 --- a/src/main/java/io/lettuce/core/cluster/models/partitions/RedisClusterNode.java +++ b/src/main/java/io/lettuce/core/cluster/models/partitions/RedisClusterNode.java @@ -16,26 +16,28 @@ package io.lettuce.core.cluster.models.partitions; import java.io.Serializable; -import java.util.ArrayList; -import java.util.List; -import java.util.Set; +import java.util.*; import io.lettuce.core.RedisURI; +import io.lettuce.core.cluster.SlotHash; import io.lettuce.core.internal.LettuceAssert; -import io.lettuce.core.internal.LettuceSets; import io.lettuce.core.models.role.RedisNodeDescription; /** - * Representation of a Redis Cluster node. A {@link RedisClusterNode} is identified by its {@code nodeId}. A - * {@link RedisClusterNode} can be a {@link #getRole() responsible master} for zero to - * {@link io.lettuce.core.cluster.SlotHash#SLOT_COUNT 16384} slots, a slave of one {@link #getSlaveOf() master} of carry - * different {@link io.lettuce.core.cluster.models.partitions.RedisClusterNode.NodeFlag flags}. + * Representation of a Redis Cluster node. A {@link RedisClusterNode} is identified by its {@code nodeId}. + *

+ * A {@link RedisClusterNode} can be a {@link #getRole() responsible master} or slave. Masters can be responsible for zero to + * {@link io.lettuce.core.cluster.SlotHash#SLOT_COUNT 16384} slots. Each slave refers to exactly one {@link #getSlaveOf() + * master}. Nodes can have different {@link io.lettuce.core.cluster.models.partitions.RedisClusterNode.NodeFlag flags} assigned. + *

+ * This class is mutable and not thread-safe if mutated by multiple threads concurrently. * * @author Mark Paluch * @since 3.0 */ @SuppressWarnings("serial") public class RedisClusterNode implements Serializable, RedisNodeDescription { + private RedisURI uri; private String nodeId; @@ -45,15 +47,15 @@ public class RedisClusterNode implements Serializable, RedisNodeDescription { private long pongReceivedTimestamp; private long configEpoch; - private List slots; - private Set flags; + private BitSet slots; + private final Set flags = EnumSet.noneOf(NodeFlag.class); public RedisClusterNode() { - } public RedisClusterNode(RedisURI uri, String nodeId, boolean connected, String slaveOf, long pingSentTimestamp, long pongReceivedTimestamp, long configEpoch, List slots, Set flags) { + this.uri = uri; this.nodeId = nodeId; this.connected = connected; @@ -61,11 +63,15 @@ public RedisClusterNode(RedisURI uri, String nodeId, boolean connected, String s this.pingSentTimestamp = pingSentTimestamp; this.pongReceivedTimestamp = pongReceivedTimestamp; this.configEpoch = configEpoch; - this.slots = slots; - this.flags = flags; + + setSlotBits(slots); + setFlags(flags); } public RedisClusterNode(RedisClusterNode redisClusterNode) { + + LettuceAssert.notNull(redisClusterNode, "RedisClusterNode must not be null"); + this.uri = redisClusterNode.uri; this.nodeId = redisClusterNode.nodeId; this.connected = redisClusterNode.connected; @@ -73,8 +79,13 @@ public RedisClusterNode(RedisClusterNode redisClusterNode) { this.pingSentTimestamp = redisClusterNode.pingSentTimestamp; this.pongReceivedTimestamp = redisClusterNode.pongReceivedTimestamp; this.configEpoch = redisClusterNode.configEpoch; - this.slots = new ArrayList<>(redisClusterNode.slots); - this.flags = LettuceSets.newHashSet(redisClusterNode.flags); + + if (redisClusterNode.slots != null && !redisClusterNode.slots.isEmpty()) { + this.slots = new BitSet(SlotHash.SLOT_COUNT); + this.slots.or(redisClusterNode.slots); + } + + setFlags(redisClusterNode.flags); } /** @@ -84,8 +95,12 @@ public RedisClusterNode(RedisClusterNode redisClusterNode) { * @return a new instance of {@link RedisClusterNode} */ public static RedisClusterNode of(String nodeId) { + + LettuceAssert.notNull(nodeId, "NodeId must not be null"); + RedisClusterNode redisClusterNode = new RedisClusterNode(); redisClusterNode.setNodeId(nodeId); + return redisClusterNode; } @@ -94,11 +109,12 @@ public RedisURI getUri() { } /** - * Sets thhe connection point details. Usually the host/ip/port where a particular Redis Cluster node server is running. + * Sets the connection point details. Usually the host/ip/port where a particular Redis Cluster node server is running. * * @param uri the {@link RedisURI}, must not be {@literal null} */ public void setUri(RedisURI uri) { + LettuceAssert.notNull(uri, "RedisURI must not be null"); this.uri = uri; } @@ -184,20 +200,52 @@ public void setConfigEpoch(long configEpoch) { } public List getSlots() { + + if (slots == null || slots.isEmpty()) { + return Collections.emptyList(); + } + + List slots = new ArrayList<>(); + + for (int i = 0; i < SlotHash.SLOT_COUNT; i++) { + + if (this.slots.get(i)) { + slots.add(i); + } + } + return slots; } /** * Sets the list of slots for which this {@link RedisClusterNode} is the - * {@link io.lettuce.core.cluster.models.partitions.RedisClusterNode.NodeFlag#MASTER}. The list is empty if this node - * is not a master or the node is not responsible for any slots at all. + * {@link io.lettuce.core.cluster.models.partitions.RedisClusterNode.NodeFlag#MASTER}. The list is empty if this node is not + * a master or the node is not responsible for any slots at all. * * @param slots list of slots, must not be {@literal null} but may be empty */ public void setSlots(List slots) { + LettuceAssert.notNull(slots, "Slots must not be null"); - this.slots = slots; + setSlotBits(slots); + } + + private void setSlotBits(List slots) { + + if (slots.isEmpty() && this.slots == null) { + return; + } + + if (this.slots == null) { + this.slots = new BitSet(SlotHash.SLOT_COUNT); + } + + this.slots.clear(); + + for (Integer slot : slots) { + this.slots.set(slot); + } } public Set getFlags() { @@ -210,7 +258,35 @@ public Set getFlags() { * @param flags the set of node flags. */ public void setFlags(Set flags) { - this.flags = flags; + + this.flags.clear(); + this.flags.addAll(flags); + } + + /** + * @param nodeFlag the node flag + * @return true if the {@linkplain NodeFlag} is contained within the flags. + */ + public boolean is(NodeFlag nodeFlag) { + return getFlags().contains(nodeFlag); + } + + /** + * @param slot the slot hash + * @return true if the slot is contained within the handled slots. + */ + public boolean hasSlot(int slot) { + return slot <= SlotHash.SLOT_COUNT && this.slots != null && this.slots.get(slot); + } + + /** + * Returns the {@link Role} of the Redis Cluster node based on the {@link #getFlags() flags}. + * + * @return the Redis Cluster node role + */ + @Override + public Role getRole() { + return is(NodeFlag.MASTER) ? Role.MASTER : Role.SLAVE; } @Override @@ -233,13 +309,12 @@ public boolean equals(Object o) { @Override public int hashCode() { - int result = 31 * (nodeId != null ? nodeId.hashCode() : 0); - return result; + return 31 * (nodeId != null ? nodeId.hashCode() : 0); } @Override public String toString() { - final StringBuilder sb = new StringBuilder(); + StringBuilder sb = new StringBuilder(); sb.append(getClass().getSimpleName()); sb.append(" [uri=").append(uri); sb.append(", nodeId='").append(nodeId).append('\''); @@ -250,46 +325,16 @@ public String toString() { sb.append(", configEpoch=").append(configEpoch); sb.append(", flags=").append(flags); if (slots != null) { - sb.append(", slot count=").append(slots.size()); + sb.append(", slot count=").append(slots.cardinality()); } sb.append(']'); return sb.toString(); } - /** - * - * @param nodeFlag the node flag - * @return true if the {@linkplain NodeFlag} is contained within the flags. - */ - public boolean is(NodeFlag nodeFlag) { - return getFlags().contains(nodeFlag); - } - - /** - * - * @param slot the slot hash - * @return true if the slot is contained within the handled slots. - */ - public boolean hasSlot(int slot) { - return getSlots().contains(slot); - } - - /** - * Returns the {@link io.lettuce.core.models.role.RedisInstance.Role} of the Redis Cluster node based on the - * {@link #getFlags() flags}. - * - * @return the Redis Cluster node role - */ - @Override - public Role getRole() { - return is(NodeFlag.MASTER) ? Role.MASTER : Role.SLAVE; - } - /** * Redis Cluster node flags. */ public enum NodeFlag { NOFLAGS, MYSELF, SLAVE, MASTER, EVENTUAL_FAIL, FAIL, HANDSHAKE, NOADDR; } - } diff --git a/src/test/java/io/lettuce/core/cluster/RedisClusterClientTest.java b/src/test/java/io/lettuce/core/cluster/RedisClusterClientTest.java index 1d3cd2c342..f78e41d799 100644 --- a/src/test/java/io/lettuce/core/cluster/RedisClusterClientTest.java +++ b/src/test/java/io/lettuce/core/cluster/RedisClusterClientTest.java @@ -24,6 +24,8 @@ import java.util.Collections; import java.util.List; import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; +import java.util.stream.IntStream; import org.junit.*; import org.junit.runners.MethodSorters; @@ -292,13 +294,9 @@ public void testClusterRedirection() throws Exception { Partitions partitions = clusterClient.getPartitions(); for (RedisClusterNode partition : partitions) { - partition.setSlots(new ArrayList<>()); + partition.setSlots(Collections.emptyList()); if (partition.getFlags().contains(RedisClusterNode.NodeFlag.MYSELF)) { - - int[] slots = createSlots(0, 16384); - for (int i = 0; i < slots.length; i++) { - partition.getSlots().add(i); - } + partition.setSlots(IntStream.range(0, SlotHash.SLOT_COUNT).boxed().collect(Collectors.toList())); } } partitions.updateCache(); @@ -335,13 +333,9 @@ public void testClusterRedirectionLimit() throws Exception { for (RedisClusterNode partition : partitions) { if (partition.getSlots().contains(15495)) { - partition.setSlots(new ArrayList<>()); + partition.setSlots(Collections.emptyList()); } else { - partition.setSlots(new ArrayList<>()); - int[] slots = createSlots(0, 16384); - for (int i = 0; i < slots.length; i++) { - partition.getSlots().add(i); - } + partition.setSlots(IntStream.range(0, SlotHash.SLOT_COUNT).boxed().collect(Collectors.toList())); } } diff --git a/src/test/java/io/lettuce/core/cluster/models/partitions/RedisClusterNodeTest.java b/src/test/java/io/lettuce/core/cluster/models/partitions/RedisClusterNodeTest.java index e395072cd8..05f121b589 100644 --- a/src/test/java/io/lettuce/core/cluster/models/partitions/RedisClusterNodeTest.java +++ b/src/test/java/io/lettuce/core/cluster/models/partitions/RedisClusterNodeTest.java @@ -17,13 +17,34 @@ import static org.assertj.core.api.Assertions.assertThat; +import java.util.Arrays; + import org.junit.Test; import io.lettuce.core.RedisURI; +import io.lettuce.core.cluster.SlotHash; +/** + * @author Mark Paluch + */ public class RedisClusterNodeTest { + @Test - public void testEquality() throws Exception { + public void shouldCopyNode() { + + RedisClusterNode node = new RedisClusterNode(); + node.setSlots(Arrays.asList(1, 2, 3, SlotHash.SLOT_COUNT - 1)); + + RedisClusterNode copy = new RedisClusterNode(node); + + assertThat(copy.getSlots()).containsExactly(1, 2, 3, SlotHash.SLOT_COUNT - 1); + assertThat(copy.hasSlot(1)).isTrue(); + assertThat(copy.hasSlot(SlotHash.SLOT_COUNT - 1)).isTrue(); + } + + @Test + public void testEquality() { + RedisClusterNode node = new RedisClusterNode(); assertThat(node).isEqualTo(new RedisClusterNode()); @@ -31,11 +52,11 @@ public void testEquality() throws Exception { node.setUri(new RedisURI()); assertThat(node.hashCode()).isNotEqualTo(new RedisClusterNode()); - } @Test - public void testToString() throws Exception { + public void testToString() { + RedisClusterNode node = new RedisClusterNode(); assertThat(node.toString()).contains(RedisClusterNode.class.getSimpleName()); diff --git a/src/test/java/io/lettuce/core/cluster/models/slots/ClusterSlotsParserTest.java b/src/test/java/io/lettuce/core/cluster/models/slots/ClusterSlotsParserTest.java index 43ab839c5d..cdb949ed8e 100644 --- a/src/test/java/io/lettuce/core/cluster/models/slots/ClusterSlotsParserTest.java +++ b/src/test/java/io/lettuce/core/cluster/models/slots/ClusterSlotsParserTest.java @@ -30,26 +30,26 @@ public class ClusterSlotsParserTest { @Test - public void testEmpty() throws Exception { + public void testEmpty() { List result = ClusterSlotsParser.parse(new ArrayList<>()); assertThat(result).isNotNull().isEmpty(); } @Test - public void testOneString() throws Exception { + public void testOneString() { List result = ClusterSlotsParser.parse(LettuceLists.newList("")); assertThat(result).isNotNull().isEmpty(); } @Test - public void testOneStringInList() throws Exception { + public void testOneStringInList() { List list = Arrays.asList(LettuceLists.newList("0")); List result = ClusterSlotsParser.parse(list); assertThat(result).isNotNull().isEmpty(); } @Test - public void testParse() throws Exception { + public void testParse() { List list = Arrays.asList(LettuceLists.newList("0", "1", LettuceLists.newList("1", "2"))); List result = ClusterSlotsParser.parse(list); assertThat(result).hasSize(1); @@ -58,7 +58,7 @@ public void testParse() throws Exception { } @Test - public void testParseWithSlave() throws Exception { + public void testParseWithSlave() { List list = Arrays.asList(LettuceLists.newList("100", "200", LettuceLists.newList("1", "2", "nodeId1"), LettuceLists.newList("1", 2, "nodeId2"))); List result = ClusterSlotsParser.parse(list); @@ -85,7 +85,7 @@ public void testParseWithSlave() throws Exception { } @Test - public void testSameNode() throws Exception { + public void testSameNode() { List list = Arrays.asList( LettuceLists.newList("100", "200", LettuceLists.newList("1", "2", "nodeId1"), LettuceLists.newList("1", 2, "nodeId2")), @@ -109,19 +109,19 @@ public void testSameNode() throws Exception { } @Test(expected = IllegalArgumentException.class) - public void testParseInvalidMaster() throws Exception { + public void testParseInvalidMaster() { List list = Arrays.asList(LettuceLists.newList("0", "1", LettuceLists.newList("1"))); ClusterSlotsParser.parse(list); } @Test(expected = IllegalArgumentException.class) - public void testParseInvalidMaster2() throws Exception { + public void testParseInvalidMaster2() { List list = Arrays.asList(LettuceLists.newList("0", "1", "")); ClusterSlotsParser.parse(list); } @Test - public void testModel() throws Exception { + public void testModel() { ClusterSlotRange range = new ClusterSlotRange(); range.setFrom(1); diff --git a/src/test/java/io/lettuce/core/cluster/topology/TopologyRefreshTest.java b/src/test/java/io/lettuce/core/cluster/topology/TopologyRefreshTest.java index 04be54e1f9..e26c11583a 100644 --- a/src/test/java/io/lettuce/core/cluster/topology/TopologyRefreshTest.java +++ b/src/test/java/io/lettuce/core/cluster/topology/TopologyRefreshTest.java @@ -17,6 +17,8 @@ import static org.assertj.core.api.Assertions.assertThat; +import java.util.Collections; +import java.util.List; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; @@ -62,22 +64,22 @@ public class TopologyRefreshTest extends AbstractTest { private RedisCommands redis2; @Before - public void openConnection() throws Exception { - clusterClient = RedisClusterClient.create(client.getResources(), - RedisURI.Builder.redis(host, AbstractClusterTest.port1).build()); + public void openConnection() { + clusterClient = RedisClusterClient.create(client.getResources(), RedisURI.Builder + .redis(host, AbstractClusterTest.port1).build()); redis1 = client.connect(RedisURI.Builder.redis(AbstractClusterTest.host, AbstractClusterTest.port1).build()).sync(); redis2 = client.connect(RedisURI.Builder.redis(AbstractClusterTest.host, AbstractClusterTest.port2).build()).sync(); } @After - public void closeConnection() throws Exception { + public void closeConnection() { redis1.getStatefulConnection().close(); redis2.getStatefulConnection().close(); FastShutdown.shutdown(clusterClient); } @Test - public void shouldUnsubscribeTopologyRefresh() throws Exception { + public void shouldUnsubscribeTopologyRefresh() { ClusterTopologyRefreshOptions topologyRefreshOptions = ClusterTopologyRefreshOptions.builder() .enablePeriodicRefresh(true) // @@ -89,8 +91,8 @@ public void shouldUnsubscribeTopologyRefresh() throws Exception { AtomicBoolean clusterTopologyRefreshActivated = (AtomicBoolean) ReflectionTestUtils.getField(clusterClient, "clusterTopologyRefreshActivated"); - AtomicReference> clusterTopologyRefreshFuture = (AtomicReference) ReflectionTestUtils - .getField(clusterClient, "clusterTopologyRefreshFuture"); + AtomicReference> clusterTopologyRefreshFuture = (AtomicReference) ReflectionTestUtils.getField( + clusterClient, "clusterTopologyRefreshFuture"); assertThat(clusterTopologyRefreshActivated.get()).isTrue(); assertThat((Future) clusterTopologyRefreshFuture.get()).isNotNull(); @@ -107,7 +109,7 @@ public void shouldUnsubscribeTopologyRefresh() throws Exception { } @Test - public void changeTopologyWhileOperations() throws Exception { + public void changeTopologyWhileOperations() { ClusterTopologyRefreshOptions topologyRefreshOptions = ClusterTopologyRefreshOptions.builder() .enablePeriodicRefresh(true)// @@ -126,7 +128,7 @@ public void changeTopologyWhileOperations() throws Exception { } @Test - public void dynamicSourcesProvidesClientCountForAllNodes() throws Exception { + public void dynamicSourcesProvidesClientCountForAllNodes() { ClusterTopologyRefreshOptions topologyRefreshOptions = ClusterTopologyRefreshOptions.create(); clusterClient.setOptions(ClusterClientOptions.builder().topologyRefreshOptions(topologyRefreshOptions).build()); @@ -143,7 +145,7 @@ public void dynamicSourcesProvidesClientCountForAllNodes() throws Exception { } @Test - public void staticSourcesProvidesClientCountForSeedNodes() throws Exception { + public void staticSourcesProvidesClientCountForSeedNodes() { ClusterTopologyRefreshOptions topologyRefreshOptions = ClusterTopologyRefreshOptions.builder() .dynamicRefreshSources(false).build(); @@ -161,7 +163,7 @@ public void staticSourcesProvidesClientCountForSeedNodes() throws Exception { } @Test - public void adaptiveTopologyUpdateOnDisconnectNodeIdConnection() throws Exception { + public void adaptiveTopologyUpdateOnDisconnectNodeIdConnection() { runReconnectTest((clusterConnection, node) -> { RedisClusterAsyncCommands connection = clusterConnection.getConnection(node.getUri().getHost(), @@ -172,7 +174,7 @@ public void adaptiveTopologyUpdateOnDisconnectNodeIdConnection() throws Exceptio } @Test - public void adaptiveTopologyUpdateOnDisconnectHostAndPortConnection() throws Exception { + public void adaptiveTopologyUpdateOnDisconnectHostAndPortConnection() { runReconnectTest((clusterConnection, node) -> { RedisClusterAsyncCommands connection = clusterConnection.getConnection(node.getUri().getHost(), @@ -183,7 +185,7 @@ public void adaptiveTopologyUpdateOnDisconnectHostAndPortConnection() throws Exc } @Test - public void adaptiveTopologyUpdateOnDisconnectDefaultConnection() throws Exception { + public void adaptiveTopologyUpdateOnDisconnectDefaultConnection() { runReconnectTest((clusterConnection, node) -> { return clusterConnection; @@ -265,7 +267,7 @@ public void adaptiveTriggerDoesNotFireOnSingleReconnect() throws Exception { } @Test - public void adaptiveTriggerOnMoveRedirection() throws Exception { + public void adaptiveTriggerOnMoveRedirection() { ClusterTopologyRefreshOptions topologyRefreshOptions = ClusterTopologyRefreshOptions.builder()// .enableAdaptiveRefreshTrigger(ClusterTopologyRefreshOptions.RefreshTrigger.MOVED_REDIRECT)// @@ -279,8 +281,10 @@ public void adaptiveTriggerOnMoveRedirection() throws Exception { RedisClusterNode node1 = partitions.getPartitionBySlot(0); RedisClusterNode node2 = partitions.getPartitionBySlot(12000); - node2.getSlots().addAll(node1.getSlots()); - node1.getSlots().clear(); + List slots = node2.getSlots(); + slots.addAll(node1.getSlots()); + node2.setSlots(slots); + node1.setSlots(Collections.emptyList()); partitions.updateCache(); assertThat(clusterClient.getPartitions().getPartitionByNodeId(node1.getNodeId()).getSlots()).hasSize(0); @@ -290,7 +294,7 @@ public void adaptiveTriggerOnMoveRedirection() throws Exception { Wait.untilEquals(12000, new Wait.Supplier() { @Override - public Integer get() throws Exception { + public Integer get() { return clusterClient.getPartitions().getPartitionByNodeId(node1.getNodeId()).getSlots().size(); } }).waitOrTimeout(); @@ -301,8 +305,7 @@ public Integer get() throws Exception { } private void runReconnectTest( - BiFunction, RedisClusterNode, BaseRedisAsyncCommands> function) - throws Exception { + BiFunction, RedisClusterNode, BaseRedisAsyncCommands> function) { ClusterTopologyRefreshOptions topologyRefreshOptions = ClusterTopologyRefreshOptions.builder()// .refreshTriggersReconnectAttempts(0)// diff --git a/src/test/jmh/io/lettuce/core/cluster/models/partitions/JmhMain.java b/src/test/jmh/io/lettuce/core/cluster/models/partitions/JmhMain.java new file mode 100644 index 0000000000..336a82b78e --- /dev/null +++ b/src/test/jmh/io/lettuce/core/cluster/models/partitions/JmhMain.java @@ -0,0 +1,54 @@ +/* + * Copyright 2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.lettuce.core.cluster.models.partitions; + +import java.util.concurrent.TimeUnit; + +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.runner.Runner; +import org.openjdk.jmh.runner.RunnerException; +import org.openjdk.jmh.runner.options.ChainedOptionsBuilder; +import org.openjdk.jmh.runner.options.OptionsBuilder; +import org.openjdk.jmh.runner.options.TimeValue; + +/** + * Manual JMH Test Launcher. + * + * @author Mark Paluch + */ +public class JmhMain { + + public static void main(String... args) throws Exception { + runClusterNodeBenchmark(); + } + + private static void runClusterNodeBenchmark() throws RunnerException { + + new Runner(prepareOptions().mode(Mode.AverageTime) // + .timeUnit(TimeUnit.NANOSECONDS) // + .include(".*RedisClusterNodeBenchmark.*") // + .build()).run(); + } + + private static ChainedOptionsBuilder prepareOptions() { + return new OptionsBuilder()// + .forks(1) // + .warmupIterations(5)// + .threads(1) // + .measurementIterations(5) // + .timeout(TimeValue.seconds(2)); + } +} diff --git a/src/test/jmh/io/lettuce/core/cluster/models/partitions/RedisClusterNodeBenchmark.java b/src/test/jmh/io/lettuce/core/cluster/models/partitions/RedisClusterNodeBenchmark.java new file mode 100644 index 0000000000..67c94f6dad --- /dev/null +++ b/src/test/jmh/io/lettuce/core/cluster/models/partitions/RedisClusterNodeBenchmark.java @@ -0,0 +1,60 @@ +/* + * Copyright 2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.lettuce.core.cluster.models.partitions; + +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.State; + +import io.lettuce.core.cluster.SlotHash; + +/** + * @author Mark Paluch + */ +@State(Scope.Benchmark) +public class RedisClusterNodeBenchmark { + + final static List ALL_SLOTS = IntStream.range(0, SlotHash.SLOT_COUNT).boxed().collect(Collectors.toList()); + final static List LOWER_SLOTS = IntStream.range(0, 8192).boxed().collect(Collectors.toList()); + + final static RedisClusterNode NODE = new RedisClusterNode(null, null, true, null, 0, 0, 0, ALL_SLOTS, + Collections.emptySet()); + + @Benchmark + public RedisClusterNode createClusterNodeAllSlots() { + return new RedisClusterNode(null, null, true, null, 0, 0, 0, ALL_SLOTS, Collections.emptySet()); + } + + @Benchmark + public RedisClusterNode createClusterNodeLowerSlots() { + return new RedisClusterNode(null, null, true, null, 0, 0, 0, LOWER_SLOTS, Collections.emptySet()); + } + + @Benchmark + public void querySlotStatusPresent() { + NODE.hasSlot(1234); + } + + @Benchmark + public void querySlotStatusAbsent() { + NODE.hasSlot(8193); + } +}