You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by dg...@apache.org on 2019/02/11 09:03:49 UTC
[ignite] branch master updated: IGNITE-10920 Optimize
HistoryAffinityAssignment heap usage - Fixes #5892.
This is an automated email from the ASF dual-hosted git repository.
dgovorukhin 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 0acfa84 IGNITE-10920 Optimize HistoryAffinityAssignment heap usage - Fixes #5892.
0acfa84 is described below
commit 0acfa84fd426a9f0f9b15d11b13319ceb7b3139e
Author: kbolyandra <kb...@gmail.com>
AuthorDate: Mon Feb 11 12:02:57 2019 +0300
IGNITE-10920 Optimize HistoryAffinityAssignment heap usage - Fixes #5892.
Signed-off-by: Dmitriy Govorukhin <dm...@gmail.com>
---
.../jol/GridAffinityAssignmentJolBenchmark.java | 188 +++++++++++++++++++--
.../affinity/GridAffinityAssignmentCache.java | 4 +-
.../affinity/GridAffinityAssignmentV2.java | 8 +-
.../affinity/HistoryAffinityAssignment.java | 176 ++++++++++++++++++-
.../GridHistoryAffinityAssignmentTest.java | 138 +++++++++++++++
...istoryAffinityAssignmentTestNoOptimization.java | 42 +++++
.../ignite/testsuites/IgniteBasicTestSuite.java | 4 +
7 files changed, 537 insertions(+), 23 deletions(-)
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
index dc63141..b89cbe6 100644
--- 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
@@ -21,10 +21,10 @@ 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 java.util.concurrent.ConcurrentSkipListMap;
import org.apache.ignite.cache.CacheMetrics;
import org.apache.ignite.cache.affinity.AffinityFunctionContext;
import org.apache.ignite.cache.affinity.rendezvous.RendezvousAffinityFunction;
@@ -35,6 +35,7 @@ import org.apache.ignite.internal.processors.affinity.AffinityAssignment;
import org.apache.ignite.internal.processors.affinity.AffinityTopologyVersion;
import org.apache.ignite.internal.processors.affinity.GridAffinityAssignmentV2;
import org.apache.ignite.internal.processors.affinity.GridAffinityFunctionContextImpl;
+import org.apache.ignite.internal.processors.affinity.HistoryAffinityAssignment;
import org.apache.ignite.lang.IgniteProductVersion;
import org.apache.ignite.spi.discovery.DiscoveryMetricsProvider;
import org.apache.ignite.spi.discovery.tcp.internal.TcpDiscoveryNode;
@@ -80,6 +81,19 @@ public class GridAffinityAssignmentJolBenchmark {
measure(aff, part, node, node);
}
+
+ // Measure history assignment for normal and huge partition count.
+ // Nodes count doesn't affect heap occupation.
+ // Best result is achieved when running one measure at a time with large enough size of new region
+ // (to avoid object relocation).
+ measureHistory(1024, 32, 0);
+ measureHistory(1024, 32, 1);
+ measureHistory(1024, 32, 2);
+ measureHistory(1024, 32, Integer.MAX_VALUE);
+ measureHistory(32768, 32, 0);
+ measureHistory(32768, 32, 1);
+ measureHistory(32768, 32, 2);
+ measureHistory(32768, 32, Integer.MAX_VALUE);
}
/**
@@ -114,16 +128,7 @@ public class GridAffinityAssignmentJolBenchmark {
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<>());
+ ClusterNode node = node(i);
nodes.add(node);
}
@@ -172,6 +177,165 @@ public class GridAffinityAssignmentJolBenchmark {
+ " " + totalSize2);
if (totalSize > totalSize2)
- throw new Exception("Optimized AffinityAssignment size " + totalSize +" is more than deoptimized" + totalSize2);
+ throw new Exception("Optimized AffinityAssignment size " + totalSize + " is more than deoptimized " + totalSize2);
+ }
+
+ /**
+ * @param parts Parts.
+ * @param nodes Nodes.
+ */
+ private static void measureHistory(int parts, int nodes, int backups) throws Exception {
+ System.gc();
+
+ long deopt = measureHistory0(parts, nodes, true, backups);
+
+ System.gc();
+
+ long opt = measureHistory0(parts, nodes, false, backups);
+
+ if (opt > deopt)
+ throw new Exception("Optimized HistoryAffinityAssignment size " + opt + " is more than deoptimized " + deopt);
+
+ float rate = deopt / (float)opt;
+
+ System.out.println("Optimization: optimized=" + opt + ", deoptimized=" + deopt + " rate: " + ((int)(rate * 1000)) / 1000. );
+ }
+
+ /**
+ * @param parts Parts.
+ * @param nodeCnt Node count.
+ * @param disableOptimization Disable optimization.
+ */
+ private static long measureHistory0(int parts, int nodeCnt, boolean disableOptimization, int backups) throws Exception {
+ System.gc();
+
+ setOptimization(disableOptimization);
+
+ RendezvousAffinityFunction aff = new RendezvousAffinityFunction(true, parts);
+
+ List<ClusterNode> nodes = new ArrayList<>(nodeCnt);
+
+ nodes.add(node(0));
+
+ Map<AffinityTopologyVersion, HistoryAffinityAssignment> affCache = new ConcurrentSkipListMap<>();
+
+ List<List<ClusterNode>> prevAssignment = new ArrayList<>();
+
+ prevAssignment = aff.assignPartitions(context(new ArrayList<>(nodes), prevAssignment, 1));
+
+ for (int i = 1; i < nodeCnt; i++) {
+ ClusterNode newNode = node(i);
+
+ nodes.add(newNode);
+
+ List<List<ClusterNode>> idealAssignment = aff.assignPartitions(context(new ArrayList<>(nodes), prevAssignment, backups));
+
+ List<List<ClusterNode>> lateAssignmemnt = new ArrayList<>(parts);
+
+ for (int j = 0; j < idealAssignment.size(); j++) {
+ List<ClusterNode> ideal0 = idealAssignment.get(j);
+ List<ClusterNode> prev = prevAssignment.get(j);
+
+ ClusterNode curPrimary = prev.get(0);
+
+ if (!curPrimary.equals(ideal0.get(0))) {
+ List<ClusterNode> cpy = new ArrayList<>(ideal0);
+
+ cpy.remove(curPrimary);
+ cpy.add(0, curPrimary);
+
+ lateAssignmemnt.add(cpy);
+ }
+ else
+ lateAssignmemnt.add(ideal0);
+ }
+
+ AffinityTopologyVersion topVer = new AffinityTopologyVersion(i + 1, 0);
+ GridAffinityAssignmentV2 a = new GridAffinityAssignmentV2(topVer, lateAssignmemnt, idealAssignment);
+ HistoryAffinityAssignment h = new HistoryAffinityAssignment(a, backups);
+
+ if (!lateAssignmemnt.equals(h.assignment()))
+ throw new RuntimeException();
+
+ if (!idealAssignment.equals(h.idealAssignment()))
+ throw new RuntimeException();
+
+ affCache.put(topVer, h);
+
+ AffinityTopologyVersion topVer0 = new AffinityTopologyVersion(i + 1, 1);
+
+ List<List<ClusterNode>> assignment = new ArrayList<>(parts);
+
+ for (int j = 0; j < idealAssignment.size(); j++) {
+ List<ClusterNode> clusterNodes = idealAssignment.get(j);
+
+ assignment.add(clusterNodes);
+ }
+
+ GridAffinityAssignmentV2 a0 = new GridAffinityAssignmentV2(topVer0, assignment, idealAssignment);
+ HistoryAffinityAssignment h0 = new HistoryAffinityAssignment(a0, backups);
+
+ if (!assignment.equals(h0.assignment()))
+ throw new RuntimeException();
+
+ if (!idealAssignment.equals(h0.idealAssignment()))
+ throw new RuntimeException();
+
+ affCache.put(topVer0, h0);
+
+ prevAssignment = idealAssignment;
+ }
+
+ System.gc();
+
+ GraphLayout l = GraphLayout.parseInstance(affCache);
+
+ // Exclude nodes from estimation.
+ GraphLayout l2 = GraphLayout.parseInstance(nodes.toArray(new Object[nodes.size()]));
+
+ GraphLayout l3 = l.subtract(l2);
+
+ System.out.println("Heap usage [optimized=" + !disableOptimization + ", parts=" + parts
+ + ", nodeCnt=" + nodeCnt
+ + ", backups=" + backups
+ + ", " + l3.toFootprint() + ']');
+
+ return l3.totalSize();
+ }
+
+ /**
+ * @param nodes Nodes.
+ * @param prevAssignment Prev assignment.
+ * @param backups Backups.
+ */
+ private static AffinityFunctionContext context(
+ List<ClusterNode> nodes,
+ List<List<ClusterNode>> prevAssignment,
+ int backups) {
+ return new GridAffinityFunctionContextImpl(
+ nodes,
+ prevAssignment,
+ new DiscoveryEvent(),
+ new AffinityTopologyVersion(),
+ backups
+ );
+ }
+
+ /**
+ * @return New test node.
+ */
+ private static ClusterNode node(int idx) {
+ TcpDiscoveryNode node = new TcpDiscoveryNode(
+ UUID.randomUUID(),
+ Collections.singletonList("127.0.0.1"),
+ Collections.singletonList("127.0.0.1"),
+ 0,
+ metrics,
+ ver,
+ "Node_" + idx
+ );
+ node.setAttributes(Collections.emptyMap());
+
+ return node;
}
}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/affinity/GridAffinityAssignmentCache.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/affinity/GridAffinityAssignmentCache.java
index f3a7357..0335552 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/affinity/GridAffinityAssignmentCache.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/affinity/GridAffinityAssignmentCache.java
@@ -207,7 +207,7 @@ public class GridAffinityAssignmentCache {
GridAffinityAssignmentV2 assignment = new GridAffinityAssignmentV2(topVer, affAssignment, idealAssignment);
- HistoryAffinityAssignment hAff = affCache.put(topVer, new HistoryAffinityAssignment(assignment));
+ HistoryAffinityAssignment hAff = affCache.put(topVer, new HistoryAffinityAssignment(assignment, backups));
head.set(assignment);
@@ -491,7 +491,7 @@ public class GridAffinityAssignmentCache {
GridAffinityAssignmentV2 assignmentCpy = new GridAffinityAssignmentV2(topVer, aff);
- HistoryAffinityAssignment hAff = affCache.put(topVer, new HistoryAffinityAssignment(assignmentCpy));
+ HistoryAffinityAssignment hAff = affCache.put(topVer, new HistoryAffinityAssignment(assignmentCpy, backups));
head.set(assignmentCpy);
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/affinity/GridAffinityAssignmentV2.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/affinity/GridAffinityAssignmentV2.java
index 4f03676..4a8f9a4 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/affinity/GridAffinityAssignmentV2.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/affinity/GridAffinityAssignmentV2.java
@@ -101,10 +101,10 @@ public class GridAffinityAssignmentV2 extends IgniteDataTransferObject implement
assert idealAssignment != null;
this.topVer = topVer;
- this.assignment = Collections.unmodifiableList(assignment);
- this.idealAssignment = Collections.unmodifiableList(
- idealAssignment.equals(assignment) ? assignment : idealAssignment
- );
+ this.assignment = Collections.unmodifiableList(assignment); // It's important to keep equal references.
+ this.idealAssignment =
+ idealAssignment.equals(assignment) ? this.assignment : Collections.unmodifiableList(idealAssignment);
+
// Temporary mirrors with modifiable partition's collections.
Map<UUID, Set<Integer>> tmpPrimary = new HashMap<>();
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 e8280cf..c6c0783 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,10 +17,15 @@
package org.apache.ignite.internal.processors.affinity;
+import java.util.AbstractList;
+import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collection;
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.cluster.ClusterNode;
@@ -42,23 +47,184 @@ public class HistoryAffinityAssignment implements AffinityAssignment {
/** */
private final List<List<ClusterNode>> idealAssignment;
+ /** */
+ private final ClusterNode[] nodes;
+
+ /** Ideal assignments are stored as sequences of indexes in nodes array. */
+ private final char[] idealParts;
+
+ /** Diff with ideal. */
+ private final Map<Integer, char[]> assignmentDiff;
+
/**
* @param assign Assignment.
+ * @param backups Backups.
*/
- HistoryAffinityAssignment(AffinityAssignment assign) {
+ public HistoryAffinityAssignment(AffinityAssignment assign, int backups) {
topVer = assign.topologyVersion();
- assignment = assign.assignment();
- idealAssignment = assign.idealAssignment();
+
+ if (IGNITE_DISABLE_AFFINITY_MEMORY_OPTIMIZATION || backups > IGNITE_AFFINITY_BACKUPS_THRESHOLD) {
+ assignment = assign.assignment();
+
+ idealAssignment = assign.idealAssignment();
+
+ nodes = null;
+
+ idealParts = null;
+
+ assignmentDiff = null;
+
+ return;
+ }
+
+ List<List<ClusterNode>> assignment = assign.assignment();
+ List<List<ClusterNode>> idealAssignment = assign.idealAssignment();
+
+ int min = Integer.MAX_VALUE;
+ int max = 0;
+
+ for (List<ClusterNode> nodes : idealAssignment) { // Estimate required size.
+ int size = nodes.size();
+
+ if (size > max)
+ max = size;
+
+ if (size < min)
+ min = size;
+ }
+
+ if (max != min) {
+ this.assignment = assign.assignment();
+
+ this.idealAssignment = assign.idealAssignment();
+
+ nodes = null;
+
+ idealParts = null;
+
+ assignmentDiff = null;
+
+ return;
+ }
+
+ int cpys = max;
+
+ boolean same = assignment == idealAssignment;
+
+ int partsCnt = assignment.size();
+
+ idealParts = new char[partsCnt * cpys];
+
+ Map<ClusterNode, Character> orderMap = new HashMap<>();
+
+ char order = 1; // Char type is used as unsigned short to avoid conversions.
+
+ assignmentDiff = new HashMap<>();
+
+ for (int p = 0; p < assignment.size(); p++) {
+ List<ClusterNode> curr = assignment.get(p);
+ List<ClusterNode> ideal = idealAssignment.get(p);
+
+ for (int i = 0; i < ideal.size(); i++) {
+ ClusterNode node = ideal.get(i);
+
+ Character nodeOrder = orderMap.get(node);
+
+ if (nodeOrder == null)
+ orderMap.put(node, (nodeOrder = order++));
+
+ idealParts[p * cpys + i] = nodeOrder;
+ }
+
+ if (!same && !curr.equals(ideal)) {
+ char[] idx = new char[curr.size()];
+
+ assignmentDiff.put(p, idx);
+
+ for (int i = 0; i < curr.size(); i++) {
+ ClusterNode node = curr.get(i);
+
+ Character nodeOrder = orderMap.get(node);
+
+ if (nodeOrder == null)
+ orderMap.put(node, (nodeOrder = order++));
+
+ idx[i] = nodeOrder;
+ }
+ }
+ }
+
+ // Fill array according to assigned order.
+ nodes = orderMap.keySet().stream().toArray(ClusterNode[]::new);
+
+ Arrays.sort(nodes, (o1, o2) -> orderMap.get(o1).compareTo(orderMap.get(o2)));
+
+ this.idealAssignment = new AbstractList<List<ClusterNode>>() {
+ @Override public List<ClusterNode> get(int idx) {
+ return partitionNodes(idx, true, cpys);
+ }
+
+ @Override public int size() {
+ return partsCnt;
+ }
+ };
+
+ this.assignment = same ? this.idealAssignment : new AbstractList<List<ClusterNode>>() {
+ @Override public List<ClusterNode> get(int idx) {
+ return partitionNodes(idx, false, cpys);
+ }
+
+ @Override public int size() {
+ return partsCnt;
+ }
+ };
+
+ assert this.assignment.equals(assign.assignment()) : "new=" + this.assignment + ", old=" + assign.assignment();
+
+ assert this.idealAssignment.equals(assign.idealAssignment()) :
+ "new=" + this.idealAssignment + ", old=" + assign.idealAssignment();
+ }
+
+ /**
+ * @param p Partion.
+ * @param ideal {@code True} for ideal assignment.
+ * @param cpys Copies.
+ */
+ private List<ClusterNode> partitionNodes(int p, boolean ideal, int cpys) {
+ char[] order;
+
+ if (!ideal && (order = assignmentDiff.get(p)) != null) {
+ List<ClusterNode> ret = new ArrayList<>(order.length);
+
+ for (int i = 0; i < order.length; i++)
+ ret.add(nodes[order[i] - 1]);
+
+ return ret;
+ }
+
+ List<ClusterNode> ret = new ArrayList<>(cpys);
+
+ for (int i = 0; i < cpys; i++) {
+ char ord = idealParts[p * cpys + i];
+
+ if (ord == 0) // Zero
+ break;
+
+ ret.add(nodes[ord - 1]);
+ }
+
+ return ret;
}
/** {@inheritDoc} */
+ @SuppressWarnings("unchecked")
@Override public List<List<ClusterNode>> idealAssignment() {
- return Collections.unmodifiableList(idealAssignment);
+ return idealAssignment;
}
/** {@inheritDoc} */
@Override public List<List<ClusterNode>> assignment() {
- return Collections.unmodifiableList(assignment);
+ return assignment;
}
/** {@inheritDoc} */
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/affinity/GridHistoryAffinityAssignmentTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/affinity/GridHistoryAffinityAssignmentTest.java
new file mode 100644
index 0000000..c282932
--- /dev/null
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/affinity/GridHistoryAffinityAssignmentTest.java
@@ -0,0 +1,138 @@
+/*
+ * 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.Arrays;
+import java.util.List;
+import java.util.UUID;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import org.apache.ignite.cluster.ClusterNode;
+import org.apache.ignite.configuration.CacheConfiguration;
+import org.apache.ignite.configuration.IgniteConfiguration;
+import org.apache.ignite.internal.IgniteEx;
+import org.apache.ignite.internal.processors.cache.GridCacheContext;
+import org.apache.ignite.internal.processors.cache.GridCacheProcessor;
+import org.apache.ignite.internal.util.typedef.internal.CU;
+import org.apache.ignite.testframework.GridTestNode;
+import org.apache.ignite.testframework.GridTestUtils;
+import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests affinity history assignment diff calculation for history assignment.
+ */
+@RunWith(JUnit4.class)
+public class GridHistoryAffinityAssignmentTest extends GridCommonAbstractTest {
+ /** {@inheritDoc} */
+ @Override protected IgniteConfiguration getConfiguration(String igniteInstanceName) throws Exception {
+ IgniteConfiguration cfg = super.getConfiguration(igniteInstanceName);
+
+ cfg.setCacheConfiguration(new CacheConfiguration(DEFAULT_CACHE_NAME));
+
+ return cfg;
+ }
+
+ /** */
+ @Test
+ public void testSimple() {
+ int cnt = 128;
+
+ List<List<ClusterNode>> curr = new ArrayList<>();
+ List<List<ClusterNode>> ideal = new ArrayList<>();
+
+ for(int i = 0; i < cnt; i++) {
+ List<ClusterNode> nodes = Arrays.asList(new GridTestNode(UUID.randomUUID()), new GridTestNode(UUID.randomUUID()));
+ curr.add(nodes);
+ ideal.add(Arrays.asList(nodes.get(1), nodes.get(0)));
+ }
+
+ AffinityTopologyVersion topVer = new AffinityTopologyVersion(1, 0);
+ HistoryAffinityAssignment lateAssign =
+ new HistoryAffinityAssignment(new GridAffinityAssignmentV2(topVer, curr, ideal), 1);
+
+ assertEquals("Late", curr, lateAssign.assignment());
+ assertEquals("Ideal late", ideal, lateAssign.idealAssignment());
+
+ HistoryAffinityAssignment idealAssign = new
+ HistoryAffinityAssignment(new GridAffinityAssignmentV2(topVer, ideal, ideal), 1);
+
+ assertSame(idealAssign.assignment(), idealAssign.idealAssignment());
+
+ assertEquals("Ideal", ideal, idealAssign.idealAssignment());
+ }
+
+ /** */
+ @Test
+ public void testHistoryAffinityAssignmentCalculation() throws Exception {
+ try {
+ IgniteEx grid0 = startGrid(0);
+
+ AffinityAssignment a0 = affinityCache(grid0).cachedAffinity(new AffinityTopologyVersion(1, 0));
+
+ startGrid(1);
+
+ awaitPartitionMapExchange();
+
+ AffinityAssignment a1 = affinityCache(grid0).cachedAffinity(new AffinityTopologyVersion(1, 0));
+
+ assertTrue(a1 instanceof HistoryAffinityAssignment);
+
+ AffinityAssignment a2 = affinityCache(grid0).cachedAffinity(new AffinityTopologyVersion(2, 0));
+ AffinityAssignment a3 = affinityCache(grid0).cachedAffinity(new AffinityTopologyVersion(2, 1));
+
+ // Compare head with history assignment.
+ assertEquals(a0.assignment(), a1.assignment());
+ assertEquals(a0.idealAssignment(), a1.idealAssignment());
+
+ startGrid(2);
+
+ awaitPartitionMapExchange();
+
+ AffinityAssignment a5 = affinityCache(grid0).cachedAffinity(new AffinityTopologyVersion(2, 0));
+ AffinityAssignment a6 = affinityCache(grid0).cachedAffinity(new AffinityTopologyVersion(2, 1));
+
+ assertTrue(a5 instanceof HistoryAffinityAssignment);
+ assertTrue(a6 instanceof HistoryAffinityAssignment);
+
+ assertEquals(a2.assignment(), a5.assignment());
+ assertEquals(a2.idealAssignment(), a5.idealAssignment());
+
+ assertEquals(a3.assignment(), a6.assignment());
+ assertEquals(a3.idealAssignment(), a6.idealAssignment());
+ }
+ finally {
+ stopAllGrids();
+ }
+ }
+
+ /**
+ * @param ignite Ignite.
+ */
+ private GridAffinityAssignmentCache affinityCache(IgniteEx ignite) {
+ GridCacheProcessor proc = ignite.context().cache();
+
+ GridCacheContext cctx = proc.context().cacheContext(CU.cacheId(DEFAULT_CACHE_NAME));
+
+ return GridTestUtils.getFieldValue(cctx.affinity(), "aff");
+ }
+}
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/affinity/GridHistoryAffinityAssignmentTestNoOptimization.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/affinity/GridHistoryAffinityAssignmentTestNoOptimization.java
new file mode 100644
index 0000000..d1822c4
--- /dev/null
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/affinity/GridHistoryAffinityAssignmentTestNoOptimization.java
@@ -0,0 +1,42 @@
+/*
+ * 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 affinity history assignment diff calculation for history assignment without optimization.
+ */
+@RunWith(JUnit4.class)
+public class GridHistoryAffinityAssignmentTestNoOptimization extends GridHistoryAffinityAssignmentTest {
+ /** */
+ @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/testsuites/IgniteBasicTestSuite.java b/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteBasicTestSuite.java
index 2a4df56..cc1a019 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
@@ -47,6 +47,8 @@ import org.apache.ignite.internal.processors.affinity.GridAffinityAssignmentV2Te
import org.apache.ignite.internal.processors.affinity.GridAffinityAssignmentV2TestNoOptimizations;
import org.apache.ignite.internal.processors.affinity.GridAffinityProcessorMemoryLeakTest;
import org.apache.ignite.internal.processors.affinity.GridAffinityProcessorRendezvousSelfTest;
+import org.apache.ignite.internal.processors.affinity.GridHistoryAffinityAssignmentTest;
+import org.apache.ignite.internal.processors.affinity.GridHistoryAffinityAssignmentTestNoOptimization;
import org.apache.ignite.internal.processors.cache.CacheLocalGetSerializationTest;
import org.apache.ignite.internal.processors.cache.CacheRebalanceConfigValidationTest;
import org.apache.ignite.internal.processors.cache.GridLocalIgniteSerializationTest;
@@ -128,6 +130,8 @@ import org.junit.runners.Suite;
GridProductVersionSelfTest.class,
GridAffinityAssignmentV2Test.class,
GridAffinityAssignmentV2TestNoOptimizations.class,
+ GridHistoryAffinityAssignmentTest.class,
+ GridHistoryAffinityAssignmentTestNoOptimization.class,
GridAffinityProcessorRendezvousSelfTest.class,
GridAffinityProcessorMemoryLeakTest.class,
GridClosureProcessorSelfTest.class,