You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by ag...@apache.org on 2019/01/20 10:29:51 UTC
[ignite] branch master updated: IGNITE-10877 Reduce memory
footprint and allocation pressure of affinity assignments - Fixes #5796
This is an automated email from the ASF dual-hosted git repository.
agoncharuk pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ignite.git
The following commit(s) were added to refs/heads/master by this push:
new 22c0c7e IGNITE-10877 Reduce memory footprint and allocation pressure of affinity assignments - Fixes #5796
22c0c7e is described below
commit 22c0c7ebad5265b66e0060d25cce9ce4568234a8
Author: Pavel Voronkin <pv...@gridgain.com>
AuthorDate: Sun Jan 20 13:23:56 2019 +0300
IGNITE-10877 Reduce memory footprint and allocation pressure of affinity assignments - Fixes #5796
---
modules/benchmarks/pom.xml | 6 +
.../SmallHashSetsVsReadOnlyViewBenchmark.java | 154 ++++++++++
.../jol/GridAffinityAssignmentJolBenchmark.java | 177 +++++++++++
.../ignite/internal/benchmarks/model/Node.java | 44 +++
.../org/apache/ignite/IgniteSystemProperties.java | 15 +
.../processors/affinity/AffinityAssignment.java | 34 ++-
.../affinity/GridAffinityAssignment.java | 133 ++++----
.../processors/affinity/GridAffinityUtils.java | 2 +-
.../affinity/HistoryAffinityAssignment.java | 45 +--
.../cache/CacheAffinitySharedManager.java | 2 +-
.../dht/topology/GridDhtPartitionTopologyImpl.java | 7 +-
.../apache/ignite/internal/util/BitSetIntSet.java | 184 +++++++++++
.../affinity/GridAffinityAssignmentTest.java | 217 +++++++++++++
.../GridAffinityAssignmentTestNoOptimizations.java | 41 +++
.../ignite/internal/util/BitSetIntSetTest.java | 339 +++++++++++++++++++++
.../ignite/testsuites/IgniteBasicTestSuite.java | 6 +
16 files changed, 1316 insertions(+), 90 deletions(-)
diff --git a/modules/benchmarks/pom.xml b/modules/benchmarks/pom.xml
index e5a8216..129387a 100644
--- a/modules/benchmarks/pom.xml
+++ b/modules/benchmarks/pom.xml
@@ -52,6 +52,12 @@
</dependency>
<dependency>
+ <groupId>org.openjdk.jol</groupId>
+ <artifactId>jol-core</artifactId>
+ <version>0.9</version>
+ </dependency>
+
+ <dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-core</artifactId>
<version>${jmh.version}</version>
diff --git a/modules/benchmarks/src/main/java/org/apache/ignite/internal/benchmarks/jmh/collections/SmallHashSetsVsReadOnlyViewBenchmark.java b/modules/benchmarks/src/main/java/org/apache/ignite/internal/benchmarks/jmh/collections/SmallHashSetsVsReadOnlyViewBenchmark.java
new file mode 100644
index 0000000..d130792
--- /dev/null
+++ b/modules/benchmarks/src/main/java/org/apache/ignite/internal/benchmarks/jmh/collections/SmallHashSetsVsReadOnlyViewBenchmark.java
@@ -0,0 +1,154 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.ignite.internal.benchmarks.jmh.collections;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Random;
+import java.util.UUID;
+import org.apache.ignite.internal.benchmarks.jmh.JmhAbstractBenchmark;
+import org.apache.ignite.internal.benchmarks.jmh.runner.JmhIdeBenchmarkRunner;
+import org.apache.ignite.internal.benchmarks.model.Node;
+import org.apache.ignite.internal.processors.affinity.AffinityAssignment;
+import org.apache.ignite.internal.util.typedef.F;
+import org.apache.ignite.lang.IgniteClosure;
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.BenchmarkMode;
+import org.openjdk.jmh.annotations.OutputTimeUnit;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.Setup;
+import org.openjdk.jmh.annotations.State;
+
+import static java.util.concurrent.TimeUnit.NANOSECONDS;
+import static org.openjdk.jmh.annotations.Mode.Throughput;
+
+/**
+ * Comparison of HashMap vs view on List on small sizes.
+ */
+@State(Scope.Benchmark)
+@OutputTimeUnit(NANOSECONDS)
+@BenchmarkMode(Throughput)
+public class SmallHashSetsVsReadOnlyViewBenchmark extends JmhAbstractBenchmark {
+ /** */
+ private static final int SIZE = AffinityAssignment.IGNITE_AFFINITY_BACKUPS_THRESHOLD;
+
+ /** */
+ private static final int PARTS = 8192;
+
+ /**
+ *
+ * @param args Args.
+ * @throws Exception Exception.
+ */
+ public static void main(String[] args) throws Exception {
+ JmhIdeBenchmarkRunner.create()
+ .threads(1)
+ .measurementIterations(20)
+ .benchmarks(SmallHashSetsVsReadOnlyViewBenchmark.class.getSimpleName())
+ .run();
+ }
+
+ /** */
+ private final Random random = new Random();
+
+ /** */
+ private final List<Collection<UUID>> hashSets = new ArrayList<>();
+
+ /** */
+ private final List<List<Node>> lists = new ArrayList<>();
+
+ /** */
+ private final Node[] nodes = new Node[SIZE];
+
+ /** */
+ @Setup
+ public void setup() {
+ for (int i = 0; i < SIZE; i++)
+ nodes[i] = new Node(UUID.randomUUID());
+
+ for (int i= 0; i < PARTS; i++) {
+ Collection<UUID> hashSet = new HashSet<>();
+
+ for (int j = 0; j < SIZE; j++)
+ hashSet.add(nodes[j].getUuid());
+
+ hashSets.add(hashSet);
+
+ List<Node> list = new ArrayList<>(SIZE);
+
+ for (int j = 0; j < SIZE; j++)
+ list.add(nodes[j]);
+
+ lists.add(list);
+ }
+ }
+
+ /** */
+ @Benchmark
+ public boolean hashSetContainsRandom() {
+ return hashSets.get(random.nextInt(PARTS))
+ .contains(nodes[random.nextInt(SIZE)].getUuid());
+ }
+
+ /** */
+ @Benchmark
+ public boolean readOnlyViewContainsRandom() {
+ return F.viewReadOnly(
+ lists.get(random.nextInt(PARTS)),
+ (IgniteClosure<Node, UUID>)Node::getUuid
+ ).contains(nodes[random.nextInt(SIZE)].getUuid());
+ }
+
+ /** */
+ @Benchmark
+ public boolean hashSetIteratorRandom() {
+ UUID randomUuid = nodes[random.nextInt(SIZE)].getUuid();
+
+ Collection<UUID> col = hashSets.get(random.nextInt(PARTS));
+
+ boolean contains = false;
+
+ for(UUID uuid : col)
+ if (randomUuid.equals(uuid))
+ contains = true;
+
+ return contains;
+ }
+
+ /** */
+ @Benchmark
+ public boolean readOnlyViewIteratorRandom() {
+ UUID randomUuid = nodes[random.nextInt(SIZE)].getUuid();
+
+ Collection<UUID> col = F.viewReadOnly(
+ lists.get(random.nextInt(PARTS)),
+ (IgniteClosure<Node, UUID>)Node::getUuid
+ );
+
+ boolean contains = false;
+
+ for(UUID uuid : col)
+ if (randomUuid.equals(uuid))
+ contains = true;
+
+ return contains;
+ }
+}
+
diff --git a/modules/benchmarks/src/main/java/org/apache/ignite/internal/benchmarks/jol/GridAffinityAssignmentJolBenchmark.java b/modules/benchmarks/src/main/java/org/apache/ignite/internal/benchmarks/jol/GridAffinityAssignmentJolBenchmark.java
new file mode 100644
index 0000000..266e734
--- /dev/null
+++ b/modules/benchmarks/src/main/java/org/apache/ignite/internal/benchmarks/jol/GridAffinityAssignmentJolBenchmark.java
@@ -0,0 +1,177 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.ignite.internal.benchmarks.jol;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import org.apache.ignite.cache.CacheMetrics;
+import org.apache.ignite.cache.affinity.AffinityFunctionContext;
+import org.apache.ignite.cache.affinity.rendezvous.RendezvousAffinityFunction;
+import org.apache.ignite.cluster.ClusterMetrics;
+import org.apache.ignite.cluster.ClusterNode;
+import org.apache.ignite.events.DiscoveryEvent;
+import org.apache.ignite.internal.processors.affinity.AffinityAssignment;
+import org.apache.ignite.internal.processors.affinity.AffinityTopologyVersion;
+import org.apache.ignite.internal.processors.affinity.GridAffinityAssignment;
+import org.apache.ignite.internal.processors.affinity.GridAffinityFunctionContextImpl;
+import org.apache.ignite.lang.IgniteProductVersion;
+import org.apache.ignite.spi.discovery.DiscoveryMetricsProvider;
+import org.apache.ignite.spi.discovery.tcp.internal.TcpDiscoveryNode;
+import org.openjdk.jol.info.GraphLayout;
+
+/**
+ *
+ */
+public class GridAffinityAssignmentJolBenchmark {
+ /** */
+ private static DiscoveryMetricsProvider metrics = new DiscoveryMetricsProvider() {
+ @Override public ClusterMetrics metrics() {
+ return null;
+ }
+
+ @Override public Map<Integer, CacheMetrics> cacheMetrics() {
+ return null;
+ }
+ };
+
+ /** */
+ private static IgniteProductVersion ver = new IgniteProductVersion();
+
+ /** */
+ private static Field field;
+
+ /** */
+ public static void main(String[] args) throws Exception {
+ RendezvousAffinityFunction aff = new RendezvousAffinityFunction(true, 65000);
+
+ int[] parts = new int[] {1024, 8192, 32768, 65000};
+
+ int[] nodes = new int[] {1, 16, 160, 600};
+
+ // We need to implement compressed bitsets https://issues.apache.org/jira/browse/IGNITE-4554.
+ // On 65k partitions and nodes > 700 HashSet take advantage over BitSet.
+ // After implementation need to check consumption on big clusters.
+ for (int part : parts)
+ for (int node : nodes) {
+ measure(aff, part, node, 0);
+
+ measure(aff, part, node, 3);
+
+ measure(aff, part, node, node);
+ }
+ }
+
+ /**
+ * @param disabled Disabled.
+ */
+ private static void setOptimization(boolean disabled) throws NoSuchFieldException, IllegalAccessException {
+ if (field == null) {
+ field = AffinityAssignment.class.getDeclaredField("IGNITE_DISABLE_AFFINITY_MEMORY_OPTIMIZATION");
+
+ Field modifiersField = Field.class.getDeclaredField("modifiers");
+ modifiersField.setAccessible(true);
+ modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
+
+ field.setAccessible(true);
+ }
+
+ field.set(null, disabled);
+ }
+
+ /**
+ * @param aff Aff.
+ * @param parts Parts.
+ * @param nodeCnt Node count.
+ * @param backups Backups.
+ */
+ private static void measure(
+ RendezvousAffinityFunction aff,
+ int parts,
+ int nodeCnt,
+ int backups
+ ) throws Exception {
+ List<ClusterNode> nodes = new ArrayList<>();
+
+ for (int i = 0; i < nodeCnt; i++) {
+ TcpDiscoveryNode node = new TcpDiscoveryNode(
+ UUID.randomUUID(),
+ Collections.singletonList("127.0.0.1"),
+ Collections.singletonList("127.0.0.1"),
+ 0,
+ metrics,
+ ver,
+ i
+ );
+ node.setAttributes(new HashMap<>());
+ nodes.add(node);
+ }
+
+ AffinityFunctionContext ctx = new GridAffinityFunctionContextImpl(
+ nodes,
+ new ArrayList<>(),
+ new DiscoveryEvent(),
+ new AffinityTopologyVersion(),
+ backups
+ );
+
+ List<List<ClusterNode>> assignment = aff.assignPartitions(ctx);
+
+ setOptimization(false);
+
+ GridAffinityAssignment ga = new GridAffinityAssignment(
+ new AffinityTopologyVersion(1, 0),
+ assignment,
+ new ArrayList<>()
+ );
+
+ System.gc();
+
+ long totalSize = GraphLayout.parseInstance(ga).totalSize();
+
+ System.out.println("Optimized, parts " + parts
+ +" nodeCount " + nodeCnt
+ +" backups " + backups
+ + " " + totalSize);
+
+ setOptimization(true);
+
+ GridAffinityAssignment ga2 = new GridAffinityAssignment(
+ new AffinityTopologyVersion(1, 0),
+ assignment,
+ new ArrayList<>()
+ );
+
+ System.gc();
+
+ long totalSize2 = GraphLayout.parseInstance(ga2).totalSize();
+
+ System.out.println("Deoptimized, parts " + parts
+ +" nodeCount " + nodeCnt
+ +" backups " + backups
+ + " " + totalSize2);
+
+ if (totalSize > totalSize2)
+ throw new Exception("Optimized AffinityAssignment size " + totalSize +" is more than deoptimized" + totalSize2);
+ }
+}
diff --git a/modules/benchmarks/src/main/java/org/apache/ignite/internal/benchmarks/model/Node.java b/modules/benchmarks/src/main/java/org/apache/ignite/internal/benchmarks/model/Node.java
new file mode 100644
index 0000000..fd23ede
--- /dev/null
+++ b/modules/benchmarks/src/main/java/org/apache/ignite/internal/benchmarks/model/Node.java
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.ignite.internal.benchmarks.model;
+
+import java.util.UUID;
+
+/**
+ *
+ */
+public class Node {
+ /** */
+ private UUID uuid;
+
+ /**
+ *
+ * @param uuid Uuid.
+ */
+ public Node(UUID uuid) {
+ this.uuid = uuid;
+ }
+
+ /**
+ *
+ * @return UUID.
+ */
+ public UUID getUuid() {
+ return uuid;
+ }
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/IgniteSystemProperties.java b/modules/core/src/main/java/org/apache/ignite/IgniteSystemProperties.java
index b20d8ad..6b16de5 100644
--- a/modules/core/src/main/java/org/apache/ignite/IgniteSystemProperties.java
+++ b/modules/core/src/main/java/org/apache/ignite/IgniteSystemProperties.java
@@ -1095,6 +1095,21 @@ public final class IgniteSystemProperties {
public static final String IGNITE_GLOBAL_METASTORAGE_HISTORY_MAX_BYTES = "IGNITE_GLOBAL_METASTORAGE_HISTORY_MAX_BYTES";
/**
+ * Size threshold to allocate and retain additional HashMap to improve contains()
+ * which leads to extra memory consumption.
+ */
+ public static final String IGNITE_AFFINITY_BACKUPS_THRESHOLD = "IGNITE_AFFINITY_BACKUUPS_THRESHOLD";
+
+ /**
+ * Flag to disable memory optimization:
+ * BitSets instead of HashSets to store partitions.
+ * When number of backups per partion is > IGNITE_AFFINITY_BACKUPS_THRESHOLD we use HashMap to improve contains()
+ * which leads to extra memory consumption, otherwise we use view on the
+ * list of cluster nodes to reduce memory consumption on redundant data structures.
+ */
+ public static final String IGNITE_DISABLE_AFFINITY_MEMORY_OPTIMIZATION = "IGNITE_DISABLE_AFFINITY_MEMORY_OPTIMIZATION";
+
+ /**
* Enforces singleton.
*/
private IgniteSystemProperties() {
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/affinity/AffinityAssignment.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/affinity/AffinityAssignment.java
index b603c32..d6f28f6 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/affinity/AffinityAssignment.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/affinity/AffinityAssignment.java
@@ -17,16 +17,32 @@
package org.apache.ignite.internal.processors.affinity;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;
+import org.apache.ignite.IgniteSystemProperties;
import org.apache.ignite.cluster.ClusterNode;
/**
* Cached affinity calculations.
*/
public interface AffinityAssignment {
+ /** Size threshold to use Map instead of List view. */
+ int IGNITE_AFFINITY_BACKUPS_THRESHOLD = IgniteSystemProperties.getInteger(
+ IgniteSystemProperties.IGNITE_AFFINITY_BACKUPS_THRESHOLD,
+ 5
+ );
+
+ /** Disable memory affinity optimizations. */
+ boolean IGNITE_DISABLE_AFFINITY_MEMORY_OPTIMIZATION = IgniteSystemProperties.getBoolean(
+ IgniteSystemProperties.IGNITE_DISABLE_AFFINITY_MEMORY_OPTIMIZATION,
+ false
+ );
+
/**
* @return Affinity assignment computed by affinity function.
*/
@@ -56,7 +72,7 @@ public interface AffinityAssignment {
* @param part Partition.
* @return Affinity nodes IDs.
*/
- public HashSet<UUID> getIds(int part);
+ public Collection<UUID> getIds(int part);
/**
* @return Nodes having parimary and backup assignments.
@@ -83,4 +99,18 @@ public interface AffinityAssignment {
* @return Backup partitions for specified node ID.
*/
public Set<Integer> backupPartitions(UUID nodeId);
-}
\ No newline at end of file
+
+ /**
+ * Converts List of Cluster Nodes to HashSet of UUIDs wrapped as unmodifiable collection.
+ * @param assignmentPart Source assignment per partition.
+ * @return List of deduplicated collections if ClusterNode's ids.
+ */
+ public default Collection<UUID> assignments2ids(List<ClusterNode> assignmentPart) {
+ Collection<UUID> partIds = new HashSet<>(assignmentPart.size());
+
+ for (ClusterNode node : assignmentPart)
+ partIds.add(node.id());
+
+ return Collections.unmodifiableCollection(partIds);
+ }
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/affinity/GridAffinityAssignment.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/affinity/GridAffinityAssignment.java
index 95cf76fc..b820605 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/affinity/GridAffinityAssignment.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/affinity/GridAffinityAssignment.java
@@ -19,6 +19,7 @@ package org.apache.ignite.internal.processors.affinity;
import java.io.Serializable;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
@@ -27,6 +28,8 @@ import java.util.Map;
import java.util.Set;
import java.util.UUID;
import org.apache.ignite.cluster.ClusterNode;
+import org.apache.ignite.internal.util.BitSetIntSet;
+import org.apache.ignite.internal.util.typedef.F;
import org.apache.ignite.internal.util.typedef.internal.S;
/**
@@ -50,7 +53,7 @@ public class GridAffinityAssignment implements AffinityAssignment, Serializable
private final Map<UUID, Set<Integer>> backup;
/** Assignment node IDs */
- private transient volatile List<HashSet<UUID>> assignmentIds;
+ private transient volatile List<Collection<UUID>> assignmentIds;
/** Nodes having primary or backup partition assignments. */
private transient volatile Set<ClusterNode> nodes;
@@ -68,8 +71,8 @@ public class GridAffinityAssignment implements AffinityAssignment, Serializable
*/
GridAffinityAssignment(AffinityTopologyVersion topVer) {
this.topVer = topVer;
- primary = new HashMap<>();
- backup = new HashMap<>();
+ primary = Collections.emptyMap();
+ backup = Collections.emptyMap();
}
/**
@@ -77,21 +80,48 @@ public class GridAffinityAssignment implements AffinityAssignment, Serializable
* @param assignment Assignment.
* @param idealAssignment Ideal assignment.
*/
- GridAffinityAssignment(AffinityTopologyVersion topVer,
+ public GridAffinityAssignment(AffinityTopologyVersion topVer,
List<List<ClusterNode>> assignment,
- List<List<ClusterNode>> idealAssignment) {
+ List<List<ClusterNode>> idealAssignment
+ ) {
assert topVer != null;
assert assignment != null;
assert idealAssignment != null;
this.topVer = topVer;
- this.assignment = assignment;
- this.idealAssignment = idealAssignment.equals(assignment) ? assignment : idealAssignment;
+ this.assignment = Collections.unmodifiableList(assignment);
+ this.idealAssignment = Collections.unmodifiableList(
+ idealAssignment.equals(assignment) ? assignment : idealAssignment
+ );
- primary = new HashMap<>();
- backup = new HashMap<>();
+ // Temporary mirrors with modifiable partition's collections.
+ Map<UUID, Set<Integer>> tmpPrimary = new HashMap<>();
+ Map<UUID, Set<Integer>> tmpBackup = new HashMap<>();
+ boolean isPrimary;
+
+ for (int partsCnt = assignment.size(), p = 0; p < partsCnt; p++) {
+ isPrimary = true;
+
+ for (ClusterNode node : assignment.get(p)) {
+ UUID id = node.id();
+
+ Map<UUID, Set<Integer>> tmp = isPrimary ? tmpPrimary : tmpBackup;
+
+ /*
+ https://issues.apache.org/jira/browse/IGNITE-4554 BitSet performs better than HashSet at most cases.
+ However with 65k partition and high number of nodes (700+) BitSet is loosing HashSet.
+ We need to replace it with sparse bitsets.
+ */
+ tmp.computeIfAbsent(id, uuid ->
+ !IGNITE_DISABLE_AFFINITY_MEMORY_OPTIMIZATION ? new BitSetIntSet() : new HashSet<>()
+ ).add(p);
+
+ isPrimary = false;
+ }
+ }
- initPrimaryBackupMaps();
+ primary = Collections.unmodifiableMap(tmpPrimary);
+ backup = Collections.unmodifiableMap(tmpBackup);
}
/**
@@ -108,14 +138,14 @@ public class GridAffinityAssignment implements AffinityAssignment, Serializable
}
/**
- * @return Affinity assignment computed by affinity function.
+ * @return Unmodifiable ideal affinity assignment computed by affinity function.
*/
@Override public List<List<ClusterNode>> idealAssignment() {
return idealAssignment;
}
/**
- * @return Affinity assignment.
+ * @return Unmodifiable affinity assignment.
*/
@Override public List<List<ClusterNode>> assignment() {
return assignment;
@@ -142,28 +172,39 @@ public class GridAffinityAssignment implements AffinityAssignment, Serializable
}
/**
- * Get affinity node IDs for partition.
- *
+ * Get affinity node IDs for partition as unmodifiable collection.
+ * Depending on AFFINITY_BACKUPS_THRESHOLD we returned newly allocated HashSet or view on List.
* @param part Partition.
* @return Affinity nodes IDs.
*/
- @Override public HashSet<UUID> getIds(int part) {
+ @Override public Collection<UUID> getIds(int part) {
assert part >= 0 && part < assignment.size() : "Affinity partition is out of range" +
" [part=" + part + ", partitions=" + assignment.size() + ']';
- List<HashSet<UUID>> assignmentIds0 = assignmentIds;
+ if (IGNITE_DISABLE_AFFINITY_MEMORY_OPTIMIZATION)
+ return getOrCreateAssignmentsIds(part);
+ else {
+ List<ClusterNode> nodes = assignment.get(part);
- if (assignmentIds0 == null) {
- assignmentIds0 = new ArrayList<>();
+ return nodes.size() > GridAffinityAssignment.IGNITE_AFFINITY_BACKUPS_THRESHOLD
+ ? getOrCreateAssignmentsIds(part)
+ : F.viewReadOnly(nodes, F.node2id());
+ }
+ }
- for (List<ClusterNode> assignmentPart : assignment) {
- HashSet<UUID> partIds = new HashSet<>();
+ /**
+ *
+ * @param part Partition ID.
+ * @return Collection of UUIDs.
+ */
+ private Collection<UUID> getOrCreateAssignmentsIds(int part) {
+ List<Collection<UUID>> assignmentIds0 = assignmentIds;
- for (ClusterNode node : assignmentPart)
- partIds.add(node.id());
+ if (assignmentIds0 == null) {
+ assignmentIds0 = new ArrayList<>(assignment.size());
- assignmentIds0.add(partIds);
- }
+ for (List<ClusterNode> assignmentPart : assignment)
+ assignmentIds0.add(assignments2ids(assignmentPart));
assignmentIds = assignmentIds0;
}
@@ -185,7 +226,7 @@ public class GridAffinityAssignment implements AffinityAssignment, Serializable
res.addAll(nodes);
}
- nodes = res;
+ nodes = Collections.unmodifiableSet(res);
}
return res;
@@ -205,7 +246,7 @@ public class GridAffinityAssignment implements AffinityAssignment, Serializable
res.add(nodes.get(0));
}
- primaryPartsNodes = res;
+ primaryPartsNodes = Collections.unmodifiableSet(res);
}
return res;
@@ -220,7 +261,7 @@ public class GridAffinityAssignment implements AffinityAssignment, Serializable
@Override public Set<Integer> primaryPartitions(UUID nodeId) {
Set<Integer> set = primary.get(nodeId);
- return set == null ? Collections.<Integer>emptySet() : set;
+ return set == null ? Collections.emptySet() : Collections.unmodifiableSet(set);
}
/**
@@ -232,39 +273,7 @@ public class GridAffinityAssignment implements AffinityAssignment, Serializable
@Override public Set<Integer> backupPartitions(UUID nodeId) {
Set<Integer> set = backup.get(nodeId);
- return set == null ? Collections.<Integer>emptySet() : set;
- }
-
- /**
- * Initializes primary and backup maps.
- */
- private void initPrimaryBackupMaps() {
- // Temporary mirrors with modifiable partition's collections.
- Map<UUID, Set<Integer>> tmpPrm = new HashMap<>();
- Map<UUID, Set<Integer>> tmpBkp = new HashMap<>();
-
- for (int partsCnt = assignment.size(), p = 0; p < partsCnt; p++) {
- // Use the first node as primary, other - backups.
- Map<UUID, Set<Integer>> tmp = tmpPrm;
- Map<UUID, Set<Integer>> map = primary;
-
- for (ClusterNode node : assignment.get(p)) {
- UUID id = node.id();
-
- Set<Integer> set = tmp.get(id);
-
- if (set == null) {
- tmp.put(id, set = new HashSet<>());
- map.put(id, Collections.unmodifiableSet(set));
- }
-
- set.add(p);
-
- // Use the first node as primary, other - backups.
- tmp = tmpBkp;
- map = backup;
- }
- }
+ return set == null ? Collections.emptySet() : Collections.unmodifiableSet(set);
}
/** {@inheritDoc} */
@@ -278,7 +287,7 @@ public class GridAffinityAssignment implements AffinityAssignment, Serializable
if (o == this)
return true;
- if (o == null || !(o instanceof AffinityAssignment))
+ if (!(o instanceof AffinityAssignment))
return false;
return topVer.equals(((AffinityAssignment)o).topologyVersion());
@@ -288,4 +297,4 @@ public class GridAffinityAssignment implements AffinityAssignment, Serializable
@Override public String toString() {
return S.toString(GridAffinityAssignment.class, this, super.toString());
}
-}
\ No newline at end of file
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/affinity/GridAffinityUtils.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/affinity/GridAffinityUtils.java
index abd5292..9baae93 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/affinity/GridAffinityUtils.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/affinity/GridAffinityUtils.java
@@ -204,4 +204,4 @@ class GridAffinityUtils {
topVer = (AffinityTopologyVersion)in.readObject();
}
}
-}
\ No newline at end of file
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/affinity/HistoryAffinityAssignment.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/affinity/HistoryAffinityAssignment.java
index 6bdf7f0..130d2a2 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/affinity/HistoryAffinityAssignment.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/affinity/HistoryAffinityAssignment.java
@@ -17,14 +17,16 @@
package org.apache.ignite.internal.processors.affinity;
+import java.util.Collection;
+import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import org.apache.ignite.cluster.ClusterNode;
+import org.apache.ignite.internal.util.BitSetIntSet;
import org.apache.ignite.internal.util.typedef.F;
import org.apache.ignite.internal.util.typedef.internal.S;
-import org.apache.ignite.internal.util.typedef.internal.U;
/**
*
@@ -44,19 +46,19 @@ public class HistoryAffinityAssignment implements AffinityAssignment {
* @param assign Assignment.
*/
HistoryAffinityAssignment(GridAffinityAssignment assign) {
- this.topVer = assign.topologyVersion();
- this.assignment = assign.assignment();
- this.idealAssignment = assign.idealAssignment();
+ topVer = assign.topologyVersion();
+ assignment = assign.assignment();
+ idealAssignment = assign.idealAssignment();
}
/** {@inheritDoc} */
@Override public List<List<ClusterNode>> idealAssignment() {
- return idealAssignment;
+ return Collections.unmodifiableList(idealAssignment);
}
/** {@inheritDoc} */
@Override public List<List<ClusterNode>> assignment() {
- return assignment;
+ return Collections.unmodifiableList(assignment);
}
/** {@inheritDoc} */
@@ -73,19 +75,20 @@ public class HistoryAffinityAssignment implements AffinityAssignment {
}
/** {@inheritDoc} */
- @Override public HashSet<UUID> getIds(int part) {
+ @Override public Collection<UUID> getIds(int part) {
assert part >= 0 && part < assignment.size() : "Affinity partition is out of range" +
" [part=" + part + ", partitions=" + assignment.size() + ']';
- List<ClusterNode> nodes = assignment.get(part);
+ if (IGNITE_DISABLE_AFFINITY_MEMORY_OPTIMIZATION)
+ return assignments2ids(assignment.get(part));
+ else {
+ List<ClusterNode> nodes = assignment.get(part);
- HashSet<UUID> ids = U.newHashSet(nodes.size());
-
- for (int i = 0; i < nodes.size(); i++)
- ids.add(nodes.get(i).id());
-
- return ids;
- }
+ return nodes.size() > AffinityAssignment.IGNITE_AFFINITY_BACKUPS_THRESHOLD
+ ? assignments2ids(nodes)
+ : F.viewReadOnly(nodes, F.node2id());
+ }
+ }
/** {@inheritDoc} */
@Override public Set<ClusterNode> nodes() {
@@ -98,7 +101,7 @@ public class HistoryAffinityAssignment implements AffinityAssignment {
res.addAll(nodes);
}
- return res;
+ return Collections.unmodifiableSet(res);
}
/** {@inheritDoc} */
@@ -112,12 +115,12 @@ public class HistoryAffinityAssignment implements AffinityAssignment {
res.add(nodes.get(0));
}
- return res;
+ return Collections.unmodifiableSet(res);
}
/** {@inheritDoc} */
@Override public Set<Integer> primaryPartitions(UUID nodeId) {
- Set<Integer> res = new HashSet<>();
+ Set<Integer> res = IGNITE_DISABLE_AFFINITY_MEMORY_OPTIMIZATION ? new HashSet<>() : new BitSetIntSet();
for (int p = 0; p < assignment.size(); p++) {
List<ClusterNode> nodes = assignment.get(p);
@@ -126,12 +129,12 @@ public class HistoryAffinityAssignment implements AffinityAssignment {
res.add(p);
}
- return res;
+ return Collections.unmodifiableSet(res);
}
/** {@inheritDoc} */
@Override public Set<Integer> backupPartitions(UUID nodeId) {
- Set<Integer> res = new HashSet<>();
+ Set<Integer> res = IGNITE_DISABLE_AFFINITY_MEMORY_OPTIMIZATION ? new HashSet<>() : new BitSetIntSet();
for (int p = 0; p < assignment.size(); p++) {
List<ClusterNode> nodes = assignment.get(p);
@@ -147,7 +150,7 @@ public class HistoryAffinityAssignment implements AffinityAssignment {
}
}
- return res;
+ return Collections.unmodifiableSet(res);
}
/** {@inheritDoc} */
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/CacheAffinitySharedManager.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/CacheAffinitySharedManager.java
index 2c4a640..ed0b7be 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/CacheAffinitySharedManager.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/CacheAffinitySharedManager.java
@@ -2219,7 +2219,7 @@ public class CacheAffinitySharedManager<K, V> extends GridCacheSharedManagerAdap
Map<UUID, GridDhtPartitionMap> map = new HashMap<>();
for (int p = 0; p < aff.assignment().size(); p++) {
- HashSet<UUID> ids = aff.getIds(p);
+ Collection<UUID> ids = aff.getIds(p);
for (UUID nodeId : ids) {
GridDhtPartitionMap partMap = map.get(nodeId);
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/topology/GridDhtPartitionTopologyImpl.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/topology/GridDhtPartitionTopologyImpl.java
index bb62d64..b0decae 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/topology/GridDhtPartitionTopologyImpl.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/topology/GridDhtPartitionTopologyImpl.java
@@ -1172,7 +1172,7 @@ public class GridDhtPartitionTopologyImpl implements GridDhtPartitionTopology {
Collection<UUID> diffIds = diffFromAffinity.get(p);
if (!F.isEmpty(diffIds)) {
- HashSet<UUID> affIds = affAssignment.getIds(p);
+ Collection<UUID> affIds = affAssignment.getIds(p);
for (UUID nodeId : diffIds) {
if (affIds.contains(nodeId)) {
@@ -1229,8 +1229,9 @@ public class GridDhtPartitionTopologyImpl implements GridDhtPartitionTopology {
", node2part=" + node2part + ']';
// Node IDs can be null if both, primary and backup, nodes disappear.
- List<ClusterNode> nodes = new ArrayList<>();
-
+ // Empirical size to reduce growing of ArrayList.
+ // We bear in mind that most of the time we filter OWNING partitions.
+ List<ClusterNode> nodes = new ArrayList<>(allIds.size() / 2 + 1);
for (UUID id : allIds) {
if (hasState(p, id, state, states)) {
ClusterNode n = ctx.discovery().node(id);
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/util/BitSetIntSet.java b/modules/core/src/main/java/org/apache/ignite/internal/util/BitSetIntSet.java
new file mode 100644
index 0000000..47c0198
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/util/BitSetIntSet.java
@@ -0,0 +1,184 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.ignite.internal.util;
+
+import java.util.BitSet;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+import java.util.Set;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * Set of Integers implementation based on BitSet.
+ *
+ * Implementation doesn't support negative values and null, cause we can't distinct null from 0 bit in BitSet.
+ */
+public class BitSetIntSet extends GridSerializableCollection<Integer> implements Set<Integer> {
+ /** */
+ private static final long serialVersionUID = 0L;
+
+ /** BitSet. */
+ private final BitSet bitSet;
+
+ /** Calculated size. */
+ private int size;
+
+ /** */
+ public BitSetIntSet() {
+ bitSet = new BitSet();
+ }
+
+ /**
+ *
+ * @param initCap initial capacity.
+ */
+ public BitSetIntSet(int initCap) {
+ bitSet = new BitSet(initCap);
+ }
+
+ /** {@inheritDoc} */
+ @Override public int size() {
+ return size;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean contains(Object o) {
+ if (o == null)
+ throw new UnsupportedOperationException("Null values are not supported!");
+
+ int val = (int)o;
+
+ if (val < 0)
+ throw new UnsupportedOperationException("Negative values are not supported!");
+
+ return bitSet.get(val);
+ }
+
+ /** {@inheritDoc} */
+ @NotNull @Override public Iterator<Integer> iterator() {
+ return new Iterator<Integer>() {
+ private int next = -1;
+
+ /** {@inheritDoc} */
+ @Override public boolean hasNext() {
+ int nextBit = bitSet.nextSetBit(next + 1);
+
+ if (nextBit != -1) {
+ next = nextBit;
+
+ return true;
+ }
+ else
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override public Integer next() {
+ if (next == -1)
+ throw new NoSuchElementException();
+
+ return next;
+ }
+ };
+ }
+
+ /** Unsupported operation. */
+ @Override public boolean add(Integer integer) {
+ if (integer == null || integer < 0)
+ throw new UnsupportedOperationException("Negative or null values are not supported!");
+
+ boolean alreadySet = bitSet.get(integer);
+
+ if (!alreadySet) {
+ bitSet.set(integer);
+
+ size++;
+ }
+
+ return !alreadySet;
+ }
+
+ /** Unsupported operation. */
+ @Override public boolean remove(Object o) {
+ if (o == null)
+ throw new UnsupportedOperationException("Null values are not supported!");
+
+ int val = (int)o;
+
+ if (val < 0)
+ throw new UnsupportedOperationException("Negative values are not supported!");
+
+ boolean alreadySet = bitSet.get(val);
+
+ if (alreadySet) {
+ bitSet.clear(val);
+
+ size--;
+ }
+
+ return alreadySet;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean containsAll(@NotNull Collection<?> c) {
+ for (Object o : c) {
+ if (!contains(o))
+ return false;
+ }
+
+ return true;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean addAll(@NotNull Collection<? extends Integer> c) {
+ boolean atLeastOneAdded = false;
+
+ for (Integer o : c) {
+ if (add(o))
+ atLeastOneAdded = true;
+ }
+
+ return atLeastOneAdded;
+ }
+
+ /**
+ * Unsupported operation.
+ */
+ @Override public boolean retainAll(@NotNull Collection<?> c) {
+ throw new UnsupportedOperationException();
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean removeAll(@NotNull Collection<?> c) {
+ boolean atLeastOneRemoved = false;
+
+ for (Object o : c) {
+ if (remove(o))
+ atLeastOneRemoved = true;
+ }
+
+ return atLeastOneRemoved;
+ }
+
+ /** {@inheritDoc} */
+ @Override public void clear() {
+ bitSet.clear();
+
+ size = 0;
+ }
+}
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/affinity/GridAffinityAssignmentTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/affinity/GridAffinityAssignmentTest.java
new file mode 100644
index 0000000..fdee72f
--- /dev/null
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/affinity/GridAffinityAssignmentTest.java
@@ -0,0 +1,217 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.ignite.internal.processors.affinity;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+import org.apache.ignite.cache.CacheMetrics;
+import org.apache.ignite.cache.affinity.AffinityFunctionContext;
+import org.apache.ignite.cache.affinity.rendezvous.RendezvousAffinityFunction;
+import org.apache.ignite.cluster.ClusterMetrics;
+import org.apache.ignite.cluster.ClusterNode;
+import org.apache.ignite.events.DiscoveryEvent;
+import org.apache.ignite.internal.util.BitSetIntSet;
+import org.apache.ignite.lang.IgniteProductVersion;
+import org.apache.ignite.spi.discovery.DiscoveryMetricsProvider;
+import org.apache.ignite.spi.discovery.tcp.internal.TcpDiscoveryNode;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.internal.util.reflection.Whitebox;
+
+import static junit.framework.TestCase.assertEquals;
+import static junit.framework.TestCase.assertSame;
+import static junit.framework.TestCase.assertTrue;
+import static junit.framework.TestCase.assertFalse;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.fail;
+
+/**
+ * Tests for {@link GridAffinityAssignment}.
+ */
+@RunWith(JUnit4.class)
+public class GridAffinityAssignmentTest {
+ /** */
+ protected DiscoveryMetricsProvider metrics = new DiscoveryMetricsProvider() {
+ @Override public ClusterMetrics metrics() {
+ return null;
+ }
+
+ @Override public Map<Integer, CacheMetrics> cacheMetrics() {
+ return null;
+ }
+ };
+
+ /** */
+ protected IgniteProductVersion ver = new IgniteProductVersion();
+
+ /**
+ * Test GridAffinityAssignment logic when backup threshold is not reached.
+ */
+ @Test
+ public void testPrimaryBackupPartitions() {
+ ClusterNode clusterNode1 = node(metrics, ver, "1");
+ ClusterNode clusterNode2 = node(metrics, ver, "2");
+ ClusterNode clusterNode3 = node(metrics, ver, "3");
+ ClusterNode clusterNode4 = node(metrics, ver, "4");
+ ClusterNode clusterNode5 = node(metrics, ver, "5");
+ ClusterNode clusterNode6 = node(metrics, ver, "6");
+
+ List<ClusterNode> clusterNodes = new ArrayList<ClusterNode>() {{
+ add(clusterNode1);
+ add(clusterNode2);
+ add(clusterNode3);
+ add(clusterNode4);
+ add(clusterNode5);
+ add(clusterNode6);
+ }};
+
+ GridAffinityAssignment gridAffinityAssignment = new GridAffinityAssignment(
+ new AffinityTopologyVersion(1, 0),
+ new ArrayList<List<ClusterNode>>() {{
+ add(new ArrayList<ClusterNode>() {{
+ add(clusterNode1);
+ add(clusterNode2);
+ add(clusterNode3);
+ add(clusterNode4);
+ }});
+ add(new ArrayList<ClusterNode>() {{
+ add(clusterNode1);
+ add(clusterNode2);
+ add(clusterNode3);
+ add(clusterNode4);
+ }});
+ add(new ArrayList<ClusterNode>() {{
+ add(clusterNode5);
+ add(clusterNode6);
+ }});
+ }},
+ new ArrayList<>()
+ );
+
+ List<Integer> parts = new ArrayList<Integer>() {{
+ add(0);
+ add(1);
+ }};
+
+ assertTrue(gridAffinityAssignment.primaryPartitions(clusterNode1.id()).containsAll(parts));
+ assertFalse(gridAffinityAssignment.primaryPartitions(clusterNode1.id()).contains(2));
+ assertTrue(gridAffinityAssignment.backupPartitions(clusterNode1.id()).isEmpty());
+
+ for (int i = 1; i < 4; i++) {
+ Set<Integer> primary = gridAffinityAssignment.primaryPartitions(clusterNodes.get(i).id());
+
+ assertTrue(primary.isEmpty());
+
+ Set<Integer> backup = gridAffinityAssignment.backupPartitions(clusterNodes.get(i).id());
+
+ assertTrue(backup.containsAll(parts));
+ assertFalse(backup.contains(2));
+ }
+
+ assertTrue(gridAffinityAssignment.primaryPartitions(clusterNode5.id()).contains(2));
+ assertTrue(gridAffinityAssignment.backupPartitions(clusterNode5.id()).isEmpty());
+
+ assertFalse(gridAffinityAssignment.primaryPartitions(clusterNode6.id()).contains(2));
+ assertTrue(gridAffinityAssignment.backupPartitions(clusterNode6.id()).contains(2));
+
+ assertEquals(4, gridAffinityAssignment.getIds(0).size());
+
+ for (int i = 0; i < 4; i++)
+ assertTrue(gridAffinityAssignment.getIds(0).contains(clusterNodes.get(i).id()));
+
+ if (AffinityAssignment.IGNITE_DISABLE_AFFINITY_MEMORY_OPTIMIZATION)
+ assertSame(gridAffinityAssignment.getIds(0), gridAffinityAssignment.getIds(0));
+ else
+ assertNotSame(gridAffinityAssignment.getIds(0), gridAffinityAssignment.getIds(0));
+
+ try {
+ gridAffinityAssignment.primaryPartitions(clusterNode1.id()).add(1000);
+
+ fail("Unmodifiable exception expected");
+ }
+ catch (UnsupportedOperationException ignored) {
+ // Ignored.
+ }
+
+ try {
+ gridAffinityAssignment.backupPartitions(clusterNode1.id()).add(1000);
+
+ fail("Unmodifiable exception expected");
+ }
+ catch (UnsupportedOperationException ignored) {
+ // Ignored.
+ }
+
+ Set<Integer> unwrapped = (Set<Integer>)Whitebox.getInternalState(
+ gridAffinityAssignment.primaryPartitions(clusterNode1.id()),
+ "c"
+ );
+
+ if (AffinityAssignment.IGNITE_DISABLE_AFFINITY_MEMORY_OPTIMIZATION)
+ assertTrue(unwrapped instanceof HashSet);
+ else
+ assertTrue(unwrapped instanceof BitSetIntSet);
+ }
+
+ /**
+ * Test GridAffinityAssignment logic when backup threshold is reached. Basically partitioned cache case.
+ */
+ @Test
+ public void testBackupsMoreThanThreshold() {
+ List<ClusterNode> nodes = new ArrayList<>();
+
+ for(int i = 0; i < 10; i++)
+ nodes.add(node(metrics, ver, "1" + i));
+
+ GridAffinityAssignment gridAffinityAssignment = new GridAffinityAssignment(
+ new AffinityTopologyVersion(1, 0),
+ new ArrayList<List<ClusterNode>>() {{
+ add(nodes);
+ }},
+ new ArrayList<>()
+ );
+
+ assertSame(gridAffinityAssignment.getIds(0), gridAffinityAssignment.getIds(0));
+ }
+
+ /**
+ *
+ * @param metrics Metrics.
+ * @param v Version.
+ * @param consistentId ConsistentId.
+ * @return TcpDiscoveryNode.
+ */
+ protected TcpDiscoveryNode node(DiscoveryMetricsProvider metrics, IgniteProductVersion v, String consistentId) {
+ return new TcpDiscoveryNode(
+ UUID.randomUUID(),
+ Collections.singletonList("127.0.0.1"),
+ Collections.singletonList("127.0.0.1"),
+ 0,
+ metrics,
+ v,
+ consistentId
+ );
+ }
+}
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/affinity/GridAffinityAssignmentTestNoOptimizations.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/affinity/GridAffinityAssignmentTestNoOptimizations.java
new file mode 100644
index 0000000..425a39c
--- /dev/null
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/affinity/GridAffinityAssignmentTestNoOptimizations.java
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.ignite.internal.processors.affinity;
+
+import org.apache.ignite.IgniteSystemProperties;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests for {@link GridAffinityAssignment} without IGNITE_DISABLE_AFFINITY_MEMORY_OPTIMIZATION.
+ */
+@RunWith(JUnit4.class)
+public class GridAffinityAssignmentTestNoOptimizations extends GridAffinityAssignmentTest {
+ /** */
+ @BeforeClass
+ public static void beforeTests() {
+ System.setProperty(IgniteSystemProperties.IGNITE_DISABLE_AFFINITY_MEMORY_OPTIMIZATION, "true");
+ }
+
+ @AfterClass
+ public static void afterTests() {
+ System.clearProperty(IgniteSystemProperties.IGNITE_DISABLE_AFFINITY_MEMORY_OPTIMIZATION);
+ }
+}
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/util/BitSetIntSetTest.java b/modules/core/src/test/java/org/apache/ignite/internal/util/BitSetIntSetTest.java
new file mode 100644
index 0000000..3f21975
--- /dev/null
+++ b/modules/core/src/test/java/org/apache/ignite/internal/util/BitSetIntSetTest.java
@@ -0,0 +1,339 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.ignite.internal.util;
+
+import java.util.ArrayList;
+import java.util.BitSet;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ *
+ */
+@RunWith(JUnit4.class)
+public class BitSetIntSetTest extends GridCommonAbstractTest {
+ /**
+ *
+ */
+ @Test
+ public void testSizeIsEmpty() {
+ sizeIsEmpty(0);
+ sizeIsEmpty(1024);
+ }
+
+ /**
+ *
+ */
+ private void sizeIsEmpty(int initCap) {
+ BitSetIntSet bitSetIntSet = initCap != 0 ? new BitSetIntSet(initCap) : new BitSetIntSet();
+
+ assertEquals(0, bitSetIntSet.size());
+ assertTrue(bitSetIntSet.isEmpty());
+
+ bitSetIntSet = new BitSetIntSet();
+
+ assertTrue(bitSetIntSet.add(1));
+ assertEquals(1, bitSetIntSet.size());
+ assertTrue(bitSetIntSet.add(10));
+ assertEquals(2, bitSetIntSet.size());
+ assertTrue(bitSetIntSet.add(1025));
+ assertEquals(3, bitSetIntSet.size());
+
+ assertEquals(3, bitSetIntSet.size());
+ assertFalse(bitSetIntSet.isEmpty());
+ }
+
+ /** */
+ @Test
+ public void testItetator() {
+ testIterator(0);
+ testIterator(1024);
+ }
+
+ /** */
+ private void testIterator(int initCap) {
+ BitSetIntSet bitSet = initCap != 0 ? new BitSetIntSet(initCap) : new BitSetIntSet();
+
+ assertTrue(bitSet.add(0));
+ assertTrue(bitSet.add(1));
+
+ assertTrue(bitSet.add(10));
+ assertFalse(bitSet.add(10));
+
+ assertTrue(bitSet.add(11));
+ assertTrue(bitSet.add(1025));
+
+ Iterator<Integer> iterator = bitSet.iterator();
+
+ assertTrue(iterator.hasNext());
+ assertEquals(0, (int)iterator.next());
+
+ assertTrue(iterator.hasNext());
+ assertEquals(1, (int)iterator.next());
+
+ assertTrue(iterator.hasNext());
+ assertEquals(10, (int)iterator.next());
+
+ assertTrue(iterator.hasNext());
+ assertEquals(11, (int)iterator.next());
+
+ assertTrue(iterator.hasNext());
+ assertEquals(1025, (int)iterator.next());
+
+ assertFalse(iterator.hasNext());
+
+ List<Integer> list = new ArrayList<>();
+
+ for(Integer i : bitSet)
+ list.add(i);
+
+ assertEquals(5, list.size());
+
+ assertEquals(0, (int)list.get(0));
+ assertEquals(1, (int)list.get(1));
+ assertEquals(10, (int)list.get(2));
+ assertEquals(11, (int)list.get(3));
+ assertEquals(1025, (int)list.get(4));
+
+
+ assertFalse(bitSet.remove(2));
+ assertEquals(5, bitSet.size());
+
+ assertTrue(bitSet.remove(1));
+ assertFalse(bitSet.remove(1));
+ assertEquals(4, bitSet.size());
+ assertFalse(bitSet.isEmpty());
+
+ assertTrue(bitSet.remove(10));
+ assertFalse(bitSet.remove(10));
+ assertEquals(3, bitSet.size());
+ assertFalse(bitSet.isEmpty());
+ }
+
+ /** */
+ @Test
+ public void testContains() {
+ BitSetIntSet bitSetInt = new BitSetIntSet();
+
+ bitSetInt.add(1);
+ bitSetInt.add(10);
+ bitSetInt.add(10);
+ bitSetInt.add(11);
+ bitSetInt.add(1025);
+
+
+ assertTrue(bitSetInt.contains(1));
+ assertFalse(bitSetInt.contains(2));
+ assertFalse(bitSetInt.contains(3));
+ assertFalse(bitSetInt.contains(4));
+ assertTrue(bitSetInt.contains(10));
+ assertTrue(bitSetInt.contains(11));
+ assertFalse(bitSetInt.contains(1024));
+ assertTrue(bitSetInt.contains(1025));
+ }
+
+ /**
+ *
+ */
+ @Test
+ public void testContainsAll() {
+ BitSetIntSet bitSetInt = new BitSetIntSet();
+
+ bitSetInt.add(1);
+ bitSetInt.add(10);
+ bitSetInt.add(10);
+ bitSetInt.add(11);
+ bitSetInt.add(1025);
+
+ assertTrue(bitSetInt.containsAll(new ArrayList<Integer>() {{
+ add(1);
+ add(10);
+ }}));
+
+ assertFalse(bitSetInt.containsAll(new ArrayList<Integer>() {{
+ add(1);
+ add(10);
+ add(11);
+ add(1025);
+ add(1026);
+ }}));
+
+ assertFalse(bitSetInt.containsAll(new ArrayList<Integer>() {{
+ add(1);
+ add(10);
+ add(12);
+ }}));
+ }
+
+ /**
+ *
+ */
+ @Test
+ public void testAddAllRemoveAllRetainAll() {
+ BitSetIntSet bitSetInt = new BitSetIntSet();
+
+ bitSetInt.add(1);
+ bitSetInt.add(10);
+ bitSetInt.add(10);
+ bitSetInt.add(11);
+ bitSetInt.add(1025);
+
+ assertFalse(bitSetInt.addAll(new ArrayList<Integer>() {{
+ add(1);
+ add(10);
+ }}));
+
+ assertEquals(4, bitSetInt.size());
+
+ assertTrue(bitSetInt.addAll(new ArrayList<Integer>() {{
+ add(1);
+ add(10);
+ add(11);
+ add(1025);
+ add(1026);
+ }}));
+
+ assertEquals(5, bitSetInt.size());
+
+ try {
+ bitSetInt.retainAll(new ArrayList<Integer>() {{
+ add(10);
+ add(1025);
+ }});
+
+ fail("retainAll is not supported");
+ }
+ catch (UnsupportedOperationException ignored) {
+ // Ignored.
+ }
+ }
+
+ /**
+ *
+ */
+ @Test
+ public void testToArray() {
+ BitSetIntSet bitSetInt = new BitSetIntSet();
+
+ assertEquals(0, bitSetInt.toArray().length);
+
+ bitSetInt.add(1);
+ bitSetInt.add(10);
+ bitSetInt.add(10);
+ bitSetInt.add(11);
+ bitSetInt.add(1025);
+
+ Object[] arr = bitSetInt.toArray();
+
+ assertEquals(4, arr.length);
+
+ assertEquals(1, (int)arr[0]);
+ assertEquals(10, (int)arr[1]);
+ assertEquals(11, (int)arr[2]);
+ assertEquals(1025, (int)arr[3]);
+
+ Integer[] input = new Integer[1];
+
+ Integer[] output = bitSetInt.toArray(input);
+
+ assertNotSame(input, output);
+
+ assertEquals(4, arr.length);
+
+ assertEquals(1, arr[0]);
+ assertEquals(10, arr[1]);
+ assertEquals(11, arr[2]);
+ assertEquals(1025, arr[3]);
+
+ input = new Integer[6];
+
+ output = bitSetInt.toArray(input);
+
+ assertSame(input, output);
+
+ assertEquals(6, output.length);
+
+ assertEquals(1, (int)output[0]);
+ assertEquals(10, (int)output[1]);
+ assertEquals(11, (int)output[2]);
+ assertEquals(1025, (int)output[3]);
+ assertNull(output[4]);
+ assertNull(output[5]);
+ }
+
+ /**
+ *
+ */
+ @Test
+ public void testInvalidValues() {
+ BitSetIntSet bitSetInt = new BitSetIntSet();
+
+ try {
+ bitSetInt.add(null);
+ fail("add should fail here");
+ }
+ catch (UnsupportedOperationException ignored) {
+ // Ignored.
+ }
+
+ try {
+ bitSetInt.add(-1);
+ fail("add should fail here");
+ }
+ catch (UnsupportedOperationException ignored) {
+ // Ignored.
+ }
+
+ try {
+ bitSetInt.contains(null);
+ fail("contains should fail here");
+ }
+ catch (UnsupportedOperationException ignored) {
+ // Ignored.
+ }
+
+ try {
+ bitSetInt.contains(-1);
+ fail("contains should fail here");
+ }
+ catch (UnsupportedOperationException ignored) {
+ // Ignored.
+ }
+
+ try {
+ bitSetInt.remove(null);
+ fail("remove should fail here");
+ }
+ catch (UnsupportedOperationException ignored) {
+ // Ignored.
+ }
+
+ try {
+ bitSetInt.remove(-1);
+ fail("remove should fail here");
+ }
+ catch (UnsupportedOperationException ignored) {
+ // Ignored.
+ }
+ }
+}
diff --git a/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteBasicTestSuite.java b/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteBasicTestSuite.java
index d6ab28d..b55f140 100644
--- a/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteBasicTestSuite.java
+++ b/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteBasicTestSuite.java
@@ -42,6 +42,8 @@ import org.apache.ignite.internal.MarshallerContextLockingSelfTest;
import org.apache.ignite.internal.TransactionsMXBeanImplTest;
import org.apache.ignite.internal.managers.IgniteDiagnosticMessagesMultipleConnectionsTest;
import org.apache.ignite.internal.managers.IgniteDiagnosticMessagesTest;
+import org.apache.ignite.internal.processors.affinity.GridAffinityAssignmentTest;
+import org.apache.ignite.internal.processors.affinity.GridAffinityAssignmentTestNoOptimizations;
import org.apache.ignite.internal.processors.affinity.GridAffinityProcessorMemoryLeakTest;
import org.apache.ignite.internal.processors.affinity.GridAffinityProcessorRendezvousSelfTest;
import org.apache.ignite.internal.processors.cache.CacheLocalGetSerializationTest;
@@ -72,6 +74,7 @@ import org.apache.ignite.internal.processors.database.SwapPathConstructionSelfTe
import org.apache.ignite.internal.processors.odbc.OdbcConfigurationValidationSelfTest;
import org.apache.ignite.internal.processors.odbc.OdbcEscapeSequenceSelfTest;
import org.apache.ignite.internal.product.GridProductVersionSelfTest;
+import org.apache.ignite.internal.util.BitSetIntSetTest;
import org.apache.ignite.internal.util.GridCleanerTest;
import org.apache.ignite.internal.util.nio.IgniteExceptionInNioWorkerSelfTest;
import org.apache.ignite.marshaller.DynamicProxySerializationMultiJvmSelfTest;
@@ -120,6 +123,8 @@ import org.junit.runners.Suite;
GridReleaseTypeSelfTest.class,
GridProductVersionSelfTest.class,
+ GridAffinityAssignmentTest.class,
+ GridAffinityAssignmentTestNoOptimizations.class,
GridAffinityProcessorRendezvousSelfTest.class,
GridAffinityProcessorMemoryLeakTest.class,
GridClosureProcessorSelfTest.class,
@@ -174,6 +179,7 @@ import org.junit.runners.Suite;
CacheFreeListImplSelfTest.class,
DataRegionMetricsSelfTest.class,
SwapPathConstructionSelfTest.class,
+ BitSetIntSetTest.class,
IgniteMarshallerCacheFSRestoreTest.class,
IgniteMarshallerCacheClassNameConflictTest.class,