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,