You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@ignite.apache.org by GitBox <gi...@apache.org> on 2019/01/09 14:57:21 UTC

[GitHub] AMashenkov closed pull request #4627: IGNITE-7259: tests fixed.

AMashenkov closed pull request #4627: IGNITE-7259: tests fixed.
URL: https://github.com/apache/ignite/pull/4627
 
 
   

This is a PR merged from a forked repository.
As GitHub hides the original diff on merge, it is displayed below for
the sake of provenance:

As this is a foreign pull request (from a fork), the diff is supplied
below (as it won't show otherwise due to GitHub magic):

diff --git a/modules/benchmarks/src/main/java/org/apache/ignite/internal/benchmarks/jmh/tree/BPlusTreeBenchmark.java b/modules/benchmarks/src/main/java/org/apache/ignite/internal/benchmarks/jmh/tree/BPlusTreeBenchmark.java
index 7ed84cbd8d93..e80e13d52d4b 100644
--- a/modules/benchmarks/src/main/java/org/apache/ignite/internal/benchmarks/jmh/tree/BPlusTreeBenchmark.java
+++ b/modules/benchmarks/src/main/java/org/apache/ignite/internal/benchmarks/jmh/tree/BPlusTreeBenchmark.java
@@ -192,7 +192,7 @@ public Long put() throws Exception {
         }
 
         /** {@inheritDoc} */
-        @Override protected Long getRow(BPlusIO<Long> io, long pageAddr, int idx, Object ignore)
+        @Override public Long getRow(BPlusIO<Long> io, long pageAddr, int idx, Object ignore)
             throws IgniteCheckedException {
             assert io.canGetRow() : io;
 
diff --git a/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcStreamingSelfTest.java b/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcStreamingSelfTest.java
index 9e11efc7280c..bc545ac70f1f 100644
--- a/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcStreamingSelfTest.java
+++ b/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcStreamingSelfTest.java
@@ -34,6 +34,7 @@
 import org.apache.ignite.configuration.IgniteConfiguration;
 import org.apache.ignite.internal.processors.query.QueryUtils;
 import org.apache.ignite.internal.util.typedef.internal.U;
+import org.apache.ignite.jdbc.thin.JdbcThinAbstractSelfTest;
 import org.apache.ignite.lang.IgniteCallable;
 import org.apache.ignite.spi.discovery.tcp.TcpDiscoverySpi;
 import org.apache.ignite.spi.discovery.tcp.ipfinder.vm.TcpDiscoveryVmIpFinder;
@@ -47,7 +48,7 @@
 /**
  * Data streaming test.
  */
-public class JdbcStreamingSelfTest extends GridCommonAbstractTest {
+public class JdbcStreamingSelfTest extends JdbcThinAbstractSelfTest {
     /** JDBC URL. */
     private static final String BASE_URL = CFG_URL_PREFIX +
         "cache=default@modules/clients/src/test/config/jdbc-config.xml";
diff --git a/modules/clients/src/test/java/org/apache/ignite/jdbc/suite/IgniteJdbcDriverTestSuite.java b/modules/clients/src/test/java/org/apache/ignite/jdbc/suite/IgniteJdbcDriverTestSuite.java
index 889a551bc0bc..275040f81619 100644
--- a/modules/clients/src/test/java/org/apache/ignite/jdbc/suite/IgniteJdbcDriverTestSuite.java
+++ b/modules/clients/src/test/java/org/apache/ignite/jdbc/suite/IgniteJdbcDriverTestSuite.java
@@ -48,6 +48,7 @@
 import org.apache.ignite.jdbc.thin.JdbcThinComplexDmlDdlSkipReducerOnUpdateSelfTest;
 import org.apache.ignite.jdbc.thin.JdbcThinComplexQuerySelfTest;
 import org.apache.ignite.jdbc.thin.JdbcThinConnectionMultipleAddressesTest;
+import org.apache.ignite.jdbc.thin.JdbcThinConnectionMvccEnabledSelfTest;
 import org.apache.ignite.jdbc.thin.JdbcThinConnectionSSLTest;
 import org.apache.ignite.jdbc.thin.JdbcThinConnectionSelfTest;
 import org.apache.ignite.jdbc.thin.JdbcThinDataSourceSelfTest;
@@ -76,6 +77,11 @@
 import org.apache.ignite.jdbc.thin.JdbcThinStreamingNotOrderedSelfTest;
 import org.apache.ignite.jdbc.thin.JdbcThinStreamingOrderedSelfTest;
 import org.apache.ignite.jdbc.thin.JdbcThinTcpIoTest;
+import org.apache.ignite.jdbc.thin.JdbcThinTransactionsClientAutoCommitComplexSelfTest;
+import org.apache.ignite.jdbc.thin.JdbcThinTransactionsClientNoAutoCommitComplexSelfTest;
+import org.apache.ignite.jdbc.thin.JdbcThinTransactionsSelfTest;
+import org.apache.ignite.jdbc.thin.JdbcThinTransactionsServerAutoCommitComplexSelfTest;
+import org.apache.ignite.jdbc.thin.JdbcThinTransactionsServerNoAutoCommitComplexSelfTest;
 import org.apache.ignite.jdbc.thin.JdbcThinUpdateStatementSelfTest;
 import org.apache.ignite.jdbc.thin.JdbcThinUpdateStatementSkipReducerOnUpdateSelfTest;
 import org.apache.ignite.jdbc.thin.JdbcThinWalModeChangeSelfTest;
@@ -146,6 +152,7 @@ public static TestSuite suite() throws Exception {
 
         // New thin JDBC
         suite.addTest(new TestSuite(JdbcThinConnectionSelfTest.class));
+        suite.addTest(new TestSuite(JdbcThinConnectionMvccEnabledSelfTest.class));
         suite.addTest(new TestSuite(JdbcThinConnectionMultipleAddressesTest.class));
         suite.addTest(new TestSuite(JdbcThinTcpIoTest.class));
         suite.addTest(new TestSuite(JdbcThinConnectionSSLTest.class));
@@ -197,6 +204,13 @@ public static TestSuite suite() throws Exception {
         suite.addTest(new TestSuite(JdbcThinComplexDmlDdlSkipReducerOnUpdateSelfTest.class));
         suite.addTest(new TestSuite(JdbcThinComplexDmlDdlCustomSchemaSelfTest.class));
 
+        // Transactions
+        suite.addTest(new TestSuite(JdbcThinTransactionsSelfTest.class));
+        suite.addTest(new TestSuite(JdbcThinTransactionsClientAutoCommitComplexSelfTest.class));
+        suite.addTest(new TestSuite(JdbcThinTransactionsServerAutoCommitComplexSelfTest.class));
+        suite.addTest(new TestSuite(JdbcThinTransactionsClientNoAutoCommitComplexSelfTest.class));
+        suite.addTest(new TestSuite(JdbcThinTransactionsServerNoAutoCommitComplexSelfTest.class));
+
         suite.addTest(new TestSuite(JdbcThinLocalQueriesSelfTest.class));
 
         // Various commands.
diff --git a/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinAbstractSelfTest.java b/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinAbstractSelfTest.java
index 2ba36c369c22..6d5f59a12c23 100644
--- a/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinAbstractSelfTest.java
+++ b/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinAbstractSelfTest.java
@@ -112,7 +112,7 @@ protected void checkResultSetClosed(final RunnableX r) {
      * @param params Connection parameters.
      * @return Thin JDBC connection to specified node.
      */
-    static Connection connect(IgniteEx node, String params) throws SQLException {
+    protected Connection connect(IgniteEx node, String params) throws SQLException {
         Collection<GridPortRecord> recs = node.context().ports().records();
 
         GridPortRecord cliLsnrRec = null;
@@ -141,7 +141,7 @@ static Connection connect(IgniteEx node, String params) throws SQLException {
      * @return Result set.
      * @throws RuntimeException if failed.
      */
-    static List<List<?>> execute(Connection conn, String sql, Object... args) throws SQLException {
+    protected List<List<?>> execute(Connection conn, String sql, Object... args) throws SQLException {
         try (PreparedStatement s = conn.prepareStatement(sql)) {
             for (int i = 0; i < args.length; i++)
                 s.setObject(i + 1, args[i]);
diff --git a/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinConnectionMvccEnabledSelfTest.java b/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinConnectionMvccEnabledSelfTest.java
new file mode 100644
index 000000000000..051d1d29be83
--- /dev/null
+++ b/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinConnectionMvccEnabledSelfTest.java
@@ -0,0 +1,378 @@
+/*
+ * 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.jdbc.thin;
+
+import java.sql.Connection;
+import java.sql.DatabaseMetaData;
+import java.sql.DriverManager;
+import java.sql.SQLException;
+import java.sql.Savepoint;
+import java.util.concurrent.Callable;
+import org.apache.ignite.configuration.CacheConfiguration;
+import org.apache.ignite.configuration.IgniteConfiguration;
+import org.apache.ignite.internal.binary.BinaryMarshaller;
+import org.apache.ignite.spi.discovery.tcp.TcpDiscoverySpi;
+import org.apache.ignite.spi.discovery.tcp.ipfinder.TcpDiscoveryIpFinder;
+import org.apache.ignite.spi.discovery.tcp.ipfinder.vm.TcpDiscoveryVmIpFinder;
+import org.apache.ignite.testframework.GridStringLogger;
+import org.apache.ignite.testframework.GridTestUtils;
+import org.jetbrains.annotations.NotNull;
+
+import static java.sql.Connection.TRANSACTION_NONE;
+import static java.sql.Connection.TRANSACTION_READ_COMMITTED;
+import static java.sql.Connection.TRANSACTION_READ_UNCOMMITTED;
+import static java.sql.Connection.TRANSACTION_REPEATABLE_READ;
+import static java.sql.Connection.TRANSACTION_SERIALIZABLE;
+
+/**
+ * Connection test.
+ */
+@SuppressWarnings("ThrowableNotThrown")
+public class JdbcThinConnectionMvccEnabledSelfTest extends JdbcThinAbstractSelfTest {
+    /** IP finder. */
+    private static final TcpDiscoveryIpFinder IP_FINDER = new TcpDiscoveryVmIpFinder(true);
+
+    /** */
+    private static final String URL = "jdbc:ignite:thin://127.0.0.1";
+
+    /** {@inheritDoc} */
+    @SuppressWarnings("deprecation")
+    @Override protected IgniteConfiguration getConfiguration(String igniteInstanceName) throws Exception {
+        IgniteConfiguration cfg = super.getConfiguration(igniteInstanceName);
+
+        cfg.setCacheConfiguration(cacheConfiguration(DEFAULT_CACHE_NAME));
+
+        TcpDiscoverySpi disco = new TcpDiscoverySpi();
+
+        disco.setIpFinder(IP_FINDER);
+
+        cfg.setDiscoverySpi(disco);
+
+        cfg.setMarshaller(new BinaryMarshaller());
+
+        cfg.setGridLogger(new GridStringLogger());
+
+        cfg.setMvccEnabled(true);
+
+        return cfg;
+    }
+
+    /**
+     * @param name Cache name.
+     * @return Cache configuration.
+     * @throws Exception In case of error.
+     */
+    private CacheConfiguration cacheConfiguration(@NotNull String name) throws Exception {
+        CacheConfiguration cfg = defaultCacheConfiguration();
+
+        cfg.setName(name);
+
+        return cfg;
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void beforeTestsStarted() throws Exception {
+        super.beforeTestsStarted();
+
+        startGridsMultiThreaded(2);
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void afterTestsStopped() throws Exception {
+        stopAllGrids();
+    }
+
+
+    /**
+     * @throws Exception If failed.
+     */
+    public void testMetadataDefaults() throws Exception {
+        try (Connection conn = DriverManager.getConnection(URL)) {
+            DatabaseMetaData meta = conn.getMetaData();
+
+            assertEquals(TRANSACTION_REPEATABLE_READ, meta.getDefaultTransactionIsolation());
+            assertTrue(meta.supportsTransactions());
+
+            assertFalse(meta.supportsTransactionIsolationLevel(TRANSACTION_NONE));
+            assertFalse(meta.supportsTransactionIsolationLevel(TRANSACTION_READ_UNCOMMITTED));
+            assertFalse(meta.supportsTransactionIsolationLevel(TRANSACTION_READ_COMMITTED));
+            assertTrue(meta.supportsTransactionIsolationLevel(TRANSACTION_REPEATABLE_READ));
+            assertFalse(meta.supportsTransactionIsolationLevel(TRANSACTION_SERIALIZABLE));
+        }
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
+    public void testGetSetAutoCommit() throws Exception {
+        try (Connection conn = DriverManager.getConnection(URL)) {
+            assertTrue(conn.getMetaData().supportsTransactions());
+
+            assertTrue(conn.getAutoCommit());
+
+            conn.setAutoCommit(false);
+
+            assertFalse(conn.getAutoCommit());
+
+            conn.setAutoCommit(true);
+
+            assertTrue(conn.getAutoCommit());
+
+            conn.close();
+
+            // Exception when called on closed connection
+            checkConnectionClosed(new RunnableX() {
+                @Override public void run() throws Exception {
+                    conn.setAutoCommit(true);
+                }
+            });
+        }
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
+    public void testCommit() throws Exception {
+        try (Connection conn = DriverManager.getConnection(URL)) {
+            assertTrue(conn.getMetaData().supportsTransactions());
+
+            // Should not be called in auto-commit mode
+            GridTestUtils.assertThrows(log,
+                new Callable<Object>() {
+                    @Override public Object call() throws Exception {
+                        conn.commit();
+
+                        return null;
+                    }
+                },
+                SQLException.class,
+                "Transaction cannot be committed explicitly in auto-commit mode"
+            );
+
+            conn.setAutoCommit(false);
+
+            conn.commit();
+
+            conn.close();
+
+            // Exception when called on closed connection
+            checkConnectionClosed(new RunnableX() {
+                @Override public void run() throws Exception {
+                    conn.commit();
+                }
+            });
+        }
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
+    public void testRollback() throws Exception {
+        try (Connection conn = DriverManager.getConnection(URL)) {
+            assertTrue(conn.getMetaData().supportsTransactions());
+
+            // Should not be called in auto-commit mode
+            GridTestUtils.assertThrows(log,
+                new Callable<Object>() {
+                    @Override public Object call() throws Exception {
+                        conn.rollback();
+
+                        return null;
+                    }
+                },
+                SQLException.class,
+                "Transaction cannot be rolled back explicitly in auto-commit mode."
+            );
+
+            conn.setAutoCommit(false);
+
+            conn.rollback();
+
+            conn.close();
+
+            // Exception when called on closed connection
+            checkConnectionClosed(new RunnableX() {
+                @Override public void run() throws Exception {
+                    conn.rollback();
+                }
+            });
+        }
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
+    public void testSetSavepoint() throws Exception {
+        try (Connection conn = DriverManager.getConnection(URL)) {
+            assert !conn.getMetaData().supportsSavepoints();
+
+            // Disallowed in auto-commit mode
+            GridTestUtils.assertThrows(log,
+                new Callable<Object>() {
+                    @Override public Object call() throws Exception {
+                        conn.setSavepoint();
+
+                        return null;
+                    }
+                },
+                SQLException.class,
+                "Savepoint cannot be set in auto-commit mode"
+            );
+
+            conn.setAutoCommit(false);
+
+            // Unsupported
+            checkNotSupported(new RunnableX() {
+                @Override public void run() throws Exception {
+                    conn.setSavepoint();
+                }
+            });
+
+            conn.close();
+
+            checkConnectionClosed(new RunnableX() {
+                @Override public void run() throws Exception {
+                    conn.setSavepoint();
+                }
+            });
+        }
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
+    public void testSetSavepointName() throws Exception {
+        try (Connection conn = DriverManager.getConnection(URL)) {
+            assert !conn.getMetaData().supportsSavepoints();
+
+            // Invalid arg
+            GridTestUtils.assertThrows(log,
+                new Callable<Object>() {
+                    @Override public Object call() throws Exception {
+                        conn.setSavepoint(null);
+
+                        return null;
+                    }
+                },
+                SQLException.class,
+                "Savepoint name cannot be null"
+            );
+
+            final String name = "savepoint";
+
+            // Disallowed in auto-commit mode
+            GridTestUtils.assertThrows(log,
+                new Callable<Object>() {
+                    @Override public Object call() throws Exception {
+                        conn.setSavepoint(name);
+
+                        return null;
+                    }
+                },
+                SQLException.class,
+                "Savepoint cannot be set in auto-commit mode"
+            );
+
+            conn.setAutoCommit(false);
+
+            // Unsupported
+            checkNotSupported(new RunnableX() {
+                @Override public void run() throws Exception {
+                    conn.setSavepoint(name);
+                }
+            });
+
+            conn.close();
+
+            checkConnectionClosed(new RunnableX() {
+                @Override public void run() throws Exception {
+                    conn.setSavepoint(name);
+                }
+            });
+        }
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
+    public void testRollbackSavePoint() throws Exception {
+        try (Connection conn = DriverManager.getConnection(URL)) {
+            assert !conn.getMetaData().supportsSavepoints();
+
+            // Invalid arg
+            GridTestUtils.assertThrows(log,
+                new Callable<Object>() {
+                    @Override public Object call() throws Exception {
+                        conn.rollback(null);
+
+                        return null;
+                    }
+                },
+                SQLException.class,
+                "Invalid savepoint"
+            );
+
+            final Savepoint savepoint = getFakeSavepoint();
+
+            // Disallowed in auto-commit mode
+            GridTestUtils.assertThrows(log,
+                new Callable<Object>() {
+                    @Override public Object call() throws Exception {
+                        conn.rollback(savepoint);
+
+                        return null;
+                    }
+                },
+                SQLException.class,
+                "Auto-commit mode"
+            );
+
+            conn.setAutoCommit(false);
+
+            // Unsupported
+            checkNotSupported(new RunnableX() {
+                @Override public void run() throws Exception {
+                    conn.rollback(savepoint);
+                }
+            });
+
+            conn.close();
+
+            checkConnectionClosed(new RunnableX() {
+                @Override public void run() throws Exception {
+                    conn.rollback(savepoint);
+                }
+            });
+        }
+    }
+
+    /**
+     * @return Savepoint.
+     */
+    private Savepoint getFakeSavepoint() {
+        return new Savepoint() {
+            @Override public int getSavepointId() throws SQLException {
+                return 100;
+            }
+
+            @Override public String getSavepointName() {
+                return "savepoint";
+            }
+        };
+    }
+}
\ No newline at end of file
diff --git a/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinConnectionSelfTest.java b/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinConnectionSelfTest.java
index 38bcab282438..80397e65e7ae 100644
--- a/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinConnectionSelfTest.java
+++ b/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinConnectionSelfTest.java
@@ -17,6 +17,9 @@
 
 package org.apache.ignite.jdbc.thin;
 
+import java.io.IOException;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
 import java.sql.Connection;
 import java.sql.DatabaseMetaData;
 import java.sql.DriverManager;
@@ -38,12 +41,16 @@
 import org.apache.ignite.configuration.IgniteConfiguration;
 import org.apache.ignite.internal.IgniteInternalFuture;
 import org.apache.ignite.internal.binary.BinaryMarshaller;
+import org.apache.ignite.internal.jdbc.thin.ConnectionProperties;
+import org.apache.ignite.internal.jdbc.thin.ConnectionPropertiesImpl;
 import org.apache.ignite.internal.jdbc.thin.JdbcThinConnection;
 import org.apache.ignite.internal.jdbc.thin.JdbcThinTcpIo;
+import org.apache.ignite.internal.util.HostAndPortRange;
 import org.apache.ignite.internal.util.typedef.internal.U;
 import org.apache.ignite.spi.discovery.tcp.TcpDiscoverySpi;
 import org.apache.ignite.spi.discovery.tcp.ipfinder.TcpDiscoveryIpFinder;
 import org.apache.ignite.spi.discovery.tcp.ipfinder.vm.TcpDiscoveryVmIpFinder;
+import org.apache.ignite.testframework.GridStringLogger;
 import org.apache.ignite.testframework.GridTestUtils;
 import org.jetbrains.annotations.NotNull;
 
@@ -58,6 +65,7 @@
 import static java.sql.ResultSet.TYPE_FORWARD_ONLY;
 import static java.sql.Statement.NO_GENERATED_KEYS;
 import static java.sql.Statement.RETURN_GENERATED_KEYS;
+import static org.apache.ignite.configuration.ClientConnectorConfiguration.DFLT_PORT;
 
 /**
  * Connection test.
@@ -93,6 +101,8 @@
 
         cfg.setMarshaller(new BinaryMarshaller());
 
+        cfg.setGridLogger(new GridStringLogger());
+
         return cfg;
     }
 
@@ -970,11 +980,18 @@ public void testGetSetAutoCommit() throws Exception {
         try (Connection conn = DriverManager.getConnection(URL)) {
             assertTrue(conn.getAutoCommit());
 
-            conn.setAutoCommit(false);
-
-            assertFalse(conn.getAutoCommit());
+            // Cannot disable autocommit when MVCC is disabled.
+            GridTestUtils.assertThrows(log,
+                new Callable<Object>() {
+                    @Override public Object call() throws Exception {
+                        conn.setAutoCommit(false);
 
-            conn.setAutoCommit(true);
+                        return null;
+                    }
+                },
+                SQLException.class,
+                "MVCC must be enabled in order to invoke transactional operation: COMMIT"
+            );
 
             assertTrue(conn.getAutoCommit());
 
@@ -994,8 +1011,6 @@ public void testGetSetAutoCommit() throws Exception {
      */
     public void testCommit() throws Exception {
         try (Connection conn = DriverManager.getConnection(URL)) {
-            assert !conn.getMetaData().supportsTransactions();
-
             // Should not be called in auto-commit mode
             GridTestUtils.assertThrows(log,
                 new Callable<Object>() {
@@ -1009,9 +1024,33 @@ public void testCommit() throws Exception {
                 "Transaction cannot be committed explicitly in auto-commit mode"
             );
 
-            conn.setAutoCommit(false);
+            // Cannot disable autocommit when MVCC is disabled.
+            GridTestUtils.assertThrows(log,
+                new Callable<Object>() {
+                    @Override public Object call() throws Exception {
+                        conn.setAutoCommit(false);
+
+                        return null;
+                    }
+                },
+                SQLException.class,
+                "MVCC must be enabled in order to invoke transactional operation: COMMIT"
+            );
+
+            assertTrue(conn.getAutoCommit());
 
-            conn.commit();
+            // Should not be called in auto-commit mode
+            GridTestUtils.assertThrows(log,
+                new Callable<Object>() {
+                    @Override public Object call() throws Exception {
+                        conn.commit();
+
+                        return null;
+                    }
+                },
+                SQLException.class,
+                "Transaction cannot be committed explicitly in auto-commit mode."
+            );
 
             conn.close();
 
@@ -1029,8 +1068,6 @@ public void testCommit() throws Exception {
      */
     public void testRollback() throws Exception {
         try (Connection conn = DriverManager.getConnection(URL)) {
-            assert !conn.getMetaData().supportsTransactions();
-
             // Should not be called in auto-commit mode
             GridTestUtils.assertThrows(log,
                 new Callable<Object>() {
@@ -1041,12 +1078,23 @@ public void testRollback() throws Exception {
                     }
                 },
                 SQLException.class,
-                "Transaction cannot rollback in auto-commit mode"
+                "Transaction cannot be rolled back explicitly in auto-commit mode."
             );
 
-            conn.setAutoCommit(false);
+            // Cannot disable autocommit when MVCC is disabled.
+            GridTestUtils.assertThrows(log,
+                new Callable<Object>() {
+                    @Override public Object call() throws Exception {
+                        conn.setAutoCommit(false);
 
-            conn.rollback();
+                        return null;
+                    }
+                },
+                SQLException.class,
+                "MVCC must be enabled in order to invoke transactional operation: COMMIT"
+            );
+
+            assertTrue(conn.getAutoCommit());
 
             conn.close();
 
@@ -1138,8 +1186,6 @@ public void testGetSetCatalog() throws Exception {
      */
     public void testGetSetTransactionIsolation() throws Exception {
         try (Connection conn = DriverManager.getConnection(URL)) {
-            assert !conn.getMetaData().supportsTransactions();
-
             // Invalid parameter value
             GridTestUtils.assertThrows(log,
                 new Callable<Object>() {
@@ -1346,14 +1392,20 @@ public void testSetSavepoint() throws Exception {
                 "Savepoint cannot be set in auto-commit mode"
             );
 
-            conn.setAutoCommit(false);
+            // Cannot disable autocommit when MVCC is disabled.
+            GridTestUtils.assertThrows(log,
+                new Callable<Object>() {
+                    @Override public Object call() throws Exception {
+                        conn.setAutoCommit(false);
 
-            // Unsupported
-            checkNotSupported(new RunnableX() {
-                @Override public void run() throws Exception {
-                    conn.setSavepoint();
-                }
-            });
+                        return null;
+                    }
+                },
+                SQLException.class,
+                "MVCC must be enabled in order to invoke transactional operation: COMMIT"
+            );
+
+            assertTrue(conn.getAutoCommit());
 
             conn.close();
 
@@ -1400,14 +1452,20 @@ public void testSetSavepointName() throws Exception {
                 "Savepoint cannot be set in auto-commit mode"
             );
 
-            conn.setAutoCommit(false);
+            // Cannot disable autocommit when MVCC is disabled.
+            GridTestUtils.assertThrows(log,
+                new Callable<Object>() {
+                    @Override public Object call() throws Exception {
+                        conn.setAutoCommit(false);
 
-            // Unsupported
-            checkNotSupported(new RunnableX() {
-                @Override public void run() throws Exception {
-                    conn.setSavepoint(name);
-                }
-            });
+                        return null;
+                    }
+                },
+                SQLException.class,
+                "MVCC must be enabled in order to invoke transactional operation: COMMIT"
+            );
+
+            assertTrue(conn.getAutoCommit());
 
             conn.close();
 
@@ -1454,14 +1512,20 @@ public void testRollbackSavePoint() throws Exception {
                 "Auto-commit mode"
             );
 
-            conn.setAutoCommit(false);
+            // Cannot disable autocommit when MVCC is disabled.
+            GridTestUtils.assertThrows(log,
+                new Callable<Object>() {
+                    @Override public Object call() throws Exception {
+                        conn.setAutoCommit(false);
 
-            // Unsupported
-            checkNotSupported(new RunnableX() {
-                @Override public void run() throws Exception {
-                    conn.rollback(savepoint);
-                }
-            });
+                        return null;
+                    }
+                },
+                SQLException.class,
+                "MVCC must be enabled in order to invoke transactional operation: COMMIT"
+            );
+
+            assertTrue(conn.getAutoCommit());
 
             conn.close();
 
@@ -1897,6 +1961,58 @@ public void testGetSetNetworkTimeout() throws Exception {
         }
     }
 
+    /**
+     * Test that attempting to supply invalid nested TX mode to driver fails on the client.
+     */
+    @SuppressWarnings("ThrowableResultOfMethodCallIgnored")
+    public void testInvalidNestedTxMode() {
+        GridTestUtils.assertThrows(null, new Callable<Object>() {
+            @Override public Object call() throws Exception {
+                DriverManager.getConnection(URL + "/?nestedTransactionsMode=invalid");
+
+                return null;
+            }
+        }, SQLException.class, "Invalid nested transactions handling mode");
+    }
+
+    /**
+     * Test that attempting to send unexpected name of nested TX mode to server on handshake yields an error.
+     * We have to do this without explicit {@link Connection} as long as there's no other way to bypass validation and
+     * supply a malformed {@link ConnectionProperties} to {@link JdbcThinTcpIo}.
+     */
+    @SuppressWarnings({"ThrowableResultOfMethodCallIgnored", "ThrowFromFinallyBlock"})
+    public void testInvalidNestedTxModeOnServerSide() throws SQLException, NoSuchMethodException,
+        IllegalAccessException, InvocationTargetException, InstantiationException, IOException {
+        ConnectionPropertiesImpl connProps = new ConnectionPropertiesImpl();
+
+        connProps.setAddresses(new HostAndPortRange[]{new HostAndPortRange("127.0.0.1", DFLT_PORT, DFLT_PORT)});
+
+        connProps.nestedTxMode("invalid");
+
+        Constructor ctor = JdbcThinTcpIo.class.getDeclaredConstructor(ConnectionProperties.class);
+
+        boolean acc = ctor.isAccessible();
+
+        ctor.setAccessible(true);
+
+        final JdbcThinTcpIo io = (JdbcThinTcpIo)ctor.newInstance(connProps);
+
+        try {
+            GridTestUtils.assertThrows(null, new Callable<Object>() {
+                @Override public Object call() throws Exception {
+                    io.start();
+
+                    return null;
+                }
+            }, SQLException.class, "err=Invalid nested transactions handling mode: invalid");
+        }
+        finally {
+            io.close();
+
+            ctor.setAccessible(acc);
+        }
+    }
+
     /**
      */
     public void testSslClientAndPlainServer()  {
diff --git a/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinStreamingAbstractSelfTest.java b/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinStreamingAbstractSelfTest.java
index 70046356b210..c83977c692d1 100644
--- a/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinStreamingAbstractSelfTest.java
+++ b/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinStreamingAbstractSelfTest.java
@@ -32,6 +32,7 @@
 import org.apache.ignite.cache.query.FieldsQueryCursor;
 import org.apache.ignite.cache.query.SqlFieldsQuery;
 import org.apache.ignite.internal.jdbc2.JdbcStreamingSelfTest;
+import org.apache.ignite.internal.processors.cache.mvcc.MvccQueryTracker;
 import org.apache.ignite.internal.processors.query.GridQueryCancel;
 import org.apache.ignite.internal.processors.query.GridQueryProcessor;
 import org.apache.ignite.internal.processors.query.SqlClientContext;
@@ -77,9 +78,19 @@
         super.afterTest();
     }
 
-        /** {@inheritDoc} */
+    /** {@inheritDoc} */
+    @Override protected Connection createStreamedConnection(boolean allowOverwrite, long flushFreq) throws Exception {
+        Connection c = connect(grid(0), null);
+
+        execute(c, "SET STREAMING 1 BATCH_SIZE " + batchSize + " ALLOW_OVERWRITE " + (allowOverwrite ? 1 : 0) +
+            " PER_NODE_BUFFER_SIZE 1000 FLUSH_FREQUENCY " + flushFreq);
+
+        return c;
+    }
+
+    /** {@inheritDoc} */
     @Override protected Connection createOrdinaryConnection() throws SQLException {
-        return JdbcThinAbstractSelfTest.connect(grid(0), null);
+        return connect(grid(0), null);
     }
 
     /**
@@ -495,11 +506,11 @@ protected void assertStreamingState(boolean on) throws Exception {
 
         /** {@inheritDoc} */
         @Override public List<FieldsQueryCursor<List<?>>> querySqlFields(String schemaName, SqlFieldsQuery qry,
-            @Nullable SqlClientContext cliCtx, boolean keepBinary, boolean failOnMultipleStmts,
+            @Nullable SqlClientContext cliCtx, boolean keepBinary, boolean failOnMultipleStmts, MvccQueryTracker tracker,
             GridQueryCancel cancel) {
             IndexingWithContext.cliCtx = cliCtx;
 
-            return super.querySqlFields(schemaName, qry, cliCtx, keepBinary, failOnMultipleStmts, cancel);
+            return super.querySqlFields(schemaName, qry, cliCtx, keepBinary, failOnMultipleStmts, tracker, cancel);
         }
     }
 }
\ No newline at end of file
diff --git a/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinStreamingNotOrderedSelfTest.java b/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinStreamingNotOrderedSelfTest.java
index b91258f8a4eb..b0788e74f1bc 100644
--- a/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinStreamingNotOrderedSelfTest.java
+++ b/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinStreamingNotOrderedSelfTest.java
@@ -25,7 +25,7 @@
 public class JdbcThinStreamingNotOrderedSelfTest extends JdbcThinStreamingAbstractSelfTest {
     /** {@inheritDoc} */
     @Override protected Connection createStreamedConnection(boolean allowOverwrite, long flushFreq) throws Exception {
-        Connection c = JdbcThinAbstractSelfTest.connect(grid(0), null);
+        Connection c = connect(grid(0), null);
 
         execute(c, "SET STREAMING 1 BATCH_SIZE " + batchSize
             + " ALLOW_OVERWRITE " + (allowOverwrite ? 1 : 0)
diff --git a/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinStreamingOrderedSelfTest.java b/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinStreamingOrderedSelfTest.java
index b615f8cd9614..c116a7dc6c48 100644
--- a/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinStreamingOrderedSelfTest.java
+++ b/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinStreamingOrderedSelfTest.java
@@ -25,7 +25,7 @@
 public class JdbcThinStreamingOrderedSelfTest extends JdbcThinStreamingAbstractSelfTest {
     /** {@inheritDoc} */
     @Override protected Connection createStreamedConnection(boolean allowOverwrite, long flushFreq) throws Exception {
-        Connection c = JdbcThinAbstractSelfTest.connect(grid(0), null);
+        Connection c = connect(grid(0), null);
 
         execute(c, "SET STREAMING 1 BATCH_SIZE " + batchSize
             + " ALLOW_OVERWRITE " + (allowOverwrite ? 1 : 0)
diff --git a/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinTransactionsAbstractComplexSelfTest.java b/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinTransactionsAbstractComplexSelfTest.java
new file mode 100644
index 000000000000..28c65a97cd6b
--- /dev/null
+++ b/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinTransactionsAbstractComplexSelfTest.java
@@ -0,0 +1,1058 @@
+/*
+ * 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.jdbc.thin;
+
+import java.sql.BatchUpdateException;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CountDownLatch;
+import org.apache.ignite.IgniteCache;
+import org.apache.ignite.IgniteCheckedException;
+import org.apache.ignite.IgniteException;
+import org.apache.ignite.cache.CacheAtomicityMode;
+import org.apache.ignite.cache.CacheMode;
+import org.apache.ignite.cache.query.SqlFieldsQuery;
+import org.apache.ignite.cache.query.annotations.QuerySqlField;
+import org.apache.ignite.configuration.CacheConfiguration;
+import org.apache.ignite.configuration.IgniteConfiguration;
+import org.apache.ignite.internal.IgniteEx;
+import org.apache.ignite.internal.IgniteInternalFuture;
+import org.apache.ignite.internal.IgniteInterruptedCheckedException;
+import org.apache.ignite.internal.util.typedef.F;
+import org.apache.ignite.internal.util.typedef.X;
+import org.apache.ignite.internal.util.typedef.internal.U;
+import org.apache.ignite.lang.IgniteInClosure;
+import org.apache.ignite.testframework.GridTestUtils;
+
+/**
+ * Test to check various transactional scenarios.
+ */
+public abstract class JdbcThinTransactionsAbstractComplexSelfTest extends JdbcThinAbstractSelfTest {
+    /** Client node index. */
+    final static int CLI_IDX = 1;
+
+    /**
+     * Closure to perform ordinary delete after repeatable read.
+     */
+    private final IgniteInClosure<Connection> afterReadDel = new IgniteInClosure<Connection>() {
+        @Override public void apply(Connection conn) {
+            execute(conn, "DELETE FROM \"Person\".Person where firstname = 'John'");
+        }
+    };
+
+    /**
+     * Closure to perform fast delete after repeatable read.
+     */
+    private final IgniteInClosure<Connection> afterReadFastDel = new IgniteInClosure<Connection>() {
+        @Override public void apply(Connection conn) {
+            execute(conn, "DELETE FROM \"Person\".Person where id = 1");
+        }
+    };
+
+    /**
+     * Closure to perform ordinary update after repeatable read.
+     */
+    private final IgniteInClosure<Connection> afterReadUpdate = new IgniteInClosure<Connection>() {
+        @Override public void apply(Connection conn) {
+            execute(conn, "UPDATE \"Person\".Person set firstname = 'Joe' where firstname = 'John'");
+        }
+    };
+
+    /**
+     * Closure to perform ordinary delete and rollback after repeatable read.
+     */
+    private final IgniteInClosure<Connection> afterReadDelAndRollback = new IgniteInClosure<Connection>() {
+        @Override public void apply(Connection conn) {
+            execute(conn, "DELETE FROM \"Person\".Person where firstname = 'John'");
+
+            rollback(conn);
+        }
+    };
+
+    /**
+     * Closure to perform fast delete after repeatable read.
+     */
+    private final IgniteInClosure<Connection> afterReadFastDelAndRollback = new IgniteInClosure<Connection>() {
+        @Override public void apply(Connection conn) {
+            execute(conn, "DELETE FROM \"Person\".Person where id = 1");
+
+            rollback(conn);
+        }
+    };
+
+    /**
+     * Closure to perform ordinary update and rollback after repeatable read.
+     */
+    private final IgniteInClosure<Connection> afterReadUpdateAndRollback = new IgniteInClosure<Connection>() {
+        @Override public void apply(Connection conn) {
+            execute(conn, "UPDATE \"Person\".Person set firstname = 'Joe' where firstname = 'John'");
+
+            rollback(conn);
+        }
+    };
+
+    /** {@inheritDoc} */
+    @Override protected IgniteConfiguration getConfiguration(String testIgniteInstanceName) throws Exception {
+        IgniteConfiguration cfg = super.getConfiguration(testIgniteInstanceName);
+
+        cfg.setMvccEnabled(true);
+
+        CacheConfiguration<Integer, Person> ccfg = new CacheConfiguration<>("Person");
+
+        ccfg.setIndexedTypes(Integer.class, Person.class);
+
+        ccfg.getQueryEntities().iterator().next().setKeyFieldName("id");
+
+        ccfg.setAtomicityMode(CacheAtomicityMode.TRANSACTIONAL);
+
+        ccfg.setCacheMode(CacheMode.PARTITIONED);
+
+        cfg.setCacheConfiguration(ccfg);
+
+        // Let the node with index 1 be client node.
+        cfg.setClientMode(F.eq(testIgniteInstanceName, getTestIgniteInstanceName(CLI_IDX)));
+
+        return cfg;
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void beforeTest() throws Exception {
+        super.beforeTest();
+
+        execute("ALTER TABLE \"Person\".person add if not exists cityid int");
+
+        execute("ALTER TABLE \"Person\".person add if not exists companyid int");
+
+        execute("CREATE TABLE City (id int primary key, name varchar, population int) WITH " +
+            "\"atomicity=transactional,template=partitioned,backups=3,cache_name=City\"");
+
+        execute("CREATE TABLE Company (id int, \"cityid\" int, name varchar, primary key (id, \"cityid\")) WITH " +
+            "\"atomicity=transactional,template=partitioned,backups=1,wrap_value=false,affinity_key=cityid," +
+            "cache_name=Company\"");
+
+        execute("CREATE TABLE Product (id int primary key, name varchar, companyid int) WITH " +
+            "\"atomicity=transactional,template=partitioned,backups=2,cache_name=Product\"");
+
+        execute("CREATE INDEX IF NOT EXISTS prodidx ON Product(companyid)");
+
+        execute("CREATE INDEX IF NOT EXISTS persidx ON \"Person\".person(cityid)");
+
+        insertPerson(1, "John", "Smith", 1, 1);
+
+        insertPerson(2, "Mike", "Johns", 1, 2);
+
+        insertPerson(3, "Sam", "Jules", 2, 2);
+
+        insertPerson(4, "Alex", "Pope", 2, 3);
+
+        insertPerson(5, "Peter", "Williams", 2, 3);
+
+        insertCity(1, "Los Angeles", 5000);
+
+        insertCity(2, "Seattle", 1500);
+
+        insertCity(3, "New York", 12000);
+
+        insertCity(4, "Cupertino", 400);
+
+        insertCompany(1, "Microsoft", 2);
+
+        insertCompany(2, "Google", 3);
+
+        insertCompany(3, "Facebook", 1);
+
+        insertCompany(4, "Uber", 1);
+
+        insertCompany(5, "Apple", 4);
+
+        insertProduct(1, "Search", 2);
+
+        insertProduct(2, "Windows", 1);
+
+        insertProduct(3, "Mac", 5);
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void beforeTestsStarted() throws Exception {
+        super.beforeTestsStarted();
+
+        startGrid(0);
+
+        startGrid(1);
+
+        startGrid(2);
+
+        startGrid(3);
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void afterTestsStopped() throws Exception {
+        stopAllGrids();
+
+        super.afterTestsStopped();
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void afterTest() throws Exception {
+        execute("DELETE FROM \"Person\".Person");
+
+        execute("DROP TABLE City");
+
+        execute("DROP TABLE Company");
+
+        execute("DROP TABLE Product");
+
+        super.afterTest();
+    }
+
+    /**
+     *
+     */
+    public void testSingleDmlStatement() throws SQLException {
+        insertPerson(6, "John", "Doe", 2, 2);
+
+        assertEquals(Collections.singletonList(l(6, "John", "Doe", 2, 2)),
+            execute("SELECT * FROM \"Person\".Person where id = 6"));
+    }
+
+    /**
+     *
+     */
+    public void testMultipleDmlStatements() throws SQLException {
+        executeInTransaction(new TransactionClosure() {
+            @Override public void apply(Connection conn) {
+                insertPerson(conn, 6, "John", "Doe", 2, 2);
+
+                // https://issues.apache.org/jira/browse/IGNITE-6938 - we can only see results of
+                // UPDATE of what we have not inserted ourselves.
+                execute(conn, "UPDATE \"Person\".person SET lastname = 'Jameson' where lastname = 'Jules'");
+
+                execute(conn, "DELETE FROM \"Person\".person where id = 5");
+            }
+        });
+
+        assertEquals(l(
+            l(3, "Sam", "Jameson", 2, 2),
+            l(6, "John", "Doe", 2, 2)
+        ), execute("SELECT * FROM \"Person\".Person where id = 3 or id >= 5 order by id"));
+    }
+
+    /**
+     *
+     */
+    public void testBatchDmlStatements() throws SQLException {
+        doBatchedInsert();
+
+        assertEquals(l(
+            l(6, "John", "Doe", 2, 2),
+            l(7, "Mary", "Lee", 1, 3)
+        ), execute("SELECT * FROM \"Person\".Person where id > 5 order by id"));
+    }
+
+    /**
+     *
+     */
+    @SuppressWarnings("ThrowableResultOfMethodCallIgnored")
+    public void testBatchDmlStatementsIntermediateFailure() throws SQLException {
+        insertPerson(6, "John", "Doe", 2, 2);
+
+        IgniteException e = (IgniteException)GridTestUtils.assertThrows(null, new Callable<Object>() {
+            @Override public Object call() throws Exception {
+                doBatchedInsert();
+
+                return null;
+            }
+        }, IgniteException.class, "Duplicate key during INSERT [key=KeyCacheObjectImpl " +
+            "[part=6, val=6, hasValBytes=true]]");
+
+        assertTrue(e.getCause() instanceof BatchUpdateException);
+
+        assertTrue(e.getCause().getMessage().contains("Duplicate key during INSERT [key=KeyCacheObjectImpl " +
+            "[part=6, val=6, hasValBytes=true]]"));
+
+        // First we insert id 7, then 6. Still, 7 is not in the cache as long as the whole batch has failed inside tx.
+        assertEquals(Collections.emptyList(), execute("SELECT * FROM \"Person\".Person where id > 6 order by id"));
+    }
+
+    /**
+     *
+     */
+    private void doBatchedInsert() throws SQLException {
+        executeInTransaction(new TransactionClosure() {
+            @Override public void apply(Connection conn) {
+                try {
+                    try (PreparedStatement ps = conn.prepareStatement("INSERT INTO \"Person\".person " +
+                        "(id, firstName, lastName, cityId, companyId) values (?, ?, ?, ?, ?)")) {
+                        ps.setInt(1, 7);
+
+                        ps.setString(2, "Mary");
+
+                        ps.setString(3, "Lee");
+
+                        ps.setInt(4, 1);
+
+                        ps.setInt(5, 3);
+
+                        ps.addBatch();
+
+                        ps.setInt(1, 6);
+
+                        ps.setString(2, "John");
+
+                        ps.setString(3, "Doe");
+
+                        ps.setInt(4, 2);
+
+                        ps.setInt(5, 2);
+
+                        ps.addBatch();
+
+                        ps.executeBatch();
+                    }
+                }
+                catch (SQLException e) {
+                    throw new IgniteException(e);
+                }
+            }
+        });
+    }
+
+    /**
+     *
+     */
+    public void testInsertAndQueryMultipleCaches() throws SQLException {
+        executeInTransaction(new TransactionClosure() {
+            @Override public void apply(Connection conn) {
+                insertCity(conn, 5, "St Petersburg", 6000);
+
+                insertCompany(conn, 6, "VK", 5);
+
+                insertPerson(conn, 6, "Peter", "Sergeev", 5, 6);
+            }
+        });
+
+        try (Connection c = connect("distributedJoins=true")) {
+            assertEquals(l(l(5, "St Petersburg", 6000, 6, 5, "VK", 6, "Peter", "Sergeev", 5, 6)),
+                execute(c, "SELECT * FROM City left join Company on City.id = Company.\"cityid\" " +
+                    "left join \"Person\".Person p on City.id = p.cityid WHERE p.id = 6 or company.id = 6"));
+        }
+    }
+
+    /**
+     *
+     */
+    public void testColocatedJoinSelectAndInsertInTransaction() throws SQLException {
+        // We'd like to put some Google into cities with over 1K population which don't have it yet
+        executeInTransaction(new TransactionClosure() {
+            @Override public void apply(Connection conn) {
+                List<Integer> ids = flat(execute(conn, "SELECT distinct City.id from City left join Company c on " +
+                    "City.id = c.\"cityid\" where population >= 1000 and c.name <> 'Google' order by City.id"));
+
+                assertEqualsCollections(l(1, 2), ids);
+
+                int i = 5;
+
+                for (int l : ids)
+                    insertCompany(conn, ++i, "Google", l);
+            }
+        });
+
+        assertEqualsCollections(l("Los Angeles", "Seattle", "New York"), flat(execute("SELECT City.name from City " +
+            "left join Company c on city.id = c.\"cityid\" WHERE c.name = 'Google' order by City.id")));
+    }
+
+    /**
+     *
+     */
+    public void testDistributedJoinSelectAndInsertInTransaction() throws SQLException {
+        try (Connection c = connect("distributedJoins=true")) {
+            // We'd like to put some Google into cities with over 1K population which don't have it yet
+            executeInTransaction(c, new TransactionClosure() {
+                @Override public void apply(Connection conn) {
+                    List<?> res = flat(execute(conn, "SELECT p.id,p.name,c.id from Company c left join Product p on " +
+                        "c.id = p.companyid left join City on city.id = c.\"cityid\" WHERE c.name <> 'Microsoft' " +
+                        "and population < 1000"));
+
+                    assertEqualsCollections(l(3, "Mac", 5), res);
+
+                    insertProduct(conn, 4, (String)res.get(1), 1);
+                }
+            });
+        }
+
+        try (Connection c = connect("distributedJoins=true")) {
+            assertEqualsCollections(l("Windows", "Mac"), flat(execute(c, "SELECT p.name from Company c left join " +
+                "Product p on c.id = p.companyid WHERE c.name = 'Microsoft' order by p.id")));
+        }
+    }
+
+    /**
+     *
+     */
+    public void testInsertFromExpression() throws SQLException {
+        executeInTransaction(new TransactionClosure() {
+            @Override public void apply(Connection conn) {
+                execute(conn, "insert into city (id, name, population) values (? + 1, ?, ?)",
+                    8, "Moscow", 15000);
+            }
+        });
+    }
+
+    /**
+     *
+     */
+    public void testAutoRollback() throws SQLException {
+        try (Connection c = connect()) {
+            begin(c);
+
+            insertPerson(c, 6, "John", "Doe", 2, 2);
+        }
+
+        // Connection has not hung on close and update has not been applied.
+        assertTrue(personCache().query(new SqlFieldsQuery("SELECT * FROM \"Person\".Person WHERE id = 6"))
+            .getAll().isEmpty());
+    }
+
+    /**
+     *
+     */
+    public void testRepeatableReadWithConcurrentDelete() throws Exception {
+        doTestRepeatableRead(new IgniteInClosure<Connection>() {
+            @Override public void apply(Connection conn) {
+                execute(conn, "DELETE FROM \"Person\".Person where firstname = 'John'");
+            }
+        }, null);
+    }
+
+    /**
+     *
+     */
+    public void testRepeatableReadWithConcurrentFastDelete() throws Exception {
+        doTestRepeatableRead(new IgniteInClosure<Connection>() {
+            @Override public void apply(Connection conn) {
+                execute(conn, "DELETE FROM \"Person\".Person where id = 1");
+            }
+        }, null);
+    }
+
+    /**
+     *
+     */
+    public void testRepeatableReadWithConcurrentCacheRemove() throws Exception {
+        doTestRepeatableRead(new IgniteInClosure<Connection>() {
+            @Override public void apply(Connection conn) {
+                personCache().remove(1);
+            }
+        }, null);
+    }
+
+    /**
+     *
+     */
+    public void testRepeatableReadAndDeleteWithConcurrentDelete() throws Exception {
+        doTestRepeatableRead(new IgniteInClosure<Connection>() {
+            @Override public void apply(Connection conn) {
+                execute(conn, "DELETE FROM \"Person\".Person where firstname = 'John'");
+            }
+        }, afterReadDel);
+    }
+
+    /**
+     *
+     */
+    public void testRepeatableReadAndDeleteWithConcurrentFastDelete() throws Exception {
+        doTestRepeatableRead(new IgniteInClosure<Connection>() {
+            @Override public void apply(Connection conn) {
+                execute(conn, "DELETE FROM \"Person\".Person where id = 1");
+            }
+        }, afterReadDel);
+    }
+
+    /**
+     *
+     */
+    public void testRepeatableReadAndDeleteWithConcurrentCacheRemove() throws Exception {
+        doTestRepeatableRead(new IgniteInClosure<Connection>() {
+            @Override public void apply(Connection conn) {
+                personCache().remove(1);
+            }
+        }, afterReadDel);
+    }
+
+    /**
+     *
+     */
+    public void testRepeatableReadAndFastDeleteWithConcurrentDelete() throws Exception {
+        doTestRepeatableRead(new IgniteInClosure<Connection>() {
+            @Override public void apply(Connection conn) {
+                execute(conn, "DELETE FROM \"Person\".Person where firstname = 'John'");
+            }
+        }, afterReadFastDel);
+    }
+
+    /**
+     *
+     */
+    public void testRepeatableReadAndFastDeleteWithConcurrentFastDelete() throws Exception {
+        doTestRepeatableRead(new IgniteInClosure<Connection>() {
+            @Override public void apply(Connection conn) {
+                execute(conn, "DELETE FROM \"Person\".Person where id = 1");
+            }
+        }, afterReadFastDel);
+    }
+
+    /**
+     *
+     */
+    public void testRepeatableReadAndFastDeleteWithConcurrentCacheRemove() throws Exception {
+        doTestRepeatableRead(new IgniteInClosure<Connection>() {
+            @Override public void apply(Connection conn) {
+                personCache().remove(1);
+            }
+        }, afterReadFastDel);
+    }
+
+    /**
+     *
+     */
+    public void testRepeatableReadAndDeleteWithConcurrentDeleteAndRollback() throws Exception {
+        doTestRepeatableRead(new IgniteInClosure<Connection>() {
+            @Override public void apply(Connection conn) {
+                execute(conn, "DELETE FROM \"Person\".Person where firstname = 'John'");
+            }
+        }, afterReadDelAndRollback);
+    }
+
+    /**
+     *
+     */
+    public void testRepeatableReadAndDeleteWithConcurrentFastDeleteAndRollback() throws Exception {
+        doTestRepeatableRead(new IgniteInClosure<Connection>() {
+            @Override public void apply(Connection conn) {
+                execute(conn, "DELETE FROM \"Person\".Person where id = 1");
+            }
+        }, afterReadDelAndRollback);
+    }
+
+    /**
+     *
+     */
+    public void testRepeatableReadAndDeleteWithConcurrentCacheRemoveAndRollback() throws Exception {
+        doTestRepeatableRead(new IgniteInClosure<Connection>() {
+            @Override public void apply(Connection conn) {
+                personCache().remove(1);
+            }
+        }, afterReadDelAndRollback);
+    }
+
+    /**
+     *
+     */
+    public void testRepeatableReadAndFastDeleteWithConcurrentDeleteAndRollback() throws Exception {
+        doTestRepeatableRead(new IgniteInClosure<Connection>() {
+            @Override public void apply(Connection conn) {
+                execute(conn, "DELETE FROM \"Person\".Person where firstname = 'John'");
+            }
+        }, afterReadFastDelAndRollback);
+    }
+
+    /**
+     *
+     */
+    public void testRepeatableReadAndFastDeleteWithConcurrentFastDeleteAndRollback() throws Exception {
+        doTestRepeatableRead(new IgniteInClosure<Connection>() {
+            @Override public void apply(Connection conn) {
+                execute(conn, "DELETE FROM \"Person\".Person where id = 1");
+            }
+        }, afterReadFastDelAndRollback);
+    }
+
+    /**
+     *
+     */
+    public void testRepeatableReadAndFastDeleteWithConcurrentCacheRemoveAndRollback() throws Exception {
+        doTestRepeatableRead(new IgniteInClosure<Connection>() {
+            @Override public void apply(Connection conn) {
+                personCache().remove(1);
+            }
+        }, afterReadFastDelAndRollback);
+    }
+
+    /**
+     *
+     */
+    public void testRepeatableReadWithConcurrentUpdate() throws Exception {
+        doTestRepeatableRead(new IgniteInClosure<Connection>() {
+            @Override public void apply(Connection conn) {
+                execute(conn, "UPDATE \"Person\".Person SET lastname = 'Fix' where firstname = 'John'");
+            }
+        }, null);
+    }
+
+    /**
+     *
+     */
+    public void testRepeatableReadWithConcurrentCacheReplace() throws Exception {
+        doTestRepeatableRead(new IgniteInClosure<Connection>() {
+            @Override public void apply(Connection conn) {
+                Person p = new Person();
+
+                p.id = 1;
+                p.firstName = "Luke";
+                p.lastName = "Maxwell";
+
+                personCache().replace(1, p);
+            }
+        }, null);
+    }
+
+    /**
+     *
+     */
+    public void testRepeatableReadAndUpdateWithConcurrentUpdate() throws Exception {
+        doTestRepeatableRead(new IgniteInClosure<Connection>() {
+            @Override public void apply(Connection conn) {
+                execute(conn, "UPDATE \"Person\".Person SET lastname = 'Fix' where firstname = 'John'");
+            }
+        }, afterReadUpdate);
+    }
+
+    /**
+     *
+     */
+    public void testRepeatableReadAndUpdateWithConcurrentCacheReplace() throws Exception {
+        doTestRepeatableRead(new IgniteInClosure<Connection>() {
+            @Override public void apply(Connection conn) {
+                Person p = new Person();
+
+                p.id = 1;
+                p.firstName = "Luke";
+                p.lastName = "Maxwell";
+
+                personCache().replace(1, p);
+            }
+        }, afterReadUpdate);
+    }
+
+    /**
+     *
+     */
+    public void testRepeatableReadAndUpdateWithConcurrentUpdateAndRollback() throws Exception {
+        doTestRepeatableRead(new IgniteInClosure<Connection>() {
+            @Override public void apply(Connection conn) {
+                execute(conn, "UPDATE \"Person\".Person SET lastname = 'Fix' where firstname = 'John'");
+            }
+        }, afterReadUpdateAndRollback);
+    }
+
+    /**
+     *
+     */
+    public void testRepeatableReadAndUpdateWithConcurrentCacheReplaceAndRollback() throws Exception {
+        doTestRepeatableRead(new IgniteInClosure<Connection>() {
+            @Override public void apply(Connection conn) {
+                Person p = new Person();
+
+                p.id = 1;
+                p.firstName = "Luke";
+                p.lastName = "Maxwell";
+
+                personCache().replace(1, p);
+            }
+        }, afterReadUpdateAndRollback);
+    }
+
+    /**
+     * Perform repeatable reads and concurrent changes.
+     * @param concurrentWriteClo Updating closure.
+     * @param afterReadClo Closure making write changes that should also be made inside repeatable read transaction
+     *     (must yield an exception).
+     * @throws Exception if failed.
+     */
+    @SuppressWarnings("ThrowableResultOfMethodCallIgnored")
+    private void doTestRepeatableRead(final IgniteInClosure<Connection> concurrentWriteClo,
+        final IgniteInClosure<Connection> afterReadClo) throws Exception {
+        final CountDownLatch repeatableReadLatch = new CountDownLatch(1);
+
+        final CountDownLatch initLatch = new CountDownLatch(1);
+
+        final IgniteInternalFuture<?> readFut = multithreadedAsync(new Callable<Object>() {
+            @Override public Object call() throws Exception {
+                executeInTransaction(new TransactionClosure() {
+                    @Override public void apply(Connection conn) {
+                        List<?> before = flat(execute(conn, "SELECT * from \"Person\".Person where id = 1"));
+
+                        assertEqualsCollections(l(1, "John", "Smith", 1, 1), before);
+
+                        initLatch.countDown();
+
+                        try {
+                            U.await(repeatableReadLatch);
+                        }
+                        catch (IgniteInterruptedCheckedException e) {
+                            throw new IgniteException(e);
+                        }
+
+                        List<?> after = flat(execute(conn, "SELECT * from \"Person\".Person where id = 1"));
+
+                        assertEqualsCollections(before, after);
+
+                        if (afterReadClo != null)
+                            afterReadClo.apply(conn);
+                    }
+                });
+
+                return null;
+            }
+        }, 1);
+
+        IgniteInternalFuture<?> conModFut = multithreadedAsync(new Callable<Object>() {
+            @Override public Object call() throws Exception {
+                executeInTransaction(new TransactionClosure() {
+                    @Override public void apply(Connection conn) {
+                        try {
+                            U.await(initLatch);
+                        }
+                        catch (IgniteInterruptedCheckedException e) {
+                            throw new IgniteException(e);
+                        }
+
+                        concurrentWriteClo.apply(conn);
+
+                        repeatableReadLatch.countDown();
+                    }
+                });
+
+                return null;
+            }
+        }, 1);
+
+        conModFut.get();
+
+        if (afterReadClo != null) {
+            IgniteCheckedException ex = (IgniteCheckedException)GridTestUtils.assertThrows(null, new Callable() {
+                @Override public Object call() throws Exception {
+                    readFut.get();
+
+                    return null;
+                }
+            }, IgniteCheckedException.class, "Mvcc version mismatch.");
+
+            assertTrue(X.hasCause(ex, SQLException.class));
+
+            assertTrue(X.getCause(ex).getMessage().contains("Mvcc version mismatch."));
+        }
+        else
+            readFut.get();
+    }
+
+    /**
+     * Create a new connection, a new transaction and run given closure in its scope.
+     * @param clo Closure.
+     * @throws SQLException if failed.
+     */
+    private void executeInTransaction(TransactionClosure clo) throws SQLException {
+        try (Connection conn = connect()) {
+            executeInTransaction(conn, clo);
+        }
+    }
+
+    /**
+     * Create a new transaction and run given closure in its scope.
+     * @param conn Connection.
+     * @param clo Closure.
+     * @throws SQLException if failed.
+     */
+    private void executeInTransaction(Connection conn, TransactionClosure clo) throws SQLException {
+        begin(conn);
+
+        clo.apply(conn);
+
+        commit(conn);
+    }
+
+    /**
+     * @return Auto commit strategy for this test.
+     */
+    abstract boolean autoCommit();
+
+    /**
+     * @param c Connection to begin a transaction on.
+     */
+    private void begin(Connection c) throws SQLException {
+        if (autoCommit())
+            execute(c, "BEGIN");
+    }
+
+    /**
+     * @param c Connection to begin a transaction on.
+     */
+    private void commit(Connection c) throws SQLException {
+        if (autoCommit())
+            execute(c, "COMMIT");
+        else
+            c.commit();
+    }
+
+    /**
+     * @param c Connection to rollback a transaction on.
+     */
+    private void rollback(Connection c) {
+        try {
+            if (autoCommit())
+                execute(c, "ROLLBACK");
+            else
+                c.rollback();
+        }
+        catch (SQLException e) {
+            throw new IgniteException(e);
+        }
+    }
+
+    /**
+     * @param sql Statement.
+     * @param args Arguments.
+     * @return Result set.
+     * @throws SQLException if failed.
+     */
+    List<List<?>> execute(String sql, Object... args) throws SQLException {
+        try (Connection c = connect()) {
+            c.setAutoCommit(true);
+
+            return execute(c, sql, args);
+        }
+    }
+
+    /**
+     * @param sql Statement.
+     * @param args Arguments.
+     * @return Result set.
+     * @throws RuntimeException if failed.
+     */
+    protected List<List<?>> execute(Connection conn, String sql, Object... args) {
+        try {
+            return super.execute(conn, sql, args);
+        }
+        catch (SQLException e) {
+            throw new IgniteException(e);
+        }
+    }
+
+    /**
+     * @return New connection to default node.
+     * @throws SQLException if failed.
+     */
+    private Connection connect() throws SQLException {
+        return connect(null);
+    }
+
+    /**
+     * @param params Connection parameters.
+     * @return New connection to default node.
+     * @throws SQLException if failed.
+     */
+    private Connection connect(String params) throws SQLException {
+        Connection c = connect(node(), params);
+
+        c.setAutoCommit(false);
+
+        return c;
+    }
+
+    /**
+     * @param node Node to connect to.
+     * @param params Connection parameters.
+     * @return Thin JDBC connection to specified node.
+     */
+    protected Connection connect(IgniteEx node, String params) {
+        try {
+            return super.connect(node, params);
+        }
+        catch (SQLException e) {
+            throw new AssertionError(e);
+        }
+    }
+
+    /**
+     * @return Default node to fire queries from.
+     */
+    private IgniteEx node() {
+        return grid(nodeIndex());
+    }
+
+    /**
+     * @return {@link Person} cache.
+     */
+    private IgniteCache<Integer, Person> personCache() {
+        return node().cache("Person");
+    }
+
+    /**
+     * @return Node index to fire queries from.
+     */
+    abstract int nodeIndex();
+
+    /**
+     * @param id New person's id.
+     * @param firstName First name.
+     * @param lastName Second name.
+     * @param cityId City id.
+     * @param companyId Company id.
+     * @throws SQLException if failed.
+     */
+    private void insertPerson(final int id, final String firstName, final String lastName, final int cityId,
+        final int companyId) throws SQLException {
+        executeInTransaction(new TransactionClosure() {
+            @Override public void apply(Connection conn) {
+                insertPerson(conn, id, firstName, lastName, cityId, companyId);
+            }
+        });
+    }
+
+    /**
+     * @param c Connection.
+     * @param id New person's id.
+     * @param firstName First name.
+     * @param lastName Second name.
+     * @param cityId City id.
+     * @param companyId Company id.
+     */
+    private void insertPerson(Connection c, int id, String firstName, String lastName, int cityId, int companyId) {
+        execute(c, "INSERT INTO \"Person\".person (id, firstName, lastName, cityId, companyId) values (?, ?, ?, ?, ?)",
+            id, firstName, lastName, cityId, companyId);
+    }
+
+    /**
+     * @param id New city's id.
+     * @param name City name.
+     * @param population Number of people.
+     * @throws SQLException if failed.
+     */
+    private void insertCity(final int id, final String name, final int population) throws SQLException {
+        executeInTransaction(new TransactionClosure() {
+            @Override public void apply(Connection conn) {
+                insertCity(conn, id, name, population);
+            }
+        });
+    }
+
+    /**
+     * @param c Connection.
+     * @param id New city's id.
+     * @param name City name.
+     * @param population Number of people.
+     */
+    private void insertCity(Connection c, int id, String name, int population) {
+        execute(c, "INSERT INTO city (id, name, population) values (?, ?, ?)", id, name, population);
+    }
+
+    /**
+     * @param id New company's id.
+     * @param name Company name.
+     * @param cityId City id.
+     * @throws SQLException if failed.
+     */
+    private void insertCompany(final int id, final String name, final int cityId) throws SQLException {
+        executeInTransaction(new TransactionClosure() {
+            @Override public void apply(Connection conn) {
+                insertCompany(conn, id, name, cityId);
+            }
+        });
+    }
+
+    /**
+     * @param c Connection.
+     * @param id New company's id.
+     * @param name Company name.
+     * @param cityId City id.
+     */
+    private void insertCompany(Connection c, int id, String name, int cityId) {
+        execute(c, "INSERT INTO company (id, name, \"cityid\") values (?, ?, ?)", id, name, cityId);
+    }
+
+    /**
+     * @param id New product's id.
+     * @param name Product name.
+     * @param companyId Company id..
+     * @throws SQLException if failed.
+     */
+    private void insertProduct(final int id, final String name, final int companyId) throws SQLException {
+        executeInTransaction(new TransactionClosure() {
+            @Override public void apply(Connection conn) {
+                insertProduct(conn, id, name, companyId);
+            }
+        });
+    }
+
+    /**
+     * @param c Connection.
+     * @param id New product's id.
+     * @param name Product name.
+     * @param companyId Company id..
+     */
+    private void insertProduct(Connection c, int id, String name, int companyId) {
+        execute(c, "INSERT INTO product (id, name, companyid) values (?, ?, ?)", id, name, companyId);
+    }
+
+    /**
+     * Person class.
+     */
+    private final static class Person {
+        /** */
+        @QuerySqlField
+        public int id;
+
+        /** */
+        @QuerySqlField
+        public String firstName;
+
+        /** */
+        @QuerySqlField
+        public String lastName;
+    }
+
+    /**
+     * Closure to be executed in scope of a transaction.
+     */
+    private abstract class TransactionClosure implements IgniteInClosure<Connection> {
+        // No-op.
+    }
+
+    /**
+     * @return List of given arguments.
+     */
+    private static List<?> l(Object... args) {
+        return F.asList(args);
+    }
+
+    /**
+     * Flatten rows.
+     * @param rows Rows.
+     * @return Rows as a single list.
+     */
+    @SuppressWarnings("unchecked")
+    private static <T> List<T> flat(Collection<? extends Collection<?>> rows) {
+        return new ArrayList<>(F.flatCollections((Collection<? extends Collection<T>>)rows));
+    }
+}
diff --git a/modules/web-console/e2e/testcafe/page-models/pageSignin.js b/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinTransactionsClientAutoCommitComplexSelfTest.java
similarity index 59%
rename from modules/web-console/e2e/testcafe/page-models/pageSignin.js
rename to modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinTransactionsClientAutoCommitComplexSelfTest.java
index 57031b6f3a0d..d5b505a784a0 100644
--- a/modules/web-console/e2e/testcafe/page-models/pageSignin.js
+++ b/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinTransactionsClientAutoCommitComplexSelfTest.java
@@ -15,18 +15,19 @@
  * limitations under the License.
  */
 
-import {Selector, t} from 'testcafe';
-import {CustomFormField} from '../components/FormField';
+package org.apache.ignite.jdbc.thin;
 
-export const pageSignin = {
-    email: new CustomFormField({model: '$ctrl.data.email'}),
-    password: new CustomFormField({model: '$ctrl.data.password'}),
-    signinButton: Selector('button').withText('Sign In'),
-    selector: Selector('page-signin'),
-    async login(email, password) {
-        return await t
-            .typeText(this.email.control, email)
-            .typeText(this.password.control, password)
-            .click(this.signinButton);
+/**
+ *
+ */
+public class JdbcThinTransactionsClientAutoCommitComplexSelfTest extends JdbcThinTransactionsAbstractComplexSelfTest {
+    /** {@inheritDoc} */
+    @Override boolean autoCommit() {
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override int nodeIndex() {
+        return CLI_IDX;
     }
-};
+}
diff --git a/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinTransactionsClientNoAutoCommitComplexSelfTest.java b/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinTransactionsClientNoAutoCommitComplexSelfTest.java
new file mode 100644
index 000000000000..7fa69fdc468a
--- /dev/null
+++ b/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinTransactionsClientNoAutoCommitComplexSelfTest.java
@@ -0,0 +1,33 @@
+/*
+ * 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.jdbc.thin;
+
+/**
+ *
+ */
+public class JdbcThinTransactionsClientNoAutoCommitComplexSelfTest extends JdbcThinTransactionsAbstractComplexSelfTest {
+    /** {@inheritDoc} */
+    @Override boolean autoCommit() {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override int nodeIndex() {
+        return CLI_IDX;
+    }
+}
diff --git a/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinTransactionsSelfTest.java b/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinTransactionsSelfTest.java
new file mode 100644
index 000000000000..1619996be146
--- /dev/null
+++ b/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinTransactionsSelfTest.java
@@ -0,0 +1,447 @@
+/*
+ * 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.jdbc.thin;
+
+import java.sql.BatchUpdateException;
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.concurrent.Callable;
+import java.util.concurrent.atomic.AtomicBoolean;
+import org.apache.ignite.IgniteCache;
+import org.apache.ignite.IgniteCheckedException;
+import org.apache.ignite.cache.query.SqlFieldsQuery;
+import org.apache.ignite.configuration.CacheConfiguration;
+import org.apache.ignite.configuration.IgniteConfiguration;
+import org.apache.ignite.internal.IgniteInternalFuture;
+import org.apache.ignite.internal.binary.BinaryMarshaller;
+import org.apache.ignite.internal.processors.query.NestedTxMode;
+import org.apache.ignite.internal.util.typedef.internal.U;
+import org.apache.ignite.spi.discovery.tcp.TcpDiscoverySpi;
+import org.apache.ignite.spi.discovery.tcp.ipfinder.TcpDiscoveryIpFinder;
+import org.apache.ignite.spi.discovery.tcp.ipfinder.vm.TcpDiscoveryVmIpFinder;
+import org.apache.ignite.testframework.GridStringLogger;
+import org.apache.ignite.testframework.GridTestUtils;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * Tests to check behavior with transactions on.
+ */
+public class JdbcThinTransactionsSelfTest extends JdbcThinAbstractSelfTest {
+    /** IP finder. */
+    private static final TcpDiscoveryIpFinder IP_FINDER = new TcpDiscoveryVmIpFinder(true);
+
+    /** */
+    private static final String URL = "jdbc:ignite:thin://127.0.0.1";
+
+    /** Logger. */
+    private GridStringLogger log;
+
+    /** {@inheritDoc} */
+    @SuppressWarnings("deprecation")
+    @Override protected IgniteConfiguration getConfiguration(String igniteInstanceName) throws Exception {
+        IgniteConfiguration cfg = super.getConfiguration(igniteInstanceName);
+
+        cfg.setMvccEnabled(true);
+
+        cfg.setCacheConfiguration(cacheConfiguration(DEFAULT_CACHE_NAME));
+
+        TcpDiscoverySpi disco = new TcpDiscoverySpi();
+
+        disco.setIpFinder(IP_FINDER);
+
+        cfg.setDiscoverySpi(disco);
+
+        cfg.setMarshaller(new BinaryMarshaller());
+
+        cfg.setGridLogger(log = new GridStringLogger());
+
+        return cfg;
+    }
+
+    /**
+     * @param name Cache name.
+     * @return Cache configuration.
+     * @throws Exception In case of error.
+     */
+    private CacheConfiguration cacheConfiguration(@NotNull String name) throws Exception {
+        CacheConfiguration cfg = defaultCacheConfiguration();
+
+        cfg.setName(name);
+
+        return cfg;
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void beforeTest() throws Exception {
+        super.beforeTest();
+
+        startGrid(0);
+
+        try (Connection c = c(true, NestedTxMode.ERROR)) {
+            try (Statement s = c.createStatement()) {
+                s.execute("CREATE TABLE INTS (k int primary key, v int) WITH \"cache_name=ints,wrap_value=false," +
+                    "atomicity=transactional\"");
+            }
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void afterTest() throws Exception {
+        stopAllGrids();
+    }
+
+    /**
+     * @param autoCommit Auto commit mode.
+     * @param nestedTxMode Nested transactions mode.
+     * @return Connection.
+     * @throws SQLException if failed.
+     */
+    private static Connection c(boolean autoCommit, NestedTxMode nestedTxMode) throws SQLException {
+        Connection res = DriverManager.getConnection(URL + "/?nestedTransactionsMode=" + nestedTxMode.name());
+
+        res.setAutoCommit(autoCommit);
+
+        return res;
+    }
+
+    /**
+     *
+     */
+    public void testTransactionsBeginCommitRollback() throws IgniteCheckedException {
+        final AtomicBoolean stop = new AtomicBoolean();
+
+        IgniteInternalFuture<?> fut = GridTestUtils.runMultiThreadedAsync(new Runnable() {
+            @Override public void run() {
+                try {
+                    try (Connection c = c(false, NestedTxMode.ERROR)) {
+                        while (!stop.get()) {
+                            try (Statement s = c.createStatement()) {
+                                s.execute("BEGIN");
+
+                                c.commit();
+
+                                s.execute("BEGIN");
+
+                                c.rollback();
+                            }
+                        }
+                    }
+                }
+                catch (SQLException e) {
+                    throw new AssertionError(e);
+                }
+            }
+        }, 8, "jdbc-transactions");
+
+        U.sleep(5000);
+
+        stop.set(true);
+
+        fut.get();
+    }
+
+    /**
+     *
+     */
+    public void testTransactionsBeginCommitRollbackAutocommit() throws IgniteCheckedException {
+        GridTestUtils.runMultiThreadedAsync(new Runnable() {
+            @Override public void run() {
+                try {
+                    try (Connection c = c(true, NestedTxMode.ERROR)) {
+                        try (Statement s = c.createStatement())  {
+                            s.execute("BEGIN");
+
+                            s.execute("COMMIT");
+
+                            s.execute("BEGIN");
+
+                            s.execute("ROLLBACK");
+                        }
+                    }
+                }
+                catch (SQLException e) {
+                    throw new AssertionError(e);
+                }
+            }
+        }, 8, "jdbc-transactions").get();
+    }
+
+    /**
+     *
+     */
+    @SuppressWarnings("ThrowableResultOfMethodCallIgnored")
+    public void testIgnoreNestedTxAutocommitOff() throws SQLException {
+        try (Connection c = c(false, NestedTxMode.IGNORE)) {
+            doNestedTxStart(c, false);
+        }
+
+        assertTrue(log.toString().contains("ignoring BEGIN command"));
+    }
+
+    /**
+     *
+     */
+    @SuppressWarnings("ThrowableResultOfMethodCallIgnored")
+    public void testCommitNestedTxAutocommitOff() throws SQLException {
+        try (Connection c = c(false, NestedTxMode.COMMIT)) {
+            doNestedTxStart(c, false);
+        }
+
+        assertFalse(log.toString().contains("ignoring BEGIN command"));
+    }
+
+    /**
+     *
+     */
+    @SuppressWarnings("ThrowableResultOfMethodCallIgnored")
+    public void testErrorNestedTxAutocommitOff() throws SQLException {
+        GridTestUtils.assertThrows(null, new Callable<Void>() {
+            @Override public Void call() throws Exception {
+                try (Connection c = c(false, NestedTxMode.ERROR)) {
+                    doNestedTxStart(c, false);
+                }
+
+                throw new AssertionError();
+            }
+        }, SQLException.class, "Transaction has already been started.");
+    }
+
+    /**
+     *
+     */
+    @SuppressWarnings("ThrowableResultOfMethodCallIgnored")
+    public void testIgnoreNestedTxAutocommitOn() throws SQLException {
+        try (Connection c = c(true, NestedTxMode.IGNORE)) {
+            doNestedTxStart(c, false);
+        }
+
+        assertTrue(log.toString().contains("ignoring BEGIN command"));
+    }
+
+    /**
+     *
+     */
+    @SuppressWarnings("ThrowableResultOfMethodCallIgnored")
+    public void testCommitNestedTxAutocommitOn() throws SQLException {
+        try (Connection c = c(true, NestedTxMode.COMMIT)) {
+            doNestedTxStart(c, false);
+        }
+
+        assertFalse(log.toString().contains("ignoring BEGIN command"));
+    }
+
+    /**
+     *
+     */
+    @SuppressWarnings("ThrowableResultOfMethodCallIgnored")
+    public void testErrorNestedTxAutocommitOn() throws SQLException {
+        GridTestUtils.assertThrows(null, new Callable<Void>() {
+            @Override public Void call() throws Exception {
+                try (Connection c = c(true, NestedTxMode.ERROR)) {
+                    doNestedTxStart(c, false);
+                }
+
+                throw new AssertionError();
+            }
+        }, SQLException.class, "Transaction has already been started.");
+    }
+
+    /**
+     *
+     */
+    @SuppressWarnings("ThrowableResultOfMethodCallIgnored")
+    public void testIgnoreNestedTxAutocommitOffBatched() throws SQLException {
+        try (Connection c = c(false, NestedTxMode.IGNORE)) {
+            doNestedTxStart(c, true);
+        }
+
+        assertTrue(log.toString().contains("ignoring BEGIN command"));
+    }
+
+    /**
+     *
+     */
+    @SuppressWarnings("ThrowableResultOfMethodCallIgnored")
+    public void testCommitNestedTxAutocommitOffBatched() throws SQLException {
+        try (Connection c = c(false, NestedTxMode.COMMIT)) {
+            doNestedTxStart(c, true);
+        }
+
+        assertFalse(log.toString().contains("ignoring BEGIN command"));
+    }
+
+    /**
+     *
+     */
+    @SuppressWarnings("ThrowableResultOfMethodCallIgnored")
+    public void testErrorNestedTxAutocommitOffBatched() throws SQLException {
+        GridTestUtils.assertThrows(null, new Callable<Void>() {
+            @Override public Void call() throws Exception {
+                try (Connection c = c(false, NestedTxMode.ERROR)) {
+                    doNestedTxStart(c, true);
+                }
+
+                throw new AssertionError();
+            }
+        }, BatchUpdateException.class, "Transaction has already been started.");
+    }
+
+    /**
+     *
+     */
+    @SuppressWarnings("ThrowableResultOfMethodCallIgnored")
+    public void testIgnoreNestedTxAutocommitOnBatched() throws SQLException {
+        try (Connection c = c(true, NestedTxMode.IGNORE)) {
+            doNestedTxStart(c, true);
+        }
+
+        assertTrue(log.toString().contains("ignoring BEGIN command"));
+    }
+
+    /**
+     *
+     */
+    @SuppressWarnings("ThrowableResultOfMethodCallIgnored")
+    public void testCommitNestedTxAutocommitOnBatched() throws SQLException {
+        try (Connection c = c(true, NestedTxMode.COMMIT)) {
+            doNestedTxStart(c, true);
+        }
+
+        assertFalse(log.toString().contains("ignoring BEGIN command"));
+    }
+
+    /**
+     *
+     */
+    @SuppressWarnings("ThrowableResultOfMethodCallIgnored")
+    public void testErrorNestedTxAutocommitOnBatched() throws SQLException {
+        GridTestUtils.assertThrows(null, new Callable<Void>() {
+            @Override public Void call() throws Exception {
+                try (Connection c = c(true, NestedTxMode.ERROR)) {
+                    doNestedTxStart(c, true);
+                }
+
+                throw new AssertionError();
+            }
+        }, BatchUpdateException.class, "Transaction has already been started.");
+    }
+
+    /**
+     * Try to start nested transaction via batch as well as separate statements.
+     * @param conn Connection.
+     * @param batched Whether {@link Statement#executeBatch()} should be used.
+     * @throws SQLException if failed.
+     */
+    private void doNestedTxStart(Connection conn, boolean batched) throws SQLException {
+        try (Statement s = conn.createStatement()) {
+            s.executeQuery("SELECT * FROM INTS");
+
+            if (batched) {
+                s.addBatch("BEGIN");
+
+                s.addBatch("BEGIN");
+
+                s.executeBatch();
+            }
+            else {
+                s.execute("BEGIN");
+
+                s.execute("BEGIN");
+            }
+        }
+    }
+
+    /**
+     * @throws SQLException if failed.
+     */
+    public void testAutoCommitSingle() throws SQLException {
+        doTestAutoCommit(false);
+    }
+
+    /**
+     * @throws SQLException if failed.
+     */
+    public void testAutoCommitBatched() throws SQLException {
+        doTestAutoCommit(true);
+    }
+
+    /**
+     * @param batched Batch mode flag.
+     * @throws SQLException if failed.
+     */
+    private void doTestAutoCommit(boolean batched) throws SQLException {
+        IgniteCache<Integer, ?> cache = grid(0).cache("ints");
+
+        try (Connection c = c(false, NestedTxMode.ERROR)) {
+            try (Statement s = c.createStatement()) {
+                assertFalse(s.executeQuery("SELECT * from INTS").next());
+
+                if (batched) {
+                    s.addBatch("INSERT INTO INTS(k, v) values(1, 1)");
+
+                    s.executeBatch();
+                }
+                else
+                    s.execute("INSERT INTO INTS(k, v) values(1, 1)");
+
+                // We haven't committed anything yet - this check shows that autoCommit flag is in effect.
+                assertTrue(cache.query(new SqlFieldsQuery("SELECT * from INTS")).getAll().isEmpty());
+
+                // We should see own updates.
+                assertTrue(s.executeQuery("SELECT * from INTS").next());
+
+                c.commit();
+
+                c.setAutoCommit(true);
+
+                assertEquals(1, cache.get(1));
+
+                assertTrue(s.executeQuery("SELECT * from INTS").next());
+            }
+        }
+    }
+
+    /**
+     * Test that exception in one of the statements does not kill connection worker altogether.
+     * @throws SQLException if failed.
+     */
+    @SuppressWarnings("ThrowableResultOfMethodCallIgnored")
+    public void testExceptionHandling() throws SQLException {
+        try (Connection c = c(true, NestedTxMode.ERROR)) {
+            try (Statement s = c.createStatement()) {
+                s.execute("INSERT INTO INTS(k, v) values(1, 1)");
+
+                assertEquals(1, grid(0).cache("ints").get(1));
+
+                GridTestUtils.assertThrows(null, new Callable<Void>() {
+                    @Override public Void call() throws Exception {
+                        s.execute("INSERT INTO INTS(x, y) values(1, 1)");
+
+                        return null;
+                    }
+                }, SQLException.class, "Failed to parse query");
+
+                s.execute("INSERT INTO INTS(k, v) values(2, 2)");
+
+                assertEquals(2, grid(0).cache("ints").get(2));
+            }
+        }
+    }
+}
diff --git a/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinTransactionsServerAutoCommitComplexSelfTest.java b/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinTransactionsServerAutoCommitComplexSelfTest.java
new file mode 100644
index 000000000000..3c473ab872d5
--- /dev/null
+++ b/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinTransactionsServerAutoCommitComplexSelfTest.java
@@ -0,0 +1,33 @@
+/*
+ * 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.jdbc.thin;
+
+/**
+ *
+ */
+public class JdbcThinTransactionsServerAutoCommitComplexSelfTest extends JdbcThinTransactionsAbstractComplexSelfTest {
+    /** {@inheritDoc} */
+    @Override boolean autoCommit() {
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override int nodeIndex() {
+        return 0;
+    }
+}
diff --git a/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinTransactionsServerNoAutoCommitComplexSelfTest.java b/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinTransactionsServerNoAutoCommitComplexSelfTest.java
new file mode 100644
index 000000000000..655d4c5c8f93
--- /dev/null
+++ b/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinTransactionsServerNoAutoCommitComplexSelfTest.java
@@ -0,0 +1,33 @@
+/*
+ * 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.jdbc.thin;
+
+/**
+ *
+ */
+public class JdbcThinTransactionsServerNoAutoCommitComplexSelfTest extends JdbcThinTransactionsAbstractComplexSelfTest {
+    /** {@inheritDoc} */
+    @Override boolean autoCommit() {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override int nodeIndex() {
+        return 0;
+    }
+}
diff --git a/modules/codegen/src/main/java/org/apache/ignite/codegen/MessageCodeGenerator.java b/modules/codegen/src/main/java/org/apache/ignite/codegen/MessageCodeGenerator.java
index 99cf84946ab9..51365f39766a 100644
--- a/modules/codegen/src/main/java/org/apache/ignite/codegen/MessageCodeGenerator.java
+++ b/modules/codegen/src/main/java/org/apache/ignite/codegen/MessageCodeGenerator.java
@@ -38,6 +38,8 @@
 import java.util.Set;
 import java.util.TreeSet;
 import java.util.UUID;
+import org.apache.ignite.internal.processors.cache.distributed.near.GridNearTxQueryEnlistRequest;
+import org.apache.ignite.internal.processors.cache.distributed.near.GridNearTxQueryResultsEnlistRequest;
 import org.apache.ignite.internal.util.IgniteUtils;
 import org.apache.ignite.internal.GridCodegenConverter;
 import org.apache.ignite.internal.GridDirectCollection;
@@ -168,7 +170,7 @@ public static void main(String[] args) throws Exception {
 
 //        gen.generateAll(true);
 
-//        gen.generateAndWrite(GridChangeGlobalStateMessageResponse.class);
+        gen.generateAndWrite(GridNearTxQueryResultsEnlistRequest.class);
 
 //        gen.generateAndWrite(GridNearAtomicUpdateRequest.class);
 
@@ -235,6 +237,8 @@ public static void main(String[] args) throws Exception {
 //        gen.generateAndWrite(GridCacheVersionEx.class);
 //        gen.generateAndWrite(GridH2DmlRequest.class);
 //        gen.generateAndWrite(GridH2DmlResponse.class);
+//        gen.generateAndWrite(GridNearTxEnlistRequest.class);
+//        gen.generateAndWrite(GridNearTxEnlistResponse.class);
     }
 
     /**
diff --git a/modules/core/src/main/java/org/apache/ignite/configuration/CacheConfiguration.java b/modules/core/src/main/java/org/apache/ignite/configuration/CacheConfiguration.java
index 2e35f37077b8..fb3789d4df22 100644
--- a/modules/core/src/main/java/org/apache/ignite/configuration/CacheConfiguration.java
+++ b/modules/core/src/main/java/org/apache/ignite/configuration/CacheConfiguration.java
@@ -1019,6 +1019,7 @@ public CacheAtomicityMode getAtomicityMode() {
      * @param atomicityMode Cache atomicity mode.
      * @return {@code this} for chaining.
      */
+    @SuppressWarnings("unchecked")
     public CacheConfiguration<K, V> setAtomicityMode(CacheAtomicityMode atomicityMode) {
         this.atomicityMode = atomicityMode;
 
diff --git a/modules/core/src/main/java/org/apache/ignite/configuration/IgniteConfiguration.java b/modules/core/src/main/java/org/apache/ignite/configuration/IgniteConfiguration.java
index ab547091dc82..3060caa6ce3e 100644
--- a/modules/core/src/main/java/org/apache/ignite/configuration/IgniteConfiguration.java
+++ b/modules/core/src/main/java/org/apache/ignite/configuration/IgniteConfiguration.java
@@ -214,6 +214,13 @@
     /** Default timeout after which long query warning will be printed. */
     public static final long DFLT_LONG_QRY_WARN_TIMEOUT = 3000;
 
+    /** Default size of MVCC vacuum thread pool. */
+    public static final int DFLT_MVCC_VACUUM_THREAD_CNT = 2;
+
+    /** Default time interval between vacuum process runs (ms). */
+    public static final int DFLT_MVCC_VACUUM_TIME_INTERVAL = 5000;
+
+
     /** Optional local Ignite instance name. */
     private String igniteInstanceName;
 
@@ -487,6 +494,15 @@
     /** Client connector configuration. */
     private ClientConnectorConfiguration cliConnCfg = ClientListenerProcessor.DFLT_CLI_CFG;
 
+    /** Flag whether MVCC is enabled. */
+    private boolean mvccEnabled;
+
+    /** Size of MVCC vacuum thread pool. */
+    private int mvccVacuumThreadCnt = DFLT_MVCC_VACUUM_THREAD_CNT;
+
+    /** Time interval between vacuum process runs (ms). */
+    private int mvccVacuumTimeInterval = DFLT_MVCC_VACUUM_TIME_INTERVAL;
+
     /** User authentication enabled. */
     private boolean authEnabled;
 
@@ -535,6 +551,7 @@ public IgniteConfiguration(IgniteConfiguration cfg) {
         addrRslvr = cfg.getAddressResolver();
         allResolversPassReq = cfg.isAllSegmentationResolversPassRequired();
         atomicCfg = cfg.getAtomicConfiguration();
+        authEnabled = cfg.isAuthenticationEnabled();
         autoActivation = cfg.isAutoActivationEnabled();
         binaryCfg = cfg.getBinaryConfiguration();
         dsCfg = cfg.getDataStorageConfiguration();
@@ -578,6 +595,9 @@ public IgniteConfiguration(IgniteConfiguration cfg) {
         metricsLogFreq = cfg.getMetricsLogFrequency();
         metricsUpdateFreq = cfg.getMetricsUpdateFrequency();
         mgmtPoolSize = cfg.getManagementThreadPoolSize();
+        mvccEnabled = cfg.isMvccEnabled();
+        mvccVacuumThreadCnt = cfg.mvccVacuumThreadCnt;
+        mvccVacuumTimeInterval = cfg.mvccVacuumTimeInterval;
         netTimeout = cfg.getNetworkTimeout();
         nodeId = cfg.getNodeId();
         odbcCfg = cfg.getOdbcConfiguration();
@@ -612,7 +632,6 @@ public IgniteConfiguration(IgniteConfiguration cfg) {
         utilityCachePoolSize = cfg.getUtilityCacheThreadPoolSize();
         waitForSegOnStart = cfg.isWaitForSegmentOnStart();
         warmupClos = cfg.getWarmupClosure();
-        authEnabled = cfg.isAuthenticationEnabled();
     }
 
     /**
@@ -2983,6 +3002,69 @@ public IgniteConfiguration setFailureHandler(FailureHandler failureHnd) {
         return cliConnCfg;
     }
 
+    /**
+     * Whether or not MVCC is enabled.
+     *
+     * @return {@code True} if MVCC is enabled.
+     */
+    public boolean isMvccEnabled() {
+        return mvccEnabled;
+    }
+
+    /**
+     * Sets MVCC enabled flag.
+     *
+     * @param mvccEnabled MVCC enabled flag.
+     * @return {@code this} for chaining.
+     */
+    public IgniteConfiguration setMvccEnabled(boolean mvccEnabled) {
+        this.mvccEnabled = mvccEnabled;
+
+        return this;
+    }
+
+    /**
+     * Returns number of MVCC vacuum cleanup threads.
+     *
+     * @return Number of MVCC vacuum cleanup threads.
+     */
+    public int getMvccVacuumThreadCnt() {
+        return mvccVacuumThreadCnt;
+    }
+
+    /**
+     * Sets number of MVCC vacuum cleanup threads.
+     *
+     * @param mvccVacuumThreadCnt Number of MVCC vacuum cleanup threads.
+     * @return {@code this} for chaining.
+     */
+    public IgniteConfiguration setMvccVacuumThreadCnt(int mvccVacuumThreadCnt) {
+        this.mvccVacuumThreadCnt = mvccVacuumThreadCnt;
+
+        return this;
+    }
+
+    /**
+     * Returns time interval between vacuum runs.
+     *
+     * @return Time interval between vacuum runs.
+     */
+    public int getMvccVacuumTimeInterval() {
+        return mvccVacuumTimeInterval;
+    }
+
+    /**
+     * Sets time interval between vacuum runs.
+     *
+     * @param mvccVacuumTimeInterval Time interval between vacuum runs.
+     * @return {@code this} for chaining.
+     */
+    public IgniteConfiguration setMvccVacuumTimeInterval(int mvccVacuumTimeInterval) {
+        this.mvccVacuumTimeInterval = mvccVacuumTimeInterval;
+
+        return this;
+    }
+
     /**
      * Returns {@code true} if user authentication is enabled for cluster. Otherwise returns {@code false}.
      * Default value is false; authentication is disabled.
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/GridComponent.java b/modules/core/src/main/java/org/apache/ignite/internal/GridComponent.java
index 504c83f34eb3..0cf3a6eb3477 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/GridComponent.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/GridComponent.java
@@ -64,7 +64,10 @@
         QUERY_PROC,
 
         /** Authentication processor. */
-        AUTH_PROC
+        AUTH_PROC,
+
+        /** */
+        CACHE_CRD_PROC
     }
 
     /**
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/GridKernalContext.java b/modules/core/src/main/java/org/apache/ignite/internal/GridKernalContext.java
index 051978c0fc72..0690565e0492 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/GridKernalContext.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/GridKernalContext.java
@@ -32,6 +32,7 @@
 import org.apache.ignite.internal.managers.failover.GridFailoverManager;
 import org.apache.ignite.internal.managers.indexing.GridIndexingManager;
 import org.apache.ignite.internal.managers.loadbalancer.GridLoadBalancerManager;
+import org.apache.ignite.internal.processors.cache.mvcc.MvccProcessor;
 import org.apache.ignite.internal.worker.WorkersRegistry;
 import org.apache.ignite.internal.processors.affinity.GridAffinityProcessor;
 import org.apache.ignite.internal.processors.authentication.IgniteAuthenticationProcessor;
@@ -670,6 +671,11 @@
      */
     public PlatformProcessor platform();
 
+    /**
+     * @return Cache mvcc coordinator processor.
+     */
+    public MvccProcessor coordinators();
+
     /**
      * @return PDS mode folder name resolver, also generates consistent ID in case new folder naming is used
      */
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/GridKernalContextImpl.java b/modules/core/src/main/java/org/apache/ignite/internal/GridKernalContextImpl.java
index 2be64e5d1330..3b7b43072d99 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/GridKernalContextImpl.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/GridKernalContextImpl.java
@@ -47,6 +47,7 @@
 import org.apache.ignite.internal.managers.failover.GridFailoverManager;
 import org.apache.ignite.internal.managers.indexing.GridIndexingManager;
 import org.apache.ignite.internal.managers.loadbalancer.GridLoadBalancerManager;
+import org.apache.ignite.internal.processors.cache.mvcc.MvccProcessor;
 import org.apache.ignite.internal.worker.WorkersRegistry;
 import org.apache.ignite.internal.processors.affinity.GridAffinityProcessor;
 import org.apache.ignite.internal.processors.authentication.IgniteAuthenticationProcessor;
@@ -290,6 +291,10 @@
     @GridToStringExclude
     private DataStructuresProcessor dataStructuresProc;
 
+    /** Cache mvcc coordinators. */
+    @GridToStringExclude
+    private MvccProcessor coordProc;
+
     /** */
     @GridToStringExclude
     private IgniteAuthenticationProcessor authProc;
@@ -356,7 +361,7 @@
 
     /** */
     @GridToStringExclude
-    Map<String, ? extends ExecutorService> customExecSvcs;
+    private Map<String, ? extends ExecutorService> customExecSvcs;
 
     /** */
     @GridToStringExclude
@@ -610,6 +615,8 @@ else if (comp instanceof PoolProcessor)
             poolProc = (PoolProcessor)comp;
         else if (comp instanceof GridMarshallerMappingProcessor)
             mappingProc = (GridMarshallerMappingProcessor)comp;
+        else if (comp instanceof MvccProcessor)
+            coordProc = (MvccProcessor)comp;
         else if (comp instanceof PdsFoldersResolver)
             pdsFolderRslvr = (PdsFoldersResolver)comp;
         else if (comp instanceof GridInternalSubscriptionProcessor)
@@ -879,6 +886,11 @@ else if (helper instanceof HadoopHelper)
         return dataStructuresProc;
     }
 
+    /** {@inheritDoc} */
+    @Override public MvccProcessor coordinators() {
+        return coordProc;
+    }
+
     /** {@inheritDoc} */
     @Override public IgniteAuthenticationProcessor authentication() {
         return authProc;
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/GridTopic.java b/modules/core/src/main/java/org/apache/ignite/internal/GridTopic.java
index 0b2d41a39ec8..68cea4f30e0f 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/GridTopic.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/GridTopic.java
@@ -127,7 +127,10 @@
     TOPIC_AUTH,
 
     /** */
-    TOPIC_EXCHANGE;
+    TOPIC_EXCHANGE,
+
+    /** */
+    TOPIC_CACHE_COORDINATOR;
 
     /** Enum values. */
     private static final GridTopic[] VALS = values();
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/IgniteKernal.java b/modules/core/src/main/java/org/apache/ignite/internal/IgniteKernal.java
index eef2140d721c..230c05c0a089 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/IgniteKernal.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/IgniteKernal.java
@@ -125,6 +125,7 @@
 import org.apache.ignite.internal.processors.cache.IgniteCacheProxy;
 import org.apache.ignite.internal.processors.cache.IgniteInternalCache;
 import org.apache.ignite.internal.processors.cache.binary.CacheObjectBinaryProcessorImpl;
+import org.apache.ignite.internal.processors.cache.mvcc.MvccUtils;
 import org.apache.ignite.internal.processors.cache.persistence.DataRegion;
 import org.apache.ignite.internal.processors.cache.persistence.filename.PdsConsistentIdProcessor;
 import org.apache.ignite.internal.processors.cacheobject.IgniteCacheObjectProcessor;
@@ -993,6 +994,7 @@ public void start(
             // be able to start receiving messages once discovery completes.
             try {
                 startProcessor(new PdsConsistentIdProcessor(ctx));
+                startProcessor(MvccUtils.createProcessor(ctx));
                 startProcessor(createComponent(DiscoveryNodeValidationProcessor.class, ctx));
                 startProcessor(new GridAffinityProcessor(ctx));
                 startProcessor(createComponent(GridSegmentationProcessor.class, ctx));
@@ -2761,7 +2763,7 @@ private void ackIPv4StackFlagIsSet() {
     /** {@inheritDoc} */
     @Override public <K, V> IgniteCache<K, V> createCache(CacheConfiguration<K, V> cacheCfg) {
         A.notNull(cacheCfg, "cacheCfg");
-        CU.validateCacheName(cacheCfg.getName());
+        CU.validateNewCacheName(cacheCfg.getName());
 
         guard();
 
@@ -2817,7 +2819,7 @@ private void ackIPv4StackFlagIsSet() {
 
     /** {@inheritDoc} */
     @Override public <K, V> IgniteCache<K, V> createCache(String cacheName) {
-        CU.validateCacheName(cacheName);
+        CU.validateNewCacheName(cacheName);
 
         guard();
 
@@ -2846,7 +2848,7 @@ private void ackIPv4StackFlagIsSet() {
     @Override public <K, V> IgniteBiTuple<IgniteCache<K, V>, Boolean> getOrCreateCache0(
         CacheConfiguration<K, V> cacheCfg, boolean sql) {
         A.notNull(cacheCfg, "cacheCfg");
-        CU.validateCacheName(cacheCfg.getName());
+        CU.validateNewCacheName(cacheCfg.getName());
 
         guard();
 
@@ -2912,7 +2914,7 @@ private void ackIPv4StackFlagIsSet() {
         NearCacheConfiguration<K, V> nearCfg
     ) {
         A.notNull(cacheCfg, "cacheCfg");
-        CU.validateCacheName(cacheCfg.getName());
+        CU.validateNewCacheName(cacheCfg.getName());
         A.notNull(nearCfg, "nearCfg");
 
         guard();
@@ -2941,7 +2943,7 @@ private void ackIPv4StackFlagIsSet() {
     @Override public <K, V> IgniteCache<K, V> getOrCreateCache(CacheConfiguration<K, V> cacheCfg,
         NearCacheConfiguration<K, V> nearCfg) {
         A.notNull(cacheCfg, "cacheCfg");
-        CU.validateCacheName(cacheCfg.getName());
+        CU.validateNewCacheName(cacheCfg.getName());
         A.notNull(nearCfg, "nearCfg");
 
         guard();
@@ -2982,7 +2984,7 @@ private void ackIPv4StackFlagIsSet() {
 
     /** {@inheritDoc} */
     @Override public <K, V> IgniteCache<K, V> createNearCache(String cacheName, NearCacheConfiguration<K, V> nearCfg) {
-        CU.validateCacheName(cacheName);
+        CU.validateNewCacheName(cacheName);
         A.notNull(nearCfg, "nearCfg");
 
         guard();
@@ -3014,7 +3016,7 @@ private void ackIPv4StackFlagIsSet() {
     /** {@inheritDoc} */
     @Override public <K, V> IgniteCache<K, V> getOrCreateNearCache(String cacheName,
         NearCacheConfiguration<K, V> nearCfg) {
-        CU.validateCacheName(cacheName);
+        CU.validateNewCacheName(cacheName);
         A.notNull(nearCfg, "nearCfg");
 
         guard();
@@ -3143,7 +3145,7 @@ private void checkNearCacheStarted(IgniteCacheProxy<?, ?> cache) throws IgniteCh
 
     /** {@inheritDoc} */
     @Override public <K, V> IgniteCache<K, V> getOrCreateCache(String cacheName) {
-        CU.validateCacheName(cacheName);
+        CU.validateNewCacheName(cacheName);
 
         guard();
 
@@ -3172,7 +3174,7 @@ private void checkNearCacheStarted(IgniteCacheProxy<?, ?> cache) throws IgniteCh
      */
     public IgniteInternalFuture<?> getOrCreateCacheAsync(String cacheName, String templateName,
         CacheConfigurationOverride cfgOverride, boolean checkThreadTx) {
-        CU.validateCacheName(cacheName);
+        CU.validateNewCacheName(cacheName);
 
         guard();
 
@@ -3192,7 +3194,7 @@ private void checkNearCacheStarted(IgniteCacheProxy<?, ?> cache) throws IgniteCh
     /** {@inheritDoc} */
     @Override public <K, V> void addCacheConfiguration(CacheConfiguration<K, V> cacheCfg) {
         A.notNull(cacheCfg, "cacheCfg");
-        CU.validateCacheName(cacheCfg.getName());
+        CU.validateNewCacheName(cacheCfg.getName());
 
         guard();
 
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/IgniteNodeAttributes.java b/modules/core/src/main/java/org/apache/ignite/internal/IgniteNodeAttributes.java
index ed16a7715b5b..61a98a138b6a 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/IgniteNodeAttributes.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/IgniteNodeAttributes.java
@@ -205,6 +205,9 @@
     /** Internal attribute name constant. */
     public static final String ATTR_DYNAMIC_CACHE_START_ROLLBACK_SUPPORTED = ATTR_PREFIX + ".dynamic.cache.start.rollback.supported";
 
+    /** Mvcc enabled flag. */
+    public static final String ATTR_MVCC_ENABLED = ATTR_PREFIX + ".mvcc.enabled";
+
     /**
      * Enforces singleton.
      */
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/IgnitionEx.java b/modules/core/src/main/java/org/apache/ignite/internal/IgnitionEx.java
index 2b408df7ef37..148bd2157841 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/IgnitionEx.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/IgnitionEx.java
@@ -2349,12 +2349,8 @@ public void initializeDefaultCacheConfiguration(IgniteConfiguration cfg) throws
                         "like TcpDiscoverySpi)");
 
                 for (CacheConfiguration ccfg : userCaches) {
-                    if (CU.isHadoopSystemCache(ccfg.getName()))
-                        throw new IgniteCheckedException("Cache name cannot be \"" + CU.SYS_CACHE_HADOOP_MR +
-                            "\" because it is reserved for internal purposes.");
-
-                    if (CU.isUtilityCache(ccfg.getName()))
-                        throw new IgniteCheckedException("Cache name cannot be \"" + CU.UTILITY_CACHE_NAME +
+                    if (CU.isReservedCacheName(ccfg.getName()))
+                        throw new IgniteCheckedException("Cache name cannot be \"" + ccfg.getName() +
                             "\" because it is reserved for internal purposes.");
 
                     if (IgfsUtils.matchIgfsCacheName(ccfg.getName()))
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/ConnectionProperties.java b/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/ConnectionProperties.java
index 92355a07a155..5e40bbfc79dd 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/ConnectionProperties.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/ConnectionProperties.java
@@ -163,6 +163,16 @@
      */
     public void setSkipReducerOnUpdate(boolean skipReducerOnUpdate);
 
+    /**
+     * @return Nested transactions handling strategy.
+     */
+    public String nestedTxMode();
+
+    /**
+     * @param nestedTxMode Nested transactions handling strategy.
+     */
+    public void nestedTxMode(String nestedTxMode);
+
     /**
      * Gets SSL connection mode.
      *
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/ConnectionPropertiesImpl.java b/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/ConnectionPropertiesImpl.java
index 9371a078a737..51a3837788bc 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/ConnectionPropertiesImpl.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/ConnectionPropertiesImpl.java
@@ -27,6 +27,7 @@
 import org.apache.ignite.configuration.ClientConnectorConfiguration;
 import org.apache.ignite.internal.processors.odbc.SqlStateCode;
 import org.apache.ignite.internal.util.HostAndPortRange;
+import org.apache.ignite.internal.processors.query.NestedTxMode;
 import org.apache.ignite.internal.util.typedef.F;
 
 /**
@@ -99,6 +100,26 @@
     private BooleanProperty skipReducerOnUpdate = new BooleanProperty(
         "skipReducerOnUpdate", "Enable execution update queries on ignite server nodes", false, false);
 
+    /** Nested transactions handling strategy. */
+    private StringProperty nestedTxMode = new StringProperty(
+        "nestedTransactionsMode", "Way to handle nested transactions", NestedTxMode.ERROR.name(),
+        new String[] { NestedTxMode.COMMIT.name(), NestedTxMode.ERROR.name(), NestedTxMode.IGNORE.name() },
+        false, new PropertyValidator() {
+        private static final long serialVersionUID = 0L;
+
+        @Override public void validate(String mode) throws SQLException {
+            if (!F.isEmpty(mode)) {
+                try {
+                    NestedTxMode.valueOf(mode.toUpperCase());
+                }
+                catch (IllegalArgumentException e) {
+                    throw new SQLException("Invalid nested transactions handling mode, allowed values: " +
+                        Arrays.toString(nestedTxMode.choices), SqlStateCode.CLIENT_CONNECTION_FAILED);
+                }
+            }
+        }
+    });
+
     /** SSL: Use SSL connection to Ignite node. */
     private StringProperty sslMode = new StringProperty("sslMode",
         "The SSL mode of the connection", SSL_MODE_DISABLE,
@@ -165,7 +186,7 @@
     /** Properties array. */
     private final ConnectionProperty [] propsArray = {
         distributedJoins, enforceJoinOrder, collocated, replicatedOnly, autoCloseServerCursor,
-        tcpNoDelay, lazy, socketSendBuffer, socketReceiveBuffer, skipReducerOnUpdate,
+        tcpNoDelay, lazy, socketSendBuffer, socketReceiveBuffer, skipReducerOnUpdate, nestedTxMode,
         sslMode, sslProtocol, sslKeyAlgorithm,
         sslClientCertificateKeyStoreUrl, sslClientCertificateKeyStorePassword, sslClientCertificateKeyStoreType,
         sslTrustCertificateKeyStoreUrl, sslTrustCertificateKeyStorePassword, sslTrustCertificateKeyStoreType,
@@ -436,6 +457,16 @@
         this.sslFactory.setValue(sslFactory);
     }
 
+    /** {@inheritDoc} */
+    @Override public String nestedTxMode() {
+        return nestedTxMode.value();
+    }
+
+    /** {@inheritDoc} */
+    @Override public void nestedTxMode(String val) {
+        nestedTxMode.setValue(val);
+    }
+
     /** {@inheritDoc} */
     @Override public void setUsername(String name) {
         user.setValue(name);
@@ -815,11 +846,11 @@ void init(Properties props) throws SQLException {
                     SqlStateCode.CLIENT_CONNECTION_FAILED);
             }
 
-            checkChoices(strVal);
-
             if (validator != null)
                 validator.validate(strVal);
 
+            checkChoices(strVal);
+
             props.remove(name);
 
             init(strVal);
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/JdbcThinConnection.java b/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/JdbcThinConnection.java
index b36b3194c1a7..547b1db8cd41 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/JdbcThinConnection.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/JdbcThinConnection.java
@@ -197,7 +197,7 @@ void executeNative(String sql, SqlCommand cmd) throws SQLException {
                 }
 
                 sendRequest(new JdbcQueryExecuteRequest(JdbcStatementType.ANY_STATEMENT_TYPE,
-                    schema, 1, 1, sql, null));
+                    schema, 1, 1, autoCommit, sql, null));
 
                 streamState = new StreamState((SqlSetStreamingCommand)cmd);
             }
@@ -328,19 +328,18 @@ private void checkCursorOptions(int resSetType, int resSetConcurrency,
     @Override public void setAutoCommit(boolean autoCommit) throws SQLException {
         ensureNotClosed();
 
-        this.autoCommit = autoCommit;
+        // Do nothing if resulting value doesn't actually change.
+        if (autoCommit != this.autoCommit) {
+            doCommit();
 
-        if (!autoCommit)
-            LOG.warning("Transactions are not supported.");
+            this.autoCommit = autoCommit;
+        }
     }
 
     /** {@inheritDoc} */
     @Override public boolean getAutoCommit() throws SQLException {
         ensureNotClosed();
 
-        if (!autoCommit)
-            LOG.warning("Transactions are not supported.");
-
         return autoCommit;
     }
 
@@ -351,7 +350,7 @@ private void checkCursorOptions(int resSetType, int resSetConcurrency,
         if (autoCommit)
             throw new SQLException("Transaction cannot be committed explicitly in auto-commit mode.");
 
-        LOG.warning("Transactions are not supported.");
+        doCommit();
     }
 
     /** {@inheritDoc} */
@@ -359,9 +358,21 @@ private void checkCursorOptions(int resSetType, int resSetConcurrency,
         ensureNotClosed();
 
         if (autoCommit)
-            throw new SQLException("Transaction cannot rollback in auto-commit mode.");
+            throw new SQLException("Transaction cannot be rolled back explicitly in auto-commit mode.");
 
-        LOG.warning("Transactions are not supported.");
+        try (Statement s = createStatement()) {
+            s.execute("ROLLBACK");
+        }
+    }
+
+    /**
+     * Send to the server {@code COMMIT} command.
+     * @throws SQLException if failed.
+     */
+    private void doCommit() throws SQLException {
+        try (Statement s = createStatement()) {
+            s.execute("COMMIT");
+        }
     }
 
     /** {@inheritDoc} */
@@ -927,7 +938,7 @@ private void executeBatch(boolean lastBatch) throws SQLException {
                 respSem.acquire();
 
                 sendRequestNotWaitResponse(
-                    new JdbcOrderedBatchExecuteRequest(schema, streamBatch, lastBatch, order));
+                    new JdbcOrderedBatchExecuteRequest(schema, streamBatch, autoCommit, lastBatch, order));
 
                 streamBatch = null;
 
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/JdbcThinDatabaseMetadata.java b/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/JdbcThinDatabaseMetadata.java
index 6c128a491d51..e24ecbb3186a 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/JdbcThinDatabaseMetadata.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/JdbcThinDatabaseMetadata.java
@@ -46,6 +46,7 @@
 import org.apache.ignite.internal.util.typedef.F;
 
 import static java.sql.Connection.TRANSACTION_NONE;
+import static java.sql.Connection.TRANSACTION_REPEATABLE_READ;
 import static java.sql.ResultSet.CONCUR_READ_ONLY;
 import static java.sql.ResultSet.HOLD_CURSORS_OVER_COMMIT;
 import static java.sql.ResultSet.TYPE_FORWARD_ONLY;
@@ -630,17 +631,19 @@
 
     /** {@inheritDoc} */
     @Override public int getDefaultTransactionIsolation() throws SQLException {
-        return TRANSACTION_NONE;
+        return conn.igniteVersion().greaterThanEqual(2, 5, 0) ? TRANSACTION_REPEATABLE_READ :
+            TRANSACTION_NONE;
     }
 
     /** {@inheritDoc} */
     @Override public boolean supportsTransactions() throws SQLException {
-        return false;
+        return conn.igniteVersion().greaterThanEqual(2, 5, 0);
     }
 
     /** {@inheritDoc} */
     @Override public boolean supportsTransactionIsolationLevel(int level) throws SQLException {
-        return false;
+        return conn.igniteVersion().greaterThanEqual(2, 5, 0) &&
+            TRANSACTION_REPEATABLE_READ == level;
     }
 
     /** {@inheritDoc} */
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/JdbcThinStatement.java b/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/JdbcThinStatement.java
index 30e446f65e3b..f0f7337db79e 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/JdbcThinStatement.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/JdbcThinStatement.java
@@ -46,6 +46,8 @@
 import org.apache.ignite.internal.processors.odbc.jdbc.JdbcResult;
 import org.apache.ignite.internal.processors.odbc.jdbc.JdbcResultInfo;
 import org.apache.ignite.internal.processors.odbc.jdbc.JdbcStatementType;
+import org.apache.ignite.internal.processors.odbc.jdbc.JdbcBulkLoadBatchRequest;
+import org.apache.ignite.internal.processors.odbc.jdbc.JdbcStatementType;
 import org.apache.ignite.internal.processors.query.IgniteSQLException;
 import org.apache.ignite.internal.sql.SqlKeyword;
 import org.apache.ignite.internal.sql.SqlParseException;
@@ -208,7 +210,7 @@ protected void execute0(JdbcStatementType stmtType, String sql, List<Object> arg
         }
 
         JdbcResult res0 = conn.sendRequest(new JdbcQueryExecuteRequest(stmtType, schema, pageSize,
-            maxRows, sql, args == null ? null : args.toArray(new Object[args.size()])));
+            maxRows, conn.getAutoCommit(), sql, args == null ? null : args.toArray(new Object[args.size()])));
 
         assert res0 != null;
 
@@ -646,7 +648,8 @@ void checkStatementEligibleForBatching(String sql) throws SQLException {
             throw new SQLException("Batch is empty.");
 
         try {
-            JdbcBatchExecuteResult res = conn.sendRequest(new JdbcBatchExecuteRequest(conn.getSchema(), batch, false));
+            JdbcBatchExecuteResult res = conn.sendRequest(new JdbcBatchExecuteRequest(conn.getSchema(), batch,
+                conn.getAutoCommit(), false));
 
             if (res.errorCode() != ClientListenerResponse.STATUS_SUCCESS) {
                 throw new BatchUpdateException(res.errorMessage(), IgniteQueryErrorCode.codeToSqlState(res.errorCode()),
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/JdbcThinTcpIo.java b/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/JdbcThinTcpIo.java
index 6128d07f0afe..2c3f321a9585 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/JdbcThinTcpIo.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/JdbcThinTcpIo.java
@@ -326,6 +326,7 @@ public void handshake(ClientListenerProtocolVersion ver) throws IOException, SQL
         writer.writeBoolean(connProps.isAutoCloseServerCursor());
         writer.writeBoolean(connProps.isLazy());
         writer.writeBoolean(connProps.isSkipReducerOnUpdate());
+        writer.writeString(connProps.nestedTxMode());
 
         if (!F.isEmpty(connProps.getUsername())) {
             assert ver.compareTo(VER_2_5_0) >= 0 : "Authentication is supported since 2.5";
@@ -374,8 +375,9 @@ public void handshake(ClientListenerProtocolVersion ver) throws IOException, SQL
                     + ", url=" + connProps.getUrl() + ']', SqlStateCode.CONNECTION_REJECTED);
             }
 
-            if (VER_2_4_0.equals(srvProtocolVer) || VER_2_3_0.equals(srvProtocolVer) ||
-                VER_2_1_5.equals(srvProtocolVer))
+            if (VER_2_4_0.equals(srvProtocolVer)
+                    || VER_2_3_0.equals(srvProtocolVer)
+                    || VER_2_1_5.equals(srvProtocolVer))
                 handshake(srvProtocolVer);
             else if (VER_2_1_0.equals(srvProtocolVer))
                 handshake_2_1_0();
@@ -541,8 +543,8 @@ private static int guessCapacity(JdbcRequest req) {
 
             int cnt = !F.isEmpty(qrys) ? Math.min(MAX_BATCH_QRY_CNT, qrys.size()) : 0;
 
-            // One additional byte for last batch flag.
-            cap = cnt * DYNAMIC_SIZE_MSG_CAP + 1;
+            // One additional byte for autocommit and last batch flags.
+            cap = cnt * DYNAMIC_SIZE_MSG_CAP + 2;
         }
         else if (req instanceof JdbcQueryCloseRequest)
             cap = QUERY_CLOSE_MSG_SIZE;
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/managers/communication/GridIoManager.java b/modules/core/src/main/java/org/apache/ignite/internal/managers/communication/GridIoManager.java
index 8d9a70034aeb..f515d579cead 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/managers/communication/GridIoManager.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/managers/communication/GridIoManager.java
@@ -67,6 +67,7 @@
 import org.apache.ignite.internal.managers.deployment.GridDeployment;
 import org.apache.ignite.internal.managers.eventstorage.GridEventStorageManager;
 import org.apache.ignite.internal.managers.eventstorage.GridLocalEventListener;
+import org.apache.ignite.internal.processors.cache.mvcc.msg.MvccMessage;
 import org.apache.ignite.internal.processors.platform.message.PlatformMessageFilter;
 import org.apache.ignite.internal.processors.pool.PoolProcessor;
 import org.apache.ignite.internal.processors.timeout.GridTimeoutObject;
@@ -101,6 +102,7 @@
 import static org.apache.ignite.events.EventType.EVT_NODE_FAILED;
 import static org.apache.ignite.events.EventType.EVT_NODE_JOINED;
 import static org.apache.ignite.events.EventType.EVT_NODE_LEFT;
+import static org.apache.ignite.internal.GridTopic.TOPIC_CACHE_COORDINATOR;
 import static org.apache.ignite.internal.GridTopic.TOPIC_COMM_USER;
 import static org.apache.ignite.internal.GridTopic.TOPIC_IO_TEST;
 import static org.apache.ignite.internal.managers.communication.GridIoPolicy.AFFINITY_POOL;
@@ -1112,6 +1114,17 @@ private void processRegularMessage(
 
             return;
         }
+        if (msg.topicOrdinal() == TOPIC_CACHE_COORDINATOR.ordinal()) {
+            MvccMessage msg0 = (MvccMessage)msg.message();
+
+            // see IGNITE-8609
+            /*if (msg0.processedFromNioThread())
+                c.run();
+            else*/
+                ctx.getStripedExecutorService().execute(-1, c);
+
+            return;
+        }
 
         if (plc == GridIoPolicy.SYSTEM_POOL && msg.partition() != GridIoMessage.STRIPE_DISABLED_PART) {
             ctx.getStripedExecutorService().execute(msg.partition(), c);
@@ -1648,6 +1661,9 @@ else if (async)
                 if (e.getCause() instanceof ClusterTopologyCheckedException)
                     throw (ClusterTopologyCheckedException)e.getCause();
 
+                if (!ctx.discovery().alive(node))
+                    throw new ClusterTopologyCheckedException("Failed to send message, node left: " + node.id());
+
                 throw new IgniteCheckedException("Failed to send message (node may have left the grid or " +
                     "TCP connection cannot be established due to firewall issues) " +
                     "[node=" + node + ", topic=" + topic +
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/managers/communication/GridIoMessageFactory.java b/modules/core/src/main/java/org/apache/ignite/internal/managers/communication/GridIoMessageFactory.java
index 581c32e4b1fb..8dddd8ba2ece 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/managers/communication/GridIoMessageFactory.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/managers/communication/GridIoMessageFactory.java
@@ -47,13 +47,13 @@
 import org.apache.ignite.internal.processors.cache.CacheObjectByteArrayImpl;
 import org.apache.ignite.internal.processors.cache.CacheObjectImpl;
 import org.apache.ignite.internal.processors.cache.GridCacheEntryInfo;
+import org.apache.ignite.internal.processors.cache.GridCacheMvccEntryInfo;
 import org.apache.ignite.internal.processors.cache.GridCacheReturn;
 import org.apache.ignite.internal.processors.cache.GridChangeGlobalStateMessageResponse;
 import org.apache.ignite.internal.processors.cache.KeyCacheObjectImpl;
 import org.apache.ignite.internal.processors.cache.WalStateAckMessage;
 import org.apache.ignite.internal.processors.cache.binary.MetadataRequestMessage;
 import org.apache.ignite.internal.processors.cache.binary.MetadataResponseMessage;
-import org.apache.ignite.internal.processors.cache.distributed.dht.preloader.latch.LatchAckMessage;
 import org.apache.ignite.internal.processors.cache.distributed.GridCacheTtlUpdateRequest;
 import org.apache.ignite.internal.processors.cache.distributed.GridCacheTxRecoveryRequest;
 import org.apache.ignite.internal.processors.cache.distributed.GridCacheTxRecoveryResponse;
@@ -68,11 +68,15 @@
 import org.apache.ignite.internal.processors.cache.distributed.dht.GridDhtAffinityAssignmentResponse;
 import org.apache.ignite.internal.processors.cache.distributed.dht.GridDhtLockRequest;
 import org.apache.ignite.internal.processors.cache.distributed.dht.GridDhtLockResponse;
+import org.apache.ignite.internal.processors.cache.distributed.dht.GridDhtPartitionsUpdateCountersMap;
 import org.apache.ignite.internal.processors.cache.distributed.dht.GridDhtTxFinishRequest;
 import org.apache.ignite.internal.processors.cache.distributed.dht.GridDhtTxFinishResponse;
 import org.apache.ignite.internal.processors.cache.distributed.dht.GridDhtTxOnePhaseCommitAckRequest;
 import org.apache.ignite.internal.processors.cache.distributed.dht.GridDhtTxPrepareRequest;
 import org.apache.ignite.internal.processors.cache.distributed.dht.GridDhtTxPrepareResponse;
+import org.apache.ignite.internal.processors.cache.distributed.dht.GridDhtTxQueryEnlistRequest;
+import org.apache.ignite.internal.processors.cache.distributed.dht.GridDhtTxQueryEnlistResponse;
+import org.apache.ignite.internal.processors.cache.distributed.dht.GridDhtTxQueryFirstEnlistRequest;
 import org.apache.ignite.internal.processors.cache.distributed.dht.GridDhtUnlockRequest;
 import org.apache.ignite.internal.processors.cache.distributed.dht.atomic.GridDhtAtomicDeferredUpdateResponse;
 import org.apache.ignite.internal.processors.cache.distributed.dht.atomic.GridDhtAtomicNearResponse;
@@ -97,6 +101,7 @@
 import org.apache.ignite.internal.processors.cache.distributed.dht.preloader.GridDhtPartitionsFullMessage;
 import org.apache.ignite.internal.processors.cache.distributed.dht.preloader.GridDhtPartitionsSingleMessage;
 import org.apache.ignite.internal.processors.cache.distributed.dht.preloader.GridDhtPartitionsSingleRequest;
+import org.apache.ignite.internal.processors.cache.distributed.dht.preloader.latch.LatchAckMessage;
 import org.apache.ignite.internal.processors.cache.distributed.near.CacheVersionedValue;
 import org.apache.ignite.internal.processors.cache.distributed.near.GridNearGetRequest;
 import org.apache.ignite.internal.processors.cache.distributed.near.GridNearGetResponse;
@@ -108,7 +113,24 @@
 import org.apache.ignite.internal.processors.cache.distributed.near.GridNearTxFinishResponse;
 import org.apache.ignite.internal.processors.cache.distributed.near.GridNearTxPrepareRequest;
 import org.apache.ignite.internal.processors.cache.distributed.near.GridNearTxPrepareResponse;
+import org.apache.ignite.internal.processors.cache.distributed.near.GridNearTxQueryEnlistRequest;
+import org.apache.ignite.internal.processors.cache.distributed.near.GridNearTxQueryEnlistResponse;
+import org.apache.ignite.internal.processors.cache.distributed.near.GridNearTxQueryResultsEnlistRequest;
+import org.apache.ignite.internal.processors.cache.distributed.near.GridNearTxQueryResultsEnlistResponse;
 import org.apache.ignite.internal.processors.cache.distributed.near.GridNearUnlockRequest;
+import org.apache.ignite.internal.processors.cache.mvcc.MvccSnapshotWithoutTxs;
+import org.apache.ignite.internal.processors.cache.mvcc.MvccVersionImpl;
+import org.apache.ignite.internal.processors.cache.mvcc.msg.MvccAckRequestQueryCntr;
+import org.apache.ignite.internal.processors.cache.mvcc.msg.MvccAckRequestQueryId;
+import org.apache.ignite.internal.processors.cache.mvcc.msg.MvccAckRequestTx;
+import org.apache.ignite.internal.processors.cache.mvcc.msg.MvccAckRequestTxAndQueryCntr;
+import org.apache.ignite.internal.processors.cache.mvcc.msg.MvccAckRequestTxAndQueryId;
+import org.apache.ignite.internal.processors.cache.mvcc.msg.MvccActiveQueriesMessage;
+import org.apache.ignite.internal.processors.cache.mvcc.msg.MvccFutureResponse;
+import org.apache.ignite.internal.processors.cache.mvcc.msg.MvccQuerySnapshotRequest;
+import org.apache.ignite.internal.processors.cache.mvcc.msg.MvccSnapshotResponse;
+import org.apache.ignite.internal.processors.cache.mvcc.msg.MvccTxSnapshotRequest;
+import org.apache.ignite.internal.processors.cache.mvcc.msg.MvccWaitTxsRequest;
 import org.apache.ignite.internal.processors.cache.query.GridCacheQueryRequest;
 import org.apache.ignite.internal.processors.cache.query.GridCacheQueryResponse;
 import org.apache.ignite.internal.processors.cache.query.GridCacheSqlQuery;
@@ -927,6 +949,116 @@ public GridIoMessageFactory(MessageFactory[] ext) {
 
                 break;
 
+            case 136:
+                msg = new MvccTxSnapshotRequest();
+
+                break;
+
+            case 137:
+                msg = new MvccAckRequestTx();
+
+                break;
+
+            case 138:
+                msg = new MvccFutureResponse();
+
+                break;
+
+            case 139:
+                msg = new MvccQuerySnapshotRequest();
+
+                break;
+
+            case 140:
+                msg = new MvccAckRequestQueryCntr();
+
+                break;
+
+            case 141:
+                msg = new MvccSnapshotResponse();
+
+                break;
+
+            case 142:
+                msg = new MvccWaitTxsRequest();
+
+                break;
+
+            case 143:
+                msg = new GridCacheMvccEntryInfo();
+
+                break;
+
+            case 144:
+                msg = new GridDhtTxQueryEnlistResponse();
+
+                break;
+
+            case 145:
+                msg = new MvccAckRequestQueryId();
+
+                break;
+
+            case 146:
+                msg = new MvccAckRequestTxAndQueryCntr();
+
+                break;
+
+            case 147:
+                msg = new MvccAckRequestTxAndQueryId();
+
+                break;
+
+            case 148:
+                msg = new MvccVersionImpl();
+
+                break;
+
+            case 149:
+                msg = new MvccActiveQueriesMessage();
+
+                break;
+
+            case 150:
+                msg = new MvccSnapshotWithoutTxs();
+
+                break;
+
+            case 151:
+                msg = new GridNearTxQueryEnlistRequest();
+
+                break;
+
+            case 152:
+                msg = new GridNearTxQueryEnlistResponse();
+
+                break;
+
+            case 153:
+                msg = new GridNearTxQueryResultsEnlistRequest();
+
+                break;
+
+            case 154:
+                msg = new GridNearTxQueryResultsEnlistResponse();
+
+                break;
+
+            case 155:
+                msg = new GridDhtTxQueryEnlistRequest();
+
+                break;
+
+            case 156:
+                msg = new GridDhtTxQueryFirstEnlistRequest();
+
+                break;
+
+            case 157:
+                msg = new GridDhtPartitionsUpdateCountersMap();
+
+                break;
+
             // [-3..119] [124..129] [-23..-27] [-36..-55]- this
             // [120..123] - DR
             // [-4..-22, -30..-35] - SQL
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/managers/discovery/DiscoCache.java b/modules/core/src/main/java/org/apache/ignite/internal/managers/discovery/DiscoCache.java
index 8cdcbf367330..84bcab1288c9 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/managers/discovery/DiscoCache.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/managers/discovery/DiscoCache.java
@@ -26,6 +26,7 @@
 import org.apache.ignite.cluster.BaselineNode;
 import org.apache.ignite.cluster.ClusterNode;
 import org.apache.ignite.internal.processors.affinity.AffinityTopologyVersion;
+import org.apache.ignite.internal.processors.cache.mvcc.MvccCoordinator;
 import org.apache.ignite.internal.processors.cluster.DiscoveryDataClusterState;
 import org.apache.ignite.internal.util.GridConcurrentHashSet;
 import org.apache.ignite.internal.util.tostring.GridToStringInclude;
@@ -108,10 +109,14 @@
     /** */
     private final P1<ClusterNode> aliveNodePred;
 
+    /** */
+    private final MvccCoordinator mvccCrd;
+
     /**
      * @param topVer Topology version.
      * @param state Current cluster state.
      * @param loc Local node.
+     * @param mvccCrd MVCC coordinator node.
      * @param rmtNodes Remote nodes.
      * @param allNodes All nodes.
      * @param srvNodes Server nodes.
@@ -130,6 +135,7 @@
         AffinityTopologyVersion topVer,
         DiscoveryDataClusterState state,
         ClusterNode loc,
+        MvccCoordinator mvccCrd,
         List<ClusterNode> rmtNodes,
         List<ClusterNode> allNodes,
         List<ClusterNode> srvNodes,
@@ -148,6 +154,7 @@
         this.topVer = topVer;
         this.state = state;
         this.loc = loc;
+        this.mvccCrd = mvccCrd;
         this.rmtNodes = rmtNodes;
         this.allNodes = allNodes;
         this.srvNodes = srvNodes;
@@ -157,7 +164,7 @@
         this.allCacheNodes = allCacheNodes;
         this.cacheGrpAffNodes = cacheGrpAffNodes;
         this.nodeMap = nodeMap;
-        alives.addAll(alives0);
+        this.alives.addAll(alives0);
         this.minNodeVer = minNodeVer;
         this.minSrvNodeVer = minSrvNodeVer;
         this.nodeIdToConsIdx = nodeIdToConsIdx;
@@ -176,6 +183,13 @@
         };
     }
 
+    /**
+     * @return Mvcc coordinator node.
+     */
+    @Nullable public MvccCoordinator mvccCoordinator() {
+        return mvccCrd;
+    }
+
     /**
      * @return Topology version.
      */
@@ -461,6 +475,7 @@ public DiscoCache copy(AffinityTopologyVersion ver, @Nullable DiscoveryDataClust
             ver,
             state == null ? this.state : state,
             loc,
+            mvccCrd,
             rmtNodes,
             allNodes,
             srvNodes,
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/managers/discovery/GridDiscoveryManager.java b/modules/core/src/main/java/org/apache/ignite/internal/managers/discovery/GridDiscoveryManager.java
index a13f31e1ac2d..d19e08bb9fba 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/managers/discovery/GridDiscoveryManager.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/managers/discovery/GridDiscoveryManager.java
@@ -84,6 +84,7 @@
 import org.apache.ignite.internal.processors.cache.DynamicCacheDescriptor;
 import org.apache.ignite.internal.processors.cache.GridCacheAdapter;
 import org.apache.ignite.internal.processors.cache.GridCacheContext;
+import org.apache.ignite.internal.processors.cache.mvcc.MvccCoordinator;
 import org.apache.ignite.internal.processors.cache.persistence.GridCacheDatabaseSharedManager;
 import org.apache.ignite.internal.processors.cluster.BaselineTopology;
 import org.apache.ignite.internal.processors.cluster.ChangeGlobalStateFinishMessage;
@@ -159,6 +160,7 @@
 import static org.apache.ignite.internal.IgniteNodeAttributes.ATTR_MACS;
 import static org.apache.ignite.internal.IgniteNodeAttributes.ATTR_MARSHALLER_USE_BINARY_STRING_SER_VER_2;
 import static org.apache.ignite.internal.IgniteNodeAttributes.ATTR_MARSHALLER_USE_DFLT_SUID;
+import static org.apache.ignite.internal.IgniteNodeAttributes.ATTR_MVCC_ENABLED;
 import static org.apache.ignite.internal.IgniteNodeAttributes.ATTR_OFFHEAP_SIZE;
 import static org.apache.ignite.internal.IgniteNodeAttributes.ATTR_PEER_CLASSLOADING;
 import static org.apache.ignite.internal.IgniteNodeAttributes.ATTR_PHY_RAM;
@@ -644,6 +646,8 @@ private void onDiscovery0(
                     updateClientNodes(node.id());
                 }
 
+                ctx.coordinators().onDiscoveryEvent(type, topSnapshot, topVer);
+
                 boolean locJoinEvt = type == EVT_NODE_JOINED && node.id().equals(locNode.id());
 
                 ChangeGlobalStateFinishMessage stateFinishMsg = null;
@@ -1204,6 +1208,8 @@ private void checkAttributes(Iterable<ClusterNode> nodes) throws IgniteCheckedEx
         Boolean locSrvcCompatibilityEnabled = locNode.attribute(ATTR_SERVICES_COMPATIBILITY_MODE);
         Boolean locSecurityCompatibilityEnabled = locNode.attribute(ATTR_SECURITY_COMPATIBILITY_MODE);
 
+        Boolean locMvccEnabled = locNode.attribute(ATTR_MVCC_ENABLED);
+
         for (ClusterNode n : nodes) {
             int rmtJvmMajVer = nodeJavaMajorVersion(n);
 
@@ -1301,6 +1307,17 @@ private void checkAttributes(Iterable<ClusterNode> nodes) throws IgniteCheckedEx
                     ", locNodeId=" + locNode.id() + ", rmtNode=" + U.toShortString(n) + "]");
             }
 
+            Boolean rmtMvccEnabled = n.attribute(ATTR_MVCC_ENABLED);
+
+            if (!F.eq(locMvccEnabled, rmtMvccEnabled)) {
+                throw new IgniteCheckedException("Remote node has MVCC mode different from local " +
+                    "[locId8=" +  U.id8(locNode.id()) +
+                    ", locMvccMode=" + (Boolean.TRUE.equals(locMvccEnabled) ? "ENABLED" : "DISABLED") +
+                    ", rmtId8=" + U.id8(n.id()) +
+                    ", rmtMvccMode=" + (Boolean.TRUE.equals(rmtMvccEnabled) ? "ENABLED" : "DISABLED") +
+                    ", rmtAddrs=" + U.addressesAsString(n) + ", rmtNode=" + U.toShortString(n) + "]");
+            }
+
             if (n.version().compareToIgnoreTimestamp(SERVICE_PERMISSIONS_SINCE) >= 0
                 && ctx.security().enabled() // Matters only if security enabled.
                ) {
@@ -2353,6 +2370,8 @@ public void reconnect() {
         Collection<ClusterNode> topSnapshot) {
         assert topSnapshot.contains(loc);
 
+        MvccCoordinator mvccCrd = ctx.coordinators().coordinatorFromDiscoveryEvent();
+
         HashSet<UUID> alives = U.newHashSet(topSnapshot.size());
         HashMap<UUID, ClusterNode> nodeMap = U.newHashMap(topSnapshot.size());
 
@@ -2454,6 +2473,7 @@ else if (node.version().compareTo(minVer) < 0)
             topVer,
             state,
             loc,
+            mvccCrd,
             Collections.unmodifiableList(rmtNodes),
             Collections.unmodifiableList(allNodes),
             Collections.unmodifiableList(srvNodes),
@@ -3362,6 +3382,7 @@ public DiscoCache createDiscoCacheOnCacheChange(
             topVer,
             discoCache.state(),
             discoCache.localNode(),
+            discoCache.mvccCoordinator(),
             discoCache.remoteNodes(),
             allNodes,
             discoCache.serverNodes(),
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/pagemem/store/IgnitePageStoreManager.java b/modules/core/src/main/java/org/apache/ignite/internal/pagemem/store/IgnitePageStoreManager.java
index 5475bef76eba..ff0c66aa0d8c 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/pagemem/store/IgnitePageStoreManager.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/pagemem/store/IgnitePageStoreManager.java
@@ -26,6 +26,7 @@
 import org.apache.ignite.internal.processors.cache.CacheGroupDescriptor;
 import org.apache.ignite.internal.processors.cache.GridCacheSharedManager;
 import org.apache.ignite.internal.processors.cache.StoredCacheData;
+import org.apache.ignite.internal.processors.cache.persistence.AllocatedPageTracker;
 import org.apache.ignite.internal.processors.cluster.IgniteChangeGlobalStateSupport;
 
 /**
@@ -42,6 +43,18 @@
      */
     public void finishRecover() throws IgniteCheckedException;
 
+    /**
+     * Initializes disk store structures.
+     *
+     * @param cacheId Cache id.
+     * @param partitions Partitions count.
+     * @param workingDir Working directory.
+     * @param tracker Allocation tracker.
+     * @throws IgniteCheckedException If failed.
+     */
+    void initialize(int cacheId, int partitions, String workingDir, AllocatedPageTracker tracker)
+        throws IgniteCheckedException;
+
     /**
      * Callback called when a cache is starting.
      *
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/pagemem/wal/record/WALRecord.java b/modules/core/src/main/java/org/apache/ignite/internal/pagemem/wal/record/WALRecord.java
index 585336ace3c8..a555aaef4f24 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/pagemem/wal/record/WALRecord.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/pagemem/wal/record/WALRecord.java
@@ -184,7 +184,16 @@
         RESERVED,
 
         /** Rotated id part record. */
-        ROTATED_ID_PART_RECORD;
+        ROTATED_ID_PART_RECORD,
+
+        /** */
+        MVCC_DATA_PAGE_MARK_UPDATED_RECORD,
+
+        /** */
+        MVCC_DATA_PAGE_TX_STATE_HINT_UPDATED_RECORD,
+
+        /** */
+        MVCC_DATA_PAGE_NEW_TX_STATE_HINT_UPDATED_RECORD;
 
         /** */
         private static final RecordType[] VALS = RecordType.values();
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/pagemem/wal/record/delta/DataPageMvccMarkUpdatedRecord.java b/modules/core/src/main/java/org/apache/ignite/internal/pagemem/wal/record/delta/DataPageMvccMarkUpdatedRecord.java
new file mode 100644
index 000000000000..5e89f8e96033
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/pagemem/wal/record/delta/DataPageMvccMarkUpdatedRecord.java
@@ -0,0 +1,103 @@
+/*
+ * 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.pagemem.wal.record.delta;
+
+import org.apache.ignite.IgniteCheckedException;
+import org.apache.ignite.internal.pagemem.PageMemory;
+import org.apache.ignite.internal.processors.cache.persistence.tree.io.DataPageIO;
+import org.apache.ignite.internal.processors.cache.persistence.tree.io.PageIO;
+import org.apache.ignite.internal.util.typedef.internal.S;
+
+/**
+ * MVCC mark updated record.
+ */
+public class DataPageMvccMarkUpdatedRecord extends PageDeltaRecord {
+    /** */
+    private int itemId;
+
+    /** */
+    private long newMvccCrd;
+
+    /** */
+    private long newMvccCntr;
+
+    /** */
+    private int newMvccOpCntr;
+
+    /**
+     * @param grpId Cache group ID.
+     * @param pageId Page ID.
+     * @param itemId Item id.
+     * @param newMvccCrd New MVCC coordinator version.
+     * @param newMvccCntr New MVCC counter version.
+     * @param newMvccOpCntr New MVCC operation counter.
+     */
+    public DataPageMvccMarkUpdatedRecord(int grpId, long pageId, int itemId, long newMvccCrd, long newMvccCntr, int newMvccOpCntr) {
+        super(grpId, pageId);
+
+        this.itemId = itemId;
+        this.newMvccCrd = newMvccCrd;
+        this.newMvccCntr = newMvccCntr;
+        this.newMvccOpCntr = newMvccOpCntr;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void applyDelta(PageMemory pageMem, long pageAddr) throws IgniteCheckedException {
+        DataPageIO io = PageIO.getPageIO(pageAddr);
+
+        io.updateNewVersion(pageAddr, itemId, pageMem.pageSize(), newMvccCrd, newMvccCntr, newMvccOpCntr);
+    }
+
+    /** {@inheritDoc} */
+    @Override public RecordType type() {
+        return RecordType.MVCC_DATA_PAGE_MARK_UPDATED_RECORD;
+    }
+
+    /**
+     * @return Item id.
+     */
+    public int itemId() {
+        return itemId;
+    }
+
+    /**
+     * @return New MVCC coordinator version.
+     */
+    public long newMvccCrd() {
+        return newMvccCrd;
+    }
+
+    /**
+     * @return New MVCC counter version.
+     */
+    public long newMvccCntr() {
+        return newMvccCntr;
+    }
+
+    /**
+     * @return New MVCC operation counter.
+     */
+    public int newMvccOpCntr() {
+        return newMvccOpCntr;
+    }
+
+    /** {@inheritDoc} */
+    @Override public String toString() {
+        return S.toString(DataPageMvccMarkUpdatedRecord.class, this, "super", super.toString());
+    }
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/pagemem/wal/record/delta/DataPageMvccUpdateNewTxStateHintRecord.java b/modules/core/src/main/java/org/apache/ignite/internal/pagemem/wal/record/delta/DataPageMvccUpdateNewTxStateHintRecord.java
new file mode 100644
index 000000000000..4a244a1f2fe3
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/pagemem/wal/record/delta/DataPageMvccUpdateNewTxStateHintRecord.java
@@ -0,0 +1,79 @@
+/*
+ * 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.pagemem.wal.record.delta;
+
+import org.apache.ignite.IgniteCheckedException;
+import org.apache.ignite.internal.pagemem.PageMemory;
+import org.apache.ignite.internal.processors.cache.persistence.tree.io.DataPageIO;
+import org.apache.ignite.internal.processors.cache.persistence.tree.io.PageIO;
+import org.apache.ignite.internal.util.typedef.internal.S;
+
+/**
+ * MVCC update tx state hint record.
+ */
+public class DataPageMvccUpdateNewTxStateHintRecord extends PageDeltaRecord {
+    /** */
+    private int itemId;
+
+    /** */
+    private byte txState;
+
+    /**
+     * @param grpId Cache group ID.
+     * @param pageId Page ID.
+     * @param itemId Item id.
+     * @param txState Tx state hint.
+     */
+    public DataPageMvccUpdateNewTxStateHintRecord(int grpId, long pageId, int itemId, byte txState) {
+        super(grpId, pageId);
+
+        this.itemId = itemId;
+        this.txState = txState;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void applyDelta(PageMemory pageMem, long pageAddr) throws IgniteCheckedException {
+        DataPageIO io = PageIO.getPageIO(pageAddr);
+
+        io.updateNewTxState(pageAddr, itemId, pageMem.pageSize(), txState);
+    }
+
+    /** {@inheritDoc} */
+    @Override public RecordType type() {
+        return RecordType.MVCC_DATA_PAGE_NEW_TX_STATE_HINT_UPDATED_RECORD;
+    }
+
+    /**
+     * @return Item id.
+     */
+    public int itemId() {
+        return itemId;
+    }
+
+    /**
+     * @return Tx state hint.
+     */
+    public byte txState() {
+        return txState;
+    }
+
+    /** {@inheritDoc} */
+    @Override public String toString() {
+        return S.toString(DataPageMvccUpdateNewTxStateHintRecord.class, this, "super", super.toString());
+    }
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/pagemem/wal/record/delta/DataPageMvccUpdateTxStateHintRecord.java b/modules/core/src/main/java/org/apache/ignite/internal/pagemem/wal/record/delta/DataPageMvccUpdateTxStateHintRecord.java
new file mode 100644
index 000000000000..7e53609064c0
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/pagemem/wal/record/delta/DataPageMvccUpdateTxStateHintRecord.java
@@ -0,0 +1,79 @@
+/*
+ * 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.pagemem.wal.record.delta;
+
+import org.apache.ignite.IgniteCheckedException;
+import org.apache.ignite.internal.pagemem.PageMemory;
+import org.apache.ignite.internal.processors.cache.persistence.tree.io.DataPageIO;
+import org.apache.ignite.internal.processors.cache.persistence.tree.io.PageIO;
+import org.apache.ignite.internal.util.typedef.internal.S;
+
+/**
+ * MVCC update tx state hint record.
+ */
+public class DataPageMvccUpdateTxStateHintRecord extends PageDeltaRecord {
+    /** */
+    private int itemId;
+
+    /** */
+    private byte txState;
+
+    /**
+     * @param grpId Cache group ID.
+     * @param pageId Page ID.
+     * @param itemId Item id.
+     * @param txState Tx state hint.
+     */
+    public DataPageMvccUpdateTxStateHintRecord(int grpId, long pageId, int itemId, byte txState) {
+        super(grpId, pageId);
+
+        this.itemId = itemId;
+        this.txState = txState;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void applyDelta(PageMemory pageMem, long pageAddr) throws IgniteCheckedException {
+        DataPageIO io = PageIO.getPageIO(pageAddr);
+
+        io.updateTxState(pageAddr, itemId, pageMem.pageSize(), txState);
+    }
+
+    /** {@inheritDoc} */
+    @Override public RecordType type() {
+        return RecordType.MVCC_DATA_PAGE_TX_STATE_HINT_UPDATED_RECORD;
+    }
+
+    /**
+     * @return Item id.
+     */
+    public int itemId() {
+        return itemId;
+    }
+
+    /**
+     * @return Tx state hint.
+     */
+    public byte txState() {
+        return txState;
+    }
+
+    /** {@inheritDoc} */
+    @Override public String toString() {
+        return S.toString(DataPageMvccUpdateTxStateHintRecord.class, this, "super", super.toString());
+    }
+}
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 f78ab603a123..1c321d3020b9 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,12 +17,12 @@
 
 package org.apache.ignite.internal.processors.affinity;
 
-import org.apache.ignite.cluster.ClusterNode;
-
 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.processors.cache.mvcc.MvccCoordinator;
 
 /**
  * Cached affinity calculations.
@@ -90,4 +90,9 @@
      * @return Backup partitions for specified node ID.
      */
     public Set<Integer> backupPartitions(UUID nodeId);
+
+    /**
+     * @return Mvcc coordinator.
+     */
+    public MvccCoordinator mvccCoordinator();
 }
\ No newline at end of file
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 cbec1a1852eb..f96bc9d0c6fa 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
@@ -27,6 +27,7 @@
 import java.util.Set;
 import java.util.UUID;
 import org.apache.ignite.cluster.ClusterNode;
+import org.apache.ignite.internal.processors.cache.mvcc.MvccCoordinator;
 import org.apache.ignite.internal.util.typedef.internal.S;
 
 /**
@@ -40,6 +41,9 @@
     /** Topology version. */
     private final AffinityTopologyVersion topVer;
 
+    /** */
+    private final MvccCoordinator mvccCrd;
+
     /** Collection of calculated affinity nodes. */
     private List<List<ClusterNode>> assignment;
 
@@ -73,6 +77,7 @@
         this.topVer = topVer;
         primary = new HashMap<>();
         backup = new HashMap<>();
+        mvccCrd = null;
         clientEvtChange = false;
     }
 
@@ -83,7 +88,8 @@
      */
     GridAffinityAssignment(AffinityTopologyVersion topVer,
         List<List<ClusterNode>> assignment,
-        List<List<ClusterNode>> idealAssignment) {
+        List<List<ClusterNode>> idealAssignment,
+        MvccCoordinator mvccCrd) {
         assert topVer != null;
         assert assignment != null;
         assert idealAssignment != null;
@@ -91,6 +97,7 @@
         this.topVer = topVer;
         this.assignment = assignment;
         this.idealAssignment = idealAssignment.equals(assignment) ? assignment : idealAssignment;
+        this.mvccCrd = mvccCrd;
 
         primary = new HashMap<>();
         backup = new HashMap<>();
@@ -110,6 +117,7 @@
         idealAssignment = aff.idealAssignment;
         primary = aff.primary;
         backup = aff.backup;
+        mvccCrd = aff.mvccCrd;
 
         clientEvtChange = true;
     }
@@ -282,6 +290,11 @@ private void initPrimaryBackupMaps() {
         }
     }
 
+    /** {@inheritDoc} */
+    @Override public MvccCoordinator mvccCoordinator() {
+        return mvccCrd;
+    }
+
     /** {@inheritDoc} */
     @Override public int hashCode() {
         return topVer.hashCode();
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 34e2b0ad19b1..cc2c17c3f3ed 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
@@ -43,6 +43,7 @@
 import org.apache.ignite.internal.IgniteInternalFuture;
 import org.apache.ignite.internal.cluster.NodeOrderComparator;
 import org.apache.ignite.internal.managers.discovery.DiscoCache;
+import org.apache.ignite.internal.processors.cache.mvcc.MvccCoordinator;
 import org.apache.ignite.internal.processors.cache.ExchangeDiscoveryEvents;
 import org.apache.ignite.internal.processors.cluster.BaselineTopology;
 import org.apache.ignite.internal.util.future.GridFutureAdapter;
@@ -202,11 +203,25 @@ public int groupId() {
      * @param affAssignment Affinity assignment for topology version.
      */
     public void initialize(AffinityTopologyVersion topVer, List<List<ClusterNode>> affAssignment) {
+        MvccCoordinator mvccCrd = ctx.cache().context().coordinators().currentCoordinator(topVer);
+
+        initialize(topVer, affAssignment, mvccCrd);
+    }
+
+    /**
+     * Initializes affinity with given topology version and assignment.
+     *
+     * @param topVer Topology version.
+     * @param affAssignment Affinity assignment for topology version.
+     * @param mvccCrd Mvcc coordinator.
+     */
+    public void initialize(AffinityTopologyVersion topVer, List<List<ClusterNode>> affAssignment, MvccCoordinator mvccCrd) {
         assert topVer.compareTo(lastVersion()) >= 0 : "[topVer = " + topVer + ", last=" + lastVersion() + ']';
 
         assert idealAssignment != null;
+        assert mvccCrd == null || topVer.compareTo(mvccCrd.topologyVersion()) >= 0 : "[mvccCrd=" + mvccCrd + ", topVer=" + topVer + ']';
 
-        GridAffinityAssignment assignment = new GridAffinityAssignment(topVer, affAssignment, idealAssignment);
+        GridAffinityAssignment assignment = new GridAffinityAssignment(topVer, affAssignment, idealAssignment, mvccCrd);
 
         HistoryAffinityAssignment hAff = affCache.put(topVer, new HistoryAffinityAssignment(assignment));
 
@@ -745,7 +760,9 @@ public void init(GridAffinityAssignmentCache aff) {
 
         idealAssignment(aff.idealAssignment());
 
-        initialize(aff.lastVersion(), aff.assignments(aff.lastVersion()));
+        AffinityAssignment assign = aff.cachedAffinity(aff.lastVersion());
+
+        initialize(aff.lastVersion(), assign.assignment(), assign.mvccCoordinator());
     }
 
     /**
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/affinity/GridAffinityProcessor.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/affinity/GridAffinityProcessor.java
index 08333c33e131..4a0908c6071f 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/affinity/GridAffinityProcessor.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/affinity/GridAffinityProcessor.java
@@ -423,7 +423,7 @@ public void removeCachedAffinity(AffinityTopologyVersion topVer) {
             try {
                 GridAffinityAssignment assign = assign0 instanceof GridAffinityAssignment ?
                     (GridAffinityAssignment)assign0 :
-                    new GridAffinityAssignment(topVer, assign0.assignment(), assign0.idealAssignment());
+                    new GridAffinityAssignment(topVer, assign0.assignment(), assign0.idealAssignment(), assign0.mvccCoordinator());
 
                 AffinityInfo info = new AffinityInfo(
                     cctx.config().getAffinity(),
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 abd529279995..15d7e4e437ea 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
@@ -184,7 +184,7 @@ public AffinityJob() {
 
             GridAffinityAssignment assign = assign0 instanceof GridAffinityAssignment ?
                 (GridAffinityAssignment)assign0 :
-                new GridAffinityAssignment(topVer, assign0.assignment(), assign0.idealAssignment());
+                new GridAffinityAssignment(topVer, assign0.assignment(), assign0.idealAssignment(), assign0.mvccCoordinator());
 
             return F.t(
                 affinityMessage(ctx, cctx.config().getAffinity()),
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 94eaab4bf54b..1a6b2efea900 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
@@ -18,6 +18,7 @@
 package org.apache.ignite.internal.processors.affinity;
 
 import org.apache.ignite.cluster.ClusterNode;
+import org.apache.ignite.internal.processors.cache.mvcc.MvccCoordinator;
 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,16 +45,25 @@
     /** */
     private final boolean clientEvtChange;
 
+    /** */
+    private final MvccCoordinator mvccCrd;
+
     /**
      * @param assign Assignment.
      */
-    public HistoryAffinityAssignment(GridAffinityAssignment assign) {
+    HistoryAffinityAssignment(GridAffinityAssignment assign) {
         this.topVer = assign.topologyVersion();
         this.assignment = assign.assignment();
         this.idealAssignment = assign.idealAssignment();
+        this.mvccCrd = assign.mvccCoordinator();
         this.clientEvtChange = assign.clientEventChange();
     }
 
+    /** {@inheritDoc} */
+    @Override public MvccCoordinator mvccCoordinator() {
+        return mvccCrd;
+    }
+
     /** {@inheritDoc} */
     @Override public boolean clientEventChange() {
         return clientEvtChange;
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 0b448e145c37..5859452e3c23 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
@@ -58,6 +58,8 @@
 import org.apache.ignite.internal.processors.cache.distributed.dht.preloader.GridDhtPartitionMap;
 import org.apache.ignite.internal.processors.cache.distributed.dht.preloader.GridDhtPartitionsExchangeFuture;
 import org.apache.ignite.internal.processors.cache.distributed.dht.preloader.GridDhtPartitionsFullMessage;
+import org.apache.ignite.internal.processors.cache.mvcc.MvccCoordinator;
+import org.apache.ignite.internal.processors.cluster.ChangeGlobalStateFinishMessage;
 import org.apache.ignite.internal.processors.cluster.DiscoveryDataClusterState;
 import org.apache.ignite.internal.util.GridLongList;
 import org.apache.ignite.internal.util.GridPartitionStateMap;
@@ -458,7 +460,11 @@ void onCacheGroupCreated(CacheGroupContext grp) {
                         if (grpHolder.client()) {
                             ClientCacheDhtTopologyFuture topFut = new ClientCacheDhtTopologyFuture(topVer);
 
-                            grp.topology().updateTopologyVersion(topFut, discoCache, -1, false);
+                            grp.topology().updateTopologyVersion(topFut,
+                                discoCache,
+                                cctx.coordinators().currentCoordinator(),
+                                -1,
+                                false);
 
                             grpHolder = new CacheGroupHolder1(grp, grpHolder.affinity());
 
@@ -506,6 +512,7 @@ else if (!fetchFuts.containsKey(grp.groupId())) {
                 assert grp != null;
 
                 GridDhtAffinityAssignmentResponse res = fetchAffinity(topVer,
+                    cctx.coordinators().currentCoordinator(),
                     null,
                     discoCache,
                     grp.affinity(),
@@ -528,7 +535,11 @@ else if (!fetchFuts.containsKey(grp.groupId())) {
                         new ClusterTopologyServerNotFoundException("All server nodes left grid."));
                 }
 
-                grp.topology().updateTopologyVersion(topFut, discoCache, -1, false);
+                grp.topology().updateTopologyVersion(topFut,
+                    discoCache,
+                    cctx.coordinators().currentCoordinator(),
+                    -1,
+                    false);
 
                 grp.topology().update(topVer, partMap, null, Collections.<Integer>emptySet(), null, null);
 
@@ -1284,7 +1295,12 @@ private void initAffinity(CacheGroupDescriptor desc,
 
             fetchFut.init(false);
 
-            fetchAffinity(evts.topologyVersion(), evts, evts.discoveryCache(), aff, fetchFut);
+            fetchAffinity(evts.topologyVersion(),
+                cctx.coordinators().currentCoordinator(),
+                evts,
+                evts.discoveryCache(),
+                aff,
+                fetchFut);
         }
     }
 
@@ -1682,6 +1698,7 @@ private void fetchAffinityOnJoin(GridDhtPartitionsExchangeFuture fut) throws Ign
             int grpId = fetchFut.groupId();
 
             fetchAffinity(topVer,
+                cctx.coordinators().currentCoordinator(),
                 fut.events(),
                 fut.events().discoveryCache(),
                 cctx.cache().cacheGroup(grpId).affinity(),
@@ -1691,6 +1708,7 @@ private void fetchAffinityOnJoin(GridDhtPartitionsExchangeFuture fut) throws Ign
 
     /**
      * @param topVer Topology version.
+     * @param mvccCrd Mvcc coordinator to set in affinity.
      * @param events Discovery events.
      * @param discoCache Discovery data cache.
      * @param affCache Affinity.
@@ -1698,7 +1716,9 @@ private void fetchAffinityOnJoin(GridDhtPartitionsExchangeFuture fut) throws Ign
      * @throws IgniteCheckedException If failed.
      * @return Affinity assignment response.
      */
-    private GridDhtAffinityAssignmentResponse fetchAffinity(AffinityTopologyVersion topVer,
+    private GridDhtAffinityAssignmentResponse fetchAffinity(
+        AffinityTopologyVersion topVer,
+        MvccCoordinator mvccCrd,
         @Nullable ExchangeDiscoveryEvents events,
         DiscoCache discoCache,
         GridAffinityAssignmentCache affCache,
@@ -1711,7 +1731,7 @@ private GridDhtAffinityAssignmentResponse fetchAffinity(AffinityTopologyVersion
         if (res == null) {
             List<List<ClusterNode>> aff = affCache.calculate(topVer, events, discoCache);
 
-            affCache.initialize(topVer, aff);
+            affCache.initialize(topVer, aff, mvccCrd);
         }
         else {
             List<List<ClusterNode>> idealAff = res.idealAffinityAssignment(discoCache);
@@ -1728,7 +1748,7 @@ private GridDhtAffinityAssignmentResponse fetchAffinity(AffinityTopologyVersion
 
             assert aff != null : res;
 
-            affCache.initialize(topVer, aff);
+            affCache.initialize(topVer, aff, mvccCrd);
         }
 
         return res;
@@ -1847,6 +1867,7 @@ public boolean onCentralizedAffinityChange(final GridDhtPartitionsExchangeFuture
                             @Override public void applyx(IgniteInternalFuture<GridDhtAffinityAssignmentResponse> fetchFut)
                                 throws IgniteCheckedException {
                                 fetchAffinity(prev.topologyVersion(),
+                                    null, // Pass null mvcc coordinator, this affinity version should be used for queries.
                                     prev.events(),
                                     prev.events().discoveryCache(),
                                     aff,
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/CacheEntryInfoCollection.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/CacheEntryInfoCollection.java
index 49f77fa808db..614d7c06fbf5 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/CacheEntryInfoCollection.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/CacheEntryInfoCollection.java
@@ -37,6 +37,18 @@
     @GridDirectCollection(GridCacheEntryInfo.class)
     private List<GridCacheEntryInfo> infos;
 
+    /** */
+    public CacheEntryInfoCollection() {
+        // No-op
+    }
+
+    /**
+     * @param infos List of cache entry info.
+     */
+    public CacheEntryInfoCollection(List<GridCacheEntryInfo> infos) {
+        this.infos = infos;
+    }
+
     /**
      *
      */
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/CacheGroupContext.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/CacheGroupContext.java
index c8fe283fc115..c100d161ff9a 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/CacheGroupContext.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/CacheGroupContext.java
@@ -30,6 +30,7 @@
 import org.apache.ignite.cluster.ClusterNode;
 import org.apache.ignite.configuration.CacheConfiguration;
 import org.apache.ignite.configuration.DataPageEvictionMode;
+import org.apache.ignite.configuration.IgniteConfiguration;
 import org.apache.ignite.configuration.TopologyValidator;
 import org.apache.ignite.events.CacheRebalancingEvent;
 import org.apache.ignite.internal.IgniteClientDisconnectedCheckedException;
@@ -60,6 +61,7 @@
 import org.apache.ignite.mxbean.CacheGroupMetricsMXBean;
 import org.jetbrains.annotations.Nullable;
 
+import static org.apache.ignite.cache.CacheAtomicityMode.TRANSACTIONAL;
 import static org.apache.ignite.cache.CacheMode.LOCAL;
 import static org.apache.ignite.cache.CacheMode.REPLICATED;
 import static org.apache.ignite.cache.CacheRebalanceMode.NONE;
@@ -147,6 +149,9 @@
     /** */
     private boolean qryEnabled;
 
+    /** */
+    private boolean mvccEnabled;
+
     /** MXBean. */
     private CacheGroupMetricsMXBean mxBean;
 
@@ -218,6 +223,28 @@
         caches = new ArrayList<>();
 
         mxBean = new CacheGroupMetricsMXBeanImpl(this);
+
+        mvccEnabled = mvccEnabled(ctx.gridConfig(), ccfg, cacheType);
+    }
+
+    /**
+     * @param cfg Ignite configuration.
+     * @param ccfg Cache configuration.
+     * @param cacheType Cache typr.
+     * @return {@code True} if mvcc is enabled for given cache.
+     */
+    public static boolean mvccEnabled(IgniteConfiguration cfg, CacheConfiguration ccfg, CacheType cacheType) {
+        return cfg.isMvccEnabled() &&
+            cacheType == CacheType.USER &&
+            ccfg.getCacheMode() != LOCAL &&
+            ccfg.getAtomicityMode() == TRANSACTIONAL;
+    }
+
+    /**
+     * @return Mvcc flag.
+     */
+    public boolean mvccEnabled() {
+        return mvccEnabled;
     }
 
     /**
@@ -395,6 +422,13 @@ public boolean eventRecordable(int type) {
         return cacheType.userCache() && ctx.gridEvents().isRecordable(type);
     }
 
+    /**
+     * @return {@code True} if cache created by user.
+     */
+    public boolean userCache() {
+        return cacheType.userCache();
+    }
+
     /**
      * Adds rebalancing event.
      *
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/CacheOperationContext.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/CacheOperationContext.java
index 8a7afe7175e4..53f9e229363a 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/CacheOperationContext.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/CacheOperationContext.java
@@ -163,7 +163,7 @@ public CacheOperationContext keepBinary() {
     /**
      * Gets data center ID.
      *
-     * @return Client ID.
+     * @return Datacenter ID.
      */
     @Nullable public Byte dataCenterId() {
         return dataCenterId;
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/ClusterCachesInfo.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/ClusterCachesInfo.java
index 3aaf7f3c08c7..8eab9c01521b 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/ClusterCachesInfo.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/ClusterCachesInfo.java
@@ -147,7 +147,7 @@ public void onStart(CacheJoinNodeDiscoveryData joinDiscoData) throws IgniteCheck
             if (ccfg == null)
                 grpCfgs.put(info.cacheData().config().getGroupName(), info.cacheData().config());
             else
-                validateCacheGroupConfiguration(ccfg, info.cacheData().config());
+                validateCacheGroupConfiguration(ccfg, info.cacheData().config(), info.cacheType());
         }
 
         String conflictErr = processJoiningNode(joinDiscoData, ctx.localNodeId(), true);
@@ -220,7 +220,7 @@ public void onKernalStart(boolean checkConsistency) throws IgniteCheckedExceptio
                 }
 
                 if (checkConsistency)
-                    validateStartCacheConfiguration(locCfg);
+                    validateStartCacheConfiguration(locCfg, cacheData.cacheType());
             }
         }
 
@@ -1864,16 +1864,17 @@ else if (exchActions == null) {
 
     /**
      * @param ccfg Cache configuration to start.
+     * @param cacheType Cache type.
      * @throws IgniteCheckedException If failed.
      */
-    public void validateStartCacheConfiguration(CacheConfiguration ccfg) throws IgniteCheckedException {
+    void validateStartCacheConfiguration(CacheConfiguration ccfg, CacheType cacheType) throws IgniteCheckedException {
         if (ccfg.getGroupName() != null) {
             CacheGroupDescriptor grpDesc = cacheGroupByName(ccfg.getGroupName());
 
             if (grpDesc != null) {
                 assert ccfg.getGroupName().equals(grpDesc.groupName());
 
-                validateCacheGroupConfiguration(grpDesc.config(), ccfg);
+                validateCacheGroupConfiguration(grpDesc.config(), ccfg, cacheType);
             }
         }
     }
@@ -1881,9 +1882,10 @@ public void validateStartCacheConfiguration(CacheConfiguration ccfg) throws Igni
     /**
      * @param cfg Existing configuration.
      * @param startCfg Cache configuration to start.
+     * @param cacheType Cache type.
      * @throws IgniteCheckedException If validation failed.
      */
-    private void validateCacheGroupConfiguration(CacheConfiguration cfg, CacheConfiguration startCfg)
+    private void validateCacheGroupConfiguration(CacheConfiguration cfg, CacheConfiguration startCfg, CacheType cacheType)
         throws IgniteCheckedException {
         GridCacheAttributes attr1 = new GridCacheAttributes(cfg);
         GridCacheAttributes attr2 = new GridCacheAttributes(startCfg);
@@ -1891,6 +1893,11 @@ private void validateCacheGroupConfiguration(CacheConfiguration cfg, CacheConfig
         CU.validateCacheGroupsAttributesMismatch(log, cfg, startCfg, "cacheMode", "Cache mode",
             cfg.getCacheMode(), startCfg.getCacheMode(), true);
 
+        CU.validateCacheGroupsAttributesMismatch(log, cfg, startCfg, "mvccEnabled", "MVCC mode",
+            CacheGroupContext.mvccEnabled(ctx.config(), cfg, cacheType),
+            CacheGroupContext.mvccEnabled(ctx.config(), startCfg, cacheType),
+            true);
+
         CU.validateCacheGroupsAttributesMismatch(log, cfg, startCfg, "affinity", "Affinity function",
             attr1.cacheAffinityClassName(), attr2.cacheAffinityClassName(), true);
 
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/ExchangeContext.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/ExchangeContext.java
index 4046c98b2941..34ed048542c1 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/ExchangeContext.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/ExchangeContext.java
@@ -17,11 +17,15 @@
 
 package org.apache.ignite.internal.processors.cache;
 
+import java.util.HashMap;
 import java.util.HashSet;
+import java.util.Map;
 import java.util.Set;
+import java.util.UUID;
 import org.apache.ignite.cluster.ClusterNode;
 import org.apache.ignite.internal.processors.cache.distributed.dht.preloader.GridDhtPartitionsExchangeFuture;
 import org.apache.ignite.internal.processors.cache.distributed.dht.preloader.GridDhtPartitionsFullMessage;
+import org.apache.ignite.internal.util.GridLongList;
 import org.apache.ignite.internal.util.typedef.internal.S;
 import org.jetbrains.annotations.Nullable;
 
@@ -51,11 +55,20 @@
     /** */
     private final boolean compatibilityNode = getBoolean(IGNITE_EXCHANGE_COMPATIBILITY_VER_1, false);
 
+    /** */
+    private final boolean newMvccCrd;
+
+    /** Currently running mvcc queries, initialized when mvcc coordinator is changed. */
+    private Map<UUID, GridLongList> activeQueries;
+
     /**
      * @param crd Coordinator flag.
+     * @param newMvccCrd {@code True} if new coordinator assigned during this exchange.
      * @param fut Exchange future.
      */
-    public ExchangeContext(boolean crd, GridDhtPartitionsExchangeFuture fut) {
+    public ExchangeContext(boolean crd, boolean newMvccCrd, GridDhtPartitionsExchangeFuture fut) {
+        this.newMvccCrd = newMvccCrd;
+
         int protocolVer = exchangeProtocolVersion(fut.firstEventCache().minimumNodeVersion());
 
         if (compatibilityNode || (crd && fut.localJoinExchange())) {
@@ -124,6 +137,34 @@ public boolean mergeExchanges() {
         return merge;
     }
 
+    /**
+     * @return {@code True} if new node assigned as mvcc coordinator node during this exchange.
+     */
+    public boolean newMvccCoordinator() {
+        return newMvccCrd;
+    }
+
+    /**
+     * @return Active queries.
+     */
+    public Map<UUID, GridLongList> activeQueries() {
+        return activeQueries;
+    }
+
+    /**
+     * @param nodeId Node ID.
+     * @param nodeQueries Node queries.
+     */
+    public void addActiveQueries(UUID nodeId, @Nullable GridLongList nodeQueries) {
+        if (nodeQueries == null)
+            return;
+
+        if (activeQueries == null)
+            activeQueries = new HashMap<>();
+
+        activeQueries.put(nodeId, nodeQueries);
+    }
+
     /** {@inheritDoc} */
     @Override public String toString() {
         return S.toString(ExchangeContext.class, this);
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GatewayProtectedCacheProxy.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GatewayProtectedCacheProxy.java
index 96c5e29fb4ae..c99eb006002e 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GatewayProtectedCacheProxy.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GatewayProtectedCacheProxy.java
@@ -49,6 +49,7 @@
 import org.apache.ignite.cache.query.SqlFieldsQuery;
 import org.apache.ignite.cluster.ClusterGroup;
 import org.apache.ignite.internal.AsyncSupportAdapter;
+import org.apache.ignite.internal.processors.cache.mvcc.MvccUtils;
 import org.apache.ignite.internal.GridKernalState;
 import org.apache.ignite.internal.util.future.GridFutureAdapter;
 import org.apache.ignite.internal.util.future.IgniteFutureImpl;
@@ -145,6 +146,8 @@ public void setCacheManager(org.apache.ignite.cache.CacheManager cacheMgr) {
         CacheOperationGate opGate = onEnter();
 
         try {
+            MvccUtils.verifyMvccOperationSupport(delegate.context(), "withExpiryPolicy");
+
             return new GatewayProtectedCacheProxy<>(delegate, opCtx.withExpiryPolicy(plc), lock);
         }
         finally {
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheAdapter.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheAdapter.java
index bc14c548af92..159d6810e052 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheAdapter.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheAdapter.java
@@ -92,6 +92,8 @@
 import org.apache.ignite.internal.processors.cache.distributed.dht.GridDhtTxLocalAdapter;
 import org.apache.ignite.internal.processors.cache.distributed.near.GridNearTxLocal;
 import org.apache.ignite.internal.processors.cache.dr.GridCacheDrInfo;
+import org.apache.ignite.internal.processors.cache.mvcc.MvccSnapshot;
+import org.apache.ignite.internal.processors.cache.mvcc.MvccUtils;
 import org.apache.ignite.internal.processors.cache.persistence.CacheDataRow;
 import org.apache.ignite.internal.processors.cache.transactions.IgniteInternalTx;
 import org.apache.ignite.internal.processors.cache.transactions.IgniteTxLocalAdapter;
@@ -138,7 +140,6 @@
 import org.apache.ignite.plugin.security.SecurityPermission;
 import org.apache.ignite.resources.IgniteInstanceResource;
 import org.apache.ignite.resources.JobContextResource;
-import org.apache.ignite.spi.discovery.tcp.internal.TcpDiscoveryNode;
 import org.apache.ignite.transactions.Transaction;
 import org.apache.ignite.transactions.TransactionConcurrency;
 import org.apache.ignite.transactions.TransactionIsolation;
@@ -816,6 +817,9 @@ public String toString() {
 
         ctx.checkSecurity(SecurityPermission.CACHE_READ);
 
+        //TODO IGNITE-7955
+        MvccUtils.verifyMvccOperationSupport(ctx, "Peek");
+
         PeekModes modes = parsePeekModes(peekModes, false);
 
         KeyCacheObject cacheKey = ctx.toCacheKeyObject(key);
@@ -1121,6 +1125,9 @@ public final void removeIfObsolete(KeyCacheObject key) {
     @Override public void clearLocally(boolean srv, boolean near, boolean readers) {
         ctx.checkSecurity(SecurityPermission.CACHE_REMOVE);
 
+        //TODO IGNITE-7952
+        MvccUtils.verifyMvccOperationSupport(ctx, "Clear");
+
         List<GridCacheClearAllRunnable<K, V>> jobs = splitClearLocally(srv, near, readers);
 
         if (!F.isEmpty(jobs)) {
@@ -1190,6 +1197,9 @@ public final void removeIfObsolete(KeyCacheObject key) {
      * @throws IgniteCheckedException In case of error.
      */
     private void clear(@Nullable Set<? extends K> keys) throws IgniteCheckedException {
+        //TODO IGNITE-7952
+        MvccUtils.verifyMvccOperationSupport(ctx, "Clear");
+
         if (isLocal()) {
             if (keys == null)
                 clearLocally(true, false, false);
@@ -1207,6 +1217,9 @@ private void clear(@Nullable Set<? extends K> keys) throws IgniteCheckedExceptio
      * @return Future.
      */
     private IgniteInternalFuture<?> clearAsync(@Nullable final Set<? extends K> keys) {
+        //TODO IGNITE-7952
+        MvccUtils.verifyMvccOperationSupport(ctx, "Clear");
+
         if (isLocal())
             return clearLocallyAsync(keys);
         else
@@ -1262,6 +1275,9 @@ public void clearLocally(Collection<KeyCacheObject> keys, boolean readers) {
         if (F.isEmpty(keys))
             return;
 
+        //TODO IGNITE-7952
+        MvccUtils.verifyMvccOperationSupport(ctx, "Clear");
+
         GridCacheVersion obsoleteVer = ctx.versions().next();
 
         for (KeyCacheObject key : keys) {
@@ -1881,7 +1897,8 @@ private boolean evictx(K key, GridCacheVersion ver,
             skipVals,
             /*keep cache objects*/false,
             recovery,
-            needVer);
+            needVer,
+            null); // TODO IGNITE-7371
     }
 
     /**
@@ -1896,6 +1913,7 @@ private boolean evictx(K key, GridCacheVersion ver,
      * @param skipVals Skip values flag.
      * @param keepCacheObjects Keep cache objects.
      * @param needVer If {@code true} returns values as tuples containing value and version.
+     * @param mvccSnapshot MVCC snapshot.
      * @return Future.
      */
     protected final <K1, V1> IgniteInternalFuture<Map<K1, V1>> getAllAsync0(
@@ -1910,7 +1928,8 @@ private boolean evictx(K key, GridCacheVersion ver,
         final boolean skipVals,
         final boolean keepCacheObjects,
         final boolean recovery,
-        final boolean needVer
+        final boolean needVer,
+        MvccSnapshot mvccSnapshot
     ) {
         if (F.isEmpty(keys))
             return new GridFinishedFuture<>(Collections.<K1, V1>emptyMap());
@@ -1967,7 +1986,8 @@ private boolean evictx(K key, GridCacheVersion ver,
                             boolean skipEntry = readNoEntry;
 
                             if (readNoEntry) {
-                                CacheDataRow row = ctx.offheap().read(ctx, key);
+                                CacheDataRow row = mvccSnapshot != null ? ctx.offheap().mvccRead(ctx, key, mvccSnapshot) :
+                                    ctx.offheap().read(ctx, key);
 
                                 if (row != null) {
                                     long expireTime = row.expireTime();
@@ -2030,6 +2050,7 @@ else if (storeEnabled)
                                         taskName,
                                         expiry,
                                         !deserializeBinary,
+                                        mvccSnapshot,
                                         readerArgs);
 
                                     assert res != null;
@@ -2054,6 +2075,7 @@ else if (storeEnabled)
                                         taskName,
                                         expiry,
                                         !deserializeBinary,
+                                        mvccSnapshot,
                                         readerArgs);
 
                                     if (res == null)
@@ -3072,7 +3094,7 @@ protected V getAndRemove0(final K key) throws IgniteCheckedException {
         List<K> keys = new ArrayList<>(Math.min(REMOVE_ALL_KEYS_BATCH, size()));
 
         do {
-            for (Iterator<CacheDataRow> it = ctx.offheap().cacheIterator(ctx.cacheId(), true, true, null);
+            for (Iterator<CacheDataRow> it = ctx.offheap().cacheIterator(ctx.cacheId(), true, true, null, null);
                 it.hasNext() && keys.size() < REMOVE_ALL_KEYS_BATCH; )
                 keys.add((K)it.next().key());
 
@@ -3397,6 +3419,9 @@ public CacheMetricsImpl metrics0() {
         if (keyCheck)
             validateCacheKeys(keys);
 
+        //TODO IGNITE-7764
+        MvccUtils.verifyMvccOperationSupport(ctx, "Lock");
+
         IgniteInternalFuture<Boolean> fut = lockAllAsync(keys, timeout);
 
         boolean isInterrupted = false;
@@ -3425,6 +3450,9 @@ public CacheMetricsImpl metrics0() {
         if (keyCheck)
             validateCacheKey(key);
 
+        //TODO IGNITE-7764
+        MvccUtils.verifyMvccOperationSupport(ctx, "Lock");
+
         return lockAllAsync(Collections.singletonList(key), timeout);
     }
 
@@ -3534,6 +3562,9 @@ protected void checkJta() throws IgniteCheckedException {
     /** {@inheritDoc} */
     @Override public void localLoadCache(final IgniteBiPredicate<K, V> p, Object[] args)
         throws IgniteCheckedException {
+        //TODO IGNITE-7954
+        MvccUtils.verifyMvccOperationSupport(ctx, "Load");
+
         final boolean replicate = ctx.isDrEnabled();
         final AffinityTopologyVersion topVer = ctx.affinity().affinityTopologyVersion();
 
@@ -3671,6 +3702,9 @@ private void loadEntry(KeyCacheObject key,
         if (!ctx.store().configured())
             return new GridFinishedFuture<>();
 
+        //TODO IGNITE-7954
+        MvccUtils.verifyMvccOperationSupport(ctx, "Load");
+
         CacheOperationContext opCtx = ctx.operationContextPerCall();
 
         ExpiryPolicy plc = opCtx != null ? opCtx.expiry() : null;
@@ -3755,6 +3789,9 @@ private void localLoadAndUpdate(final Collection<? extends K> keys) throws Ignit
      */
     public void localLoad(Collection<? extends K> keys, @Nullable ExpiryPolicy plc, final boolean keepBinary)
         throws IgniteCheckedException {
+        //TODO IGNITE-7954
+        MvccUtils.verifyMvccOperationSupport(ctx, "Load");
+
         final boolean replicate = ctx.isDrEnabled();
         final AffinityTopologyVersion topVer = ctx.affinity().affinityTopologyVersion();
 
@@ -3827,6 +3864,9 @@ void globalLoadCache(@Nullable IgniteBiPredicate<K, V> p, @Nullable Object... ar
 
         assert !F.isEmpty(nodes) : "There are not datanodes fo cache: " + ctx.name();
 
+        //TODO IGNITE-7954
+        MvccUtils.verifyMvccOperationSupport(ctx, "Load");
+
         final boolean keepBinary = opCtx != null && opCtx.isKeepBinary();
 
         ComputeTaskInternalFuture fut = ctx.kernalContext().closure().callAsync(BROADCAST,
@@ -4185,6 +4225,7 @@ public void awaitLastFut() {
                     READ_COMMITTED,
                     tCfg.getDefaultTxTimeout(),
                     !ctx.skipStore(),
+                    false,
                     0,
                     null
                 );
@@ -4286,6 +4327,7 @@ public void awaitLastFut() {
                     READ_COMMITTED,
                     txCfg.getDefaultTxTimeout(),
                     !skipStore,
+                    false,
                     0,
                     null);
 
@@ -4556,6 +4598,9 @@ public void onIgfsDataSizeChanged(long delta) {
      * @param readers Whether to clear readers.
      */
     private boolean clearLocally0(K key, boolean readers) {
+        //TODO IGNITE-7952
+        MvccUtils.verifyMvccOperationSupport(ctx, "Clear");
+
         ctx.checkSecurity(SecurityPermission.CACHE_REMOVE);
 
         if (keyCheck)
@@ -4593,6 +4638,9 @@ private boolean clearLocally0(K key, boolean readers) {
         if (keyCheck)
             validateCacheKey(key);
 
+        //TODO IGNITE-7956
+        MvccUtils.verifyMvccOperationSupport(ctx, "Evict");
+
         return evictx(key, ctx.versions().next(), CU.empty0());
     }
 
@@ -4606,6 +4654,9 @@ private boolean clearLocally0(K key, boolean readers) {
         if (keyCheck)
             validateCacheKey(keys);
 
+        //TODO IGNITE-7956
+        MvccUtils.verifyMvccOperationSupport(ctx, "Evict");
+
         GridCacheVersion obsoleteVer = ctx.versions().next();
 
         try {
@@ -4890,7 +4941,8 @@ private void advance() {
             /*transformClo*/null,
             /*taskName*/null,
             /*expiryPlc*/null,
-            !deserializeBinary);
+            !deserializeBinary,
+            null); // TODO IGNITE-7371
 
         if (val == null)
             return null;
@@ -4950,6 +5002,7 @@ public void execute(boolean retry) {
                 READ_COMMITTED,
                 CU.transactionConfiguration(ctx, ctx.kernalContext().config()).getDefaultTxTimeout(),
                 opCtx == null || !opCtx.skipStore(),
+                false,
                 0,
                 null);
 
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheAffinityManager.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheAffinityManager.java
index c9ee38cf898d..cf4344d9b74b 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheAffinityManager.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheAffinityManager.java
@@ -32,6 +32,7 @@
 import org.apache.ignite.internal.processors.affinity.AffinityAssignment;
 import org.apache.ignite.internal.processors.affinity.AffinityTopologyVersion;
 import org.apache.ignite.internal.processors.affinity.GridAffinityAssignmentCache;
+import org.apache.ignite.internal.processors.cache.mvcc.MvccCoordinator;
 import org.apache.ignite.internal.util.future.GridFinishedFuture;
 import org.apache.ignite.internal.util.typedef.F;
 import org.jetbrains.annotations.Nullable;
@@ -243,6 +244,10 @@ public AffinityAssignment assignment(AffinityTopologyVersion topVer) {
         return aff0.cachedAffinity(topVer);
     }
 
+    public MvccCoordinator mvccCoordinator(AffinityTopologyVersion topVer) {
+        return assignment(topVer).mvccCoordinator();
+    }
+
     /**
      * @param key Key to check.
      * @param topVer Topology version.
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheContext.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheContext.java
index 61b187883bb1..ac9de7ce0944 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheContext.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheContext.java
@@ -2113,6 +2113,13 @@ public boolean readNoEntry(@Nullable IgniteCacheExpiryPolicy expiryPlc, boolean
         return !config().isOnheapCacheEnabled() && !readers && expiryPlc == null;
     }
 
+    /**
+     * @return {@code True} if mvcc is enabled for cache.
+     */
+    public boolean mvccEnabled() {
+        return grp.mvccEnabled();
+    }
+
     /**
      * @param part Partition.
      * @param topVer Topology version.
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheEntryEx.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheEntryEx.java
index 1445335cefda..3a2af5dd9c18 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheEntryEx.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheEntryEx.java
@@ -18,6 +18,7 @@
 package org.apache.ignite.internal.processors.cache;
 
 import java.util.Collection;
+import java.util.List;
 import java.util.UUID;
 import javax.cache.Cache;
 import javax.cache.expiry.ExpiryPolicy;
@@ -27,7 +28,11 @@
 import org.apache.ignite.internal.processors.affinity.AffinityTopologyVersion;
 import org.apache.ignite.internal.processors.cache.distributed.GridDistributedLockCancelledException;
 import org.apache.ignite.internal.processors.cache.distributed.dht.GridDhtLocalPartition;
+import org.apache.ignite.internal.processors.cache.distributed.dht.GridDhtTxLocalAdapter;
 import org.apache.ignite.internal.processors.cache.distributed.dht.atomic.GridDhtAtomicAbstractUpdateFuture;
+import org.apache.ignite.internal.processors.cache.mvcc.MvccSnapshot;
+import org.apache.ignite.internal.processors.cache.mvcc.MvccVersion;
+import org.apache.ignite.internal.processors.cache.mvcc.txlog.TxState;
 import org.apache.ignite.internal.processors.cache.persistence.CacheDataRow;
 import org.apache.ignite.internal.processors.cache.transactions.IgniteInternalTx;
 import org.apache.ignite.internal.processors.cache.transactions.IgniteTxKey;
@@ -186,6 +191,11 @@
      */
     @Nullable public GridCacheEntryInfo info();
 
+    /**
+     * @return Entry info for each MVCC version.
+     */
+    @Nullable public List<GridCacheEntryInfo> allVersionsInfo() throws IgniteCheckedException;
+
     /**
      * Invalidates this entry.
      *
@@ -264,7 +274,8 @@ public boolean evictInternal(GridCacheVersion obsoleteVer, @Nullable CacheEntryP
         Object transformClo,
         String taskName,
         @Nullable IgniteCacheExpiryPolicy expiryPlc,
-        boolean keepBinary)
+        boolean keepBinary,
+        @Nullable MvccSnapshot mvccVer)
         throws IgniteCheckedException, GridCacheEntryRemovedException;
 
     /**
@@ -292,6 +303,7 @@ public EntryGetResult innerGetVersioned(
         String taskName,
         @Nullable IgniteCacheExpiryPolicy expiryPlc,
         boolean keepBinary,
+        @Nullable MvccSnapshot mvccVer,
         @Nullable ReaderArguments readerArgs)
         throws IgniteCheckedException, GridCacheEntryRemovedException;
 
@@ -313,6 +325,7 @@ public EntryGetResult innerGetAndReserveForLoad(boolean updateMetrics,
         String taskName,
         @Nullable IgniteCacheExpiryPolicy expiryPlc,
         boolean keepBinary,
+        @Nullable MvccSnapshot mvccVer,
         @Nullable ReaderArguments readerArgs) throws IgniteCheckedException, GridCacheEntryRemovedException;
 
     /**
@@ -329,6 +342,64 @@ public EntryGetResult innerGetAndReserveForLoad(boolean updateMetrics,
      */
     @Nullable public CacheObject innerReload() throws IgniteCheckedException, GridCacheEntryRemovedException;
 
+    /**
+     * @param tx Cache transaction.
+     * @param affNodeId Partitioned node iD.
+     * @param val Value to set.
+     * @param ttl0 TTL.
+     * @param topVer Topology version.
+     * @param updateCntr Update counter.
+     * @param mvccVer Mvcc version.
+     * @param op Cache operation.
+     * @param needHistory Whether to collect rows created or affected by the current tx.
+     * @param noCreate Entry should not be created when enabled, e.g. SQL INSERT.
+     * @return Tuple containing success flag and old value. If success is {@code false},
+     *      then value is {@code null}.
+     * @throws IgniteCheckedException If storing value failed.
+     * @throws GridCacheEntryRemovedException If entry has been removed.
+     */
+    public GridCacheUpdateTxResult mvccSet(
+        @Nullable IgniteInternalTx tx,
+        UUID affNodeId,
+        CacheObject val,
+        long ttl0,
+        AffinityTopologyVersion topVer,
+        @Nullable Long updateCntr,
+        MvccSnapshot mvccVer,
+        GridCacheOperation op,
+        boolean needHistory,
+        boolean noCreate) throws IgniteCheckedException, GridCacheEntryRemovedException;
+
+    /**
+     * @param tx Cache transaction.
+     * @param affNodeId Partitioned node iD.
+     * @param topVer Topology version.
+     * @param updateCntr Update counter.
+     * @param mvccVer Mvcc version.
+     * @param needHistory Whether to collect rows created or affected by the current tx.
+     * @return Tuple containing success flag and old value. If success is {@code false},
+     *      then value is {@code null}.
+     * @throws IgniteCheckedException If storing value failed.
+     * @throws GridCacheEntryRemovedException If entry has been removed.
+     */
+    public GridCacheUpdateTxResult mvccRemove(
+        @Nullable IgniteInternalTx tx,
+        UUID affNodeId,
+        AffinityTopologyVersion topVer,
+        @Nullable Long updateCntr,
+        MvccSnapshot mvccVer,
+        boolean needHistory) throws IgniteCheckedException, GridCacheEntryRemovedException;
+
+    /**
+     * @param tx Transaction adapter.
+     * @param mvccVer Mvcc version.
+     * @return Lock result.
+     * @throws GridCacheEntryRemovedException If entry has been removed.
+     * @throws IgniteCheckedException If locking failed
+     */
+    GridCacheUpdateTxResult mvccLock(GridDhtTxLocalAdapter tx,
+        MvccSnapshot mvccVer) throws GridCacheEntryRemovedException, IgniteCheckedException;
+
     /**
      * @param tx Cache transaction.
      * @param evtNodeId ID of node responsible for this change.
@@ -377,7 +448,8 @@ public GridCacheUpdateTxResult innerSet(
         @Nullable UUID subjId,
         String taskName,
         @Nullable GridCacheVersion dhtVer,
-        @Nullable Long updateCntr
+        @Nullable Long updateCntr,
+        @Nullable MvccSnapshot mvccVer
     ) throws IgniteCheckedException, GridCacheEntryRemovedException;
 
     /**
@@ -419,7 +491,8 @@ public GridCacheUpdateTxResult innerRemove(
         @Nullable UUID subjId,
         String taskName,
         @Nullable GridCacheVersion dhtVer,
-        @Nullable Long updateCntr
+        @Nullable Long updateCntr,
+        MvccSnapshot mvccVer
     ) throws IgniteCheckedException, GridCacheEntryRemovedException;
 
     /**
@@ -663,8 +736,43 @@ public boolean tmLock(IgniteInternalTx tx,
      * @throws IgniteCheckedException In case of error.
      * @throws GridCacheEntryRemovedException If entry was removed.
      */
+    default boolean initialValue(CacheObject val,
+        GridCacheVersion ver,
+        long ttl,
+        long expireTime,
+        boolean preload,
+        AffinityTopologyVersion topVer,
+        GridDrType drType,
+        boolean fromStore) throws IgniteCheckedException, GridCacheEntryRemovedException {
+        return initialValue(val, ver, null, null, TxState.NA, TxState.NA,
+            ttl, expireTime, preload, topVer, drType, fromStore);
+    }
+
+    /**
+     * Sets new value if current version is <tt>0</tt>
+     *
+     * @param val New value.
+     * @param ver Version to use.
+     * @param mvccVer Mvcc version.
+     * @param newMvccVer New mvcc version.
+     * @param mvccTxState Tx state hint for mvcc version.
+     * @param newMvccTxState Tx state hint for new mvcc version.
+     * @param ttl Time to live.
+     * @param expireTime Expiration time.
+     * @param preload Flag indicating whether entry is being preloaded.
+     * @param topVer Topology version.
+     * @param drType DR type.
+     * @param fromStore {@code True} if value was loaded from store.
+     * @return {@code True} if initial value was set.
+     * @throws IgniteCheckedException In case of error.
+     * @throws GridCacheEntryRemovedException If entry was removed.
+     */
     public boolean initialValue(CacheObject val,
         GridCacheVersion ver,
+        @Nullable MvccVersion mvccVer,
+        @Nullable MvccVersion newMvccVer,
+        byte mvccTxState,
+        byte newMvccTxState,
         long ttl,
         long expireTime,
         boolean preload,
@@ -1044,6 +1152,27 @@ public void updateIndex(SchemaIndexCacheFilter filter, SchemaIndexCacheVisitorCl
      */
     public boolean lockedByCurrentThread();
 
+    /**
+     *
+     * @param tx Transaction.
+     * @param affNodeId Affinity node id.
+     * @param topVer Topology version.
+     * @param updateCntr Update counter.
+     * @param op Cache operation.
+     * @param mvccVer Mvcc version.  @return Update result.
+     * @throws IgniteCheckedException, If failed.
+     * @throws GridCacheEntryRemovedException, If entry has been removed.
+     */
+    public GridCacheUpdateTxResult mvccUpdateRowsWithPreloadInfo(
+        IgniteInternalTx tx,
+        UUID affNodeId,
+        AffinityTopologyVersion topVer,
+        Long updateCntr,
+        List<GridCacheEntryInfo> entries,
+        GridCacheOperation op,
+        MvccSnapshot mvccVer)
+        throws IgniteCheckedException, GridCacheEntryRemovedException;
+
     /**
      * Touch this entry in its context's eviction manager.
      *
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheEntryInfo.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheEntryInfo.java
index 7371153d4ae5..5c8164c96a5d 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheEntryInfo.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheEntryInfo.java
@@ -17,9 +17,11 @@
 
 package org.apache.ignite.internal.processors.cache;
 
-import java.nio.ByteBuffer;
 import org.apache.ignite.IgniteCheckedException;
 import org.apache.ignite.internal.GridDirectTransient;
+import org.apache.ignite.internal.processors.cache.mvcc.MvccUpdateVersionAware;
+import org.apache.ignite.internal.processors.cache.mvcc.MvccVersion;
+import org.apache.ignite.internal.processors.cache.mvcc.MvccVersionAware;
 import org.apache.ignite.internal.processors.cache.version.GridCacheVersion;
 import org.apache.ignite.internal.util.tostring.GridToStringInclude;
 import org.apache.ignite.internal.util.typedef.internal.S;
@@ -28,6 +30,8 @@
 import org.apache.ignite.plugin.extensions.communication.MessageReader;
 import org.apache.ignite.plugin.extensions.communication.MessageWriter;
 
+import java.nio.ByteBuffer;
+
 /**
  * Entry information that gets passed over wire.
  */
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheMapEntry.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheMapEntry.java
index a1722db072b6..249fbb3a14ec 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheMapEntry.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheMapEntry.java
@@ -34,6 +34,7 @@
 import org.apache.ignite.IgniteException;
 import org.apache.ignite.IgniteLogger;
 import org.apache.ignite.cache.eviction.EvictableEntry;
+import org.apache.ignite.internal.IgniteInternalFuture;
 import org.apache.ignite.internal.NodeStoppingException;
 import org.apache.ignite.internal.UnregisteredBinaryTypeException;
 import org.apache.ignite.internal.UnregisteredClassException;
@@ -45,12 +46,17 @@
 import org.apache.ignite.internal.processors.cache.GridCacheUpdateAtomicResult.UpdateOutcome;
 import org.apache.ignite.internal.processors.cache.distributed.dht.GridDhtCacheEntry;
 import org.apache.ignite.internal.processors.cache.distributed.dht.GridDhtLocalPartition;
+import org.apache.ignite.internal.processors.cache.distributed.dht.GridDhtTxLocalAdapter;
 import org.apache.ignite.internal.processors.cache.distributed.dht.atomic.GridDhtAtomicAbstractUpdateFuture;
 import org.apache.ignite.internal.processors.cache.distributed.near.GridNearCacheEntry;
 import org.apache.ignite.internal.processors.cache.extras.GridCacheEntryExtras;
 import org.apache.ignite.internal.processors.cache.extras.GridCacheMvccEntryExtras;
 import org.apache.ignite.internal.processors.cache.extras.GridCacheObsoleteEntryExtras;
 import org.apache.ignite.internal.processors.cache.extras.GridCacheTtlEntryExtras;
+import org.apache.ignite.internal.processors.cache.mvcc.MvccSnapshot;
+import org.apache.ignite.internal.processors.cache.mvcc.MvccUtils;
+import org.apache.ignite.internal.processors.cache.mvcc.MvccVersion;
+import org.apache.ignite.internal.processors.cache.mvcc.txlog.TxState;
 import org.apache.ignite.internal.processors.cache.persistence.CacheDataRow;
 import org.apache.ignite.internal.processors.cache.persistence.CacheDataRowAdapter;
 import org.apache.ignite.internal.processors.cache.persistence.DataRegion;
@@ -59,6 +65,8 @@
 import org.apache.ignite.internal.processors.cache.transactions.IgniteTxEntry;
 import org.apache.ignite.internal.processors.cache.transactions.IgniteTxKey;
 import org.apache.ignite.internal.processors.cache.transactions.IgniteTxLocalAdapter;
+import org.apache.ignite.internal.processors.cache.tree.mvcc.data.MvccUpdateResult;
+import org.apache.ignite.internal.processors.cache.tree.mvcc.data.ResultType;
 import org.apache.ignite.internal.processors.cache.version.GridCacheLazyPlainVersionedEntry;
 import org.apache.ignite.internal.processors.cache.version.GridCacheVersion;
 import org.apache.ignite.internal.processors.cache.version.GridCacheVersionConflictContext;
@@ -66,10 +74,14 @@
 import org.apache.ignite.internal.processors.cache.version.GridCacheVersionManager;
 import org.apache.ignite.internal.processors.cache.version.GridCacheVersionedEntryEx;
 import org.apache.ignite.internal.processors.dr.GridDrType;
+import org.apache.ignite.internal.processors.query.IgniteSQLException;
 import org.apache.ignite.internal.processors.query.schema.SchemaIndexCacheFilter;
 import org.apache.ignite.internal.processors.query.schema.SchemaIndexCacheVisitorClosure;
+import org.apache.ignite.internal.util.GridLongList;
 import org.apache.ignite.internal.util.IgniteTree;
+import org.apache.ignite.internal.util.future.GridFutureAdapter;
 import org.apache.ignite.internal.util.lang.GridClosureException;
+import org.apache.ignite.internal.util.lang.GridCursor;
 import org.apache.ignite.internal.util.lang.GridMetadataAwareAdapter;
 import org.apache.ignite.internal.util.lang.GridTuple;
 import org.apache.ignite.internal.util.lang.GridTuple3;
@@ -82,6 +94,7 @@
 import org.apache.ignite.internal.util.typedef.internal.S;
 import org.apache.ignite.internal.util.typedef.internal.U;
 import org.apache.ignite.lang.IgniteBiTuple;
+import org.apache.ignite.lang.IgniteInClosure;
 import org.apache.ignite.lang.IgnitePredicate;
 import org.apache.ignite.thread.IgniteThread;
 import org.jetbrains.annotations.Nullable;
@@ -92,13 +105,19 @@
 import static org.apache.ignite.events.EventType.EVT_CACHE_OBJECT_READ;
 import static org.apache.ignite.events.EventType.EVT_CACHE_OBJECT_REMOVED;
 import static org.apache.ignite.events.EventType.EVT_CACHE_OBJECT_UNLOCKED;
+import static org.apache.ignite.internal.processors.cache.GridCacheOperation.CREATE;
 import static org.apache.ignite.internal.processors.cache.GridCacheOperation.DELETE;
 import static org.apache.ignite.internal.processors.cache.GridCacheOperation.READ;
 import static org.apache.ignite.internal.processors.cache.GridCacheOperation.TRANSFORM;
 import static org.apache.ignite.internal.processors.cache.GridCacheOperation.UPDATE;
+import static org.apache.ignite.internal.processors.cache.persistence.CacheDataRowAdapter.RowData.NO_KEY;
+import static org.apache.ignite.internal.processors.cache.query.IgniteQueryErrorCode.CONCURRENT_UPDATE;
+import static org.apache.ignite.internal.processors.cache.query.IgniteQueryErrorCode.DUPLICATE_KEY;
 import static org.apache.ignite.internal.processors.cache.GridCacheUpdateAtomicResult.UpdateOutcome.INVOKE_NO_OP;
 import static org.apache.ignite.internal.processors.cache.GridCacheUpdateAtomicResult.UpdateOutcome.REMOVE_NO_VAL;
+import static org.apache.ignite.internal.processors.dr.GridDrType.DR_BACKUP;
 import static org.apache.ignite.internal.processors.dr.GridDrType.DR_NONE;
+import static org.apache.ignite.internal.processors.dr.GridDrType.DR_PRIMARY;
 
 /**
  * Adapter for cache entry.
@@ -358,6 +377,77 @@ protected GridDhtLocalPartition localPartition() {
         return info;
     }
 
+    /** {@inheritDoc} */
+    @Nullable @Override public List<GridCacheEntryInfo> allVersionsInfo() throws IgniteCheckedException {
+        assert cctx.mvccEnabled();
+
+        lockEntry();
+
+        try {
+            if (obsolete())
+                return Collections.emptyList();
+
+            GridCursor<? extends CacheDataRow> cur =
+                cctx.offheap().dataStore(localPartition()).mvccAllVersionsCursor(cctx, key, NO_KEY);
+
+            List<GridCacheEntryInfo> res = new ArrayList<>();
+
+            while (cur.next()) {
+                CacheDataRow row = cur.get();
+
+                GridCacheMvccEntryInfo info = new GridCacheMvccEntryInfo();
+
+                info.key(key);
+                info.value(row.value());
+                info.cacheId(cctx.cacheId());
+                info.version(row.version());
+                info.setNew(false);
+                info.setDeleted(false);
+
+                byte txState = row.mvccTxState() != TxState.NA ? row.mvccTxState() :
+                    MvccUtils.state(cctx, row.mvccCoordinatorVersion(), row.mvccCounter(),
+                        row.mvccOperationCounter());
+
+                if (txState == TxState.ABORTED)
+                    continue;
+
+                info.mvccVersion(row.mvccCoordinatorVersion(), row.mvccCounter(), row.mvccOperationCounter());
+                info.mvccTxState(txState);
+
+                byte newTxState = row.newMvccTxState() != TxState.NA ? row.newMvccTxState() :
+                    MvccUtils.state(cctx, row.newMvccCoordinatorVersion(), row.newMvccCounter(),
+                        row.newMvccOperationCounter());
+
+                if (newTxState != TxState.ABORTED) {
+                    info.newMvccVersion(row.newMvccCoordinatorVersion(),
+                        row.newMvccCounter(),
+                        row.newMvccOperationCounter());
+
+                    info.newMvccTxState(newTxState);
+                }
+
+                long expireTime = row.expireTime();
+
+                long ttl;
+
+                ttl = expireTime == CU.EXPIRE_TIME_ETERNAL ? CU.TTL_ETERNAL : expireTime - U.currentTimeMillis();
+
+                if (ttl < 0)
+                    ttl = CU.TTL_MINIMUM;
+
+                info.ttl(ttl);
+                info.expireTime(expireTime);
+
+                res.add(info);
+            }
+
+            return res;
+        }
+        finally {
+            unlockEntry();
+        }
+    }
+
     /** {@inheritDoc} */
     @Override public final CacheObject unswap() throws IgniteCheckedException, GridCacheEntryRemovedException {
         return unswap(true);
@@ -490,7 +580,8 @@ protected GridDhtLocalPartition localPartition() {
         Object transformClo,
         String taskName,
         @Nullable IgniteCacheExpiryPolicy expirePlc,
-        boolean keepBinary)
+        boolean keepBinary,
+        MvccSnapshot mvccVer)
         throws IgniteCheckedException, GridCacheEntryRemovedException {
         return (CacheObject)innerGet0(
             ver,
@@ -505,6 +596,7 @@ protected GridDhtLocalPartition localPartition() {
             false,
             keepBinary,
             false,
+            mvccVer,
             null);
     }
 
@@ -515,6 +607,7 @@ protected GridDhtLocalPartition localPartition() {
         String taskName,
         @Nullable IgniteCacheExpiryPolicy expiryPlc,
         boolean keepBinary,
+        MvccSnapshot mvccVer,
         @Nullable ReaderArguments readerArgs) throws IgniteCheckedException, GridCacheEntryRemovedException {
         return (EntryGetResult)innerGet0(
             /*ver*/null,
@@ -529,6 +622,7 @@ protected GridDhtLocalPartition localPartition() {
             true,
             keepBinary,
             /*reserve*/true,
+            mvccVer,
             readerArgs);
     }
 
@@ -543,6 +637,7 @@ protected GridDhtLocalPartition localPartition() {
         String taskName,
         @Nullable IgniteCacheExpiryPolicy expiryPlc,
         boolean keepBinary,
+        MvccSnapshot mvccVer,
         @Nullable ReaderArguments readerArgs)
         throws IgniteCheckedException, GridCacheEntryRemovedException {
         return (EntryGetResult)innerGet0(
@@ -558,6 +653,7 @@ protected GridDhtLocalPartition localPartition() {
             true,
             keepBinary,
             false,
+            mvccVer,
             readerArgs);
     }
 
@@ -576,6 +672,7 @@ private Object innerGet0(
         boolean retVer,
         boolean keepBinary,
         boolean reserveForLoad,
+        @Nullable MvccSnapshot mvccVer,
         @Nullable ReaderArguments readerArgs
     ) throws IgniteCheckedException, GridCacheEntryRemovedException {
         assert !(retVer && readThrough);
@@ -599,41 +696,53 @@ private Object innerGet0(
         try {
             checkObsolete();
 
-            boolean valid = valid(tx != null ? tx.topologyVersion() : cctx.affinity().affinityTopologyVersion());
-
             CacheObject val;
 
-            if (valid) {
-                val = this.val;
-
-                if (val == null) {
-                    if (isStartVersion()) {
-                        unswap(null, false);
+            if (mvccVer != null) {
+                CacheDataRow row = cctx.offheap().mvccRead(cctx, key, mvccVer);
 
-                        val = this.val;
-                    }
+                if (row != null) {
+                    val = row.value();
+                    resVer = row.version();
                 }
+                else
+                    val = null;
+            }
+            else {
+                boolean valid = valid(tx != null ? tx.topologyVersion() : cctx.affinity().affinityTopologyVersion());
 
-                if (val != null) {
-                    long expireTime = expireTimeExtras();
+                if (valid) {
+                    val = this.val;
 
-                    if (expireTime > 0 && (expireTime < U.currentTimeMillis())) {
-                        if (onExpired((CacheObject)cctx.unwrapTemporary(val), null)) {
-                            val = null;
-                            evt = false;
+                    if (val == null) {
+                        if (isStartVersion()) {
+                            unswap(null, false);
 
-                            if (cctx.deferredDelete()) {
-                                deferred = true;
-                                ver0 = ver;
+                            val = this.val;
+                        }
+                    }
+
+                    if (val != null) {
+                        long expireTime = expireTimeExtras();
+
+                        if (expireTime > 0 && (expireTime < U.currentTimeMillis())) {
+                            if (onExpired((CacheObject)cctx.unwrapTemporary(val), null)) {
+                                val = null;
+                                evt = false;
+
+                                if (cctx.deferredDelete()) {
+                                    deferred = true;
+                                    ver0 = ver;
+                                }
+                                else
+                                    obsolete = true;
                             }
-                            else
-                                obsolete = true;
                         }
                     }
                 }
+                else
+                    val = null;
             }
-            else
-                val = null;
 
             CacheObject ret = val;
 
@@ -673,7 +782,7 @@ private Object innerGet0(
             if (ret != null && expiryPlc != null)
                 updateTtl(expiryPlc);
 
-            if (retVer) {
+            if (retVer && resVer == null) {
                 resVer = (isNear() && cctx.transactional()) ? ((GridNearCacheEntry)this).dhtVersion() : this.ver;
 
                 if (resVer == null)
@@ -759,7 +868,10 @@ else if (tx.dht()) {
                     long expTime = CU.toExpireTime(ttl);
 
                     // Update indexes before actual write to entry.
-                    storeValue(ret, expTime, nextVer);
+                    if (cctx.mvccEnabled())
+                        cctx.offheap().mvccInitialValue(this, ret, nextVer, expTime);
+                    else
+                        storeValue(ret, expTime, nextVer);
 
                     update(ret, expTime, ttl, nextVer, true);
 
@@ -870,13 +982,19 @@ private EntryGetResult entryGetResult(CacheObject val, GridCacheVersion ver, boo
 
                     // Update indexes.
                     if (ret != null) {
-                        storeValue(ret, expTime, nextVer);
+                        if (cctx.mvccEnabled())
+                            cctx.offheap().mvccInitialValue(this, ret, nextVer, expTime);
+                        else
+                            storeValue(ret, expTime, nextVer);
 
                         if (cctx.deferredDelete() && !isInternal() && !detached() && deletedUnlocked())
                             deletedUnlocked(false);
                     }
                     else {
-                        removeValue();
+                        if (cctx.mvccEnabled())
+                            cctx.offheap().mvccRemoveAll(this);
+                        else
+                            removeValue();
 
                         if (cctx.deferredDelete() && !isInternal() && !detached() && !deletedUnlocked())
                             deletedUnlocked(true);
@@ -911,6 +1029,295 @@ protected void recordNodeId(UUID nodeId, AffinityTopologyVersion topVer) {
         // No-op.
     }
 
+    /** {@inheritDoc} */
+    @Override public final GridCacheUpdateTxResult mvccSet(
+        IgniteInternalTx tx,
+        UUID affNodeId,
+        CacheObject val,
+        long ttl0,
+        AffinityTopologyVersion topVer,
+        @Nullable Long updateCntr,
+        MvccSnapshot mvccVer,
+        GridCacheOperation op,
+        boolean needHistory,
+        boolean noCreate) throws IgniteCheckedException, GridCacheEntryRemovedException {
+        assert tx != null;
+
+        final boolean valid = valid(tx.topologyVersion());
+
+        final GridCacheVersion newVer;
+
+        WALPointer logPtr = null;
+
+        ensureFreeSpace();
+
+        lockEntry();
+
+        MvccUpdateResult res;
+
+        try {
+            checkObsolete();
+
+            newVer = tx.writeVersion();
+
+            assert newVer != null : "Failed to get write version for tx: " + tx;
+
+            // Determine new ttl and expire time.
+            long expireTime, ttl = ttl0;
+
+            if (ttl == -1L) {
+                ttl = ttlExtras();
+                expireTime = expireTimeExtras();
+            }
+            else
+                expireTime = CU.toExpireTime(ttl);
+
+            assert ttl >= 0 : ttl;
+            assert expireTime >= 0 : expireTime;
+
+            // Detach value before index update.
+            val = cctx.kernalContext().cacheObjects().prepareForCache(val, cctx);
+
+            assert val != null;
+
+            res = cctx.offheap().mvccUpdate(
+                this, val, newVer, expireTime, mvccVer, tx.local(), needHistory, noCreate);
+
+            assert res != null;
+
+            // VERSION_FOUND is possible only on primary node when inserting the same key, or on backup when
+            // updating the key which just has been rebalanced.
+            assert res.resultType() != ResultType.VERSION_FOUND || op == CREATE && tx.local() || !tx.local();
+
+            // PREV_NOT_NULL on CREATE is possible only on primary.
+            assert res.resultType() != ResultType.PREV_NOT_NULL || op != CREATE || tx.local();
+
+            if (res.resultType() == ResultType.VERSION_MISMATCH)
+                throw new IgniteSQLException("Mvcc version mismatch.", CONCURRENT_UPDATE);
+            else if (noCreate && res.resultType() == ResultType.PREV_NULL)
+                return new GridCacheUpdateTxResult(false);
+            else if (res.resultType() == ResultType.LOCKED) {
+                unlockEntry();
+
+                MvccVersion lockVer = res.resultVersion();
+
+                GridFutureAdapter<GridCacheUpdateTxResult> resFut = new GridFutureAdapter<>();
+
+                IgniteInternalFuture<?> lockFut = cctx.kernalContext().coordinators().waitFor(cctx, lockVer);
+
+                lockFut.listen(new MvccUpdateLockListener(tx, this, affNodeId, topVer, val, ttl0, updateCntr, mvccVer,
+                    op, needHistory, noCreate, resFut));
+
+                return new GridCacheUpdateTxResult(false, resFut);
+            }
+            else if (op == CREATE && tx.local() && (res.resultType() == ResultType.PREV_NOT_NULL ||
+                res.resultType() == ResultType.VERSION_FOUND))
+                throw new IgniteSQLException("Duplicate key during INSERT [key=" + key + ']', DUPLICATE_KEY);
+
+            if (cctx.deferredDelete() && deletedUnlocked() && !detached())
+                deletedUnlocked(false);
+
+            assert tx.local() && updateCntr == null || !tx.local() && updateCntr != null && updateCntr > 0;
+
+            if (tx.local())
+                updateCntr = nextMvccPartitionCounter();
+
+            if (cctx.group().persistenceEnabled() && cctx.group().walEnabled())
+                logPtr = cctx.shared().wal().log(new DataRecord(new DataEntry(
+                        cctx.cacheId(),
+                        key,
+                        val,
+                        res.resultType() == ResultType.PREV_NULL ? CREATE : UPDATE,
+                        tx.nearXidVersion(),
+                        newVer,
+                        expireTime,
+                        key.partition(),
+                        updateCntr)));
+
+            update(val, expireTime, ttl, newVer, true);
+
+            mvccDrReplicate(tx.local() ? DR_PRIMARY : DR_BACKUP, val, newVer, topVer, mvccVer);
+
+            recordNodeId(affNodeId, topVer);
+        }
+        finally {
+            if (lockedByCurrentThread()) {
+                unlockEntry();
+
+                cctx.evicts().touch(this, AffinityTopologyVersion.NONE);
+            }
+        }
+
+        onUpdateFinished(updateCntr);
+
+        GridCacheUpdateTxResult updRes = valid ? new GridCacheUpdateTxResult(true, updateCntr, logPtr) :
+            new GridCacheUpdateTxResult(false, logPtr);
+
+        updRes.mvccHistory(res.history());
+
+        return updRes;
+    }
+
+    /** {@inheritDoc} */
+    @Override public final GridCacheUpdateTxResult mvccRemove(
+        IgniteInternalTx tx,
+        UUID affNodeId,
+        AffinityTopologyVersion topVer,
+        @Nullable Long updateCntr,
+        MvccSnapshot mvccVer,
+        boolean needHistory) throws IgniteCheckedException, GridCacheEntryRemovedException {
+        assert tx != null;
+        assert mvccVer != null;
+
+        final boolean valid = valid(tx.topologyVersion());
+
+        final GridCacheVersion newVer;
+
+        WALPointer logPtr = null;
+
+        lockEntry();
+
+        MvccUpdateResult res;
+
+        try {
+            checkObsolete();
+
+            newVer = tx.writeVersion();
+
+            assert newVer != null : "Failed to get write version for tx: " + tx;
+
+            res = cctx.offheap().mvccRemove(this, mvccVer, tx.local(), needHistory);
+
+            assert res != null;
+
+            if (res.resultType() == ResultType.VERSION_MISMATCH)
+                throw new IgniteSQLException("Mvcc version mismatch.", CONCURRENT_UPDATE);
+            else if (res.resultType() == ResultType.PREV_NULL)
+                return new GridCacheUpdateTxResult(false);
+            else if (res.resultType() == ResultType.LOCKED) {
+                unlockEntry();
+
+                MvccVersion lockVer = res.resultVersion();
+
+                GridFutureAdapter<GridCacheUpdateTxResult> resFut = new GridFutureAdapter<>();
+
+                IgniteInternalFuture<?> lockFut = cctx.kernalContext().coordinators().waitFor(cctx, lockVer);
+
+                lockFut.listen(new MvccRemoveLockListener(tx, this, affNodeId, topVer, updateCntr, mvccVer, needHistory,
+                    resFut));
+
+                return new GridCacheUpdateTxResult(false, resFut);
+            }
+
+            if (cctx.deferredDelete() && deletedUnlocked() && !detached())
+                deletedUnlocked(false);
+
+            assert tx.local() && updateCntr == null || !tx.local() && updateCntr != null && updateCntr > 0;
+
+            if (tx.local())
+                updateCntr = nextMvccPartitionCounter();
+
+            if (cctx.group().persistenceEnabled() && cctx.group().walEnabled())
+                logPtr = logTxUpdate(tx, null, 0, updateCntr);
+
+            update(null, 0, 0, newVer, true);
+
+            mvccDrReplicate(tx.local() ? DR_PRIMARY : DR_BACKUP, null, newVer, topVer, mvccVer);
+
+            recordNodeId(affNodeId, topVer);
+        }
+        finally {
+            if (lockedByCurrentThread()) {
+                unlockEntry();
+
+                cctx.evicts().touch(this, AffinityTopologyVersion.NONE);
+            }
+        }
+
+        onUpdateFinished(updateCntr);
+
+        GridCacheUpdateTxResult updRes = valid ? new GridCacheUpdateTxResult(true, updateCntr, logPtr) :
+            new GridCacheUpdateTxResult(false, logPtr);
+
+        updRes.mvccHistory(res.history());
+
+        return updRes;
+    }
+
+    /** {@inheritDoc} */
+    @Override public GridCacheUpdateTxResult mvccLock(GridDhtTxLocalAdapter tx, MvccSnapshot mvccVer)
+        throws GridCacheEntryRemovedException, IgniteCheckedException {
+        assert tx != null;
+        assert mvccVer != null;
+
+        final boolean valid = valid(tx.topologyVersion());
+
+        final GridCacheVersion newVer;
+
+        WALPointer logPtr = null;
+
+        lockEntry();
+
+        try {
+            checkObsolete();
+
+            newVer = tx.writeVersion();
+
+            assert newVer != null : "Failed to get write version for tx: " + tx;
+
+            assert tx.local();
+
+            MvccUpdateResult res = cctx.offheap().mvccLock(this, mvccVer);
+
+            assert res != null;
+
+            if (res.resultType() == ResultType.VERSION_MISMATCH)
+                throw new IgniteSQLException("Mvcc version mismatch.", CONCURRENT_UPDATE);
+            else if (res.resultType() == ResultType.LOCKED) {
+                unlockEntry();
+
+                MvccVersion lockVer = res.resultVersion();
+
+                GridFutureAdapter<GridCacheUpdateTxResult> resFut = new GridFutureAdapter<>();
+
+                IgniteInternalFuture<?> lockFut = cctx.kernalContext().coordinators().waitFor(cctx, lockVer);
+
+                lockFut.listen(new MvccAcquireLockListener(tx, this, mvccVer, resFut));
+
+                return new GridCacheUpdateTxResult(false, resFut);
+            }
+        }
+        finally {
+            if (lockedByCurrentThread()) {
+                unlockEntry();
+
+                cctx.evicts().touch(this, AffinityTopologyVersion.NONE);
+            }
+        }
+
+        onUpdateFinished(0L);
+
+        return new GridCacheUpdateTxResult(valid, logPtr);
+    }
+
+    /**
+     * Enlist for DR if needed.
+     *
+     * @param drType DR type.
+     * @param val Value.
+     * @param ver Version.
+     * @param topVer Topology version.
+     * @param mvccVer MVCC snapshot.
+     * @throws IgniteCheckedException In case of exception.
+     */
+    private void mvccDrReplicate(GridDrType drType, CacheObject val, GridCacheVersion ver,
+        AffinityTopologyVersion topVer,
+        MvccSnapshot mvccVer) throws IgniteCheckedException {
+
+        if (cctx.isDrEnabled() && drType != DR_NONE && !isInternal())
+            cctx.dr().mvccReplicate(key, val, rawTtl(), rawExpireTime(), ver.conflictVersion(), drType, topVer, mvccVer);
+    }
+
     /** {@inheritDoc} */
     @Override public final GridCacheUpdateTxResult innerSet(
         @Nullable IgniteInternalTx tx,
@@ -933,15 +1340,16 @@ protected void recordNodeId(UUID nodeId, AffinityTopologyVersion topVer) {
         @Nullable UUID subjId,
         String taskName,
         @Nullable GridCacheVersion dhtVer,
-        @Nullable Long updateCntr
+        @Nullable Long updateCntr,
+        @Nullable MvccSnapshot mvccVer
     ) throws IgniteCheckedException, GridCacheEntryRemovedException {
         CacheObject old;
 
-        boolean valid = valid(tx != null ? tx.topologyVersion() : topVer);
+        final boolean valid = valid(tx != null ? tx.topologyVersion() : topVer);
 
         // Lock should be held by now.
         if (!cctx.isAll(this, filter))
-            return new GridCacheUpdateTxResult(false, null, null);
+            return new GridCacheUpdateTxResult(false);
 
         final GridCacheVersion newVer;
 
@@ -955,6 +1363,8 @@ protected void recordNodeId(UUID nodeId, AffinityTopologyVersion topVer) {
 
         ensureFreeSpace();
 
+        GridLongList mvccWaitTxs = null;
+
         lockEntry();
 
         try {
@@ -965,7 +1375,7 @@ protected void recordNodeId(UUID nodeId, AffinityTopologyVersion topVer) {
 
                 // It is possible that 'get' could load more recent value.
                 if (!((GridNearCacheEntry)this).recordDhtVersion(dhtVer))
-                    return new GridCacheUpdateTxResult(false, null, logPtr);
+                    return new GridCacheUpdateTxResult(false, logPtr);
             }
 
             assert tx == null || (!tx.local() && tx.onePhaseCommit()) || tx.ownsLock(this) :
@@ -1001,7 +1411,7 @@ protected void recordNodeId(UUID nodeId, AffinityTopologyVersion topVer) {
                 key0 = e.key();
 
                 if (interceptorVal == null)
-                    return new GridCacheUpdateTxResult(false, (CacheObject)cctx.unwrapTemporary(old), logPtr);
+                    return new GridCacheUpdateTxResult(false, logPtr);
                 else if (interceptorVal != val0)
                     val0 = cctx.unwrapTemporary(interceptorVal);
 
@@ -1033,7 +1443,18 @@ else if (interceptorVal != val0)
 
             assert val != null;
 
-            storeValue(val, expireTime, newVer);
+            if (cctx.mvccEnabled()) {
+                assert mvccVer != null;
+
+                mvccWaitTxs = cctx.offheap().mvccUpdateNative(tx.local(),
+                    this,
+                    val,
+                    newVer,
+                    expireTime,
+                    mvccVer);
+            }
+            else
+                storeValue(val, expireTime, newVer);
 
             if (cctx.deferredDelete() && deletedUnlocked() && !isInternal() && !detached())
                 deletedUnlocked(false);
@@ -1112,8 +1533,8 @@ else if (interceptorVal != val0)
         if (intercept)
             cctx.config().getInterceptor().onAfterPut(new CacheLazyEntry(cctx, key, key0, val, val0, keepBinary, updateCntr0));
 
-        return valid ? new GridCacheUpdateTxResult(true, retval ? old : null, updateCntr0, logPtr) :
-            new GridCacheUpdateTxResult(false, null, logPtr);
+        return valid ? new GridCacheUpdateTxResult(true, updateCntr0, logPtr, mvccWaitTxs) :
+            new GridCacheUpdateTxResult(false, logPtr);
     }
 
     /**
@@ -1142,7 +1563,8 @@ protected Object keyValue(boolean cpy) {
         @Nullable UUID subjId,
         String taskName,
         @Nullable GridCacheVersion dhtVer,
-        @Nullable Long updateCntr
+        @Nullable Long updateCntr,
+        @Nullable MvccSnapshot mvccVer
     ) throws IgniteCheckedException, GridCacheEntryRemovedException {
         assert cctx.transactional();
 
@@ -1150,11 +1572,11 @@ protected Object keyValue(boolean cpy) {
 
         GridCacheVersion newVer;
 
-        boolean valid = valid(tx != null ? tx.topologyVersion() : topVer);
+        final boolean valid = valid(tx != null ? tx.topologyVersion() : topVer);
 
         // Lock should be held by now.
         if (!cctx.isAll(this, filter))
-            return new GridCacheUpdateTxResult(false, null, null);
+            return new GridCacheUpdateTxResult(false);
 
         GridCacheVersion obsoleteVer = null;
 
@@ -1172,6 +1594,8 @@ protected Object keyValue(boolean cpy) {
 
         boolean marked = false;
 
+        GridLongList mvccWaitTxs = null;
+
         lockEntry();
 
         try {
@@ -1182,7 +1606,7 @@ protected Object keyValue(boolean cpy) {
 
                 // It is possible that 'get' could load more recent value.
                 if (!((GridNearCacheEntry)this).recordDhtVersion(dhtVer))
-                    return new GridCacheUpdateTxResult(false, null, logPtr);
+                    return new GridCacheUpdateTxResult(false, logPtr);
             }
 
             assert tx == null || (!tx.local() && tx.onePhaseCommit()) || tx.ownsLock(this) :
@@ -1210,11 +1634,17 @@ protected Object keyValue(boolean cpy) {
                 if (cctx.cancelRemove(interceptRes)) {
                     CacheObject ret = cctx.toCacheObject(cctx.unwrapTemporary(interceptRes.get2()));
 
-                    return new GridCacheUpdateTxResult(false, ret, logPtr);
+                    return new GridCacheUpdateTxResult(false, logPtr);
                 }
             }
 
-            removeValue();
+            if (cctx.mvccEnabled()) {
+                assert mvccVer != null;
+
+                mvccWaitTxs = cctx.offheap().mvccRemoveNative(tx.local(), this, mvccVer);
+            }
+            else
+                removeValue();
 
             update(null, 0, 0, newVer, true);
 
@@ -1336,18 +1766,10 @@ else if (log.isDebugEnabled())
         if (intercept)
             cctx.config().getInterceptor().onAfterRemove(entry0);
 
-        if (valid) {
-            CacheObject ret;
-
-            if (interceptRes != null)
-                ret = cctx.toCacheObject(cctx.unwrapTemporary(interceptRes.get2()));
-            else
-                ret = old;
-
-            return new GridCacheUpdateTxResult(true, ret, updateCntr0, logPtr);
-        }
+        if (valid)
+            return new GridCacheUpdateTxResult(true, updateCntr0, logPtr, mvccWaitTxs);
         else
-            return new GridCacheUpdateTxResult(false, null, logPtr);
+            return new GridCacheUpdateTxResult(false, logPtr);
     }
 
     /**
@@ -2176,7 +2598,10 @@ protected void clearReader(UUID nodeId) throws GridCacheEntryRemovedException {
                     ", val=" + val + ']');
             }
 
-            removeValue();
+            if (cctx.mvccEnabled())
+                cctx.offheap().mvccRemoveAll(this);
+            else
+                removeValue();
         }
         finally {
             unlockEntry();
@@ -2408,7 +2833,10 @@ protected final boolean markObsolete0(GridCacheVersion ver, boolean clear, GridC
             ver = newVer;
             flags &= ~IS_EVICT_DISABLED;
 
-            removeValue();
+            if (cctx.mvccEnabled())
+                cctx.offheap().mvccRemoveAll(this);
+            else
+                removeValue();
 
             onInvalidate();
 
@@ -2673,7 +3101,10 @@ private boolean checkExpired() throws IgniteCheckedException {
             long delta = expireTime - U.currentTimeMillis();
 
             if (delta <= 0) {
-                removeValue();
+                if (cctx.mvccEnabled())
+                    cctx.offheap().mvccRemoveAll(this);
+                else
+                    removeValue();
 
                 return true;
             }
@@ -2738,6 +3169,10 @@ protected final boolean hasValueUnlocked() {
     @Override public boolean initialValue(
         CacheObject val,
         GridCacheVersion ver,
+        MvccVersion mvccVer,
+        MvccVersion newMvccVer,
+        byte mvccTxState,
+        byte newMvccTxState,
         long ttl,
         long expireTime,
         boolean preload,
@@ -2812,11 +3247,63 @@ else if (val == null)
                         }
                     }
 
-                    storeValue(val, expTime, ver);
+                    if (cctx.mvccEnabled()) {
+                        if (preload && mvccVer != null) {
+                            cctx.offheap().mvccInitialValueIfAbsent(this,
+                                val,
+                                ver,
+                                expTime,
+                                mvccVer,
+                                newMvccVer,
+                                mvccTxState,
+                                newMvccTxState);
+                        }
+                        else
+                            cctx.offheap().mvccInitialValue(this, val, ver, expTime, mvccVer, newMvccVer);
+                    }
+                    else
+                        storeValue(val, expTime, ver);
                 }
             }
-            else // Optimization to access storage only once.
-                update = storeValue(val, expTime, ver, p);
+            else {
+                if (cctx.mvccEnabled()) {
+                    // cannot identify whether the entry is exist on the fly
+                    unswap(false);
+
+                    if (update = p.apply(null)) {
+                        // If entry is already unswapped and we are modifying it, we must run deletion callbacks for old value.
+                        long oldExpTime = expireTimeUnlocked();
+                        long delta = (oldExpTime == 0 ? 0 : oldExpTime - U.currentTimeMillis());
+
+                        if (delta < 0) {
+                            if (onExpired(this.val, null)) {
+                                if (cctx.deferredDelete()) {
+                                    deferred = true;
+                                    oldVer = this.ver;
+                                }
+                                else if (val == null)
+                                    obsolete = true;
+                            }
+                        }
+
+                        if (preload && mvccVer != null) {
+                            cctx.offheap().mvccInitialValueIfAbsent(this,
+                                val,
+                                ver,
+                                expTime,
+                                mvccVer,
+                                newMvccVer,
+                                mvccTxState,
+                                newMvccTxState);
+                        }
+                        else
+                            cctx.offheap().mvccInitialValue(this, val, ver, expTime, mvccVer, newMvccVer);
+                    }
+                }
+                else
+                    // Optimization to access storage only once.
+                    update = storeValue(val, expTime, ver, p);
+            }
 
             if (update) {
                 update(val, expTime, ttl, ver, true);
@@ -2917,6 +3404,14 @@ protected long nextPartitionCounter(AffinityTopologyVersion topVer, boolean prim
         return 0;
     }
 
+    /**
+     * @return Next mvcc update counter.
+     */
+    protected long nextMvccPartitionCounter() {
+        return 0;
+    }
+
+
     /** {@inheritDoc} */
     @Override public GridCacheVersionedEntryEx versionedEntry(final boolean keepBinary)
         throws IgniteCheckedException, GridCacheEntryRemovedException {
@@ -3004,7 +3499,10 @@ protected long nextPartitionCounter(AffinityTopologyVersion topVer, boolean prim
                     // Detach value before index update.
                     val = cctx.kernalContext().cacheObjects().prepareForCache(val, cctx);
 
-                    if (val != null) {
+                if (val != null) {
+                    if (cctx.mvccEnabled())
+                        cctx.offheap().mvccInitialValue(this, val, newVer, expTime);
+                    else
                         storeValue(val, expTime, newVer);
 
                         if (deletedUnlocked())
@@ -3459,7 +3957,10 @@ private boolean onExpired(CacheObject expiredVal, GridCacheVersion obsoleteVer)
         cctx.shared().database().checkpointReadLock();
 
         try {
-            removeValue();
+            if (cctx.mvccEnabled())
+                cctx.offheap().mvccRemoveAll(this);
+            else
+                removeValue();
         }
         finally {
             cctx.shared().database().checkpointReadUnlock();
@@ -3634,8 +4135,7 @@ protected boolean storeValue(@Nullable CacheObject val,
     }
 
     /**
-     * Stores value in offheap.
-     *
+     * Stores value inoffheap.*
      * @param val Value.
      * @param expireTime Expire time.
      * @param ver New entry version.
@@ -4375,6 +4875,450 @@ private int extrasSize() {
         }
     }
 
+    /** */
+    private static class MvccRemoveLockListener implements IgniteInClosure<IgniteInternalFuture> {
+        /** */
+        private static final long serialVersionUID = -1578749008606139541L;
+
+        /** */
+        private final IgniteInternalTx tx;
+
+        /** */
+        private final AffinityTopologyVersion topVer;
+
+        /** */
+        private final UUID affNodeId;
+
+        /** */
+        private final MvccSnapshot mvccVer;
+
+        /** */
+        private final Long updateCntr;
+
+        /** */
+        private final boolean needHistory;
+
+        /** */
+        private final GridFutureAdapter<GridCacheUpdateTxResult> resFut;
+
+        /** */
+        private GridCacheMapEntry entry;
+
+        /** */
+        MvccRemoveLockListener(IgniteInternalTx tx,
+            GridCacheMapEntry entry,
+            UUID affNodeId,
+            AffinityTopologyVersion topVer,
+            Long updateCntr,
+            MvccSnapshot mvccVer,
+            boolean needHistory,
+            GridFutureAdapter<GridCacheUpdateTxResult> resFut) {
+            this.tx = tx;
+            this.entry = entry;
+            this.topVer = topVer;
+            this.affNodeId = affNodeId;
+            this.mvccVer = mvccVer;
+            this.updateCntr = updateCntr;
+            this.needHistory = needHistory;
+            this.resFut = resFut;
+        }
+
+        /** {@inheritDoc} */
+        @Override public void apply(IgniteInternalFuture lockFut) {
+            WALPointer logPtr = null;
+            long updateCntr0;
+            boolean valid;
+
+            GridCacheContext cctx = entry.context();
+            GridCacheVersion newVer = tx.writeVersion();
+
+            MvccUpdateResult res;
+
+            try {
+                lockFut.get();
+
+                while (true) {
+                    entry.lockEntry();
+
+                    if (entry.obsoleteVersionExtras() == null)
+                        break;
+
+                    entry.unlockEntry();
+
+                    entry = (GridCacheMapEntry)cctx.cache().entryEx(entry.key());
+                }
+
+                valid = entry.valid(tx.topologyVersion());
+
+                cctx.shared().database().checkpointReadLock();
+
+                try {
+                    res = cctx.offheap().mvccRemove(entry, mvccVer, tx.local(), needHistory);
+                } finally {
+                    cctx.shared().database().checkpointReadUnlock();
+                }
+
+                assert res != null;
+
+                if (res.resultType() == ResultType.VERSION_MISMATCH) {
+                    resFut.onDone(new IgniteSQLException("Mvcc version mismatch.", CONCURRENT_UPDATE));
+
+                    return;
+                }
+                else if (res.resultType() == ResultType.PREV_NULL) {
+                    resFut.onDone(new GridCacheUpdateTxResult(false));
+
+                    return;
+                }
+                else if (res.resultType() == ResultType.LOCKED) {
+                    entry.unlockEntry();
+
+                    IgniteInternalFuture<?> lockFuture = cctx.kernalContext().coordinators().waitFor(cctx, res.resultVersion());
+
+                    lockFuture.listen(this);
+
+                    return;
+                }
+
+                if (cctx.deferredDelete() && entry.deletedUnlocked() && !entry.detached())
+                    entry.deletedUnlocked(false);
+
+                assert tx.local() && updateCntr == null || !tx.local() && updateCntr != null && updateCntr > 0;
+
+                updateCntr0 = tx.local() ? entry.nextMvccPartitionCounter() : updateCntr;
+
+                if (updateCntr != null && updateCntr != 0)
+                    updateCntr0 = updateCntr;
+
+                if (cctx.group().persistenceEnabled() && cctx.group().walEnabled())
+                    logPtr = cctx.shared().wal().log(new DataRecord(new DataEntry(
+                            cctx.cacheId(),
+                            entry.key(),
+                            null,
+                            DELETE,
+                            tx.nearXidVersion(),
+                            tx.writeVersion(),
+                            0,
+                            entry.key().partition(),
+                            updateCntr0)));
+
+                entry.update(null, 0, 0, newVer, true);
+
+                entry.mvccDrReplicate(tx.local() ? DR_PRIMARY : DR_BACKUP, null, newVer, topVer, mvccVer);
+
+                entry.recordNodeId(affNodeId, topVer);
+            }
+            catch (IgniteCheckedException e) {
+                resFut.onDone(e);
+
+                return;
+            }
+            finally {
+                if (entry.lockedByCurrentThread()) {
+                    entry.unlockEntry();
+
+                    cctx.evicts().touch(entry, AffinityTopologyVersion.NONE);
+                }
+            }
+
+            entry.onUpdateFinished(updateCntr0);
+
+            GridCacheUpdateTxResult updRes = valid ? new GridCacheUpdateTxResult(true, updateCntr0, logPtr)
+                : new GridCacheUpdateTxResult(false, logPtr);
+
+            updRes.mvccHistory(res.history());
+
+            resFut.onDone(updRes);
+        }
+    }
+
+    /** */
+    private static class MvccAcquireLockListener implements IgniteInClosure<IgniteInternalFuture> {
+        /** */
+        private static final long serialVersionUID = -1578749008606139541L;
+
+        /** */
+        private final IgniteInternalTx tx;
+
+        /** */
+        private final MvccSnapshot mvccVer;
+
+        /** */
+        private final GridFutureAdapter<GridCacheUpdateTxResult> resFut;
+
+        /** */
+        private GridCacheMapEntry entry;
+
+        /** */
+        MvccAcquireLockListener(IgniteInternalTx tx,
+            GridCacheMapEntry entry,
+            MvccSnapshot mvccVer,
+            GridFutureAdapter<GridCacheUpdateTxResult> resFut) {
+            this.tx = tx;
+            this.entry = entry;
+            this.mvccVer = mvccVer;
+            this.resFut = resFut;
+        }
+
+        /** {@inheritDoc} */
+        @SuppressWarnings("unchecked")
+        @Override public void apply(IgniteInternalFuture lockFut) {
+            WALPointer logPtr = null;
+            boolean valid;
+
+            GridCacheContext cctx = entry.context();
+
+            try {
+                lockFut.get();
+
+                while (true) {
+                    entry.lockEntry();
+
+                    if (entry.obsoleteVersionExtras() == null)
+                        break;
+
+                    entry.unlockEntry();
+
+                    entry = (GridCacheMapEntry)cctx.cache().entryEx(entry.key());
+                }
+
+                valid = entry.valid(tx.topologyVersion());
+
+                cctx.shared().database().checkpointReadLock();
+
+                MvccUpdateResult res;
+
+                try {
+                    res = cctx.offheap().mvccLock(entry, mvccVer);
+                }
+                finally {
+                    cctx.shared().database().checkpointReadUnlock();
+                }
+
+                assert res != null;
+
+                if (res.resultType() == ResultType.VERSION_MISMATCH) {
+                    resFut.onDone(new IgniteSQLException("Mvcc version mismatch.", CONCURRENT_UPDATE));
+
+                    return;
+                }
+                else if (res.resultType() == ResultType.LOCKED) {
+                    entry.unlockEntry();
+
+                    cctx.kernalContext().coordinators().waitFor(cctx, res.resultVersion()).listen(this);
+
+                    return;
+                }
+            }
+            catch (IgniteCheckedException e) {
+                resFut.onDone(e);
+
+                return;
+            }
+            finally {
+                if (entry.lockedByCurrentThread()) {
+                    entry.unlockEntry();
+
+                    cctx.evicts().touch(entry, AffinityTopologyVersion.NONE);
+                }
+            }
+
+            entry.onUpdateFinished(0L);
+
+            resFut.onDone(new GridCacheUpdateTxResult(valid, logPtr));
+        }
+    }
+
+    /** */
+    private static class MvccUpdateLockListener implements IgniteInClosure<IgniteInternalFuture> {
+        /** */
+        private static final long serialVersionUID = 8452738214760268397L;
+
+        /** */
+        private final IgniteInternalTx tx;
+
+        /** */
+        private final UUID affNodeId;
+
+        /** */
+        private final AffinityTopologyVersion topVer;
+
+        /** */
+        private final CacheObject val;
+
+        /** */
+        private final long ttl;
+
+        /** */
+        private final Long updateCntr;
+
+        /** */
+        private final MvccSnapshot mvccVer;
+
+        /** */
+        private final GridFutureAdapter<GridCacheUpdateTxResult> resFut;
+
+        /** */
+        private GridCacheMapEntry entry;
+
+        /** */
+        private GridCacheOperation op;
+
+        /** */
+        private final boolean needHistory;
+
+        /** */
+        private final boolean noCreate;
+
+        /** */
+        MvccUpdateLockListener(IgniteInternalTx tx,
+            GridCacheMapEntry entry,
+            UUID affNodeId,
+            AffinityTopologyVersion topVer,
+            CacheObject val,
+            long ttl,
+            Long updateCntr,
+            MvccSnapshot mvccVer,
+            GridCacheOperation op,
+            boolean needHistory,
+            boolean noCreate,
+            GridFutureAdapter<GridCacheUpdateTxResult> resFut) {
+            this.tx = tx;
+            this.entry = entry;
+            this.affNodeId = affNodeId;
+            this.topVer = topVer;
+            this.val = val;
+            this.ttl = ttl;
+            this.updateCntr = updateCntr;
+            this.mvccVer = mvccVer;
+            this.op = op;
+            this.needHistory = needHistory;
+            this.noCreate = noCreate;
+            this.resFut = resFut;
+        }
+
+        /** {@inheritDoc} */
+        @Override public void apply(IgniteInternalFuture lockFut) {
+            WALPointer logPtr = null;
+            long updateCntr0;
+            boolean valid;
+
+            GridCacheContext cctx = entry.context();
+            GridCacheVersion newVer = tx.writeVersion();
+
+            MvccUpdateResult res;
+
+            try {
+                lockFut.get();
+
+                entry.ensureFreeSpace();
+
+                while (true) {
+                    entry.lockEntry();
+
+                    if (entry.obsoleteVersionExtras() == null)
+                        break;
+
+                    entry.unlockEntry();
+
+                    entry = (GridCacheMapEntry)cctx.cache().entryEx(entry.key());
+                }
+
+                valid = entry.valid(tx.topologyVersion());
+
+                // Determine new ttl and expire time.
+                long expireTime, ttl = this.ttl;
+
+                if (ttl == -1L) {
+                    ttl = entry.ttlExtras();
+                    expireTime = entry.expireTimeExtras();
+                }
+                else
+                    expireTime = CU.toExpireTime(ttl);
+
+                assert ttl >= 0 : ttl;
+                assert expireTime >= 0 : expireTime;
+
+                cctx.shared().database().checkpointReadLock();
+
+                try {
+                    res = cctx.offheap().mvccUpdate(
+                        entry, val, newVer, expireTime, mvccVer, tx.local(), needHistory, noCreate);
+                } finally {
+                    cctx.shared().database().checkpointReadUnlock();
+                }
+
+                assert res != null;
+
+                if (res.resultType() == ResultType.VERSION_MISMATCH) {
+                    resFut.onDone(new IgniteSQLException("Mvcc version mismatch.", CONCURRENT_UPDATE));
+
+                    return;
+                }
+                else if (res.resultType() == ResultType.LOCKED) {
+                    entry.unlockEntry();
+
+                    cctx.kernalContext().coordinators().waitFor(cctx, res.resultVersion()).listen(this);
+
+                    return;
+                }
+                else if (op == CREATE && tx.local() && (res.resultType() == ResultType.PREV_NOT_NULL ||
+                    res.resultType() == ResultType.VERSION_FOUND)) {
+                    resFut.onDone(new IgniteSQLException("Duplicate key during INSERT [key=" + entry.key() + ']',
+                        DUPLICATE_KEY));
+
+                    return;
+                }
+
+                if (cctx.deferredDelete() && entry.deletedUnlocked() && !entry.detached())
+                    entry.deletedUnlocked(false);
+
+                assert tx.local() && updateCntr == null || !tx.local() && updateCntr != null && updateCntr > 0;
+
+                updateCntr0 = tx.local() ? entry.nextMvccPartitionCounter() : updateCntr;
+
+                if (cctx.group().persistenceEnabled() && cctx.group().walEnabled())
+                    logPtr = cctx.shared().wal().log(new DataRecord(new DataEntry(
+                            cctx.cacheId(),
+                            entry.key(),
+                            val,
+                            res.resultType() == ResultType.PREV_NULL ? CREATE : UPDATE,
+                            tx.nearXidVersion(),
+                            newVer,
+                            expireTime,
+                            entry.key().partition(),
+                            updateCntr0)));
+
+                entry.update(val, expireTime, ttl, newVer, true);
+
+                entry.mvccDrReplicate(tx.local() ? DR_PRIMARY : DR_BACKUP, val, newVer, topVer, mvccVer);
+
+                entry.recordNodeId(affNodeId, topVer);
+            }
+            catch (IgniteCheckedException e) {
+                resFut.onDone(e);
+
+                return;
+            }
+            finally {
+                if (entry.lockedByCurrentThread()) {
+                    entry.unlockEntry();
+
+                    cctx.evicts().touch(entry, AffinityTopologyVersion.NONE);
+                }
+            }
+
+            entry.onUpdateFinished(updateCntr0);
+
+            GridCacheUpdateTxResult updRes = valid ? new GridCacheUpdateTxResult(true, updateCntr0, logPtr)
+                : new GridCacheUpdateTxResult(false, logPtr);
+
+            updRes.mvccHistory(res.history());
+
+            resFut.onDone(updRes);
+        }
+    }
+
     /**
      *
      */
@@ -5459,4 +6403,83 @@ private void versionCheck(@Nullable IgniteBiTuple<Object, Exception> invokeRes)
             return S.toString(AtomicCacheUpdateClosure.class, this);
         }
     }
+
+    /** {@inheritDoc} */
+    @Override public GridCacheUpdateTxResult mvccUpdateRowsWithPreloadInfo(
+        IgniteInternalTx tx,
+        UUID affNodeId,
+        AffinityTopologyVersion topVer,
+        Long updateCntr,
+        List<GridCacheEntryInfo> entries,
+        GridCacheOperation op,
+        MvccSnapshot mvccVer)
+        throws IgniteCheckedException, GridCacheEntryRemovedException {
+        assert updateCntr != null && updateCntr > 0 && !tx.local();
+
+        WALPointer logPtr = null;
+
+        ensureFreeSpace();
+
+        lockEntry();
+
+        try {
+            checkObsolete();
+
+            CacheObject val = null;
+
+            for (int i = 0; i < entries.size(); i++) {
+                GridCacheMvccEntryInfo info = (GridCacheMvccEntryInfo)entries.get(i);
+
+                if (val == null && op != DELETE && MvccUtils.compare(info.mvccVersion(),
+                    mvccVer.coordinatorVersion(),
+                    mvccVer.counter(),
+                    mvccVer.operationCounter()) == 0)
+                    val = info.value();
+
+                cctx.offheap().mvccUpdateRowWithPreloadInfo(this,
+                    info.value(),
+                    info.version(),
+                    info.expireTime(),
+                    info.mvccVersion(),
+                    info.newMvccVersion(),
+                    info.mvccTxState(),
+                    info.newMvccTxState());
+            }
+
+            if (cctx.deferredDelete() && deletedUnlocked() && !detached())
+                deletedUnlocked(false);
+
+            long expireTime = CU.EXPIRE_TIME_ETERNAL;
+            long ttl = CU.TTL_ETERNAL;
+
+            GridCacheVersion ver = tx.writeVersion();
+
+            if (cctx.group().persistenceEnabled() && cctx.group().walEnabled())
+                logPtr = cctx.shared().wal().log(new DataRecord(new DataEntry(
+                    cctx.cacheId(),
+                    key,
+                    val,
+                    op,
+                    tx.nearXidVersion(),
+                    ver,
+                    CU.EXPIRE_TIME_ETERNAL,
+                    key.partition(),
+                    updateCntr)));
+
+            update(val, expireTime, ttl, ver, true);
+
+            mvccDrReplicate(DR_BACKUP, val, ver, topVer, mvccVer);
+
+            recordNodeId(affNodeId, topVer);
+        }
+        finally {
+            if (lockedByCurrentThread()) {
+                unlockEntry();
+
+                cctx.evicts().touch(this, AffinityTopologyVersion.NONE);
+            }
+        }
+
+        return new GridCacheUpdateTxResult(true, logPtr);
+    }
 }
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheMvccEntryInfo.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheMvccEntryInfo.java
new file mode 100644
index 000000000000..8df78eda2ea5
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheMvccEntryInfo.java
@@ -0,0 +1,257 @@
+/*
+ * 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.cache;
+
+import java.nio.ByteBuffer;
+import org.apache.ignite.internal.processors.cache.mvcc.MvccUpdateVersionAware;
+import org.apache.ignite.internal.processors.cache.mvcc.MvccVersionAware;
+import org.apache.ignite.internal.util.typedef.internal.S;
+import org.apache.ignite.plugin.extensions.communication.MessageReader;
+import org.apache.ignite.plugin.extensions.communication.MessageWriter;
+
+import static org.apache.ignite.internal.processors.cache.persistence.tree.io.PageIO.MVCC_HINTS_BIT_OFF;
+import static org.apache.ignite.internal.processors.cache.persistence.tree.io.PageIO.MVCC_HINTS_MASK;
+
+/**
+ *
+ */
+public class GridCacheMvccEntryInfo extends GridCacheEntryInfo implements MvccVersionAware, MvccUpdateVersionAware {
+    /** */
+    private static final long serialVersionUID = 0L;
+
+    /** */
+    private long mvccCrdVer;
+
+    /** */
+    private long mvccCntr;
+
+    /** */
+    private int mvccOpCntr;
+
+    /** */
+    private long newMvccCrdVer;
+
+    /** */
+    private long newMvccCntr;
+
+    /** */
+    private int newMvccOpCntr;
+
+    /** {@inheritDoc} */
+    @Override public long newMvccCoordinatorVersion() {
+        return newMvccCrdVer;
+    }
+
+    /** {@inheritDoc} */
+    @Override public long newMvccCounter() {
+        return newMvccCntr;
+    }
+
+    /** {@inheritDoc} */
+    @Override public int newMvccOperationCounter() {
+        return newMvccOpCntr & ~MVCC_HINTS_MASK;
+    }
+
+    /** {@inheritDoc} */
+    @Override public byte newMvccTxState() {
+        return (byte)(newMvccOpCntr >>> MVCC_HINTS_BIT_OFF);
+    }
+
+    /** {@inheritDoc} */
+    @Override public long mvccCoordinatorVersion() {
+        return mvccCrdVer;
+    }
+
+    /** {@inheritDoc} */
+    @Override public long mvccCounter() {
+        return mvccCntr;
+    }
+
+    /** {@inheritDoc} */
+    @Override public int mvccOperationCounter() {
+        return mvccOpCntr & ~MVCC_HINTS_MASK;
+    }
+
+    /** {@inheritDoc} */
+    @Override public byte mvccTxState() {
+        return (byte)(mvccOpCntr >>> MVCC_HINTS_BIT_OFF);
+    }
+
+    /**
+     * @param mvccTxState Mvcc version Tx state hint.
+     */
+    public void mvccTxState(byte mvccTxState) {
+        mvccOpCntr = (mvccOpCntr & ~MVCC_HINTS_MASK) | ((int)mvccTxState << MVCC_HINTS_BIT_OFF);
+    }
+
+    /**
+     * @param newMvccTxState New mvcc version Tx state hint.
+     */
+    public void newMvccTxState(byte newMvccTxState) {
+        newMvccOpCntr = (newMvccOpCntr & ~MVCC_HINTS_MASK) | ((int)newMvccTxState << MVCC_HINTS_BIT_OFF);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void newMvccVersion(long crd, long cntr, int opCntr) {
+        newMvccCrdVer = crd;
+        newMvccCntr = cntr;
+        newMvccOpCntr = opCntr;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void mvccVersion(long crd, long cntr, int opCntr) {
+        mvccCrdVer = crd;
+        mvccCntr = cntr;
+        mvccOpCntr = opCntr;
+    }
+
+    /** {@inheritDoc} */
+    public boolean writeTo(ByteBuffer buf, MessageWriter writer) {
+        writer.setBuffer(buf);
+
+        if (!super.writeTo(buf, writer))
+            return false;
+
+        if (!writer.isHeaderWritten()) {
+            if (!writer.writeHeader(directType(), fieldsCount()))
+                return false;
+
+            writer.onHeaderWritten();
+        }
+
+        switch (writer.state()) {
+            case 6:
+                if (!writer.writeLong("mvccCntr", mvccCntr))
+                    return false;
+
+                writer.incrementState();
+
+            case 7:
+                if (!writer.writeLong("mvccCrdVer", mvccCrdVer))
+                    return false;
+
+                writer.incrementState();
+
+            case 8:
+                if (!writer.writeInt("mvccOpCntr", mvccOpCntr))
+                    return false;
+
+                writer.incrementState();
+
+            case 9:
+                if (!writer.writeLong("newMvccCntr", newMvccCntr))
+                    return false;
+
+                writer.incrementState();
+
+            case 10:
+                if (!writer.writeLong("newMvccCrdVer", newMvccCrdVer))
+                    return false;
+
+                writer.incrementState();
+
+            case 11:
+                if (!writer.writeInt("newMvccOpCntr", newMvccOpCntr))
+                    return false;
+
+                writer.incrementState();
+
+        }
+
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean readFrom(ByteBuffer buf, MessageReader reader) {
+        reader.setBuffer(buf);
+
+        if (!reader.beforeMessageRead())
+            return false;
+
+        if (!super.readFrom(buf, reader))
+            return false;
+
+        switch (reader.state()) {
+            case 6:
+                mvccCntr = reader.readLong("mvccCntr");
+
+                if (!reader.isLastRead())
+                    return false;
+
+                reader.incrementState();
+
+            case 7:
+                mvccCrdVer = reader.readLong("mvccCrdVer");
+
+                if (!reader.isLastRead())
+                    return false;
+
+                reader.incrementState();
+
+            case 8:
+                mvccOpCntr = reader.readInt("mvccOpCntr");
+
+                if (!reader.isLastRead())
+                    return false;
+
+                reader.incrementState();
+
+            case 9:
+                newMvccCntr = reader.readLong("newMvccCntr");
+
+                if (!reader.isLastRead())
+                    return false;
+
+                reader.incrementState();
+
+            case 10:
+                newMvccCrdVer = reader.readLong("newMvccCrdVer");
+
+                if (!reader.isLastRead())
+                    return false;
+
+                reader.incrementState();
+
+            case 11:
+                newMvccOpCntr = reader.readInt("newMvccOpCntr");
+
+                if (!reader.isLastRead())
+                    return false;
+
+                reader.incrementState();
+
+        }
+
+        return reader.afterMessageRead(GridCacheMvccEntryInfo.class);
+    }
+
+    /** {@inheritDoc} */
+    @Override public byte fieldsCount() {
+        return 12;
+    }
+
+    /** {@inheritDoc} */
+    @Override public short directType() {
+        return 143;
+    }
+
+    /** {@inheritDoc} */
+    @Override public String toString() {
+        return S.toString(GridCacheMvccEntryInfo.class, this);
+    }
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheOperation.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheOperation.java
index 555f825199eb..377f95f6e4bd 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheOperation.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheOperation.java
@@ -17,7 +17,7 @@
 
 package org.apache.ignite.internal.processors.cache;
 
-import org.jetbrains.annotations.*;
+import org.jetbrains.annotations.Nullable;
 
 /**
  * Cache value operations.
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCachePartitionExchangeManager.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCachePartitionExchangeManager.java
index 9d88152e28ce..ca2d1c8a888a 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCachePartitionExchangeManager.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCachePartitionExchangeManager.java
@@ -894,7 +894,7 @@ public void lastFinishedFuture(GridDhtTopologyFuture fut) {
      * @param ver Topology version.
      * @return Future or {@code null} is future is already completed.
      */
-    @Nullable public IgniteInternalFuture<?> affinityReadyFuture(AffinityTopologyVersion ver) {
+    @Nullable public IgniteInternalFuture<AffinityTopologyVersion> affinityReadyFuture(AffinityTopologyVersion ver) {
         GridDhtPartitionsExchangeFuture lastInitializedFut0 = lastInitializedFut;
 
         if (lastInitializedFut0 != null && lastInitializedFut0.initialVersion().compareTo(ver) == 0) {
@@ -1873,9 +1873,15 @@ private void dumpPendingObjects(@Nullable AffinityTopologyVersion exchTopVer,
         IgniteTxManager tm = cctx.tm();
 
         if (tm != null) {
-            U.warn(diagnosticLog, "Pending transactions:");
+            boolean first = true;
 
             for (IgniteInternalTx tx : tm.activeTransactions()) {
+                if (first) {
+                    U.warn(diagnosticLog, "Pending transactions:");
+
+                    first = false;
+                }
+
                 if (exchTopVer != null) {
                     U.warn(diagnosticLog, ">>> [txVer=" + tx.topologyVersionSnapshot() +
                         ", exchWait=" + tm.needWaitTransaction(tx, exchTopVer) +
@@ -1889,31 +1895,66 @@ private void dumpPendingObjects(@Nullable AffinityTopologyVersion exchTopVer,
         GridCacheMvccManager mvcc = cctx.mvcc();
 
         if (mvcc != null) {
-            U.warn(diagnosticLog, "Pending explicit locks:");
+            boolean first = true;
+
+            for (GridCacheExplicitLockSpan lockSpan : mvcc.activeExplicitLocks()) {
+                if (first) {
+                    U.warn(diagnosticLog, "Pending explicit locks:");
+
+                    first = false;
+                }
 
-            for (GridCacheExplicitLockSpan lockSpan : mvcc.activeExplicitLocks())
                 U.warn(diagnosticLog, ">>> " + lockSpan);
+            }
 
-            U.warn(diagnosticLog, "Pending cache futures:");
+            first = true;
+
+            for (GridCacheFuture<?> fut : mvcc.activeFutures()) {
+                if (first) {
+                    U.warn(diagnosticLog, "Pending cache futures:");
+
+                    first = false;
+                }
 
-            for (GridCacheFuture<?> fut : mvcc.activeFutures())
                 dumpDiagnosticInfo(fut, diagCtx);
+            }
 
-            U.warn(diagnosticLog, "Pending atomic cache futures:");
+            first = true;
+
+            for (GridCacheFuture<?> fut : mvcc.atomicFutures()) {
+                if (first) {
+                    U.warn(diagnosticLog, "Pending atomic cache futures:");
+
+                    first = false;
+                }
 
-            for (GridCacheFuture<?> fut : mvcc.atomicFutures())
                 dumpDiagnosticInfo(fut, diagCtx);
+            }
+
+            first = true;
 
-            U.warn(diagnosticLog, "Pending data streamer futures:");
+            for (IgniteInternalFuture<?> fut : mvcc.dataStreamerFutures()) {
+                if (first) {
+                    U.warn(diagnosticLog, "Pending data streamer futures:");
+
+                    first = false;
+                }
 
-            for (IgniteInternalFuture<?> fut : mvcc.dataStreamerFutures())
                 dumpDiagnosticInfo(fut, diagCtx);
+            }
 
             if (tm != null) {
-                U.warn(diagnosticLog, "Pending transaction deadlock detection futures:");
+                first = true;
+
+                for (IgniteInternalFuture<?> fut : tm.deadlockDetectionFutures()) {
+                    if (first) {
+                        U.warn(diagnosticLog, "Pending transaction deadlock detection futures:");
+
+                        first = false;
+                    }
 
-                for (IgniteInternalFuture<?> fut : tm.deadlockDetectionFutures())
                     dumpDiagnosticInfo(fut, diagCtx);
+                }
             }
         }
 
@@ -1935,6 +1976,8 @@ private void dumpPendingObjects(@Nullable AffinityTopologyVersion exchTopVer,
                     affDumpCnt++;
             }
         }
+
+        cctx.kernalContext().coordinators().dumpDebugInfo(diagnosticLog, diagCtx);
     }
 
     /**
@@ -2070,12 +2113,21 @@ public boolean mergeExchangesOnCoordinator(GridDhtPartitionsExchangeFuture curFu
 
                     ClusterNode node = evt.eventNode();
 
+                    if ((evt.type() == EVT_NODE_FAILED || evt.type() == EVT_NODE_LEFT) &&
+                        node.equals(cctx.coordinators().currentCoordinator())) {
+                        if (log.isInfoEnabled())
+                            log.info("Stop merge, need exchange for mvcc coordinator failure: " + node);
+
+                        break;
+                    }
+
                     if (!curFut.context().supportsMergeExchanges(node)) {
                         if (log.isInfoEnabled())
                             log.info("Stop merge, node does not support merge: " + node);
 
                         break;
                     }
+
                     if (evt.type() == EVT_NODE_JOINED && cctx.cache().hasCachesReceivedFromJoin(node)) {
                         if (log.isInfoEnabled())
                             log.info("Stop merge, received caches from node: " + node);
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheProcessor.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheProcessor.java
index 272aad40d90b..ca4c8bd3d4eb 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheProcessor.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheProcessor.java
@@ -35,6 +35,7 @@
 import java.util.concurrent.ConcurrentMap;
 import java.util.concurrent.CountDownLatch;
 import java.util.stream.Collectors;
+import javax.cache.expiry.EternalExpiryPolicy;
 import javax.management.MBeanServer;
 import org.apache.ignite.IgniteCheckedException;
 import org.apache.ignite.IgniteException;
@@ -97,7 +98,6 @@
 import org.apache.ignite.internal.processors.cache.persistence.IgniteCacheDatabaseSharedManager;
 import org.apache.ignite.internal.processors.cache.persistence.file.FilePageStoreManager;
 import org.apache.ignite.internal.processors.cache.persistence.freelist.FreeList;
-import org.apache.ignite.internal.processors.cache.persistence.metastorage.MetaStorage;
 import org.apache.ignite.internal.processors.cache.persistence.snapshot.IgniteCacheSnapshotManager;
 import org.apache.ignite.internal.processors.cache.persistence.snapshot.SnapshotDiscoveryMessage;
 import org.apache.ignite.internal.processors.cache.persistence.tree.reuse.ReuseList;
@@ -165,6 +165,7 @@
 import static org.apache.ignite.IgniteSystemProperties.IGNITE_SKIP_CONFIGURATION_CONSISTENCY_CHECK;
 import static org.apache.ignite.IgniteSystemProperties.getBoolean;
 import static org.apache.ignite.cache.CacheAtomicityMode.ATOMIC;
+import static org.apache.ignite.cache.CacheAtomicityMode.TRANSACTIONAL;
 import static org.apache.ignite.cache.CacheMode.LOCAL;
 import static org.apache.ignite.cache.CacheMode.PARTITIONED;
 import static org.apache.ignite.cache.CacheMode.REPLICATED;
@@ -542,6 +543,24 @@ else if (cc.getRebalanceMode() == SYNC) {
                 "other cache types [cacheName=" + cc.getName() + ", groupName=" + cc.getGroupName() +
                 ", cacheType=" + cacheType + "]");
 
+        if (cc.getAtomicityMode() == TRANSACTIONAL && c.isMvccEnabled()) {
+            if (cc.getCacheStoreFactory() != null) {
+                throw new IgniteCheckedException("Transactional cache may not have a third party cache store when " +
+                    "MVCC is enabled.");
+            }
+
+            if (cc.getExpiryPolicyFactory() != null && !(cc.getExpiryPolicyFactory().create() instanceof
+                EternalExpiryPolicy)) {
+                throw new IgniteCheckedException("Transactional cache may not have expiry policy when " +
+                    "MVCC is enabled.");
+            }
+
+            if (cc.getInterceptor() != null) {
+                throw new IgniteCheckedException("Transactional cache may not have an interceptor when " +
+                    "MVCC is enabled.");
+            }
+        }
+
         // Make sure we do not use sql schema for system views.
         if (ctx.query().moduleEnabled()) {
             String schema = QueryUtils.normalizeSchemaName(cc.getName(), cc.getSqlSchema());
@@ -1224,7 +1243,8 @@ private void startCache(GridCacheAdapter<?, ?> cache, QuerySchema schema) throws
                 ", memoryPolicyName=" + memPlcName +
                 ", mode=" + cfg.getCacheMode() +
                 ", atomicity=" + cfg.getAtomicityMode() +
-                ", backups=" + cfg.getBackups() + ']');
+                ", backups=" + cfg.getBackups() +
+                ", mvcc=" + cacheCtx.mvccEnabled() +']');
         }
     }
 
@@ -3416,7 +3436,7 @@ public void cleanupCachesDirectories() throws IgniteCheckedException {
                     CacheConfiguration ccfg = req.startCacheConfiguration();
 
                     try {
-                        cachesInfo.validateStartCacheConfiguration(ccfg);
+                        cachesInfo.validateStartCacheConfiguration(ccfg, req.cacheType());
                     }
                     catch (IgniteCheckedException e) {
                         fut.onDone(e);
@@ -4472,7 +4492,7 @@ private DynamicCacheChangeRequest prepareCacheChangeRequest(
         if (ccfg != null) {
             cloneCheckSerializable(ccfg);
 
-            if (desc != null || MetaStorage.METASTORAGE_CACHE_NAME.equals(cacheName)) {
+            if (desc != null) {
                 if (failIfExists) {
                     throw new CacheExistsException("Failed to start cache " +
                         "(a cache with the same name is already started): " + cacheName);
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheSharedContext.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheSharedContext.java
index 4b98060d9e60..bfe0001aafc6 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheSharedContext.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheSharedContext.java
@@ -37,7 +37,6 @@
 import org.apache.ignite.internal.IgniteInternalFuture;
 import org.apache.ignite.internal.managers.communication.GridIoManager;
 import org.apache.ignite.internal.managers.deployment.GridDeploymentManager;
-import org.apache.ignite.internal.managers.discovery.DiscoCache;
 import org.apache.ignite.internal.managers.discovery.GridDiscoveryManager;
 import org.apache.ignite.internal.managers.eventstorage.GridEventStorageManager;
 import org.apache.ignite.internal.pagemem.store.IgnitePageStoreManager;
@@ -48,6 +47,7 @@
 import org.apache.ignite.internal.processors.cache.distributed.dht.PartitionsEvictManager;
 import org.apache.ignite.internal.processors.cache.distributed.near.GridNearTxLocal;
 import org.apache.ignite.internal.processors.cache.jta.CacheJtaManagerAdapter;
+import org.apache.ignite.internal.processors.cache.mvcc.MvccProcessor;
 import org.apache.ignite.internal.processors.cache.persistence.IgniteCacheDatabaseSharedManager;
 import org.apache.ignite.internal.processors.cache.persistence.snapshot.IgniteCacheSnapshotManager;
 import org.apache.ignite.internal.processors.cache.store.CacheStoreManager;
@@ -793,6 +793,13 @@ public GridTimeoutProcessor time() {
         return kernalCtx.timeout();
     }
 
+    /**
+     * @return Cache mvcc coordinator manager.
+     */
+    public MvccProcessor coordinators() {
+        return kernalCtx.coordinators();
+    }
+
     /**
      * @return Partition evict manager.
      */
@@ -855,7 +862,7 @@ public IgniteLogger logger(String category) {
     /**
      * Captures all ongoing operations that we need to wait before we can proceed to the next topology version.
      * This method must be called only after
-     * {@link GridDhtPartitionTopology#updateTopologyVersion(GridDhtTopologyFuture, DiscoCache, long, boolean)}
+     * {@link GridDhtPartitionTopology#updateTopologyVersion}
      * method is called so that all new updates will wait to switch to the new version.
      * This method will capture:
      * <ul>
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheUpdateTxResult.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheUpdateTxResult.java
index 92af83b11fa9..b646cf9f1ed1 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheUpdateTxResult.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheUpdateTxResult.java
@@ -17,8 +17,12 @@
 
 package org.apache.ignite.internal.processors.cache;
 
+import java.util.List;
+import org.apache.ignite.internal.IgniteInternalFuture;
 import org.apache.ignite.internal.pagemem.wal.WALPointer;
-import org.apache.ignite.internal.util.tostring.GridToStringInclude;
+import org.apache.ignite.internal.processors.cache.tree.mvcc.search.MvccLinkAwareSearchRow;
+import org.apache.ignite.internal.util.GridLongList;
+import org.apache.ignite.internal.util.future.GridFutureAdapter;
 import org.apache.ignite.internal.util.typedef.internal.S;
 import org.jetbrains.annotations.Nullable;
 
@@ -29,26 +33,62 @@
     /** Success flag.*/
     private final boolean success;
 
-    /** Old value. */
-    @GridToStringInclude
-    private final CacheObject oldVal;
-
-    /** Partition idx. */
+    /** Partition update counter. */
     private long updateCntr;
 
+    /** */
+    private GridLongList mvccWaitTxs;
+
+    /** */
+    private  GridFutureAdapter<GridCacheUpdateTxResult> fut;
+
     /** */
     private WALPointer logPtr;
 
+    /** */
+    private List<MvccLinkAwareSearchRow> mvccHistory;
+
+    /**
+     * Constructor.
+     *
+     * @param success Success flag.
+     */
+    GridCacheUpdateTxResult(boolean success) {
+        this.success = success;
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param success Success flag.
+     * @param logPtr Logger WAL pointer for the update.
+     */
+    GridCacheUpdateTxResult(boolean success, WALPointer logPtr) {
+        this.success = success;
+        this.logPtr = logPtr;
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param success Success flag.
+     * @param fut Update future.
+     */
+    GridCacheUpdateTxResult(boolean success, GridFutureAdapter<GridCacheUpdateTxResult> fut) {
+        this.success = success;
+        this.fut = fut;
+    }
+
     /**
      * Constructor.
      *
      * @param success Success flag.
-     * @param oldVal Old value (if any),
+     * @param updateCntr Update counter.
      * @param logPtr Logger WAL pointer for the update.
      */
-    GridCacheUpdateTxResult(boolean success, @Nullable CacheObject oldVal, WALPointer logPtr) {
+    GridCacheUpdateTxResult(boolean success, long updateCntr, WALPointer logPtr) {
         this.success = success;
-        this.oldVal = oldVal;
+        this.updateCntr = updateCntr;
         this.logPtr = logPtr;
     }
 
@@ -56,20 +96,21 @@
      * Constructor.
      *
      * @param success Success flag.
-     * @param oldVal Old value (if any).
+     * @param updateCntr Update counter.
      * @param logPtr Logger WAL pointer for the update.
+     * @param mvccWaitTxs List of transactions to wait for completion.
      */
-    GridCacheUpdateTxResult(boolean success, @Nullable CacheObject oldVal, long updateCntr, WALPointer logPtr) {
+    GridCacheUpdateTxResult(boolean success, long updateCntr, WALPointer logPtr, GridLongList mvccWaitTxs) {
         this.success = success;
-        this.oldVal = oldVal;
         this.updateCntr = updateCntr;
         this.logPtr = logPtr;
+        this.mvccWaitTxs = mvccWaitTxs;
     }
 
     /**
-     * @return Partition idx.
+     * @return Partition update counter.
      */
-    public long updatePartitionCounter() {
+    public long updateCounter() {
         return updateCntr;
     }
 
@@ -88,10 +129,33 @@ public WALPointer loggedPointer() {
     }
 
     /**
-     * @return Old value.
+     * @return Update future.
+     */
+    @Nullable public IgniteInternalFuture<GridCacheUpdateTxResult> updateFuture() {
+        return fut;
+    }
+
+    /**
+     * @return List of transactions to wait for completion.
+     */
+    @Nullable public GridLongList mvccWaitTransactions() {
+        return mvccWaitTxs;
+    }
+
+    /**
+     *
+     * @return Mvcc history rows.
+     */
+    @Nullable public List<MvccLinkAwareSearchRow> mvccHistory() {
+        return mvccHistory;
+    }
+
+    /**
+     *
+     * @param mvccHistory Mvcc history rows.
      */
-    @Nullable public CacheObject oldValue() {
-        return oldVal;
+    public void mvccHistory(List<MvccLinkAwareSearchRow> mvccHistory) {
+        this.mvccHistory = mvccHistory;
     }
 
     /** {@inheritDoc} */
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheUtils.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheUtils.java
index 213fb2e33ccc..9ef470cbf246 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheUtils.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheUtils.java
@@ -32,6 +32,7 @@
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentLinkedQueue;
 import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.LongAdder;
 import javax.cache.Cache;
 import javax.cache.CacheException;
 import javax.cache.configuration.Factory;
@@ -67,10 +68,13 @@
 import org.apache.ignite.internal.managers.discovery.IgniteClusterNode;
 import org.apache.ignite.internal.processors.affinity.AffinityTopologyVersion;
 import org.apache.ignite.internal.processors.cache.distributed.GridDistributedLockCancelledException;
+import org.apache.ignite.internal.processors.cache.distributed.GridDistributedTxMapping;
 import org.apache.ignite.internal.processors.cache.distributed.dht.GridDhtCacheAdapter;
 import org.apache.ignite.internal.processors.cache.distributed.dht.GridDhtInvalidPartitionException;
 import org.apache.ignite.internal.processors.cache.distributed.near.GridNearCacheAdapter;
 import org.apache.ignite.internal.processors.cache.distributed.near.GridNearTxLocal;
+import org.apache.ignite.internal.processors.cache.mvcc.txlog.TxLog;
+import org.apache.ignite.internal.processors.cache.persistence.metastorage.MetaStorage;
 import org.apache.ignite.internal.processors.cache.transactions.IgniteInternalTx;
 import org.apache.ignite.internal.processors.cache.transactions.IgniteTxEntry;
 import org.apache.ignite.internal.processors.cache.version.GridCacheVersion;
@@ -99,7 +103,6 @@
 import org.apache.ignite.lifecycle.LifecycleAware;
 import org.apache.ignite.plugin.CachePluginConfiguration;
 import org.apache.ignite.plugin.security.SecurityException;
-import org.apache.ignite.spi.discovery.tcp.internal.TcpDiscoveryNode;
 import org.apache.ignite.transactions.Transaction;
 import org.apache.ignite.transactions.TransactionConcurrency;
 import org.apache.ignite.transactions.TransactionIsolation;
@@ -173,6 +176,14 @@ public static boolean cheatCache(int id) {
     /** System cache name. */
     public static final String UTILITY_CACHE_NAME = "ignite-sys-cache";
 
+    /** Reserved cache names */
+    public static final String[] RESERVED_NAMES = new String[] {
+        SYS_CACHE_HADOOP_MR,
+        UTILITY_CACHE_NAME,
+        MetaStorage.METASTORAGE_CACHE_NAME,
+        TxLog.TX_LOG_CACHE_NAME,
+    };
+
     /** */
     public static final String CONTINUOUS_QRY_LOG_CATEGORY = "org.apache.ignite.continuous.query";
 
@@ -319,6 +330,18 @@ public static boolean cheatCache(int id) {
         }
     };
 
+    /** Query mapped filter. */
+    public static final IgnitePredicate<GridDistributedTxMapping> FILTER_QUERY_MAPPING = new P1<GridDistributedTxMapping>() {
+
+        @Override public boolean apply(GridDistributedTxMapping m) {
+            return m.queryUpdate();
+        }
+
+        @Override public String toString() {
+            return "FILTER_QUERY_MAPPING";
+        }
+    };
+
     /** Transaction entry to key. */
     private static final IgniteClosure tx2key = new C1<IgniteTxEntry, Object>() {
         @Override public Object apply(IgniteTxEntry e) {
@@ -588,6 +611,30 @@ public static boolean isNearEnabled(CacheConfiguration cfg) {
         };
     }
 
+    /**
+     * @return Long reducer.
+     */
+    public static IgniteReducer<Long, Long> longReducer() {
+        return new IgniteReducer<Long, Long>() {
+            private final LongAdder res = new LongAdder();
+
+            @Override public boolean collect(Long l) {
+                if(l != null)
+                    res.add(l);
+
+                return true;
+            }
+
+            @Override public Long reduce() {
+                return res.sum();
+            }
+
+            @Override public String toString() {
+                return "Long reducer: " + res;
+            }
+        };
+    }
+
     /**
      * Gets reducer that aggregates maps into one.
      *
@@ -1542,6 +1589,17 @@ public static void validateCacheName(String name) throws IllegalArgumentExceptio
         A.ensure(name != null && !name.isEmpty(), "Cache name must not be null or empty.");
     }
 
+    /**
+     * @param name Cache name.
+     * @throws IllegalArgumentException In case the name is not valid.
+     */
+    public static void validateNewCacheName(String name) throws IllegalArgumentException {
+        validateCacheName(name);
+
+        A.ensure(!isReservedCacheName(name), "Cache name cannot be \"" + name +
+            "\" because it is reserved for internal purposes.");
+    }
+
     /**
      * @param cacheNames Cache names to validate.
      * @throws IllegalArgumentException In case the name is not valid.
@@ -1558,7 +1616,20 @@ public static void validateCacheNames(Collection<String> cacheNames) throws Ille
     public static void validateConfigurationCacheNames(Collection<CacheConfiguration> ccfgs)
         throws IllegalArgumentException {
         for (CacheConfiguration ccfg : ccfgs)
-            validateCacheName(ccfg.getName());
+            validateNewCacheName(ccfg.getName());
+    }
+
+    /**
+     * @param name Cache name.
+     * @return {@code True} if it is a reserved cache name.
+     */
+    public static boolean isReservedCacheName(String name) {
+        for (String reserved : RESERVED_NAMES) {
+            if (reserved.equals(name))
+                return true;
+        }
+
+        return false;
     }
 
     /**
@@ -1829,6 +1900,44 @@ public static boolean isPersistenceEnabled(DataStorageConfiguration cfg) {
         return false;
     }
 
+    /**
+     * @param sctx Shared context.
+     * @param cacheIds Cache ids.
+     * @return First partitioned cache or {@code null} in case no partitioned cache ids are in list.
+     */
+    public static GridCacheContext<?, ?> firstPartitioned(GridCacheSharedContext<?, ?> sctx, int[] cacheIds) {
+        for (int i = 0; i < cacheIds.length; i++) {
+            GridCacheContext<?, ?> cctx = sctx.cacheContext(cacheIds[i]);
+
+            if (cctx == null)
+                throw new CacheException("Failed to find cache.");
+
+            if (!cctx.isLocal() && !cctx.isReplicated())
+                return cctx;
+        }
+
+        return null;
+    }
+
+    /**
+     * @param sctx Shared context.
+     * @param cacheIds Cache ids.
+     * @return First partitioned cache or {@code null} in case no partitioned cache ids are in list.
+     */
+    public static GridCacheContext<?, ?> firstPartitioned(GridCacheSharedContext<?, ?> sctx, Iterable<Integer> cacheIds) {
+        for (Integer i : cacheIds) {
+            GridCacheContext<?, ?> cctx = sctx.cacheContext(i);
+
+            if (cctx == null)
+                throw new CacheException("Failed to find cache.");
+
+            if (!cctx.isLocal() && !cctx.isReplicated())
+                return cctx;
+        }
+
+        return null;
+    }
+
     /**
      * @param cacheName Name of cache or cache template.
      * @return {@code true} if cache name ends with asterisk (*), and therefire is a template name.
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/IgniteCacheOffheapManager.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/IgniteCacheOffheapManager.java
index fa25412a81b1..a0213940de37 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/IgniteCacheOffheapManager.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/IgniteCacheOffheapManager.java
@@ -17,25 +17,33 @@
 
 package org.apache.ignite.internal.processors.cache;
 
+import java.util.List;
 import java.util.Map;
 import javax.cache.Cache;
 import org.apache.ignite.IgniteCheckedException;
 import org.apache.ignite.internal.processors.affinity.AffinityTopologyVersion;
 import org.apache.ignite.internal.processors.cache.distributed.dht.GridDhtLocalPartition;
 import org.apache.ignite.internal.processors.cache.distributed.dht.preloader.IgniteDhtDemandedPartitionsMap;
+import org.apache.ignite.internal.processors.cache.mvcc.MvccSnapshot;
+import org.apache.ignite.internal.processors.cache.mvcc.MvccVersion;
 import org.apache.ignite.internal.processors.cache.persistence.CacheDataRow;
+import org.apache.ignite.internal.processors.cache.persistence.CacheSearchRow;
 import org.apache.ignite.internal.processors.cache.persistence.RootPage;
 import org.apache.ignite.internal.processors.cache.persistence.RowStore;
 import org.apache.ignite.internal.processors.cache.persistence.tree.reuse.ReuseList;
 import org.apache.ignite.internal.processors.cache.tree.PendingEntriesTree;
+import org.apache.ignite.internal.processors.cache.tree.mvcc.data.MvccUpdateResult;
+import org.apache.ignite.internal.processors.cache.tree.mvcc.search.MvccLinkAwareSearchRow;
 import org.apache.ignite.internal.processors.cache.version.GridCacheVersion;
 import org.apache.ignite.internal.processors.query.GridQueryRowCacheCleaner;
 import org.apache.ignite.internal.util.GridAtomicLong;
+import org.apache.ignite.internal.util.GridLongList;
 import org.apache.ignite.internal.util.IgniteTree;
 import org.apache.ignite.internal.util.lang.GridCloseableIterator;
 import org.apache.ignite.internal.util.lang.GridCursor;
 import org.apache.ignite.internal.util.lang.GridIterator;
 import org.apache.ignite.internal.util.lang.IgniteInClosure2X;
+import org.apache.ignite.lang.IgniteBiTuple;
 import org.jetbrains.annotations.Nullable;
 
 /**
@@ -168,6 +176,202 @@ public boolean expire(GridCacheContext cctx, IgniteInClosure2X<GridCacheEntryEx,
     public void invoke(GridCacheContext cctx, KeyCacheObject key, GridDhtLocalPartition part, OffheapInvokeClosure c)
         throws IgniteCheckedException;
 
+    /**
+     * @param cctx Cache context.
+     * @param key Key.
+     * @return Cached row, if available, null otherwise.
+     * @throws IgniteCheckedException If failed.
+     */
+    @Nullable public CacheDataRow mvccRead(GridCacheContext cctx, KeyCacheObject key, MvccSnapshot ver)
+        throws IgniteCheckedException;
+
+    /**
+     * For testing only.
+     *
+     * @param cctx Cache context.
+     * @param key Key.
+     * @return All stored versions for given key.
+     * @throws IgniteCheckedException If failed.
+     */
+    public List<IgniteBiTuple<Object, MvccVersion>> mvccAllVersions(GridCacheContext cctx, KeyCacheObject key)
+        throws IgniteCheckedException;
+
+    /**
+     * Returns iterator over the all row versions for the given key.
+     *
+     * @param cctx Cache context.
+     * @param key Key.
+     * @param x Implementation specific argument, {@code null} always means that we need to return full detached data row.
+     * @return Iterator over all versions.
+     * @throws IgniteCheckedException If failed.
+     */
+    GridCursor<CacheDataRow> mvccAllVersionsCursor(GridCacheContext cctx, KeyCacheObject key, Object x)
+        throws IgniteCheckedException;
+
+    /**
+     * @param entry Entry.
+     * @param val Value.
+     * @param ver Version.
+     * @param expireTime Expire time.
+     * @return {@code True} if value was inserted.
+     * @throws IgniteCheckedException If failed.
+     */
+    default boolean mvccInitialValue(
+        GridCacheMapEntry entry,
+        @Nullable CacheObject val,
+        GridCacheVersion ver,
+        long expireTime
+    ) throws IgniteCheckedException {
+        return mvccInitialValue(entry, val, ver, expireTime, null, null);
+    }
+
+    /**
+     * @param entry Entry.
+     * @param val Value.
+     * @param ver Version.
+     * @param expireTime Expire time.
+     * @param mvccVer MVCC version.
+     * @param newMvccVer New MVCC version.
+     * @return {@code True} if value was inserted.
+     * @throws IgniteCheckedException If failed.
+     */
+    public boolean mvccInitialValue(
+        GridCacheMapEntry entry,
+        @Nullable CacheObject val,
+        GridCacheVersion ver,
+        long expireTime,
+        MvccVersion mvccVer,
+        MvccVersion newMvccVer
+    ) throws IgniteCheckedException;
+
+    /**
+     * @param entry Entry.
+     * @param val Value.
+     * @param ver Version.
+     * @param expireTime Expire time.
+     * @param mvccVer MVCC version.
+     * @param newMvccVer New MVCC version.
+     * @param txState Tx state hint for the mvcc version.
+     * @param newTxState Tx state hint for the new mvcc version.
+     * @return {@code True} if value was inserted.
+     * @throws IgniteCheckedException If failed.
+     */
+    public boolean mvccInitialValueIfAbsent(
+        GridCacheMapEntry entry,
+        @Nullable CacheObject val,
+        GridCacheVersion ver,
+        long expireTime,
+        MvccVersion mvccVer,
+        MvccVersion newMvccVer,
+        byte txState,
+        byte newTxState
+    ) throws IgniteCheckedException;
+
+    /**
+     * @param entry Entry.
+     * @param val Value.
+     * @param ver Cache version.
+     * @param expireTime Expire time.
+     * @param mvccSnapshot MVCC snapshot.
+     * @param primary {@code True} if on primary node.
+     * @param needHistory Flag to collect history.
+     * @param noCreate Flag indicating that row should not be created if absent.
+     * @return Update result.
+     * @throws IgniteCheckedException If failed.
+     */
+    @Nullable public MvccUpdateResult mvccUpdate(
+        GridCacheMapEntry entry,
+        CacheObject val,
+        GridCacheVersion ver,
+        long expireTime,
+        MvccSnapshot mvccSnapshot,
+        boolean primary,
+        boolean needHistory,
+        boolean noCreate) throws IgniteCheckedException;
+
+    /**
+     * @param entry Entry.
+     * @param mvccSnapshot MVCC snapshot.
+     * @param primary {@code True} if on primary node.
+     * @param needHistory Flag to collect history.
+     * @return Update result.
+     * @throws IgniteCheckedException If failed.
+     */
+    @Nullable public MvccUpdateResult mvccRemove(
+        GridCacheMapEntry entry,
+        MvccSnapshot mvccSnapshot,
+        boolean primary,
+        boolean needHistory) throws IgniteCheckedException;
+
+    /**
+     * @param entry Entry.
+     * @param mvccSnapshot MVCC snapshot.
+     * @return Update result.
+     * @throws IgniteCheckedException If failed.
+     */
+    @Nullable public MvccUpdateResult mvccLock(
+        GridCacheMapEntry entry,
+        MvccSnapshot mvccSnapshot
+    ) throws IgniteCheckedException;
+
+    /**
+     * @param entry Entry.
+     * @param val Value.
+     * @param ver Version.
+     * @param mvccVer MVCC version.
+     * @param newMvccVer New MVCC version.
+     * @return {@code True} if value was inserted.
+     * @throws IgniteCheckedException If failed.
+     */
+    public boolean mvccUpdateRowWithPreloadInfo(
+        GridCacheMapEntry entry,
+        @Nullable CacheObject val,
+        GridCacheVersion ver,
+        long expireTime,
+        MvccVersion mvccVer,
+        MvccVersion newMvccVer,
+        byte mvccTxState,
+        byte newMvccTxState
+    ) throws IgniteCheckedException;
+
+    /**
+     * @param primary {@code True} if on primary node.
+     * @param entry Entry.
+     * @param val Value.
+     * @param ver Cache version.
+     * @param expireTime Expire time.
+     * @param mvccSnapshot MVCC snapshot.
+     * @return Transactions to wait for before finishing current transaction.
+     * @throws IgniteCheckedException If failed.
+     */
+    GridLongList mvccUpdateNative(
+            boolean primary,
+            GridCacheMapEntry entry,
+            CacheObject val,
+            GridCacheVersion ver,
+            long expireTime,
+            MvccSnapshot mvccSnapshot) throws IgniteCheckedException;
+
+    /**
+     * @param primary {@code True} if on primary node.
+     * @param entry Entry.
+     * @param mvccSnapshot MVCC snapshot.
+     * @return Transactions to wait for before finishing current transaction.
+     * @throws IgniteCheckedException If failed.
+     */
+    GridLongList mvccRemoveNative(
+            boolean primary,
+            GridCacheMapEntry entry,
+            MvccSnapshot mvccSnapshot
+    ) throws IgniteCheckedException;
+
+    /**
+     * @param entry Entry.
+     * @throws IgniteCheckedException If failed.
+     */
+    public void mvccRemoveAll(GridCacheMapEntry entry)
+        throws IgniteCheckedException;
+
     /**
      * @param cctx Cache context.
      * @param key Key.
@@ -209,6 +413,7 @@ public void remove(
     public int onUndeploy(ClassLoader ldr);
 
     /**
+     *
      * @param cacheId Cache ID.
      * @param primary Primary entries flag.
      * @param backup Backup entries flag.
@@ -219,7 +424,8 @@ public void remove(
     public GridIterator<CacheDataRow> cacheIterator(int cacheId,
         boolean primary,
         boolean backup,
-        final AffinityTopologyVersion topVer)
+        AffinityTopologyVersion topVer,
+        @Nullable MvccSnapshot mvccSnapshot)
         throws IgniteCheckedException;
 
     /**
@@ -228,7 +434,20 @@ public void remove(
      * @return Partition data iterator.
      * @throws IgniteCheckedException If failed.
      */
-    public GridIterator<CacheDataRow> cachePartitionIterator(int cacheId, final int part) throws IgniteCheckedException;
+    public default GridIterator<CacheDataRow> cachePartitionIterator(int cacheId, final int part)
+        throws IgniteCheckedException {
+        return cachePartitionIterator(cacheId, part, null);
+    }
+
+    /**
+     * @param cacheId Cache ID.
+     * @param part Partition.
+     * @param mvccSnapshot MVCC snapshot.
+     * @return Partition data iterator.
+     * @throws IgniteCheckedException If failed.
+     */
+    public GridIterator<CacheDataRow> cachePartitionIterator(int cacheId, final int part,
+        @Nullable MvccSnapshot mvccSnapshot) throws IgniteCheckedException;
 
     /**
      * @param part Partition number.
@@ -251,6 +470,7 @@ public void remove(
      * @return Partition data iterator.
      * @throws IgniteCheckedException If failed.
      */
+    // TODO: MVCC>
     public IgniteRebalanceIterator rebalanceIterator(IgniteDhtDemandedPartitionsMap parts, AffinityTopologyVersion topVer)
         throws IgniteCheckedException;
 
@@ -263,6 +483,7 @@ public IgniteRebalanceIterator rebalanceIterator(IgniteDhtDemandedPartitionsMap
      * @return Entries iterator.
      * @throws IgniteCheckedException If failed.
      */
+    // TODO: MVCC>
     public <K, V> GridCloseableIterator<Cache.Entry<K, V>> cacheEntriesIterator(
         GridCacheContext cctx,
         final boolean primary,
@@ -276,6 +497,7 @@ public IgniteRebalanceIterator rebalanceIterator(IgniteDhtDemandedPartitionsMap
      * @return Iterator.
      * @throws IgniteCheckedException If failed.
      */
+    // TODO: MVCC>
     public GridCloseableIterator<KeyCacheObject> cacheKeysIterator(int cacheId, final int part)
         throws IgniteCheckedException;
 
@@ -287,6 +509,7 @@ public IgniteRebalanceIterator rebalanceIterator(IgniteDhtDemandedPartitionsMap
      * @return Entries count.
      * @throws IgniteCheckedException If failed.
      */
+    // TODO: MVCC>
     public long cacheEntriesCount(int cacheId, boolean primary, boolean backup, AffinityTopologyVersion topVer)
         throws IgniteCheckedException;
 
@@ -402,7 +625,7 @@ public long cacheEntriesCount(int cacheId, boolean primary, boolean backup, Affi
         long updateCounter();
 
         /**
-         *
+         * @param val Update counter.
          */
         void updateCounter(long val);
 
@@ -411,6 +634,16 @@ public long cacheEntriesCount(int cacheId, boolean primary, boolean backup, Affi
          */
         public long nextUpdateCounter();
 
+        /**
+         * @return Next mvcc update counter.
+         */
+        long nextMvccUpdateCounter();
+
+        /**
+         * @return Current mvcc update counter value.
+         */
+        long mvccUpdateCounter();
+
         /**
          * @return Initial update counter.
          */
@@ -434,6 +667,24 @@ CacheDataRow createRow(
             long expireTime,
             @Nullable CacheDataRow oldRow) throws IgniteCheckedException;
 
+        /**
+         * @param cctx Cache context.
+         * @param cleanupRows Rows to cleanup.
+         * @throws IgniteCheckedException If failed.
+         * @return Cleaned rows count.
+         */
+        public int cleanup(GridCacheContext cctx, @Nullable List<MvccLinkAwareSearchRow> cleanupRows)
+            throws IgniteCheckedException;
+
+        /**
+         *
+         * @param cctx Cache context.
+         * @param row Row.
+         * @throws IgniteCheckedException
+         */
+        public void updateTxState(GridCacheContext cctx, CacheSearchRow row)
+            throws IgniteCheckedException;
+
         /**
          * @param cctx Cache context.
          * @param key Key.
@@ -451,6 +702,163 @@ void update(
             long expireTime,
             @Nullable CacheDataRow oldRow) throws IgniteCheckedException;
 
+        /**
+         * @param cctx Cache context.
+         * @param key Key.
+         * @param val Value.
+         * @param ver Version.
+         * @param mvccVer MVCC version.
+         * @param newMvccVer New MVCC version.
+         * @return {@code True} if new value was inserted.
+         * @throws IgniteCheckedException If failed.
+         */
+        boolean mvccInitialValue(
+            GridCacheContext cctx,
+            KeyCacheObject key,
+            @Nullable CacheObject val,
+            GridCacheVersion ver,
+            long expireTime,
+            MvccVersion mvccVer,
+            MvccVersion newMvccVer) throws IgniteCheckedException;
+
+        /**
+         * @param cctx Cache context.
+         * @param key Key.
+         * @param val Value.
+         * @param ver Version.
+         * @param mvccVer MVCC version.
+         * @param newMvccVer New MVCC version.
+         * @param txState Tx state hint for the mvcc version.
+         * @param newTxState Tx state hint for the new mvcc version.
+         * @return {@code True} if new value was inserted.
+         * @throws IgniteCheckedException If failed.
+         */
+        boolean mvccInitialValueIfAbsent(
+            GridCacheContext cctx,
+            KeyCacheObject key,
+            @Nullable CacheObject val,
+            GridCacheVersion ver,
+            long expireTime,
+            MvccVersion mvccVer,
+            MvccVersion newMvccVer,
+            byte txState,
+            byte newTxState) throws IgniteCheckedException;
+
+        /**
+         *
+         * @param cctx Grid cache context.
+         * @param key Key.
+         * @param val Value.
+         * @param ver Version.
+         * @param expireTime Expiration time.
+         * @param mvccVer Mvcc version.
+         * @param newMvccVer New mvcc version.
+         * @return {@code true} on success.
+         * @throws IgniteCheckedException, if failed.
+         */
+        boolean mvccUpdateRowWithPreloadInfo(
+            GridCacheContext cctx,
+            KeyCacheObject key,
+            @Nullable CacheObject val,
+            GridCacheVersion ver,
+            long expireTime,
+            MvccVersion mvccVer,
+            MvccVersion newMvccVer,
+            byte mvccTxState,
+            byte newMvccTxState) throws IgniteCheckedException;
+
+        /**
+         * @param cctx Cache context.
+         * @param key Key.
+         * @param val Value.
+         * @param ver Version.
+         * @param expireTime Expire time.
+         * @param mvccSnapshot MVCC snapshot.
+         * @param primary {@code True} if update is executed on primary node.
+         * @param needHistory Flag to collect history.
+         * @param noCreate Flag indicating that row should not be created if absent.
+         * @return Update result.
+         * @throws IgniteCheckedException If failed.
+         */
+        MvccUpdateResult mvccUpdate(
+            GridCacheContext cctx,
+            KeyCacheObject key,
+            CacheObject val,
+            GridCacheVersion ver,
+            long expireTime,
+            MvccSnapshot mvccSnapshot,
+            boolean primary,
+            boolean needHistory,
+            boolean noCreate) throws IgniteCheckedException;
+
+        /**
+         * @param cctx Cache context.
+         * @param key Key.
+         * @param mvccSnapshot MVCC snapshot.
+         * @param primary {@code True} if update is executed on primary node.
+         * @param needHistory Flag to collect history.
+         * @return List of transactions to wait for.
+         * @throws IgniteCheckedException If failed.
+         */
+        MvccUpdateResult mvccRemove(
+            GridCacheContext cctx,
+            KeyCacheObject key,
+            MvccSnapshot mvccSnapshot,
+            boolean primary,
+            boolean needHistory) throws IgniteCheckedException;
+
+        /**
+         * @param cctx Cache context.
+         * @param key Key.
+         * @param mvccSnapshot MVCC snapshot.
+         * @return List of transactions to wait for.
+         * @throws IgniteCheckedException If failed.
+         */
+        MvccUpdateResult mvccLock(
+            GridCacheContext cctx,
+            KeyCacheObject key,
+            MvccSnapshot mvccSnapshot) throws IgniteCheckedException;
+
+        /**
+         * @param cctx Cache context.
+         * @param primary {@code True} if update is executed on primary node.
+         * @param key Key.
+         * @param val Value.
+         * @param ver Version.
+         * @param expireTime Expire time.
+         * @param mvccSnapshot MVCC snapshot.
+         * @return Update result.
+         * @throws IgniteCheckedException If failed.
+         */
+        @Nullable GridLongList mvccUpdateNative(
+                GridCacheContext cctx,
+                boolean primary,
+                KeyCacheObject key,
+                CacheObject val,
+                GridCacheVersion ver,
+                long expireTime,
+                MvccSnapshot mvccSnapshot) throws IgniteCheckedException;
+
+        /**
+         * @param cctx Cache context.
+         * @param primary {@code True} if update is executed on primary node.
+         * @param key Key.
+         * @param mvccSnapshot MVCC snapshot.
+         * @return List of transactions to wait for.
+         * @throws IgniteCheckedException If failed.
+         */
+        @Nullable GridLongList mvccRemoveNative(GridCacheContext cctx,
+                                      boolean primary,
+                                      KeyCacheObject key,
+                                      MvccSnapshot mvccSnapshot) throws IgniteCheckedException;
+
+        /**
+         * @param cctx Cache context.
+         * @param key Key.
+         * @throws IgniteCheckedException If failed.
+         */
+        void mvccRemoveAll(GridCacheContext cctx, KeyCacheObject key) throws IgniteCheckedException;
+
         /**
          * @param cctx Cache context.
          * @param key Key.
@@ -475,12 +883,58 @@ void update(
          */
         public CacheDataRow find(GridCacheContext cctx, KeyCacheObject key) throws IgniteCheckedException;
 
+        /**
+         * Returns iterator over the all row versions for the given key.
+         *
+         * @param cctx Cache context.
+         * @param key Key.
+         * @param x Implementation specific argument, {@code null} always means that we need to return full detached data row.
+         * @return Iterator over all versions.
+         * @throws IgniteCheckedException If failed.
+         */
+        GridCursor<CacheDataRow> mvccAllVersionsCursor(GridCacheContext cctx, KeyCacheObject key, Object x)
+            throws IgniteCheckedException;
+
+        /**
+         * @param cctx Cache context.
+         * @param key Key.
+         * @return Data row.
+         * @throws IgniteCheckedException If failed.
+         */
+        public CacheDataRow mvccFind(GridCacheContext cctx, KeyCacheObject key, MvccSnapshot snapshot)
+            throws IgniteCheckedException;
+
+        /**
+         * For testing only.
+         *
+         * @param cctx Cache context.
+         * @param key Key.
+         * @return All stored versions for given key.
+         * @throws IgniteCheckedException If failed.
+         */
+        List<IgniteBiTuple<Object, MvccVersion>> mvccFindAllVersions(GridCacheContext cctx, KeyCacheObject key)
+            throws IgniteCheckedException;
+
         /**
          * @return Data cursor.
          * @throws IgniteCheckedException If failed.
          */
         public GridCursor<? extends CacheDataRow> cursor() throws IgniteCheckedException;
 
+        /**
+         * @param x Implementation specific argument, {@code null} always means that we need to return full detached data row.
+         * @return Data cursor.
+         * @throws IgniteCheckedException If failed.
+         */
+        public GridCursor<? extends CacheDataRow> cursor(Object x) throws IgniteCheckedException;
+
+        /**
+         * @param mvccSnapshot MVCC snapshot.
+         * @return Data cursor.
+         * @throws IgniteCheckedException If failed.
+         */
+        public GridCursor<? extends CacheDataRow> cursor(MvccSnapshot mvccSnapshot) throws IgniteCheckedException;
+
         /**
          * @param cacheId Cache ID.
          * @return Data cursor.
@@ -488,6 +942,15 @@ void update(
          */
         public GridCursor<? extends CacheDataRow> cursor(int cacheId) throws IgniteCheckedException;
 
+        /**
+         * @param cacheId Cache ID.
+         * @param mvccSnapshot Mvcc snapshot.
+         * @return Data cursor.
+         * @throws IgniteCheckedException If failed.
+         */
+        public GridCursor<? extends CacheDataRow> cursor(int cacheId, MvccSnapshot mvccSnapshot)
+            throws IgniteCheckedException;
+
         /**
          * @param cacheId Cache ID.
          * @param lower Lower bound.
@@ -509,6 +972,18 @@ void update(
         public GridCursor<? extends CacheDataRow> cursor(int cacheId, KeyCacheObject lower,
             KeyCacheObject upper, Object x) throws IgniteCheckedException;
 
+        /**
+         * @param cacheId Cache ID.
+         * @param lower Lower bound.
+         * @param upper Upper bound.
+         * @param x Implementation specific argument, {@code null} always means that we need to return full detached data row.
+         * @param snapshot Mvcc snapshot.
+         * @return Data cursor.
+         * @throws IgniteCheckedException If failed.
+         */
+        public GridCursor<? extends CacheDataRow> cursor(int cacheId, KeyCacheObject lower,
+            KeyCacheObject upper, Object x, MvccSnapshot snapshot) throws IgniteCheckedException;
+
         /**
          * Destroys the tree associated with the store.
          *
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/IgniteCacheOffheapManagerImpl.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/IgniteCacheOffheapManagerImpl.java
index a8ad32d5a203..f20ed60bfe15 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/IgniteCacheOffheapManagerImpl.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/IgniteCacheOffheapManagerImpl.java
@@ -17,10 +17,12 @@
 
 package org.apache.ignite.internal.processors.cache;
 
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
+import java.util.List;
 import java.util.Map;
 import java.util.NoSuchElementException;
 import java.util.Set;
@@ -36,6 +38,9 @@
 import org.apache.ignite.IgniteSystemProperties;
 import org.apache.ignite.internal.NodeStoppingException;
 import org.apache.ignite.internal.pagemem.FullPageId;
+import org.apache.ignite.internal.pagemem.wal.record.delta.DataPageMvccMarkUpdatedRecord;
+import org.apache.ignite.internal.pagemem.wal.record.delta.DataPageMvccUpdateNewTxStateHintRecord;
+import org.apache.ignite.internal.pagemem.wal.record.delta.DataPageMvccUpdateTxStateHintRecord;
 import org.apache.ignite.internal.processors.affinity.AffinityTopologyVersion;
 import org.apache.ignite.internal.processors.cache.distributed.dht.GridDhtInvalidPartitionException;
 import org.apache.ignite.internal.processors.cache.distributed.dht.GridDhtLocalPartition;
@@ -43,28 +48,48 @@
 import org.apache.ignite.internal.processors.cache.distributed.dht.preloader.IgniteDhtDemandedPartitionsMap;
 import org.apache.ignite.internal.processors.cache.distributed.dht.preloader.IgniteHistoricalIterator;
 import org.apache.ignite.internal.processors.cache.distributed.dht.preloader.IgniteRebalanceIteratorImpl;
+import org.apache.ignite.internal.processors.cache.mvcc.MvccSnapshot;
+import org.apache.ignite.internal.processors.cache.mvcc.MvccVersion;
+import org.apache.ignite.internal.processors.cache.mvcc.txlog.TxState;
 import org.apache.ignite.internal.processors.cache.persistence.CacheDataRow;
 import org.apache.ignite.internal.processors.cache.persistence.CacheDataRowAdapter;
 import org.apache.ignite.internal.processors.cache.persistence.CacheSearchRow;
 import org.apache.ignite.internal.processors.cache.persistence.RootPage;
 import org.apache.ignite.internal.processors.cache.persistence.RowStore;
 import org.apache.ignite.internal.processors.cache.persistence.tree.BPlusTree;
+import org.apache.ignite.internal.processors.cache.persistence.tree.io.BPlusIO;
 import org.apache.ignite.internal.processors.cache.persistence.tree.io.DataPageIO;
+import org.apache.ignite.internal.processors.cache.persistence.tree.io.PageIO;
 import org.apache.ignite.internal.processors.cache.persistence.tree.reuse.ReuseList;
+import org.apache.ignite.internal.processors.cache.persistence.tree.util.PageHandler;
 import org.apache.ignite.internal.processors.cache.query.GridCacheQueryManager;
 import org.apache.ignite.internal.processors.cache.tree.CacheDataRowStore;
 import org.apache.ignite.internal.processors.cache.tree.CacheDataTree;
 import org.apache.ignite.internal.processors.cache.tree.DataRow;
 import org.apache.ignite.internal.processors.cache.tree.PendingEntriesTree;
 import org.apache.ignite.internal.processors.cache.tree.PendingRow;
+import org.apache.ignite.internal.processors.cache.tree.RowLinkIO;
 import org.apache.ignite.internal.processors.cache.tree.SearchRow;
+import org.apache.ignite.internal.processors.cache.tree.mvcc.data.MvccDataRow;
+import org.apache.ignite.internal.processors.cache.tree.mvcc.data.MvccUpdateDataRow;
+import org.apache.ignite.internal.processors.cache.tree.mvcc.data.MvccUpdateDataRowNative;
+import org.apache.ignite.internal.processors.cache.tree.mvcc.data.MvccUpdateResult;
+import org.apache.ignite.internal.processors.cache.tree.mvcc.data.ResultType;
+import org.apache.ignite.internal.processors.cache.tree.mvcc.search.MvccFirstRowTreeClosure;
+import org.apache.ignite.internal.processors.cache.tree.mvcc.search.MvccLinkAwareSearchRow;
+import org.apache.ignite.internal.processors.cache.tree.mvcc.search.MvccMaxSearchRow;
+import org.apache.ignite.internal.processors.cache.tree.mvcc.search.MvccMinSearchRow;
+import org.apache.ignite.internal.processors.cache.tree.mvcc.search.MvccSnapshotSearchRow;
+import org.apache.ignite.internal.processors.cache.tree.mvcc.search.MvccTreeClosure;
 import org.apache.ignite.internal.processors.cache.version.GridCacheVersion;
 import org.apache.ignite.internal.processors.query.GridQueryRowCacheCleaner;
 import org.apache.ignite.internal.util.GridAtomicLong;
 import org.apache.ignite.internal.util.GridCloseableIteratorAdapter;
 import org.apache.ignite.internal.util.GridEmptyCloseableIterator;
+import org.apache.ignite.internal.util.GridLongList;
 import org.apache.ignite.internal.util.GridSpinBusyLock;
 import org.apache.ignite.internal.util.GridStripedLock;
+import org.apache.ignite.internal.util.IgniteTree;
 import org.apache.ignite.internal.util.lang.GridCloseableIterator;
 import org.apache.ignite.internal.util.lang.GridCursor;
 import org.apache.ignite.internal.util.lang.GridIterator;
@@ -72,6 +97,7 @@
 import org.apache.ignite.internal.util.typedef.F;
 import org.apache.ignite.internal.util.typedef.internal.CU;
 import org.apache.ignite.internal.util.typedef.internal.U;
+import org.apache.ignite.lang.IgniteBiTuple;
 import org.apache.ignite.lang.IgniteClosure;
 import org.apache.ignite.lang.IgniteInClosure;
 import org.apache.ignite.lang.IgnitePredicate;
@@ -80,6 +106,22 @@
 import static org.apache.ignite.internal.pagemem.PageIdAllocator.FLAG_IDX;
 import static org.apache.ignite.internal.pagemem.PageIdAllocator.INDEX_PARTITION;
 import static org.apache.ignite.internal.processors.cache.distributed.dht.GridDhtPartitionState.OWNING;
+import static org.apache.ignite.internal.processors.cache.mvcc.MvccUtils.INITIAL_VERSION;
+import static org.apache.ignite.internal.processors.cache.mvcc.MvccUtils.MVCC_COUNTER_NA;
+import static org.apache.ignite.internal.processors.cache.mvcc.MvccUtils.MVCC_CRD_COUNTER_NA;
+import static org.apache.ignite.internal.processors.cache.mvcc.MvccUtils.MVCC_OP_COUNTER_NA;
+import static org.apache.ignite.internal.processors.cache.mvcc.MvccUtils.compare;
+import static org.apache.ignite.internal.processors.cache.mvcc.MvccUtils.compareNewVersion;
+import static org.apache.ignite.internal.processors.cache.mvcc.MvccUtils.isVisible;
+import static org.apache.ignite.internal.processors.cache.mvcc.MvccUtils.mvccVersionIsValid;
+import static org.apache.ignite.internal.processors.cache.mvcc.MvccUtils.state;
+import static org.apache.ignite.internal.processors.cache.mvcc.MvccUtils.unexpectedStateException;
+import static org.apache.ignite.internal.processors.cache.persistence.GridCacheOffheapManager.EMPTY_CURSOR;
+import static org.apache.ignite.internal.processors.cache.persistence.tree.io.DataPageIO.MVCC_INFO_SIZE;
+import static org.apache.ignite.internal.processors.cache.persistence.tree.io.PageIO.MVCC_HINTS_BIT_OFF;
+import static org.apache.ignite.internal.processors.cache.persistence.tree.io.PageIO.MVCC_HINTS_MASK;
+import static org.apache.ignite.internal.util.IgniteTree.OperationType.NOOP;
+import static org.apache.ignite.internal.util.IgniteTree.OperationType.PUT;
 
 /**
  *
@@ -396,6 +438,170 @@ public CacheDataStore dataStore(int part) {
         dataStore(part).update(cctx, key, val, ver, expireTime, oldRow);
     }
 
+    /** {@inheritDoc} */
+    @Override public boolean mvccInitialValue(
+        GridCacheMapEntry entry,
+        CacheObject val,
+        GridCacheVersion ver,
+        long expireTime,
+        MvccVersion mvccVer,
+        MvccVersion newMvccVer) throws IgniteCheckedException {
+        return dataStore(entry.localPartition()).mvccInitialValue(
+            entry.context(),
+            entry.key(),
+            val,
+            ver,
+            expireTime,
+            mvccVer,
+            newMvccVer);
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean mvccInitialValueIfAbsent(
+        GridCacheMapEntry entry,
+        CacheObject val,
+        GridCacheVersion ver,
+        long expireTime,
+        MvccVersion mvccVer,
+        MvccVersion newMvccVer,
+        byte txState,
+        byte newTxState) throws IgniteCheckedException {
+        return dataStore(entry.localPartition()).mvccInitialValueIfAbsent(
+            entry.context(),
+            entry.key(),
+            val,
+            ver,
+            expireTime,
+            mvccVer,
+            newMvccVer,
+            txState,
+            newTxState);
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean mvccUpdateRowWithPreloadInfo(
+        GridCacheMapEntry entry,
+        @Nullable CacheObject val,
+        GridCacheVersion ver,
+        long expireTime,
+        MvccVersion mvccVer,
+        MvccVersion newMvccVer,
+        byte mvccTxState,
+        byte newMvccTxState
+    ) throws IgniteCheckedException {
+        assert entry.lockedByCurrentThread();
+
+        return dataStore(entry.localPartition()).mvccUpdateRowWithPreloadInfo(
+            entry.context(),
+            entry.key(),
+            val,
+            ver,
+            expireTime,
+            mvccVer,
+            newMvccVer,
+            mvccTxState,
+            newMvccTxState
+        );
+    }
+
+    /** {@inheritDoc} */
+    @Override public MvccUpdateResult mvccUpdate(
+        GridCacheMapEntry entry,
+        CacheObject val,
+        GridCacheVersion ver,
+        long expireTime,
+        MvccSnapshot mvccSnapshot,
+        boolean primary,
+        boolean needHistory,
+        boolean noCreate) throws IgniteCheckedException {
+        if (entry.detached() || entry.isNear())
+            return null;
+
+        assert entry.lockedByCurrentThread();
+
+        return dataStore(entry.localPartition()).mvccUpdate(entry.context(),
+            entry.key(),
+            val,
+            ver,
+            expireTime,
+            mvccSnapshot,
+            primary,
+            needHistory,
+            noCreate);
+    }
+
+    /** {@inheritDoc} */
+    @Override public MvccUpdateResult mvccRemove(
+        GridCacheMapEntry entry,
+        MvccSnapshot mvccSnapshot,
+        boolean primary,
+        boolean needHistory) throws IgniteCheckedException {
+        if (entry.detached() || entry.isNear())
+            return null;
+
+        assert entry.lockedByCurrentThread();
+
+        return dataStore(entry.localPartition()).mvccRemove(entry.context(),
+            entry.key(),
+            mvccSnapshot,
+            primary,
+            needHistory);
+    }
+
+    /** {@inheritDoc} */
+    @Override public GridLongList mvccUpdateNative(
+        boolean primary,
+        GridCacheMapEntry entry,
+        CacheObject val,
+        GridCacheVersion ver,
+        long expireTime,
+        MvccSnapshot mvccSnapshot) throws IgniteCheckedException {
+        if (entry.detached() || entry.isNear())
+            return null;
+
+        return dataStore(entry.localPartition()).mvccUpdateNative(entry.context(),
+            primary,
+            entry.key(),
+            val,
+            ver,
+            expireTime,
+            mvccSnapshot);
+    }
+
+    /** {@inheritDoc} */
+    @Override public GridLongList mvccRemoveNative(
+        boolean primary,
+        GridCacheMapEntry entry,
+        MvccSnapshot mvccSnapshot
+    ) throws IgniteCheckedException {
+        if (entry.detached() || entry.isNear())
+            return null;
+
+        return dataStore(entry.localPartition()).mvccRemoveNative(entry.context(),
+            primary,
+            entry.key(),
+            mvccSnapshot);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void mvccRemoveAll(GridCacheMapEntry entry) throws IgniteCheckedException {
+        if (entry.detached() || entry.isNear())
+            return;
+
+        dataStore(entry.localPartition()).mvccRemoveAll(entry.context(), entry.key());
+    }
+
+    /** {@inheritDoc} */
+    @Nullable @Override public MvccUpdateResult mvccLock(GridCacheMapEntry entry,
+        MvccSnapshot mvccSnapshot) throws IgniteCheckedException {
+        if (entry.detached() || entry.isNear())
+            return null;
+
+        assert entry.lockedByCurrentThread();
+
+        return dataStore(entry.localPartition()).mvccLock(entry.context(), entry.key(), mvccSnapshot);
+    }
+
     /** {@inheritDoc} */
     @Override public void remove(
         GridCacheContext cctx,
@@ -418,22 +624,62 @@ public CacheDataStore dataStore(int part) {
     }
 
     /** {@inheritDoc} */
-    @Nullable @Override public CacheDataRow read(GridCacheContext cctx, KeyCacheObject key) throws IgniteCheckedException {
-        CacheDataRow row;
+    @Nullable @Override public CacheDataRow read(GridCacheContext cctx, KeyCacheObject key)
+        throws IgniteCheckedException {
+        CacheDataStore dataStore = dataStore(cctx, key);
 
-        if (cctx.isLocal())
-            row = locCacheDataStore.find(cctx, key);
-        else {
-            GridDhtLocalPartition part = cctx.topology().localPartition(cctx.affinity().partition(key), null, false);
+        CacheDataRow row = dataStore != null ? dataStore.find(cctx, key) : null;
 
-            row = part != null ? dataStore(part).find(cctx, key) : null;
-        }
+        assert row == null || row.value() != null : row;
+
+        return row;
+    }
+
+    /** {@inheritDoc} */
+    @Nullable @Override public CacheDataRow mvccRead(GridCacheContext cctx, KeyCacheObject key, MvccSnapshot ver)
+        throws IgniteCheckedException {
+        assert ver != null;
+
+        CacheDataStore dataStore = dataStore(cctx, key);
+
+        CacheDataRow row = dataStore != null ? dataStore.mvccFind(cctx, key, ver) : null;
 
         assert row == null || row.value() != null : row;
 
         return row;
     }
 
+    /** {@inheritDoc} */
+    @Override public List<IgniteBiTuple<Object, MvccVersion>> mvccAllVersions(GridCacheContext cctx, KeyCacheObject key)
+        throws IgniteCheckedException {
+        CacheDataStore dataStore = dataStore(cctx, key);
+
+        return dataStore != null ? dataStore.mvccFindAllVersions(cctx, key) :
+            Collections.emptyList();
+    }
+
+    /** {@inheritDoc} */
+    @Override public GridCursor<CacheDataRow> mvccAllVersionsCursor(GridCacheContext cctx,
+        KeyCacheObject key, Object x) throws IgniteCheckedException {
+        CacheDataStore dataStore = dataStore(cctx, key);
+
+        return dataStore != null ? dataStore.mvccAllVersionsCursor(cctx, key, x) : EMPTY_CURSOR;
+    }
+
+    /**
+     * @param cctx Cache context.
+     * @param key Key.
+     * @return Data store.
+     */
+    @Nullable private CacheDataStore dataStore(GridCacheContext cctx, KeyCacheObject key) {
+        if (grp.isLocal())
+            return locCacheDataStore;
+
+        GridDhtLocalPartition part = grp.topology().localPartition(cctx.affinity().partition(key), null, false);
+
+        return part != null ? dataStore(part) : null;
+    }
+
     /** {@inheritDoc} */
     @Override public boolean containsKey(GridCacheMapEntry entry) {
         try {
@@ -470,7 +716,7 @@ public CacheDataStore dataStore(int part) {
     @Override public void clearCache(GridCacheContext cctx, boolean readers) {
         GridCacheVersion obsoleteVer = null;
 
-        try (GridCloseableIterator<CacheDataRow> it = grp.isLocal() ? iterator(cctx.cacheId(), cacheDataStores().iterator()) :
+        try (GridCloseableIterator<CacheDataRow> it = grp.isLocal() ? iterator(cctx.cacheId(), cacheDataStores().iterator(), null) :
             evictionSafeIterator(cctx.cacheId(), cacheDataStores().iterator())) {
             while (it.hasNext()) {
                 cctx.shared().database().checkpointReadLock();
@@ -529,7 +775,7 @@ public CacheDataStore dataStore(int part) {
         final boolean backup,
         final AffinityTopologyVersion topVer,
         final boolean keepBinary) throws IgniteCheckedException {
-        final Iterator<CacheDataRow> it = cacheIterator(cctx.cacheId(), primary, backup, topVer);
+        final Iterator<CacheDataRow> it = cacheIterator(cctx.cacheId(), primary, backup, topVer, null);
 
         return new GridCloseableIteratorAdapter<Cache.Entry<K, V>>() {
             /** */
@@ -570,7 +816,8 @@ public CacheDataStore dataStore(int part) {
     }
 
     /** {@inheritDoc} */
-    @Override public GridCloseableIterator<KeyCacheObject> cacheKeysIterator(int cacheId, final int part) throws IgniteCheckedException {
+    @Override public GridCloseableIterator<KeyCacheObject> cacheKeysIterator(int cacheId, final int part)
+        throws IgniteCheckedException {
         CacheDataStore data = partitionData(part);
 
         if (data == null)
@@ -611,19 +858,21 @@ public CacheDataStore dataStore(int part) {
         int cacheId,
         boolean primary,
         boolean backups,
-        final AffinityTopologyVersion topVer)
+        final AffinityTopologyVersion topVer,
+        @Nullable MvccSnapshot mvccSnapshot)
         throws IgniteCheckedException {
-        return iterator(cacheId, cacheData(primary, backups, topVer));
+        return iterator(cacheId, cacheData(primary, backups, topVer), mvccSnapshot);
     }
 
     /** {@inheritDoc} */
-    @Override public GridIterator<CacheDataRow> cachePartitionIterator(int cacheId, int part) throws IgniteCheckedException {
+    @Override public GridIterator<CacheDataRow> cachePartitionIterator(int cacheId, int part,
+        @Nullable MvccSnapshot mvccSnapshot) throws IgniteCheckedException {
         CacheDataStore data = partitionData(part);
 
         if (data == null)
             return new GridEmptyCloseableIterator<>();
 
-        return iterator(cacheId, singletonIterator(data));
+        return iterator(cacheId, singletonIterator(data), mvccSnapshot);
     }
 
     /** {@inheritDoc} */
@@ -633,15 +882,19 @@ public CacheDataStore dataStore(int part) {
         if (data == null)
             return new GridEmptyCloseableIterator<>();
 
-        return iterator(CU.UNDEFINED_CACHE_ID, singletonIterator(data));
+        return iterator(CU.UNDEFINED_CACHE_ID, singletonIterator(data), null);
     }
 
     /**
      * @param cacheId Cache ID.
      * @param dataIt Data store iterator.
+     * @param mvccSnapshot Mvcc snapshot.
      * @return Rows iterator
      */
-    private GridCloseableIterator<CacheDataRow> iterator(final int cacheId, final Iterator<CacheDataStore> dataIt) {
+    private GridCloseableIterator<CacheDataRow> iterator(final int cacheId,
+        final Iterator<CacheDataStore> dataIt,
+        final MvccSnapshot mvccSnapshot)
+    {
         return new GridCloseableIteratorAdapter<CacheDataRow>() {
             /** */
             private GridCursor<? extends CacheDataRow> cur;
@@ -670,7 +923,13 @@ public CacheDataStore dataStore(int part) {
                             CacheDataStore ds = dataIt.next();
 
                             curPart = ds.partId();
-                            cur = cacheId == CU.UNDEFINED_CACHE_ID ? ds.cursor() : ds.cursor(cacheId);
+
+                            if (mvccSnapshot == null)
+                                cur = cacheId == CU.UNDEFINED_CACHE_ID ? ds.cursor() : ds.cursor(cacheId);
+                            else {
+                                cur = cacheId == CU.UNDEFINED_CACHE_ID ?
+                                    ds.cursor(mvccSnapshot) : ds.cursor(cacheId, mvccSnapshot);
+                            }
                         }
                         else
                             break;
@@ -696,7 +955,8 @@ public CacheDataStore dataStore(int part) {
      * @param dataIt Data store iterator.
      * @return Rows iterator
      */
-    private GridCloseableIterator<CacheDataRow> evictionSafeIterator(final int cacheId, final Iterator<CacheDataStore> dataIt) {
+    private GridCloseableIterator<CacheDataRow> evictionSafeIterator(final int cacheId,
+        final Iterator<CacheDataStore> dataIt) {
         return new GridCloseableIteratorAdapter<CacheDataRow>() {
             /** */
             private GridCursor<? extends CacheDataRow> cur;
@@ -1142,6 +1402,13 @@ private int expireInternal(
         /** Update counter. */
         protected final AtomicLong cntr = new AtomicLong();
 
+        /**
+         * Mvcc update counter. This counter is used for an mvcc-style entries updates where this counter is
+         * incremented on each entry write (which happens before commit), but main update counter is updated
+         * on commit phase only.
+         */
+        protected final AtomicLong mvccUpdCntr = new AtomicLong();
+
         /** Partition size. */
         private final AtomicLong storageSize = new AtomicLong();
 
@@ -1151,6 +1418,15 @@ private int expireInternal(
         /** Initial update counter. */
         protected long initCntr;
 
+        /** Mvcc remove handler. */
+        private final PageHandler<MvccVersion, Boolean> mvccUpdateMarker = new MvccMarkUpdatedHandler();
+
+        /** Mvcc update tx state hint handler. */
+        private final PageHandler<Void, Boolean> mvccUpdateTxStateHint = new MvccUpdateTxStateHintHandler();
+
+        /** */
+        private final PageHandler<MvccDataRow, Boolean> mvccApplyChanges = new MvccApplyChangesHandler();
+
         /**
          * @param partId Partition number.
          * @param name Name.
@@ -1239,14 +1515,17 @@ void decrementSize(int cacheId) {
             return storageSize.get();
         }
 
+        /** {@inheritDoc} */
+        @Override public long nextUpdateCounter() {
+            return cntr.incrementAndGet();
+        }
+
         /** {@inheritDoc} */
         @Override public long updateCounter() {
             return cntr.get();
         }
 
-        /**
-         * @param val Update index value.
-         */
+        /** {@inheritDoc} */
         @Override public void updateCounter(long val) {
             while (true) {
                 long val0 = cntr.get();
@@ -1259,6 +1538,16 @@ void decrementSize(int cacheId) {
             }
         }
 
+        /** {@inheritDoc} */
+        @Override public long nextMvccUpdateCounter() {
+            return mvccUpdCntr.incrementAndGet();
+        }
+
+        /** {@inheritDoc} */
+        @Override public long mvccUpdateCounter() {
+            return mvccUpdCntr.get();
+        }
+
         /** {@inheritDoc} */
         @Override public String name() {
             return name;
@@ -1273,7 +1562,7 @@ void decrementSize(int cacheId) {
          */
         private boolean canUpdateOldRow(GridCacheContext cctx, @Nullable CacheDataRow oldRow, DataRow dataRow)
             throws IgniteCheckedException {
-            if (oldRow == null || cctx.queries().enabled())
+            if (oldRow == null || cctx.queries().enabled() || grp.mvccEnabled())
                 return false;
 
             if (oldRow.expireTime() != dataRow.expireTime())
@@ -1282,28 +1571,39 @@ private boolean canUpdateOldRow(GridCacheContext cctx, @Nullable CacheDataRow ol
             // Use grp.sharedGroup() flag since it is possible cacheId is not yet set here.
             boolean sizeWithCacheId = grp.sharedGroup();
 
-            int oldLen = DataPageIO.getRowSize(oldRow, sizeWithCacheId);
+            int oldLen = oldRow.size();
 
             if (oldLen > updateValSizeThreshold)
                 return false;
 
-            int newLen = DataPageIO.getRowSize(dataRow, sizeWithCacheId);
+            int newLen = dataRow.size();
 
             return oldLen == newLen;
         }
 
         /** {@inheritDoc} */
         @Override public void invoke(GridCacheContext cctx, KeyCacheObject key, OffheapInvokeClosure c)
+            throws IgniteCheckedException {
+            int cacheId = grp.sharedGroup() ? cctx.cacheId() : CU.UNDEFINED_CACHE_ID;
+
+            invoke0(cctx, new SearchRow(cacheId, key), c);
+        }
+
+        /**
+         * @param cctx Cache context.
+         * @param row Search row.
+         * @param c Closure.
+         * @throws IgniteCheckedException If failed.
+         */
+        private void invoke0(GridCacheContext cctx, CacheSearchRow row, OffheapInvokeClosure c)
             throws IgniteCheckedException {
             if (!busyLock.enterBusy())
                 throw new NodeStoppingException("Operation has been cancelled (node is stopping).");
 
             try {
-                int cacheId = grp.sharedGroup() ? cctx.cacheId() : CU.UNDEFINED_CACHE_ID;
-
                 assert cctx.shared().database().checkpointLockIsHeldByThread();
 
-                dataTree.invoke(new SearchRow(cacheId, key), CacheDataRowAdapter.RowData.NO_KEY, c);
+                dataTree.invoke(row, CacheDataRowAdapter.RowData.NO_KEY, c);
 
                 switch (c.operationType()) {
                     case PUT: {
@@ -1319,7 +1619,7 @@ private boolean canUpdateOldRow(GridCacheContext cctx, @Nullable CacheDataRow ol
                     case REMOVE: {
                         CacheDataRow oldRow = c.oldRow();
 
-                        finishRemove(cctx, key, oldRow);
+                        finishRemove(cctx, row.key(), oldRow);
 
                         break;
                     }
@@ -1368,152 +1668,817 @@ private boolean canUpdateOldRow(GridCacheContext cctx, @Nullable CacheDataRow ol
         }
 
         /** {@inheritDoc} */
-        @Override public void update(
+        @Override public boolean mvccInitialValue(
             GridCacheContext cctx,
             KeyCacheObject key,
-            CacheObject val,
+            @Nullable CacheObject val,
             GridCacheVersion ver,
             long expireTime,
-            @Nullable CacheDataRow oldRow
-        ) throws IgniteCheckedException {
-            assert oldRow == null || oldRow.link() != 0L : oldRow;
+            MvccVersion mvccVer,
+            MvccVersion newMvccVer)
+            throws IgniteCheckedException
+        {
+            assert mvccVer != null || newMvccVer == null : newMvccVer;
 
             if (!busyLock.enterBusy())
                 throw new NodeStoppingException("Operation has been cancelled (node is stopping).");
 
             try {
-                int cacheId = grp.storeCacheIdInDataPage() ? cctx.cacheId() : CU.UNDEFINED_CACHE_ID;
-
-                assert oldRow == null || oldRow.cacheId() == cacheId : oldRow;
-
-                if (key.partition() == -1)
-                    key.partition(partId);
-
-                DataRow dataRow = new DataRow(key, val, ver, partId, expireTime, cacheId);
-
                 CacheObjectContext coCtx = cctx.cacheObjectContext();
 
                 // Make sure value bytes initialized.
                 key.valueBytes(coCtx);
-                val.valueBytes(coCtx);
 
-                CacheDataRow old;
+                // null is passed for loaded from store.
+                if (mvccVer == null) {
+                    mvccVer = INITIAL_VERSION;
 
-                assert cctx.shared().database().checkpointLockIsHeldByThread();
+                    // Clean all versions of row
+                    mvccRemoveAll(cctx, key);
+                }
 
-                if (canUpdateOldRow(cctx, oldRow, dataRow) && rowStore.updateRow(oldRow.link(), dataRow)) {
-                    old = oldRow;
+                if (val != null) {
+                    val.valueBytes(coCtx);
 
-                    dataRow.link(oldRow.link());
-                }
-                else {
-                    rowStore.addRow(dataRow);
+                    MvccDataRow updateRow = new MvccDataRow(
+                        key,
+                        val,
+                        ver,
+                        partId,
+                        expireTime,
+                        cctx.cacheId(),
+                        mvccVer,
+                        newMvccVer);
 
-                    assert dataRow.link() != 0 : dataRow;
+                    assert cctx.shared().database().checkpointLockIsHeldByThread();
 
-                    if (grp.sharedGroup() && dataRow.cacheId() == CU.UNDEFINED_CACHE_ID)
-                        dataRow.cacheId(cctx.cacheId());
+                    if (!grp.storeCacheIdInDataPage() && updateRow.cacheId() != CU.UNDEFINED_CACHE_ID) {
+                        updateRow.cacheId(CU.UNDEFINED_CACHE_ID);
 
-                    if (oldRow != null) {
-                        old = oldRow;
+                        rowStore.addRow(updateRow);
 
-                        dataTree.putx(dataRow);
+                        updateRow.cacheId(cctx.cacheId());
                     }
                     else
-                        old = dataTree.put(dataRow);
-                }
+                        rowStore.addRow(updateRow);
 
-                finishUpdate(cctx, dataRow, old);
+                    dataTree.putx(updateRow);
+
+                    incrementSize(cctx.cacheId());
+
+                    if (cctx.queries().enabled())
+                        cctx.queries().store(updateRow, null, true);
+
+                    return true;
+                }
             }
             finally {
                 busyLock.leaveBusy();
             }
-        }
 
-        /**
-         * @param cctx Cache context.
-         * @param newRow New row.
-         * @param oldRow Old row if available.
-         * @throws IgniteCheckedException If failed.
-         */
-        private void finishUpdate(GridCacheContext cctx, CacheDataRow newRow, @Nullable CacheDataRow oldRow)
-            throws IgniteCheckedException {
-            if (oldRow == null)
-                incrementSize(cctx.cacheId());
+            return false;
+        }
 
-            KeyCacheObject key = newRow.key();
+        /** {@inheritDoc} */
+        @Override public boolean mvccInitialValueIfAbsent(
+            GridCacheContext cctx,
+            KeyCacheObject key,
+            @Nullable CacheObject val,
+            GridCacheVersion ver,
+            long expireTime,
+            MvccVersion mvccVer,
+            MvccVersion newMvccVer,
+            byte txState,
+            byte newTxState)
+            throws IgniteCheckedException
+        {
+            assert mvccVer != null;
 
-            long expireTime = newRow.expireTime();
+            if (!busyLock.enterBusy())
+                throw new NodeStoppingException("Operation has been cancelled (node is stopping).");
 
-            GridCacheQueryManager qryMgr = cctx.queries();
+            try {
+                CacheObjectContext coCtx = cctx.cacheObjectContext();
 
-            int cacheId = grp.sharedGroup() ? cctx.cacheId() : CU.UNDEFINED_CACHE_ID;
+                // Make sure value bytes initialized.
+                key.valueBytes(coCtx);
 
-            if (qryMgr.enabled())
-                qryMgr.store(newRow, oldRow, true);
+                if (val != null)
+                    val.valueBytes(coCtx);
 
-            if (oldRow != null) {
-                assert oldRow.link() != 0 : oldRow;
+                assert cctx.shared().database().checkpointLockIsHeldByThread();
 
-                if (pendingTree() != null && oldRow.expireTime() != 0)
-                    pendingTree().removex(new PendingRow(cacheId, oldRow.expireTime(), oldRow.link()));
+                MvccPutIfAbsentClosure clo = new MvccPutIfAbsentClosure(key,
+                    val,
+                    ver,
+                    partId,
+                    expireTime,
+                    cctx.cacheId(),
+                    mvccVer,
+                    newMvccVer,
+                    txState,
+                    newTxState);
 
-                if (newRow.link() != oldRow.link())
-                    rowStore.removeRow(oldRow.link());
-            }
+                dataTree.invoke(clo, CacheDataRowAdapter.RowData.LINK_ONLY, clo);
 
-            if (pendingTree() != null && expireTime != 0) {
-                pendingTree().putx(new PendingRow(cacheId, expireTime, newRow.link()));
+                if (clo.operationType() == PUT)
+                    finishUpdate(cctx, clo, null);
 
-                hasPendingEntries = true;
+                return clo.operationType() == PUT;
+            }
+            finally {
+                busyLock.leaveBusy();
             }
-
-            updateIgfsMetrics(cctx, key, (oldRow != null ? oldRow.value() : null), newRow.value());
         }
 
         /** {@inheritDoc} */
-        @Override public void remove(GridCacheContext cctx, KeyCacheObject key, int partId) throws IgniteCheckedException {
+        @Override public boolean mvccUpdateRowWithPreloadInfo(
+            GridCacheContext cctx,
+            KeyCacheObject key,
+            @Nullable CacheObject val,
+            GridCacheVersion ver,
+            long expireTime,
+            MvccVersion mvccVer,
+            MvccVersion newMvccVer,
+            byte mvccTxState,
+            byte newMvccTxState) throws IgniteCheckedException {
             if (!busyLock.enterBusy())
                 throw new NodeStoppingException("Operation has been cancelled (node is stopping).");
 
             try {
-                int cacheId = grp.sharedGroup() ? cctx.cacheId() : CU.UNDEFINED_CACHE_ID;
+                CacheObjectContext coCtx = cctx.cacheObjectContext();
 
-                assert cctx.shared().database().checkpointLockIsHeldByThread();
+                // Make sure value bytes initialized.
+                key.valueBytes(coCtx);
 
-                CacheDataRow oldRow = dataTree.remove(new SearchRow(cacheId, key));
+                if (val != null)
+                    val.valueBytes(coCtx);
 
-                finishRemove(cctx, key, oldRow);
+                assert cctx.shared().database().checkpointLockIsHeldByThread();
+
+                MvccUpdateRowWithPreloadInfoClosure clo = new MvccUpdateRowWithPreloadInfoClosure(cctx,
+                    key,
+                    val,
+                    ver,
+                    expireTime,
+                    mvccVer,
+                    newMvccVer,
+                    mvccTxState,
+                    newMvccTxState);
+
+                invoke0(cctx, clo, clo);
             }
             finally {
                 busyLock.leaveBusy();
             }
+
+            return true;
         }
 
-        /**
-         * @param cctx Cache context.
-         * @param key Key.
-         * @param oldRow Removed row.
-         * @throws IgniteCheckedException If failed.
-         */
-        private void finishRemove(GridCacheContext cctx, KeyCacheObject key, @Nullable CacheDataRow oldRow) throws IgniteCheckedException {
-            if (oldRow != null) {
+        /** {@inheritDoc} */
+        @Override public MvccUpdateResult mvccUpdate(
+            GridCacheContext cctx,
+            KeyCacheObject key,
+            CacheObject val,
+            GridCacheVersion ver,
+            long expireTime,
+            MvccSnapshot mvccSnapshot,
+            boolean primary,
+            boolean needHistory,
+            boolean noCreate) throws IgniteCheckedException {
+            assert mvccSnapshot != null;
+            assert primary || !needHistory;
+
+            if (!busyLock.enterBusy())
+                throw new NodeStoppingException("Operation has been cancelled (node is stopping).");
+
+            try {
                 int cacheId = grp.sharedGroup() ? cctx.cacheId() : CU.UNDEFINED_CACHE_ID;
 
-                assert oldRow.link() != 0 : oldRow;
-                assert cacheId == CU.UNDEFINED_CACHE_ID || oldRow.cacheId() == cacheId :
-                    "Incorrect cache ID [expected=" + cacheId + ", actual=" + oldRow.cacheId() + "].";
+                CacheObjectContext coCtx = cctx.cacheObjectContext();
 
-                if (pendingTree() != null && oldRow.expireTime() != 0)
-                    pendingTree().removex(new PendingRow(cacheId, oldRow.expireTime(), oldRow.link()));
+                // Make sure value bytes initialized.
+                key.valueBytes(coCtx);
+                val.valueBytes(coCtx);
 
-                decrementSize(cctx.cacheId());
-            }
+                MvccUpdateDataRow updateRow = new MvccUpdateDataRow(
+                    cctx,
+                    key,
+                    val,
+                    ver,
+                    partId,
+                    expireTime,
+                    mvccSnapshot,
+                    null,
+                    primary,
+                    false,
+                    needHistory,
+                    // we follow fast update visit flow here if row cannot be created by current operation
+                    noCreate);
 
-            GridCacheQueryManager qryMgr = cctx.queries();
+                assert cctx.shared().database().checkpointLockIsHeldByThread();
 
-            if (qryMgr.enabled())
-                qryMgr.remove(key, oldRow);
+                dataTree.visit(new MvccMaxSearchRow(cacheId, key), new MvccMinSearchRow(cacheId, key), updateRow);
+
+                ResultType res = updateRow.resultType();
+
+                if (res == ResultType.LOCKED // cannot update locked
+                    || res == ResultType.VERSION_MISMATCH) // cannot update on write conflict
+                    return updateRow;
+                else if (res == ResultType.VERSION_FOUND) {
+                    // Do nothing, except cleaning up not needed versions
+                    cleanup(cctx, updateRow.cleanupRows());
+
+                    return updateRow;
+                }
+                else if (res == ResultType.PREV_NULL && noCreate) {
+                    cleanup(cctx, updateRow.cleanupRows());
+
+                    return updateRow;
+                }
+
+                CacheDataRow oldRow = null;
+
+                if (res == ResultType.PREV_NOT_NULL) {
+                    oldRow = updateRow.oldRow();
+
+                    assert oldRow != null && oldRow.link() != 0 : oldRow;
+
+                    oldRow.key(key);
+
+                    rowStore.updateDataRow(oldRow.link(), mvccUpdateMarker, mvccSnapshot);
+                }
+                else
+                    assert res == ResultType.PREV_NULL;
+
+                if (!grp.storeCacheIdInDataPage() && updateRow.cacheId() != CU.UNDEFINED_CACHE_ID) {
+                    updateRow.cacheId(CU.UNDEFINED_CACHE_ID);
+
+                    rowStore.addRow(updateRow);
+
+                    updateRow.cacheId(cctx.cacheId());
+                }
+                else
+                    rowStore.addRow(updateRow);
+
+                if (needHistory) {
+                    assert updateRow.link() != 0;
+
+                    updateRow.history().add(new MvccLinkAwareSearchRow(cacheId,
+                        key,
+                        updateRow.mvccCoordinatorVersion(),
+                        updateRow.mvccCounter(),
+                        updateRow.mvccOperationCounter(),
+                        updateRow.link()));
+                }
+
+                boolean old = dataTree.putx(updateRow);
+
+                assert !old;
+
+                incrementSize(cctx.cacheId());
+
+                GridCacheQueryManager qryMgr = cctx.queries();
+
+                if (qryMgr.enabled())
+                    qryMgr.store(updateRow, null, true);
+
+                updatePendingEntries(cctx, updateRow, oldRow);
+
+                cleanup(cctx, updateRow.cleanupRows());
+
+                return updateRow;
+            }
+            finally {
+                busyLock.leaveBusy();
+            }
+        }
+
+        /** {@inheritDoc} */
+        @Override public MvccUpdateResult mvccRemove(GridCacheContext cctx,
+            KeyCacheObject key,
+            MvccSnapshot mvccSnapshot,
+            boolean primary,
+            boolean needHistory) throws IgniteCheckedException {
+            assert mvccSnapshot != null;
+            assert primary || mvccSnapshot.activeTransactions().size() == 0 : mvccSnapshot;
+            assert primary || !needHistory;
+
+            if (!busyLock.enterBusy())
+                throw new NodeStoppingException("Operation has been cancelled (node is stopping).");
+
+            try {
+                int cacheId = grp.sharedGroup() ? cctx.cacheId() : CU.UNDEFINED_CACHE_ID;
+
+                CacheObjectContext coCtx = cctx.cacheObjectContext();
+
+                // Make sure value bytes initialized.
+                key.valueBytes(coCtx);
+
+                MvccUpdateDataRow updateRow = new MvccUpdateDataRow(
+                    cctx,
+                    key,
+                    null,
+                    null,
+                    partId,
+                    0,
+                    mvccSnapshot,
+                    null,
+                    primary,
+                    false,
+                    needHistory,
+                    true);
+
+                assert cctx.shared().database().checkpointLockIsHeldByThread();
+
+                dataTree.visit(new MvccMaxSearchRow(cacheId, key), new MvccMinSearchRow(cacheId, key), updateRow);
+
+                ResultType res = updateRow.resultType();
+
+                if (res == ResultType.LOCKED // cannot update locked
+                    || res == ResultType.VERSION_MISMATCH) // cannot update on write conflict
+                    return updateRow;
+                else if (res == ResultType.VERSION_FOUND) {
+                    // Do nothing, except cleaning up not needed versions
+                    cleanup(cctx, updateRow.cleanupRows());
+
+                    return updateRow;
+                }
+                else if (res == ResultType.PREV_NOT_NULL) {
+                    CacheDataRow oldRow = updateRow.oldRow();
+
+                    assert oldRow != null && oldRow.link() != 0 : oldRow;
+
+                    rowStore.updateDataRow(oldRow.link(), mvccUpdateMarker, mvccSnapshot);
+
+                    clearPendingEntries(cctx, oldRow);
+                }
+
+                cleanup(cctx, updateRow.cleanupRows());
+
+                return updateRow;
+            }
+            finally {
+                busyLock.leaveBusy();
+            }
+        }
+
+        /** {@inheritDoc} */
+        @Override public MvccUpdateResult mvccLock(GridCacheContext cctx, KeyCacheObject key,
+            MvccSnapshot mvccSnapshot) throws IgniteCheckedException {
+            assert mvccSnapshot != null;
+
+            if (!busyLock.enterBusy())
+                throw new NodeStoppingException("Operation has been cancelled (node is stopping).");
+
+            try {
+                int cacheId = grp.sharedGroup() ? cctx.cacheId() : CU.UNDEFINED_CACHE_ID;
+
+                CacheObjectContext coCtx = cctx.cacheObjectContext();
+
+                // Make sure value bytes initialized.
+                key.valueBytes(coCtx);
+
+                MvccUpdateDataRow updateRow = new MvccUpdateDataRow(
+                    cctx,
+                    key,
+                    null,
+                    null,
+                    partId,
+                    0,
+                    mvccSnapshot,
+                    null,
+                    true,
+                    true,
+                    false,
+                    false);
+
+                assert cctx.shared().database().checkpointLockIsHeldByThread();
+
+                dataTree.visit(new MvccMaxSearchRow(cacheId, key), new MvccMinSearchRow(cacheId, key), updateRow);
+
+                ResultType res = updateRow.resultType();
+
+                // cannot update locked, cannot update on write conflict
+                if (res == ResultType.LOCKED || res == ResultType.VERSION_MISMATCH)
+                    return updateRow;
+
+                // Do nothing, except cleaning up not needed versions
+                cleanup(cctx, updateRow.cleanupRows());
+
+                return updateRow;
+            }
+            finally {
+                busyLock.leaveBusy();
+            }
+        }
+
+        /** {@inheritDoc} */
+        @Override public GridLongList mvccUpdateNative(
+            GridCacheContext cctx,
+            boolean primary,
+            KeyCacheObject key,
+            CacheObject val,
+            GridCacheVersion ver,
+            long expireTime,
+            MvccSnapshot mvccSnapshot) throws IgniteCheckedException {
+            assert mvccSnapshot != null;
+            assert primary || mvccSnapshot.activeTransactions().size() == 0 : mvccSnapshot;
+
+            if (!busyLock.enterBusy())
+                throw new NodeStoppingException("Operation has been cancelled (node is stopping).");
+
+            try {
+                int cacheId = grp.sharedGroup() ? cctx.cacheId() : CU.UNDEFINED_CACHE_ID;
+
+                CacheObjectContext coCtx = cctx.cacheObjectContext();
+
+                // Make sure value bytes initialized.
+                key.valueBytes(coCtx);
+                val.valueBytes(coCtx);
+
+                MvccUpdateDataRowNative updateRow = new MvccUpdateDataRowNative(
+                    key,
+                    val,
+                    ver,
+                    expireTime,
+                    mvccSnapshot,
+                    null,
+                    partId,
+                    cctx);
+
+                assert cctx.shared().database().checkpointLockIsHeldByThread();
+
+                dataTree.iterate(new MvccMaxSearchRow(cacheId, key), new MvccMinSearchRow(cacheId, key), updateRow);
+
+                ResultType res = updateRow.resultType();
+
+                if (res == ResultType.VERSION_FOUND) {
+                    // Do nothing, except cleaning up not needed versions
+                    cleanup(cctx, updateRow.cleanupRows());
+
+                    return null;
+                }
+
+                CacheDataRow oldRow = null;
+
+                if (res == ResultType.PREV_NOT_NULL) {
+                    oldRow = updateRow.oldRow();
+
+                    assert oldRow != null && oldRow.link() != 0 : oldRow;
+
+                    oldRow.key(key);
+
+                    rowStore.updateDataRow(oldRow.link(), mvccUpdateMarker, mvccSnapshot);
+                }
+                else
+                    assert res == ResultType.PREV_NULL;
+
+                if (!grp.storeCacheIdInDataPage() && updateRow.cacheId() != CU.UNDEFINED_CACHE_ID) {
+                    updateRow.cacheId(CU.UNDEFINED_CACHE_ID);
+
+                    rowStore.addRow(updateRow);
+
+                    updateRow.cacheId(cctx.cacheId());
+                }
+                else
+                    rowStore.addRow(updateRow);
+
+                boolean old = dataTree.putx(updateRow);
+
+                assert !old;
+
+                incrementSize(cctx.cacheId());
+
+                GridCacheQueryManager qryMgr = cctx.queries();
+
+                if (qryMgr.enabled())
+                    qryMgr.store(updateRow, null, true);
+
+                updatePendingEntries(cctx, updateRow, oldRow);
+
+                cleanup(cctx, updateRow.cleanupRows());
+
+                return updateRow.activeTransactions();
+            }
+            finally {
+                busyLock.leaveBusy();
+            }
+        }
+
+        /** {@inheritDoc} */
+        @Override public GridLongList mvccRemoveNative(GridCacheContext cctx,
+            boolean primary,
+            KeyCacheObject key,
+            MvccSnapshot mvccSnapshot) throws IgniteCheckedException {
+            assert mvccSnapshot != null;
+            assert primary || mvccSnapshot.activeTransactions().size() == 0 : mvccSnapshot;
+
+            if (!busyLock.enterBusy())
+                throw new NodeStoppingException("Operation has been cancelled (node is stopping).");
+
+            try {
+                int cacheId = grp.sharedGroup() ? cctx.cacheId() : CU.UNDEFINED_CACHE_ID;
+
+                CacheObjectContext coCtx = cctx.cacheObjectContext();
+
+                // Make sure value bytes initialized.
+                key.valueBytes(coCtx);
+
+                MvccUpdateDataRowNative updateRow = new MvccUpdateDataRowNative(
+                    key,
+                    null,
+                    null,
+                    0,
+                    mvccSnapshot,
+                    null,
+                    partId,
+                    cctx);
+
+                assert cctx.shared().database().checkpointLockIsHeldByThread();
+
+                dataTree.iterate(new MvccMaxSearchRow(cacheId, key), new MvccMinSearchRow(cacheId, key), updateRow);
+
+                ResultType res = updateRow.resultType();
+
+                if (res == ResultType.VERSION_FOUND) {
+                    assert !primary : updateRow;
+
+                    // Do nothing, except cleaning up not needed versions
+                    cleanup(cctx, updateRow.cleanupRows());
+
+                    return null;
+                }
+                else if (res == ResultType.PREV_NOT_NULL) {
+                    CacheDataRow oldRow = updateRow.oldRow();
+
+                    assert oldRow != null && oldRow.link() != 0 : oldRow;
+
+                    rowStore.updateDataRow(oldRow.link(), mvccUpdateMarker, mvccSnapshot);
+
+                    clearPendingEntries(cctx, oldRow);
+                }
+
+                cleanup(cctx, updateRow.cleanupRows());
+
+                return updateRow.activeTransactions();
+            }
+            finally {
+                busyLock.leaveBusy();
+            }
+        }
+
+        /** {@inheritDoc} */
+        @Override public void mvccRemoveAll(GridCacheContext cctx, KeyCacheObject key) throws IgniteCheckedException {
+            key.valueBytes(cctx.cacheObjectContext());
+
+            int cacheId = grp.sharedGroup() ? cctx.cacheId() : CU.UNDEFINED_CACHE_ID;
+
+            boolean cleanup = cctx.queries().enabled() || hasPendingEntries;
+
+            assert cctx.shared().database().checkpointLockIsHeldByThread();
+
+            GridCursor<CacheDataRow> cur = dataTree.find(
+                new MvccMaxSearchRow(cacheId, key),
+                new MvccMinSearchRow(cacheId, key),
+                cleanup ? CacheDataRowAdapter.RowData.NO_KEY : CacheDataRowAdapter.RowData.LINK_ONLY
+            );
+
+            boolean first = true;
+
+            while (cur.next()) {
+                CacheDataRow row = cur.get();
+
+                row.key(key);
+
+                assert row.link() != 0 : row;
+
+                boolean rmvd = dataTree.removex(row);
+
+                assert rmvd : row;
+
+                if (cleanup) {
+                    if (cctx.queries().enabled())
+                        cctx.queries().remove(key, row);
+
+                    if (first)
+                        clearPendingEntries(cctx, row);
+                }
+
+                rowStore.removeRow(row.link());
+
+                decrementSize(cctx.cacheId());
+
+                if (first)
+                    first = false;
+            }
+        }
+
+        /** {@inheritDoc} */
+        @Override public int cleanup(GridCacheContext cctx, @Nullable List<MvccLinkAwareSearchRow> cleanupRows)
+            throws IgniteCheckedException {
+            int res = 0;
+
+            if (cleanupRows != null) {
+                GridCacheQueryManager qryMgr = cctx.queries();
+
+                for (int i = 0; i < cleanupRows.size(); i++) {
+                    MvccLinkAwareSearchRow cleanupRow = cleanupRows.get(i);
+
+                    assert cleanupRow.link() != 0 : cleanupRow;
+
+                    assert cctx.shared().database().checkpointLockIsHeldByThread();
+
+                    CacheDataRow oldRow = dataTree.remove(cleanupRow);
+
+                    if (oldRow != null) { // oldRow == null means it was cleaned by another cleanup process.
+                        assert oldRow.mvccCounter() == cleanupRow.mvccCounter();
+
+                        if (qryMgr.enabled())
+                            qryMgr.remove(oldRow.key(), oldRow);
+
+                        clearPendingEntries(cctx, oldRow);
+
+                        rowStore.removeRow(cleanupRow.link());
+
+                        decrementSize(cctx.cacheId());
+
+                        res++;
+                    }
+                }
+            }
+
+            return res;
+        }
+
+        /** {@inheritDoc} */
+        @Override public void updateTxState(GridCacheContext cctx, CacheSearchRow row)
+            throws IgniteCheckedException {
+            assert grp.mvccEnabled();
+            assert mvccVersionIsValid(row.mvccCoordinatorVersion(), row.mvccCounter(), row.mvccOperationCounter()) : row;
+
+            // Need an extra lookup because the row may be already cleaned by another thread.
+            CacheDataRow row0 = dataTree.findOne(row, CacheDataRowAdapter.RowData.LINK_ONLY);
+
+            if (row0 != null)
+                rowStore.updateDataRow(row0.link(), mvccUpdateTxStateHint, null);
+        }
+
+        /** {@inheritDoc} */
+        @Override public void update(GridCacheContext cctx,
+            KeyCacheObject key,
+            CacheObject val,
+            GridCacheVersion ver,
+            long expireTime,
+            @Nullable CacheDataRow oldRow
+        ) throws IgniteCheckedException {
+            assert oldRow == null || oldRow.link() != 0L : oldRow;
+
+            if (!busyLock.enterBusy())
+                throw new NodeStoppingException("Operation has been cancelled (node is stopping).");
+
+            try {
+                int cacheId = grp.storeCacheIdInDataPage() ? cctx.cacheId() : CU.UNDEFINED_CACHE_ID;
+
+                assert oldRow == null || oldRow.cacheId() == cacheId : oldRow;
+
+                if (key.partition() == -1)
+                    key.partition(partId);
+
+                DataRow dataRow = new DataRow(key, val, ver, partId, expireTime, cacheId);
+
+                CacheObjectContext coCtx = cctx.cacheObjectContext();
+
+                // Make sure value bytes initialized.
+                key.valueBytes(coCtx);
+                val.valueBytes(coCtx);
+
+                CacheDataRow old;
+
+                assert cctx.shared().database().checkpointLockIsHeldByThread();
+
+                if (canUpdateOldRow(cctx, oldRow, dataRow) && rowStore.updateRow(oldRow.link(), dataRow)) {
+                    old = oldRow;
+
+                    dataRow.link(oldRow.link());
+                }
+                else {
+                    rowStore.addRow(dataRow);
+
+                    assert dataRow.link() != 0 : dataRow;
+
+                    if (grp.sharedGroup() && dataRow.cacheId() == CU.UNDEFINED_CACHE_ID)
+                        dataRow.cacheId(cctx.cacheId());
+
+                    if (oldRow != null) {
+                        old = oldRow;
+
+                        dataTree.putx(dataRow);
+                    }
+                    else
+                        old = dataTree.put(dataRow);
+                }
+
+                finishUpdate(cctx, dataRow, old);
+            }
+            finally {
+                busyLock.leaveBusy();
+            }
+        }
+
+        /**
+         * @param cctx Cache context.
+         * @param newRow New row.
+         * @param oldRow Old row if available.
+         * @throws IgniteCheckedException If failed.
+         */
+        private void finishUpdate(GridCacheContext cctx, CacheDataRow newRow, @Nullable CacheDataRow oldRow)
+            throws IgniteCheckedException {
+            if (oldRow == null)
+                incrementSize(cctx.cacheId());
+
+            KeyCacheObject key = newRow.key();
+
+            GridCacheQueryManager qryMgr = cctx.queries();
+
+            if (qryMgr.enabled())
+                qryMgr.store(newRow, oldRow, true);
+
+            updatePendingEntries(cctx, newRow, oldRow);
+
+            if (oldRow != null) {
+                assert oldRow.link() != 0 : oldRow;
+
+                if (newRow.link() != oldRow.link())
+                    rowStore.removeRow(oldRow.link());
+            }
+
+            updateIgfsMetrics(cctx, key, (oldRow != null ? oldRow.value() : null), newRow.value());
+        }
+
+        /**
+         * @param cctx Cache context.
+         * @param newRow New row.
+         * @param oldRow Old row.
+         * @throws IgniteCheckedException If failed.
+         */
+        private void updatePendingEntries(GridCacheContext cctx, CacheDataRow newRow, @Nullable CacheDataRow oldRow)
+            throws IgniteCheckedException {
+            long expireTime = newRow.expireTime();
+
+            int cacheId = grp.sharedGroup() ? cctx.cacheId() : CU.UNDEFINED_CACHE_ID;
+
+            if (oldRow != null) {
+                assert oldRow.link() != 0 : oldRow;
+
+                if (pendingTree() != null && oldRow.expireTime() != 0)
+                    pendingTree().removex(new PendingRow(cacheId, oldRow.expireTime(), oldRow.link()));
+            }
+
+            if (pendingTree() != null && expireTime != 0) {
+                pendingTree().putx(new PendingRow(cacheId, expireTime, newRow.link()));
+
+                hasPendingEntries = true;
+            }
+        }
+
+        /** {@inheritDoc} */
+        @Override public void remove(GridCacheContext cctx, KeyCacheObject key,
+            int partId) throws IgniteCheckedException {
+            if (!busyLock.enterBusy())
+                throw new NodeStoppingException("Operation has been cancelled (node is stopping).");
+
+            try {
+                int cacheId = grp.sharedGroup() ? cctx.cacheId() : CU.UNDEFINED_CACHE_ID;
+
+                assert cctx.shared().database().checkpointLockIsHeldByThread();
+
+                CacheDataRow oldRow = dataTree.remove(new SearchRow(cacheId, key));
+
+                finishRemove(cctx, key, oldRow);
+            }
+            finally {
+                busyLock.leaveBusy();
+            }
+        }
+
+        /**
+         * @param cctx Cache context.
+         * @param key Key.
+         * @param oldRow Removed row.
+         * @throws IgniteCheckedException If failed.
+         */
+        private void finishRemove(GridCacheContext cctx, KeyCacheObject key, @Nullable CacheDataRow oldRow) throws IgniteCheckedException {
+            if (oldRow != null) {
+                clearPendingEntries(cctx, oldRow);
+
+                decrementSize(cctx.cacheId());
+            }
+
+            GridCacheQueryManager qryMgr = cctx.queries();
+
+            if (qryMgr.enabled())
+                qryMgr.remove(key, oldRow);
 
             if (oldRow != null)
                 rowStore.removeRow(oldRow.link());
@@ -1521,21 +2486,133 @@ private void finishRemove(GridCacheContext cctx, KeyCacheObject key, @Nullable C
             updateIgfsMetrics(cctx, key, (oldRow != null ? oldRow.value() : null), null);
         }
 
+        /**
+         * @param cctx Cache context.
+         * @param oldRow Old row.
+         * @throws IgniteCheckedException
+         */
+        private void clearPendingEntries(GridCacheContext cctx, CacheDataRow oldRow)
+            throws IgniteCheckedException {
+            int cacheId = grp.sharedGroup() ? cctx.cacheId() : CU.UNDEFINED_CACHE_ID;
+
+            assert oldRow.link() != 0 : oldRow;
+            assert cacheId == CU.UNDEFINED_CACHE_ID || oldRow.cacheId() == cacheId :
+                "Incorrect cache ID [expected=" + cacheId + ", actual=" + oldRow.cacheId() + "].";
+
+            if (pendingTree() != null && oldRow.expireTime() != 0)
+                pendingTree().removex(new PendingRow(cacheId, oldRow.expireTime(), oldRow.link()));
+        }
+
         /** {@inheritDoc} */
         @Override public CacheDataRow find(GridCacheContext cctx, KeyCacheObject key) throws IgniteCheckedException {
             key.valueBytes(cctx.cacheObjectContext());
 
             int cacheId = grp.sharedGroup() ? cctx.cacheId() : CU.UNDEFINED_CACHE_ID;
 
-            CacheDataRow row = dataTree.findOne(new SearchRow(cacheId, key), CacheDataRowAdapter.RowData.NO_KEY);
+            CacheDataRow row;
+
+            if (grp.mvccEnabled()) {
+                MvccFirstRowTreeClosure clo = new MvccFirstRowTreeClosure(cctx);
+
+                dataTree.iterate(
+                    new MvccMaxSearchRow(cacheId, key),
+                    new MvccMinSearchRow(cacheId, key),
+                    clo
+                );
+
+                row = clo.row();
+            }
+            else
+                row = dataTree.findOne(new SearchRow(cacheId, key), CacheDataRowAdapter.RowData.NO_KEY);
+
+            afterRowFound(row, key);
+
+            return row;
+        }
+
+        /** {@inheritDoc} */
+        @Override public List<IgniteBiTuple<Object, MvccVersion>> mvccFindAllVersions(
+            GridCacheContext cctx,
+            KeyCacheObject key)
+            throws IgniteCheckedException
+        {
+            assert grp.mvccEnabled();
+
+            // Note: this method is intended for testing only.
+
+            key.valueBytes(cctx.cacheObjectContext());
+
+            int cacheId = grp.sharedGroup() ? cctx.cacheId() : CU.UNDEFINED_CACHE_ID;
+
+            GridCursor<CacheDataRow> cur = dataTree.find(
+                new MvccMaxSearchRow(cacheId, key),
+                new MvccMinSearchRow(cacheId, key)
+            );
+
+            List<IgniteBiTuple<Object, MvccVersion>> res = new ArrayList<>();
+
+            long crd = MVCC_CRD_COUNTER_NA;
+            long cntr = MVCC_COUNTER_NA;
+            int opCntr = MVCC_OP_COUNTER_NA;
+
+            while (cur.next()) {
+                CacheDataRow row = cur.get();
+
+                if (compareNewVersion(row, crd, cntr, opCntr) != 0) // deleted row
+                    res.add(F.t(null, row.newMvccVersion()));
+
+                res.add(F.t(row.value().value(cctx.cacheObjectContext(), false), row.mvccVersion()));
+
+                crd = row.mvccCoordinatorVersion();
+                cntr = row.mvccCounter();
+                opCntr = row.mvccOperationCounter();
+            }
+
+            return res;
+        }
+
+        /** {@inheritDoc} */
+        @Override public GridCursor<CacheDataRow> mvccAllVersionsCursor(GridCacheContext cctx, KeyCacheObject key, Object x)
+            throws IgniteCheckedException {
+            int cacheId = cctx.cacheId();
+
+            return dataTree.find(new MvccMaxSearchRow(cacheId, key), new MvccMinSearchRow(cacheId, key), x);
+        }
+
+        /** {@inheritDoc} */
+        @Override public CacheDataRow mvccFind(GridCacheContext cctx,
+            KeyCacheObject key,
+            MvccSnapshot snapshot) throws IgniteCheckedException {
+            key.valueBytes(cctx.cacheObjectContext());
+
+            int cacheId = grp.sharedGroup() ? cctx.cacheId() : CU.UNDEFINED_CACHE_ID;
+
+            MvccSnapshotSearchRow clo = new MvccSnapshotSearchRow(cctx, key, snapshot);
+
+            dataTree.iterate(
+                clo,
+                new MvccMinSearchRow(cacheId, key),
+                clo
+            );
+
+            CacheDataRow row = clo.row();
+
+            afterRowFound(row, key);
+
+            return row;
+        }
 
+        /**
+         * @param row Row.
+         * @param key Key.
+         * @throws IgniteCheckedException If failed.
+         */
+        private void afterRowFound(@Nullable CacheDataRow row, KeyCacheObject key) throws IgniteCheckedException {
             if (row != null) {
                 row.key(key);
 
                 grp.dataRegion().evictionTracker().touchPage(row.link());
             }
-
-            return row;
         }
 
         /** {@inheritDoc} */
@@ -1543,11 +2620,36 @@ private void finishRemove(GridCacheContext cctx, KeyCacheObject key, @Nullable C
             return dataTree.find(null, null);
         }
 
+        /** {@inheritDoc} */
+        @Override public GridCursor<? extends CacheDataRow> cursor(Object x) throws IgniteCheckedException {
+            return dataTree.find(null, null, x);
+        }
+
+        /** {@inheritDoc} */
+        @Override public GridCursor<? extends CacheDataRow> cursor(MvccSnapshot mvccSnapshot)
+            throws IgniteCheckedException {
+
+            if (mvccSnapshot != null) {
+                assert grp.mvccEnabled();
+
+                return dataTree.find(null, null,
+                    new MvccFirstVisibleRowTreeClosure(grp.singleCacheContext(), mvccSnapshot), null);
+            }
+
+            return dataTree.find(null, null);
+        }
+
         /** {@inheritDoc} */
         @Override public GridCursor<? extends CacheDataRow> cursor(int cacheId) throws IgniteCheckedException {
             return cursor(cacheId, null, null);
         }
 
+        /** {@inheritDoc} */
+        @Override public GridCursor<? extends CacheDataRow> cursor(int cacheId,
+            MvccSnapshot mvccSnapshot) throws IgniteCheckedException {
+            return cursor(cacheId, null, null, null, mvccSnapshot);
+        }
+
         /** {@inheritDoc} */
         @Override public GridCursor<? extends CacheDataRow> cursor(int cacheId, KeyCacheObject lower,
             KeyCacheObject upper) throws IgniteCheckedException {
@@ -1557,6 +2659,12 @@ private void finishRemove(GridCacheContext cctx, KeyCacheObject key, @Nullable C
         /** {@inheritDoc} */
         @Override public GridCursor<? extends CacheDataRow> cursor(int cacheId, KeyCacheObject lower,
             KeyCacheObject upper, Object x) throws IgniteCheckedException {
+            return cursor(cacheId, lower, upper, null, null);
+        }
+
+        /** {@inheritDoc} */
+        @Override public GridCursor<? extends CacheDataRow> cursor(int cacheId, KeyCacheObject lower,
+            KeyCacheObject upper, Object x, MvccSnapshot snapshot) throws IgniteCheckedException {
             SearchRow lowerRow;
             SearchRow upperRow;
 
@@ -1571,6 +2679,15 @@ private void finishRemove(GridCacheContext cctx, KeyCacheObject key, @Nullable C
                 upperRow = upper != null ? new SearchRow(CU.UNDEFINED_CACHE_ID, upper) : null;
             }
 
+            if (snapshot != null) {
+                assert grp.mvccEnabled();
+
+                GridCacheContext cctx =
+                    grp.sharedGroup() ? grp.shared().cacheContext(cacheId) : grp.singleCacheContext();
+
+                return dataTree.find(lowerRow, upperRow, new MvccFirstVisibleRowTreeClosure(cctx, snapshot), x);
+            }
+
             return dataTree.find(lowerRow, upperRow, x);
         }
 
@@ -1645,13 +2762,6 @@ private void finishRemove(GridCacheContext cctx, KeyCacheObject key, @Nullable C
             return rowStore;
         }
 
-        /**
-         * @return Next update index.
-         */
-        @Override public long nextUpdateCounter() {
-            return cntr.incrementAndGet();
-        }
-
         /** {@inheritDoc} */
         @Override public long initialUpdateCounter() {
             return initCntr;
@@ -1739,5 +2849,336 @@ private int valueLength(GridCacheContext cctx, @Nullable CacheObject val) {
             else
                 return 0;
         }
+
+        /** */
+        private final class MvccFirstVisibleRowTreeClosure implements MvccTreeClosure {
+            /** */
+            private final GridCacheContext cctx;
+
+            /** */
+            private final MvccSnapshot snapshot;
+
+            /**
+             * @param cctx Cache context.
+             * @param snapshot MVCC snapshot.
+             */
+            MvccFirstVisibleRowTreeClosure(GridCacheContext cctx, MvccSnapshot snapshot) {
+                this.cctx = cctx;
+                this.snapshot = snapshot;
+            }
+
+            /** {@inheritDoc} */
+            @Override public boolean apply(BPlusTree<CacheSearchRow, CacheDataRow> tree, BPlusIO<CacheSearchRow> io,
+                long pageAddr, int idx) throws IgniteCheckedException {
+                RowLinkIO rowIo = (RowLinkIO)io;
+
+                long rowCrdVer = rowIo.getMvccCoordinatorVersion(pageAddr, idx);
+                long rowCntr = rowIo.getMvccCounter(pageAddr, idx);
+                int rowOpCntr = rowIo.getMvccOperationCounter(pageAddr, idx);
+
+                assert mvccVersionIsValid(rowCrdVer, rowCntr, rowOpCntr);
+
+                return isVisible(cctx, snapshot, rowCrdVer, rowCntr, rowOpCntr, rowIo.getLink(pageAddr, idx));
+            }
+        }
+
+        /**
+         * Put row if it doesn't exist yet.
+         */
+        private class MvccPutIfAbsentClosure extends MvccDataRow implements IgniteTree.InvokeClosure<CacheDataRow> {
+            /** */
+            private IgniteTree.OperationType op;
+
+            /**
+             * @param key Key.
+             * @param val Value.
+             * @param ver Version.
+             * @param part Partition.
+             * @param expireTime Expire time.
+             * @param cacheId Cache ID.
+             * @param mvccVer Mvcc version.
+             * @param newMvccVer New mvcc version.
+             * @param txState Tx state hint for mvcc version.
+             * @param newTxState Tx state hint for new mvcc version.
+             */
+            MvccPutIfAbsentClosure(KeyCacheObject key,
+                CacheObject val,
+                GridCacheVersion ver,
+                int part,
+                long expireTime,
+                int cacheId,
+                MvccVersion mvccVer,
+                MvccVersion newMvccVer,
+                byte txState,
+                byte newTxState) {
+                super(key, val, ver, part, expireTime, cacheId, mvccVer, newMvccVer);
+
+                mvccTxState(txState);
+                newMvccTxState(newTxState);
+            }
+
+            /** {@inheritDoc} */
+            @Override public void call(@Nullable CacheDataRow old) throws IgniteCheckedException {
+                if (old == null) {
+                    op = PUT;
+
+                    int cacheId = cacheId();
+
+                    if (!grp.storeCacheIdInDataPage() && cacheId != CU.UNDEFINED_CACHE_ID)
+                        cacheId(CU.UNDEFINED_CACHE_ID);
+
+                    rowStore().addRow(this);
+
+                    cacheId(cacheId);
+                }
+                else
+                    op = NOOP;
+            }
+
+            /** {@inheritDoc} */
+            @Override public MvccDataRow newRow() {
+                return this;
+            }
+
+            /** {@inheritDoc} */
+            @Override public IgniteTree.OperationType operationType() {
+                return op;
+            }
+        }
+
+        /**
+         *
+         */
+        private class MvccUpdateRowWithPreloadInfoClosure extends MvccDataRow implements OffheapInvokeClosure {
+            /** */
+            private CacheDataRow oldRow;
+            /** */
+            private IgniteTree.OperationType op;
+
+            /**
+             * @param cctx Cache context.
+             * @param key Key.
+             * @param val Value.
+             * @param ver Version.
+             * @param expireTime Expire time.
+             * @param mvccVer Mvcc created version.
+             * @param newMvccVer Mvcc updated version.
+             * @param mvccTxState Mvcc Tx state hint.
+             * @param newMvccTxState New Mvcc Tx state hint.
+             */
+            MvccUpdateRowWithPreloadInfoClosure(GridCacheContext cctx,
+                KeyCacheObject key,
+                @Nullable CacheObject val,
+                GridCacheVersion ver,
+                long expireTime,
+                MvccVersion mvccVer,
+                MvccVersion newMvccVer,
+                byte mvccTxState,
+                byte newMvccTxState) {
+                super(key,
+                    val,
+                    ver,
+                    CacheDataStoreImpl.this.partId(),
+                    expireTime,
+                    cctx.cacheId(),
+                    mvccVer,
+                    newMvccVer);
+
+                mvccTxState(mvccTxState);
+                newMvccTxState(newMvccTxState);
+            }
+
+            /** {@inheritDoc} */
+            @Nullable @Override public CacheDataRow oldRow() {
+                return oldRow;
+            }
+
+            /** {@inheritDoc} */
+            @Override public void call(@Nullable CacheDataRow oldRow) throws IgniteCheckedException {
+                this.oldRow = oldRow;
+
+                if (oldRow == null) {
+                    op = PUT;
+
+                    int cacheId = cacheId();
+
+                    if (!grp.storeCacheIdInDataPage() && cacheId != CU.UNDEFINED_CACHE_ID)
+                        cacheId(CU.UNDEFINED_CACHE_ID);
+
+                    rowStore().addRow(this);
+
+                    cacheId(cacheId);
+                }
+                else {
+                    op = NOOP;
+
+                    if (oldRow.mvccTxState() != mvccTxState() ||
+                        oldRow.newMvccCoordinatorVersion() != newMvccCoordinatorVersion() ||
+                        oldRow.newMvccCounter() != newMvccCounter() ||
+                        oldRow.newMvccOperationCounter() != newMvccOperationCounter() ||
+                        oldRow.newMvccTxState() != newMvccTxState()) {
+
+                        rowStore().updateDataRow(oldRow.link(), mvccApplyChanges, this);
+                    }
+                }
+            }
+
+            /** {@inheritDoc} */
+            @Override public CacheDataRow newRow() {
+                return op == PUT ? this : null;
+            }
+
+            /** {@inheritDoc} */
+            @Override public IgniteTree.OperationType operationType() {
+                return op == null ? NOOP : op;
+            }
+        }
+    }
+
+    /**
+     * Mvcc remove handler.
+     */
+    private final class MvccMarkUpdatedHandler extends PageHandler<MvccVersion, Boolean> {
+        /** {@inheritDoc} */
+        @Override public Boolean run(int cacheId, long pageId, long page, long pageAddr, PageIO io, Boolean walPlc,
+            MvccVersion newVer, int itemId) throws IgniteCheckedException {
+            assert grp.mvccEnabled();
+
+            DataPageIO iox = (DataPageIO)io;
+
+            int offset = iox.getPayloadOffset(pageAddr, itemId, grp.dataRegion().pageMemory().pageSize(), MVCC_INFO_SIZE);
+
+            long newCrd = iox.newMvccCoordinator(pageAddr, offset);
+            long newCntr = iox.newMvccCounter(pageAddr, offset);
+            int newOpCntr = iox.newMvccOperationCounter(pageAddr, offset);
+
+            assert newCrd == MVCC_CRD_COUNTER_NA || state(grp, newCrd, newCntr, newOpCntr) == TxState.ABORTED;
+
+            iox.updateNewVersion(pageAddr, offset, newVer, TxState.NA);
+
+            if (isWalDeltaRecordNeeded(grp.dataRegion().pageMemory(), cacheId, pageId, page, ctx.wal(), walPlc))
+                ctx.wal().log(new DataPageMvccMarkUpdatedRecord(cacheId, pageId, itemId,
+                    newVer.coordinatorVersion(), newVer.counter(), newVer.operationCounter()));
+
+            return Boolean.TRUE;
+        }
+    }
+
+    /**
+     * Mvcc update operation counter hints handler.
+     */
+    private final class MvccUpdateTxStateHintHandler extends PageHandler<Void, Boolean> {
+        /** {@inheritDoc} */
+        @Override public Boolean run(int cacheId, long pageId, long page, long pageAddr, PageIO io,
+            Boolean walPlc, Void ignore,
+            int itemId) throws IgniteCheckedException {
+
+            DataPageIO iox = (DataPageIO)io;
+
+            int pageSize = grp.dataRegion().pageMemory().pageSize();
+            int offset = iox.getPayloadOffset(pageAddr, itemId, pageSize, MVCC_INFO_SIZE);
+
+            long crd = iox.mvccCoordinator(pageAddr, offset);
+            long cntr = iox.mvccCounter(pageAddr, offset);
+            int opCntr = iox.mvccOperationCounter(pageAddr, offset);
+            byte txState = (byte)(opCntr >>> MVCC_HINTS_BIT_OFF);
+
+            if (txState == TxState.NA) {
+                byte state = state(grp, crd, cntr, opCntr);
+
+                if (state == TxState.COMMITTED || state == TxState.ABORTED) {
+                    iox.mvccOperationCounter(pageAddr, offset, opCntr | (state << MVCC_HINTS_BIT_OFF));
+
+                    if (isWalDeltaRecordNeeded(grp.dataRegion().pageMemory(), cacheId, pageId, page, ctx.wal(), walPlc))
+                        ctx.wal().log(new DataPageMvccUpdateTxStateHintRecord(cacheId, pageId, itemId, state));
+                }
+                else
+                    throw unexpectedStateException(grp, state, crd, cntr, opCntr);
+            }
+
+            long newCrd = iox.newMvccCoordinator(pageAddr, offset);
+            long newCntr = iox.newMvccCounter(pageAddr, offset);
+            int newOpCntr = iox.newMvccOperationCounter(pageAddr, offset);
+            byte newTxState = (byte)(newOpCntr >>> MVCC_HINTS_BIT_OFF);
+
+            if (newCrd != MVCC_CRD_COUNTER_NA && newTxState == TxState.NA) {
+                byte state = state(grp, newCrd, newCntr, newOpCntr);
+
+                if (state == TxState.COMMITTED || state == TxState.ABORTED) {
+                    iox.newMvccOperationCounter(pageAddr, offset, newOpCntr | (state << MVCC_HINTS_BIT_OFF));
+
+                    if (isWalDeltaRecordNeeded(grp.dataRegion().pageMemory(), cacheId, pageId, page, ctx.wal(), walPlc))
+                        ctx.wal().log(new DataPageMvccUpdateNewTxStateHintRecord(cacheId, pageId, itemId, state));
+                }
+
+                // We do not throw an exception here because new version may be updated by active Tx at this moment.
+            }
+
+            return Boolean.TRUE;
+        }
+    }
+
+    /**
+     * Applies changes to the row.
+     */
+    private final class MvccApplyChangesHandler extends PageHandler<MvccDataRow, Boolean> {
+        /** {@inheritDoc} */
+        @Override public Boolean run(int cacheId, long pageId, long page, long pageAddr, PageIO io, Boolean walPlc,
+            MvccDataRow newRow, int itemId) throws IgniteCheckedException {
+            assert grp.mvccEnabled();
+
+            DataPageIO iox = (DataPageIO)io;
+
+            int offset = iox.getPayloadOffset(pageAddr, itemId, grp.dataRegion().pageMemory().pageSize(), MVCC_INFO_SIZE);
+
+            long crd = iox.mvccCoordinator(pageAddr, offset);
+            long cntr = iox.mvccCounter(pageAddr, offset);
+            int opCntrAndHint = iox.mvccOperationCounter(pageAddr, offset);
+            int opCntr = opCntrAndHint & ~MVCC_HINTS_MASK;
+            byte txState = (byte)(opCntrAndHint >>> MVCC_HINTS_BIT_OFF);
+
+            long newCrd = iox.newMvccCoordinator(pageAddr, offset);
+            long newCntr = iox.newMvccCounter(pageAddr, offset);
+            int newOpCntrAndHint = iox.newMvccOperationCounter(pageAddr, offset);
+            int newOpCntr = newOpCntrAndHint & ~MVCC_HINTS_MASK;
+            byte newTxState = (byte)(newOpCntrAndHint >>> MVCC_HINTS_BIT_OFF);
+
+            assert crd == newRow.mvccCoordinatorVersion();
+            assert cntr == newRow.mvccCounter();
+            assert opCntr == newRow.mvccOperationCounter();
+
+            if (txState != newRow.mvccTxState() && newRow.mvccTxState() != TxState.NA) {
+                assert txState == TxState.NA;
+
+                iox.mvccOperationCounter(pageAddr, offset, opCntr | (newRow.mvccTxState() << MVCC_HINTS_BIT_OFF));
+
+                if (isWalDeltaRecordNeeded(grp.dataRegion().pageMemory(), cacheId, pageId, page, ctx.wal(), walPlc))
+                    ctx.wal().log(new DataPageMvccUpdateTxStateHintRecord(cacheId, pageId, itemId, newRow.mvccTxState()));
+            }
+
+            if (compare(newCrd,
+                newCntr,
+                newOpCntr,
+                newRow.newMvccCoordinatorVersion(),
+                newRow.newMvccCounter(),
+                newRow.newMvccOperationCounter()) != 0) {
+
+                iox.updateNewVersion(pageAddr, offset, newRow.newMvccVersion(), newRow.newMvccTxState());
+
+                if (isWalDeltaRecordNeeded(grp.dataRegion().pageMemory(), cacheId, pageId, page, ctx.wal(), walPlc))
+                    ctx.wal().log(new DataPageMvccMarkUpdatedRecord(cacheId, pageId, itemId,
+                        newRow.newMvccCoordinatorVersion(), newRow.newMvccCounter(), newRow.newMvccOperationCounter()));
+            }
+            else if (newTxState != newRow.newMvccTxState() && newRow.newMvccTxState() != TxState.NA) {
+                assert newTxState == TxState.NA;
+
+                iox.newMvccOperationCounter(pageAddr, offset, newOpCntr | (newRow.newMvccTxState() << MVCC_HINTS_BIT_OFF));
+
+                if (isWalDeltaRecordNeeded(grp.dataRegion().pageMemory(), cacheId, pageId, page, ctx.wal(), walPlc))
+                    ctx.wal().log(new DataPageMvccUpdateNewTxStateHintRecord(cacheId, pageId, itemId, newRow.newMvccTxState()));
+            }
+
+            return Boolean.TRUE;
+        }
     }
 }
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/IgniteCacheProxyImpl.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/IgniteCacheProxyImpl.java
index e34f22f9dd54..c21ad0bb8701 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/IgniteCacheProxyImpl.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/IgniteCacheProxyImpl.java
@@ -74,6 +74,7 @@
 import org.apache.ignite.internal.IgniteEx;
 import org.apache.ignite.internal.IgniteInternalFuture;
 import org.apache.ignite.internal.processors.affinity.AffinityTopologyVersion;
+import org.apache.ignite.internal.processors.cache.mvcc.MvccUtils;
 import org.apache.ignite.internal.processors.cache.query.CacheQuery;
 import org.apache.ignite.internal.processors.cache.query.CacheQueryFuture;
 import org.apache.ignite.internal.processors.cache.query.GridCacheQueryType;
@@ -347,6 +348,9 @@ private IgniteCacheProxyImpl(
 
     /** {@inheritDoc} */
     @Override public Lock lockAll(final Collection<? extends K> keys) {
+        //TODO IGNITE-7764
+        MvccUtils.verifyMvccOperationSupport(ctx, "Lock");
+
         return new CacheLockImpl<>(ctx.gate(), delegate, ctx.operationContextPerCall(), keys);
     }
 
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/GridCacheTxFinishSync.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/GridCacheTxFinishSync.java
index 7cc368a999b6..1f688f64cc9e 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/GridCacheTxFinishSync.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/GridCacheTxFinishSync.java
@@ -248,7 +248,7 @@ public void onSend() {
                     log.trace("Moved transaction synchronizer to waiting state [nodeId=" + nodeId +
                         ", threadId=" + threadId + ']');
 
-                assert cnt == 0 || nodeLeft;
+                assert cnt == 0 || nodeLeft : cnt;
 
                 if (nodeLeft)
                     return;
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/GridCacheTxRecoveryFuture.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/GridCacheTxRecoveryFuture.java
index 3eeb0dbe45fc..41e8abae60b6 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/GridCacheTxRecoveryFuture.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/GridCacheTxRecoveryFuture.java
@@ -146,6 +146,17 @@ else if (log.isInfoEnabled())
      */
     @SuppressWarnings("ConstantConditions")
     public void prepare() {
+        if (tx.txState().mvccEnabled(cctx)) { // TODO IGNITE-7313
+            U.error(log, "Cannot commit MVCC enabled transaction by recovery procedure. " +
+                "Operation is usupported at the moment [tx=" + CU.txString(tx) + ']');
+
+            onDone(false);
+
+            markInitialized();
+
+            return;
+        }
+
         if (nearTxCheck) {
             UUID nearNodeId = tx.eventNodeId();
 
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/GridDistributedTxMapping.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/GridDistributedTxMapping.java
index 481c95486a27..b5437869c659 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/GridDistributedTxMapping.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/GridDistributedTxMapping.java
@@ -46,6 +46,9 @@
     /** Explicit lock flag. */
     private boolean explicitLock;
 
+    /** Query update flag. */
+    private boolean queryUpdate;
+
     /** DHT version. */
     private GridCacheVersion dhtVer;
 
@@ -132,6 +135,20 @@ public ClusterNode primary() {
         return F.view(entries, CU.FILTER_NEAR_CACHE_ENTRY);
     }
 
+    /**
+     * @return {@code True} if mapping was created for a query update.
+     */
+    public boolean queryUpdate() {
+        return queryUpdate;
+    }
+
+    /**
+     * Sets query update flag to {@code true}.
+     */
+    public void markQueryUpdate() {
+        queryUpdate = true;
+    }
+
     /**
      * @return {@code True} if lock is explicit.
      */
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/GridDistributedTxPrepareRequest.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/GridDistributedTxPrepareRequest.java
index 91dcd9e621de..a5aa0d838935 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/GridDistributedTxPrepareRequest.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/GridDistributedTxPrepareRequest.java
@@ -78,6 +78,9 @@
     /** */
     public static final int STORE_WRITE_THROUGH_FLAG_MASK = 0x20;
 
+    /** */
+    public static final int QUERY_UPDATE_FLAG_MASK = 0x40;
+
     /** Collection to message converter. */
     private static final C1<Collection<UUID>, UUIDCollectionMessage> COL_TO_MSG = new C1<Collection<UUID>, UUIDCollectionMessage>() {
         @Override public UUIDCollectionMessage apply(Collection<UUID> uuids) {
@@ -252,6 +255,25 @@ public void storeWriteThrough(boolean storeWriteThrough) {
             flags &= ~STORE_WRITE_THROUGH_FLAG_MASK;
     }
 
+    /**
+     *
+     * @return Flag indicating whether it is a query update.
+     */
+    public boolean queryUpdate() {
+        return (flags & QUERY_UPDATE_FLAG_MASK) != 0;
+    }
+
+    /**
+     *
+     * @param queryUpdate Query update value.
+     */
+    public void queryUpdate(boolean queryUpdate) {
+        if (queryUpdate)
+            flags = (byte)(flags | QUERY_UPDATE_FLAG_MASK);
+        else
+            flags &= ~QUERY_UPDATE_FLAG_MASK;
+    }
+
     /**
      * @return IO policy.
      */
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/GridDistributedTxRemoteAdapter.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/GridDistributedTxRemoteAdapter.java
index c1293fc0772b..dd6ea48e1b33 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/GridDistributedTxRemoteAdapter.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/GridDistributedTxRemoteAdapter.java
@@ -50,6 +50,8 @@
 import org.apache.ignite.internal.processors.cache.GridCacheSharedContext;
 import org.apache.ignite.internal.processors.cache.GridCacheUpdateTxResult;
 import org.apache.ignite.internal.processors.cache.KeyCacheObject;
+import org.apache.ignite.internal.processors.cache.distributed.dht.GridDhtLocalPartition;
+import org.apache.ignite.internal.processors.cache.distributed.dht.GridDhtPartitionsUpdateCountersMap;
 import org.apache.ignite.internal.processors.cache.distributed.near.GridNearCacheEntry;
 import org.apache.ignite.internal.processors.cache.persistence.StorageException;
 import org.apache.ignite.internal.processors.cache.transactions.IgniteInternalTx;
@@ -205,6 +207,16 @@ public GridDistributedTxRemoteAdapter(
         return false;
     }
 
+    /** {@inheritDoc} */
+    @Override public void activeCachesDeploymentEnabled(boolean depEnabled) {
+        throw new UnsupportedOperationException("Remote tx doesn't support deployment.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public void addActiveCache(GridCacheContext cacheCtx, boolean recovery) throws IgniteCheckedException {
+        txState.addActiveCache(cacheCtx, recovery, this);
+    }
+
     /**
      * @return Checks if transaction has no entries.
      */
@@ -460,7 +472,7 @@ private void commitIfLocked() throws IgniteCheckedException {
 
                 GridCacheReturnCompletableWrapper wrapper = null;
 
-                if (!F.isEmpty(writeMap)) {
+                if (!F.isEmpty(writeMap) || mvccSnapshot != null) {
                     GridCacheReturn ret = null;
 
                     if (!near() && !local() && onePhaseCommit()) {
@@ -488,6 +500,8 @@ private void commitIfLocked() throws IgniteCheckedException {
                     cctx.database().checkpointReadLock();
 
                     try {
+                        assert !txState.mvccEnabled(cctx) || mvccSnapshot != null : "Mvcc is not initialized: " + this;
+
                         Collection<IgniteTxEntry> entries = near() || cctx.snapshot().needTxReadLogging() ? allEntries() : writeEntries();
 
                         // Data entry to write to WAL and associated with it TxEntry.
@@ -613,7 +627,8 @@ else if (conflictCtx.isMerge()) {
                                                         CU.subjectId(this, cctx),
                                                         resolveTaskName(),
                                                         dhtVer,
-                                                        txEntry.updateCounter());
+                                                        txEntry.updateCounter(),
+                                                        mvccSnapshot());
                                                 else {
                                                     assert val != null : txEntry;
 
@@ -637,9 +652,10 @@ else if (conflictCtx.isMerge()) {
                                                         CU.subjectId(this, cctx),
                                                         resolveTaskName(),
                                                         dhtVer,
-                                                        txEntry.updateCounter());
+                                                        txEntry.updateCounter(),
+                                                        mvccSnapshot());
 
-                                                    txEntry.updateCounter(updRes.updatePartitionCounter());
+                                                    txEntry.updateCounter(updRes.updateCounter());
 
                                                     if (updRes.loggedPointer() != null)
                                                         ptr = updRes.loggedPointer();
@@ -674,9 +690,10 @@ else if (op == DELETE) {
                                                     CU.subjectId(this, cctx),
                                                     resolveTaskName(),
                                                     dhtVer,
-                                                    txEntry.updateCounter());
+                                                    txEntry.updateCounter(),
+                                                    mvccSnapshot());
 
-                                                txEntry.updateCounter(updRes.updatePartitionCounter());
+                                                txEntry.updateCounter(updRes.updateCounter());
 
                                                 if (updRes.loggedPointer() != null)
                                                     ptr = updRes.loggedPointer();
@@ -790,6 +807,8 @@ else if (!isNodeStopping) { // Skip fair uncommit in case of node stopping or in
                                 }
                             }
 
+                            updateLocalCounters();
+
                             if (!near() && !F.isEmpty(dataEntries) && cctx.wal() != null) {
                                 // Set new update counters for data entries received from persisted tx entries.
                                 List<DataEntry> entriesWithCounters = dataEntries.stream()
@@ -803,6 +822,8 @@ else if (!isNodeStopping) { // Skip fair uncommit in case of node stopping or in
                                 cctx.wal().flush(ptr, false);
                         }
                         catch (StorageException e) {
+                            err = e;
+
                             throw new IgniteCheckedException("Failed to log transaction record " +
                                 "(transaction will be rolled back): " + this, e);
                         }
@@ -810,6 +831,8 @@ else if (!isNodeStopping) { // Skip fair uncommit in case of node stopping or in
                     finally {
                         cctx.database().checkpointReadUnlock();
 
+                        notifyDrManager(state() == COMMITTING && err == null);
+
                         if (wrapper != null)
                             wrapper.initialize(ret);
                     }
@@ -822,6 +845,33 @@ else if (!isNodeStopping) { // Skip fair uncommit in case of node stopping or in
         }
     }
 
+    /**
+     * Applies update counters to the local partitions.
+     */
+    private void updateLocalCounters() {
+        Map<Integer, GridDhtPartitionsUpdateCountersMap> updCntrsMap = updateCountersMap();
+
+        if (F.isEmpty(updCntrsMap))
+            return;
+
+        for (Map.Entry<Integer, GridDhtPartitionsUpdateCountersMap> entry : updCntrsMap.entrySet()) {
+            GridCacheContext cacheCtx = cctx.cacheContext(entry.getKey());
+
+            GridDhtPartitionsUpdateCountersMap cacheUpdCntrs = entry.getValue();
+
+            assert cacheUpdCntrs != null && !F.isEmpty(cacheUpdCntrs.updateCounters());
+
+            for (Map.Entry<Integer, Long> e : cacheUpdCntrs.updateCounters().entrySet()) {
+                Long updCntr = e.getValue();
+                GridDhtLocalPartition part = cacheCtx.topology().localPartition(e.getKey());
+
+                assert part != null && updCntr != null && updCntr > 0;
+
+                part.updateCounter(updCntr);
+            }
+        }
+    }
+
     /** {@inheritDoc} */
     @Override public final void commitRemoteTx() throws IgniteCheckedException {
         if (optimistic())
@@ -901,6 +951,8 @@ public void forceCommit() throws IgniteCheckedException {
     /** {@inheritDoc} */
     @Override public final void rollbackRemoteTx() {
         try {
+            notifyDrManager(false);
+
             // Note that we don't evict near entries here -
             // they will be deleted by their corresponding transactions.
             if (state(ROLLING_BACK) || state() == UNKNOWN) {
@@ -970,9 +1022,18 @@ protected void addExplicit(IgniteTxEntry e) {
         }
     }
 
+    /** {@inheritDoc} */
+    @Override public void updateCountersMap(Map<Integer, GridDhtPartitionsUpdateCountersMap> updCntrsMap) {
+        // No-op.
+    }
+
+    /** {@inheritDoc} */
+    @Override public Map<Integer, GridDhtPartitionsUpdateCountersMap> updateCountersMap() {
+        return null;
+    }
+
     /** {@inheritDoc} */
     @Override public String toString() {
         return GridToStringBuilder.toString(GridDistributedTxRemoteAdapter.class, this, "super", super.toString());
     }
-
 }
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/CompoundLockFuture.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/CompoundLockFuture.java
new file mode 100644
index 000000000000..63eff40f8295
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/CompoundLockFuture.java
@@ -0,0 +1,112 @@
+/*
+ * 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.cache.distributed.dht;
+
+import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
+import org.apache.ignite.internal.IgniteInternalFuture;
+import org.apache.ignite.internal.processors.cache.distributed.near.GridNearTxAbstractEnlistFuture;
+import org.apache.ignite.internal.processors.cache.distributed.near.GridNearTxSelectForUpdateFuture;
+import org.apache.ignite.internal.util.future.GridFutureAdapter;
+import org.apache.ignite.lang.IgniteInClosure;
+
+/**
+ *
+ */
+public class CompoundLockFuture extends GridFutureAdapter<Void> implements DhtLockFuture<Void>, IgniteInClosure<IgniteInternalFuture<?>> {
+    /** */
+    private static final long serialVersionUID = 4644646033267042131L;
+    /** */
+    private static final AtomicIntegerFieldUpdater<CompoundLockFuture> CNT_UPD =
+        AtomicIntegerFieldUpdater.newUpdater(CompoundLockFuture.class, "cnt");
+    /** */
+    private volatile int cnt;
+    /** */
+    private final GridDhtTxLocalAdapter tx;
+
+    /**
+     * @param cnt ResultSet futures count.
+     * @param tx Transaction.
+     */
+    public CompoundLockFuture(int cnt, GridDhtTxLocalAdapter tx) {
+        this.cnt = cnt;
+        this.tx = tx;
+    }
+
+    /**
+     * @param fut ResultSet future.
+     */
+    public void register(IgniteInternalFuture<?> fut) {
+        fut.listen(this);
+    }
+
+    /**
+     *  Init method.
+     */
+    public void init() {
+        while(true) {
+            IgniteInternalFuture<?> fut = tx.lockFuture();
+
+            if (fut == GridDhtTxLocalAdapter.ROLLBACK_FUT) {
+                onDone(tx.timedOut() ? tx.timeoutException() : tx.rollbackException());
+
+                break;
+            }
+            else if (fut != null) {
+                // Wait for previous future.
+                assert fut instanceof GridNearTxAbstractEnlistFuture
+                    || fut instanceof GridDhtTxAbstractEnlistFuture
+                    || fut instanceof CompoundLockFuture
+                    || fut instanceof GridNearTxSelectForUpdateFuture : fut;
+
+                // Terminate this future if parent future is terminated by rollback.
+                if (!fut.isDone()) {
+                    fut.listen(new IgniteInClosure<IgniteInternalFuture>() {
+                        @Override public void apply(IgniteInternalFuture fut) {
+                            if (fut.error() != null)
+                                onDone(fut.error());
+                        }
+                    });
+                }
+                else if (fut.error() != null)
+                    onDone(fut.error());
+
+                break;
+            }
+            else if (tx.updateLockFuture(null, this))
+                break;
+        }
+    }
+
+    @Override public void apply(IgniteInternalFuture<?> future) {
+        if (!isDone() && (future.error() != null || CNT_UPD.decrementAndGet(this) == 0)) {
+            Throwable err = future.error();
+
+            if (err == null)
+                tx.clearLockFuture(this);
+
+            onDone(err);
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override public void onError(Throwable error) {
+        assert error != null;
+
+        onDone(error);
+    }
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/DhtLockFuture.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/DhtLockFuture.java
new file mode 100644
index 000000000000..b729dcd7f788
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/DhtLockFuture.java
@@ -0,0 +1,30 @@
+/*
+ * 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.cache.distributed.dht;
+
+import org.apache.ignite.internal.IgniteInternalFuture;
+
+/**
+ * Marker interface.
+ */
+public interface DhtLockFuture<T> extends IgniteInternalFuture<T> {
+    /**
+     * @param error Error.
+     */
+    public void onError(Throwable error);
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/ExceptionAware.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/ExceptionAware.java
new file mode 100644
index 000000000000..036492fc7c2d
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/ExceptionAware.java
@@ -0,0 +1,30 @@
+/*
+ * 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.cache.distributed.dht;
+
+import org.jetbrains.annotations.Nullable;
+
+/**
+ *
+ */
+public interface ExceptionAware {
+    /**
+     * @return Exception.
+     */
+    @Nullable Throwable error();
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridClientPartitionTopology.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridClientPartitionTopology.java
index 54a850c12451..70ce0821ae93 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridClientPartitionTopology.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridClientPartitionTopology.java
@@ -46,6 +46,7 @@
 import org.apache.ignite.internal.processors.cache.distributed.dht.preloader.GridDhtPartitionFullMap;
 import org.apache.ignite.internal.processors.cache.distributed.dht.preloader.GridDhtPartitionMap;
 import org.apache.ignite.internal.processors.cache.distributed.dht.preloader.GridDhtPartitionsExchangeFuture;
+import org.apache.ignite.internal.processors.cache.mvcc.MvccCoordinator;
 import org.apache.ignite.internal.util.F0;
 import org.apache.ignite.internal.util.GridAtomicLong;
 import org.apache.ignite.internal.util.GridPartitionStateMap;
@@ -198,6 +199,11 @@ private String mapString(GridDhtPartitionMap map) {
         lock.readLock().unlock();
     }
 
+    /** {@inheritDoc} */
+    @Override public MvccCoordinator mvccCoordinator() {
+        throw new UnsupportedOperationException();
+    }
+
     /** {@inheritDoc} */
     @Override public boolean holdsLock() {
         return lock.isWriteLockedByCurrentThread() || lock.getReadHoldCount() > 0;
@@ -207,6 +213,7 @@ private String mapString(GridDhtPartitionMap map) {
     @Override public void updateTopologyVersion(
         GridDhtTopologyFuture exchFut,
         DiscoCache discoCache,
+        MvccCoordinator mvccCrd,
         long updSeq,
         boolean stopping
     ) throws IgniteInterruptedCheckedException {
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridDhtCacheAdapter.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridDhtCacheAdapter.java
index 5adbda750d77..cd37650c671b 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridDhtCacheAdapter.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridDhtCacheAdapter.java
@@ -68,6 +68,8 @@
 import org.apache.ignite.internal.processors.cache.distributed.near.GridNearGetResponse;
 import org.apache.ignite.internal.processors.cache.distributed.near.GridNearSingleGetRequest;
 import org.apache.ignite.internal.processors.cache.distributed.near.GridNearSingleGetResponse;
+import org.apache.ignite.internal.processors.cache.mvcc.MvccSnapshot;
+import org.apache.ignite.internal.processors.cache.mvcc.MvccUtils;
 import org.apache.ignite.internal.processors.cache.version.GridCacheVersion;
 import org.apache.ignite.internal.processors.platform.cache.PlatformCacheEntryFilter;
 import org.apache.ignite.internal.util.future.GridCompoundFuture;
@@ -230,16 +232,30 @@ private void processForceKeysRequest0(ClusterNode node, GridDhtForceKeysRequest
 
                         entry.unswap();
 
-                        GridCacheEntryInfo info = entry.info();
+                        if (ctx.mvccEnabled()) {
+                            List<GridCacheEntryInfo> infos = entry.allVersionsInfo();
 
-                        if (info == null) {
-                            assert entry.obsolete() : entry;
+                            if (infos == null) {
+                                assert entry.obsolete() : entry;
 
-                            continue;
+                                continue;
+                            }
+
+                            for (int i = 0; i < infos.size(); i++)
+                                res.addInfo(infos.get(i));
                         }
+                        else {
+                            GridCacheEntryInfo info = entry.info();
+
+                            if (info == null) {
+                                assert entry.obsolete() : entry;
 
-                        if (!info.isNew())
-                            res.addInfo(info);
+                                continue;
+                            }
+
+                            if (!info.isNew())
+                                res.addInfo(info);
+                        }
 
                         entry.touch(msg.topologyVersion());
 
@@ -588,6 +604,9 @@ protected GridDistributedCacheEntry createEntry(KeyCacheObject key) {
             return;
         }
 
+        //TODO IGNITE-7954
+        MvccUtils.verifyMvccOperationSupport(ctx, "Load");
+
         final AffinityTopologyVersion topVer = ctx.affinity().affinityTopologyVersion();
 
         // Version for all loaded entries.
@@ -766,6 +785,7 @@ else if (log.isDebugEnabled())
      * @param taskName Task name.
      * @param expiry Expiry policy.
      * @param skipVals Skip values flag.
+     * @param mvccSnapshot MVCC snapshot.
      * @return Get future.
      */
     IgniteInternalFuture<Map<KeyCacheObject, EntryGetResult>> getDhtAllAsync(
@@ -776,7 +796,8 @@ else if (log.isDebugEnabled())
         String taskName,
         @Nullable IgniteCacheExpiryPolicy expiry,
         boolean skipVals,
-        boolean recovery
+        boolean recovery,
+        MvccSnapshot mvccSnapshot
     ) {
         return getAllAsync0(keys,
             readerArgs,
@@ -789,7 +810,8 @@ else if (log.isDebugEnabled())
             skipVals,
             /*keep cache objects*/true,
             recovery,
-            /*need version*/true);
+            /*need version*/true,
+            mvccSnapshot);
     }
 
     /**
@@ -803,6 +825,7 @@ else if (log.isDebugEnabled())
      * @param taskNameHash Task name hash code.
      * @param expiry Expiry policy.
      * @param skipVals Skip values flag.
+     * @param mvccSnapshot MVCC snapshot.
      * @return DHT future.
      */
     public GridDhtFuture<Collection<GridCacheEntryInfo>> getDhtAsync(UUID reader,
@@ -815,7 +838,8 @@ else if (log.isDebugEnabled())
         int taskNameHash,
         @Nullable IgniteCacheExpiryPolicy expiry,
         boolean skipVals,
-        boolean recovery
+        boolean recovery,
+        MvccSnapshot mvccSnapshot
     ) {
         GridDhtGetFuture<K, V> fut = new GridDhtGetFuture<>(ctx,
             msgId,
@@ -828,7 +852,8 @@ else if (log.isDebugEnabled())
             expiry,
             skipVals,
             recovery,
-            addReaders);
+            addReaders,
+            mvccSnapshot);
 
         fut.init();
 
@@ -846,9 +871,10 @@ else if (log.isDebugEnabled())
      * @param taskNameHash Task name hash.
      * @param expiry Expiry.
      * @param skipVals Skip vals flag.
+     * @param mvccSnapshot Mvcc snapshot.
      * @return Future for the operation.
      */
-    public GridDhtGetSingleFuture getDhtSingleAsync(
+    GridDhtGetSingleFuture getDhtSingleAsync(
         UUID nodeId,
         long msgId,
         KeyCacheObject key,
@@ -859,7 +885,8 @@ public GridDhtGetSingleFuture getDhtSingleAsync(
         int taskNameHash,
         @Nullable IgniteCacheExpiryPolicy expiry,
         boolean skipVals,
-        boolean recovery
+        boolean recovery,
+        MvccSnapshot mvccSnapshot
     ) {
         GridDhtGetSingleFuture fut = new GridDhtGetSingleFuture<>(
             ctx,
@@ -873,7 +900,8 @@ public GridDhtGetSingleFuture getDhtSingleAsync(
             taskNameHash,
             expiry,
             skipVals,
-            recovery);
+            recovery,
+            mvccSnapshot);
 
         fut.init();
 
@@ -901,7 +929,8 @@ protected void processNearSingleGetRequest(final UUID nodeId, final GridNearSing
                 req.taskNameHash(),
                 expiryPlc,
                 req.skipValues(),
-                req.recovery());
+                req.recovery(),
+                req.mvccSnapshot());
 
         fut.listen(new CI1<IgniteInternalFuture<GridCacheEntryInfo>>() {
             @Override public void apply(IgniteInternalFuture<GridCacheEntryInfo> f) {
@@ -1004,7 +1033,8 @@ protected void processNearGetRequest(final UUID nodeId, final GridNearGetRequest
                 req.taskNameHash(),
                 expiryPlc,
                 req.skipValues(),
-                req.recovery());
+                req.recovery(),
+                req.mvccSnapshot());
 
         fut.listen(new CI1<IgniteInternalFuture<Collection<GridCacheEntryInfo>>>() {
             @Override public void apply(IgniteInternalFuture<Collection<GridCacheEntryInfo>> f) {
@@ -1230,6 +1260,8 @@ protected final boolean needRemap(AffinityTopologyVersion expVer, AffinityTopolo
         if (expVer.equals(curVer))
             return false;
 
+        // TODO IGNITE-7164 check mvcc crd for mvcc enabled txs.
+
         Collection<ClusterNode> cacheNodes0 = ctx.discovery().cacheGroupAffinityNodes(ctx.groupId(), expVer);
         Collection<ClusterNode> cacheNodes1 = ctx.discovery().cacheGroupAffinityNodes(ctx.groupId(), curVer);
 
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridDhtCacheEntry.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridDhtCacheEntry.java
index fe02090a9c2c..343e418891f8 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridDhtCacheEntry.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridDhtCacheEntry.java
@@ -101,6 +101,11 @@ public GridDhtCacheEntry(
         return locPart.nextUpdateCounter(cctx.cacheId(), topVer, primary, primaryCntr);
     }
 
+    /** {@inheritDoc} */
+    @Override protected long nextMvccPartitionCounter() {
+        return locPart.nextMvccUpdateCounter();
+    }
+
     /** {@inheritDoc} */
     @Override public int memorySize() throws IgniteCheckedException {
         int rdrsOverhead;
@@ -648,7 +653,10 @@ public boolean clearInternal(
                     ']');
             }
 
-            removeValue();
+                if (cctx.mvccEnabled())
+                    cctx.offheap().mvccRemoveAll(this);
+                else
+                    removeValue();
 
             // Give to GC.
             update(null, 0L, 0L, ver, true);
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridDhtGetFuture.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridDhtGetFuture.java
index a969dd32dab6..023c0583ed24 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridDhtGetFuture.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridDhtGetFuture.java
@@ -37,6 +37,7 @@
 import org.apache.ignite.internal.processors.cache.IgniteCacheExpiryPolicy;
 import org.apache.ignite.internal.processors.cache.KeyCacheObject;
 import org.apache.ignite.internal.processors.cache.ReaderArguments;
+import org.apache.ignite.internal.processors.cache.mvcc.MvccSnapshot;
 import org.apache.ignite.internal.processors.cache.version.GridCacheVersion;
 import org.apache.ignite.internal.util.future.GridCompoundFuture;
 import org.apache.ignite.internal.util.future.GridCompoundIdentityFuture;
@@ -114,6 +115,9 @@
     /** */
     private final boolean addReaders;
 
+    /** */
+    private final MvccSnapshot mvccSnapshot;
+
     /**
      * @param cctx Context.
      * @param msgId Message ID.
@@ -125,6 +129,7 @@
      * @param taskNameHash Task name hash code.
      * @param expiryPlc Expiry policy.
      * @param skipVals Skip values flag.
+     * @param mvccSnapshot MVCC snapshot.
      */
     public GridDhtGetFuture(
         GridCacheContext<K, V> cctx,
@@ -138,7 +143,8 @@ public GridDhtGetFuture(
         @Nullable IgniteCacheExpiryPolicy expiryPlc,
         boolean skipVals,
         boolean recovery,
-        boolean addReaders
+        boolean addReaders,
+        MvccSnapshot mvccSnapshot
     ) {
         super(CU.<GridCacheEntryInfo>collectionsReducer(keys.size()));
 
@@ -157,6 +163,7 @@ public GridDhtGetFuture(
         this.skipVals = skipVals;
         this.recovery = recovery;
         this.addReaders = addReaders;
+        this.mvccSnapshot = mvccSnapshot;
 
         futId = IgniteUuid.randomUuid();
 
@@ -422,7 +429,8 @@ private boolean map(KeyCacheObject key) {
                 taskName,
                 expiryPlc,
                 skipVals,
-                recovery);
+                recovery,
+                mvccSnapshot);
         }
         else {
             final ReaderArguments args = readerArgs;
@@ -445,7 +453,8 @@ private boolean map(KeyCacheObject key) {
                             taskName,
                             expiryPlc,
                             skipVals,
-                            recovery);
+                            recovery,
+                            mvccSnapshot);
                     }
                 }
             );
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridDhtGetSingleFuture.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridDhtGetSingleFuture.java
index 99a8135cfc44..de76eb1c9ecf 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridDhtGetSingleFuture.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridDhtGetSingleFuture.java
@@ -35,6 +35,7 @@
 import org.apache.ignite.internal.processors.cache.IgniteCacheExpiryPolicy;
 import org.apache.ignite.internal.processors.cache.KeyCacheObject;
 import org.apache.ignite.internal.processors.cache.ReaderArguments;
+import org.apache.ignite.internal.processors.cache.mvcc.MvccSnapshot;
 import org.apache.ignite.internal.processors.cache.version.GridCacheVersion;
 import org.apache.ignite.internal.util.future.GridFutureAdapter;
 import org.apache.ignite.internal.util.typedef.F;
@@ -103,6 +104,9 @@
     /** Recovery context flag. */
     private final boolean recovery;
 
+    /** */
+    private final MvccSnapshot mvccSnapshot;
+
     /**
      * @param cctx Context.
      * @param msgId Message ID.
@@ -115,6 +119,7 @@
      * @param taskNameHash Task name hash code.
      * @param expiryPlc Expiry policy.
      * @param skipVals Skip values flag.
+     * @param mvccSnapshot Mvcc snapshot.
      */
     public GridDhtGetSingleFuture(
         GridCacheContext<K, V> cctx,
@@ -128,7 +133,8 @@ public GridDhtGetSingleFuture(
         int taskNameHash,
         @Nullable IgniteCacheExpiryPolicy expiryPlc,
         boolean skipVals,
-        boolean recovery
+        boolean recovery,
+        @Nullable MvccSnapshot mvccSnapshot
     ) {
         assert reader != null;
         assert key != null;
@@ -145,6 +151,7 @@ public GridDhtGetSingleFuture(
         this.expiryPlc = expiryPlc;
         this.skipVals = skipVals;
         this.recovery = recovery;
+        this.mvccSnapshot = mvccSnapshot;
 
         futId = IgniteUuid.randomUuid();
 
@@ -365,7 +372,8 @@ private void getAsync() {
                 taskName,
                 expiryPlc,
                 skipVals,
-                recovery);
+                recovery,
+                mvccSnapshot);
         }
         else {
             final ReaderArguments args = readerArgs;
@@ -390,7 +398,8 @@ private void getAsync() {
                                 taskName,
                                 expiryPlc,
                                 skipVals,
-                                recovery);
+                                recovery,
+                                mvccSnapshot);
 
                         fut0.listen(createGetFutureListener());
                     }
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridDhtLocalPartition.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridDhtLocalPartition.java
index 5f976c9e1674..f56df00e9e3b 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridDhtLocalPartition.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridDhtLocalPartition.java
@@ -975,6 +975,20 @@ public long updateCounter() {
         return store.updateCounter();
     }
 
+    /**
+     * @return Current mvcc update counter value.
+     */
+    public long mvccUpdateCounter() {
+        return store.mvccUpdateCounter();
+    }
+
+    /**
+     * @return Next mvcc update counter.
+     */
+    public long nextMvccUpdateCounter() {
+        return store.nextMvccUpdateCounter();
+    }
+
     /**
      * @return Initial update counter.
      */
@@ -1258,6 +1272,15 @@ private void clearDeferredDeletes() {
         }
     }
 
+    /**
+     * Returns group context.
+     *
+     * @return Group context.
+     */
+    public CacheGroupContext group() {
+        return grp;
+    }
+
     /**
      * @param cacheId Cache ID.
      */
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridDhtLockFuture.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridDhtLockFuture.java
index 529d96575e82..c2cd781b070e 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridDhtLockFuture.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridDhtLockFuture.java
@@ -54,6 +54,9 @@
 import org.apache.ignite.internal.processors.cache.distributed.GridDistributedLockCancelledException;
 import org.apache.ignite.internal.processors.cache.distributed.dht.colocated.GridDhtColocatedLockFuture;
 import org.apache.ignite.internal.processors.cache.distributed.near.GridNearCacheAdapter;
+import org.apache.ignite.internal.processors.cache.mvcc.MvccUpdateVersionAware;
+import org.apache.ignite.internal.processors.cache.mvcc.MvccVersionAware;
+import org.apache.ignite.internal.processors.cache.mvcc.txlog.TxState;
 import org.apache.ignite.internal.processors.cache.transactions.IgniteTxEntry;
 import org.apache.ignite.internal.processors.cache.transactions.IgniteTxKey;
 import org.apache.ignite.internal.processors.cache.version.GridCacheVersion;
@@ -82,7 +85,7 @@
  * Cache lock future.
  */
 public final class GridDhtLockFuture extends GridCacheCompoundIdentityFuture<Boolean>
-    implements GridCacheVersionedFuture<Boolean>, GridDhtFuture<Boolean>, GridCacheMappedVersion {
+    implements GridCacheVersionedFuture<Boolean>, GridDhtFuture<Boolean>, GridCacheMappedVersion, DhtLockFuture<Boolean> {
     /** */
     private static final long serialVersionUID = 0L;
 
@@ -264,7 +267,7 @@ public GridDhtLockFuture(
 
         if (tx != null) {
             while(true) {
-                IgniteInternalFuture<Boolean> fut = tx.lockFut;
+                IgniteInternalFuture fut = tx.lockFut;
 
                 if (fut != null) {
                     if (fut == GridDhtTxLocalAdapter.ROLLBACK_FUT)
@@ -274,8 +277,8 @@ public GridDhtLockFuture(
                         assert fut instanceof GridDhtColocatedLockFuture : fut;
 
                         // Terminate this future if parent(collocated) future is terminated by rollback.
-                        fut.listen(new IgniteInClosure<IgniteInternalFuture<Boolean>>() {
-                            @Override public void apply(IgniteInternalFuture<Boolean> fut) {
+                        fut.listen(new IgniteInClosure<IgniteInternalFuture>() {
+                            @Override public void apply(IgniteInternalFuture fut) {
                                 try {
                                     fut.get();
                                 }
@@ -1307,6 +1310,10 @@ void onResult(GridDhtLockResponse res) {
                             try {
                                 if (entry.initialValue(info.value(),
                                     info.version(),
+                                    cctx.mvccEnabled() ? ((MvccVersionAware)info).mvccVersion() : null,
+                                    cctx.mvccEnabled() ? ((MvccUpdateVersionAware)info).newMvccVersion() : null,
+                                    cctx.mvccEnabled() ? ((MvccVersionAware)entry).mvccTxState() : TxState.NA,
+                                    cctx.mvccEnabled() ? ((MvccUpdateVersionAware)entry).newMvccTxState() : TxState.NA,
                                     info.ttl(),
                                     info.expireTime(),
                                     true,
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridDhtPartitionTopology.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridDhtPartitionTopology.java
index 42ef309eafed..3ae3c8f72633 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridDhtPartitionTopology.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridDhtPartitionTopology.java
@@ -35,6 +35,7 @@
 import org.apache.ignite.internal.processors.cache.distributed.dht.preloader.GridDhtPartitionFullMap;
 import org.apache.ignite.internal.processors.cache.distributed.dht.preloader.GridDhtPartitionMap;
 import org.apache.ignite.internal.processors.cache.distributed.dht.preloader.GridDhtPartitionsExchangeFuture;
+import org.apache.ignite.internal.processors.cache.mvcc.MvccCoordinator;
 import org.apache.ignite.internal.util.tostring.GridToStringExclude;
 import org.jetbrains.annotations.Nullable;
 
@@ -74,6 +75,7 @@
     public void updateTopologyVersion(
         GridDhtTopologyFuture exchFut,
         DiscoCache discoCache,
+        MvccCoordinator mvccCrd,
         long updateSeq,
         boolean stopping
     ) throws IgniteInterruptedCheckedException;
@@ -421,4 +423,6 @@ public boolean update(@Nullable GridDhtPartitionExchangeId exchId,
      * @param updateRebalanceVer {@code True} if need check rebalance state.
      */
     public void onExchangeDone(GridDhtPartitionsExchangeFuture fut, AffinityAssignment assignment, boolean updateRebalanceVer);
+
+    public MvccCoordinator mvccCoordinator();
 }
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridDhtPartitionTopologyImpl.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridDhtPartitionTopologyImpl.java
index c3ff15ae5adc..1256a8bee303 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridDhtPartitionTopologyImpl.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridDhtPartitionTopologyImpl.java
@@ -52,6 +52,7 @@
 import org.apache.ignite.internal.processors.cache.distributed.dht.preloader.GridDhtPartitionFullMap;
 import org.apache.ignite.internal.processors.cache.distributed.dht.preloader.GridDhtPartitionMap;
 import org.apache.ignite.internal.processors.cache.distributed.dht.preloader.GridDhtPartitionsExchangeFuture;
+import org.apache.ignite.internal.processors.cache.mvcc.MvccCoordinator;
 import org.apache.ignite.internal.util.F0;
 import org.apache.ignite.internal.util.GridAtomicLong;
 import org.apache.ignite.internal.util.GridPartitionStateMap;
@@ -146,6 +147,9 @@
     /** */
     private volatile AffinityTopologyVersion rebalancedTopVer = AffinityTopologyVersion.NONE;
 
+    /** */
+    private volatile MvccCoordinator mvccCrd;
+
     /**
      * @param ctx Cache shared context.
      * @param grp Cache group.
@@ -237,6 +241,11 @@ private String mapString(GridDhtPartitionMap map) {
         lock.readLock().unlock();
     }
 
+    /** {@inheritDoc} */
+    @Override public MvccCoordinator mvccCoordinator() {
+        return mvccCrd;
+    }
+
     /** {@inheritDoc} */
     @Override public boolean holdsLock() {
         return lock.isWriteLockedByCurrentThread() || lock.getReadHoldCount() > 0;
@@ -246,6 +255,7 @@ private String mapString(GridDhtPartitionMap map) {
     @Override public void updateTopologyVersion(
         GridDhtTopologyFuture exchFut,
         @NotNull DiscoCache discoCache,
+        MvccCoordinator mvccCrd,
         long updSeq,
         boolean stopping
     ) throws IgniteInterruptedCheckedException {
@@ -272,6 +282,7 @@ private String mapString(GridDhtPartitionMap map) {
             lastTopChangeVer = exchTopVer;
 
             this.discoCache = discoCache;
+            this.mvccCrd = mvccCrd;
         }
         finally {
             lock.writeLock().unlock();
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridDhtPartitionsStateValidator.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridDhtPartitionsStateValidator.java
index 866c5133b8aa..7faf0ff02d2e 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridDhtPartitionsStateValidator.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridDhtPartitionsStateValidator.java
@@ -32,6 +32,7 @@
 import org.apache.ignite.internal.processors.cache.distributed.dht.preloader.CachePartitionPartialCountersMap;
 import org.apache.ignite.internal.processors.cache.distributed.dht.preloader.GridDhtPartitionsExchangeFuture;
 import org.apache.ignite.internal.processors.cache.distributed.dht.preloader.GridDhtPartitionsSingleMessage;
+import org.apache.ignite.internal.processors.cache.mvcc.MvccUtils;
 import org.apache.ignite.internal.util.typedef.T2;
 import org.apache.ignite.internal.util.typedef.internal.SB;
 import org.apache.ignite.lang.IgniteProductVersion;
@@ -93,11 +94,13 @@ public void validatePartitionCountersAndSizes(GridDhtPartitionsExchangeFuture fu
                 ignoringNodes.add(id);
         }
 
-        // Validate cache sizes.
-        result = validatePartitionsSizes(top, messages, ignoringNodes);
+        if (!MvccUtils.mvccEnabled(cctx.kernalContext())) { // TODO: Remove "if" clause in IGNITE-8149.
+            // Validate cache sizes.
+            result = validatePartitionsSizes(top, messages, ignoringNodes);
 
-        if (!result.isEmpty())
-            throw new IgniteCheckedException("Partitions cache sizes are inconsistent for " + fold(topVer, result));
+            if (!result.isEmpty())
+                throw new IgniteCheckedException("Partitions cache sizes are inconsistent for " + fold(topVer, result));
+        }
     }
 
     /**
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridDhtPartitionsUpdateCountersMap.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridDhtPartitionsUpdateCountersMap.java
new file mode 100644
index 000000000000..7a345d13dfbb
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridDhtPartitionsUpdateCountersMap.java
@@ -0,0 +1,119 @@
+/*
+ * 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.cache.distributed.dht;
+
+import java.nio.ByteBuffer;
+import java.util.HashMap;
+import java.util.Map;
+import org.apache.ignite.internal.GridDirectMap;
+import org.apache.ignite.plugin.extensions.communication.Message;
+import org.apache.ignite.plugin.extensions.communication.MessageCollectionItemType;
+import org.apache.ignite.plugin.extensions.communication.MessageReader;
+import org.apache.ignite.plugin.extensions.communication.MessageWriter;
+
+/**
+ * Partitions update counters message.
+ */
+public class GridDhtPartitionsUpdateCountersMap implements Message {
+    /** */
+    private static final long serialVersionUID = -4599730112233297219L;
+
+    /** Map of update counters made by this tx. Mapping: partId -> updCntr. */
+    @GridDirectMap(keyType = Integer.class, valueType = Long.class)
+    private  Map<Integer, Long> updCntrs;
+
+    /**
+     *
+     */
+    public GridDhtPartitionsUpdateCountersMap() {
+        updCntrs = new HashMap<>();
+    }
+
+    /**
+     * @return Update counters.
+     */
+    public Map<Integer, Long> updateCounters() {
+        return updCntrs;
+    }
+
+    /**
+     * @param updCntrs Update counters.
+     */
+    public void updateCounters(Map<Integer, Long> updCntrs) {
+        this.updCntrs = updCntrs;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean writeTo(ByteBuffer buf, MessageWriter writer) {
+        writer.setBuffer(buf);
+
+        if (!writer.isHeaderWritten()) {
+            if (!writer.writeHeader(directType(), fieldsCount()))
+                return false;
+
+            writer.onHeaderWritten();
+        }
+
+        switch (writer.state()) {
+            case 0:
+                if (!writer.writeMap("updCntrs", updCntrs, MessageCollectionItemType.INT, MessageCollectionItemType.LONG))
+                    return false;
+
+                writer.incrementState();
+
+        }
+
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean readFrom(ByteBuffer buf, MessageReader reader) {
+        reader.setBuffer(buf);
+
+        if (!reader.beforeMessageRead())
+            return false;
+
+        switch (reader.state()) {
+            case 0:
+                updCntrs = reader.readMap("updCntrs", MessageCollectionItemType.INT, MessageCollectionItemType.LONG, false);
+
+                if (!reader.isLastRead())
+                    return false;
+
+                reader.incrementState();
+
+        }
+
+        return reader.afterMessageRead(GridDhtPartitionsUpdateCountersMap.class);
+    }
+
+    /** {@inheritDoc} */
+    @Override public short directType() {
+        return 157;
+    }
+
+    /** {@inheritDoc} */
+    @Override public byte fieldsCount() {
+        return 1;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void onAckReceived() {
+        // No-op.
+    }
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridDhtTransactionalCacheAdapter.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridDhtTransactionalCacheAdapter.java
index f93e6a33d4e3..be1c7e282f5b 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridDhtTransactionalCacheAdapter.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridDhtTransactionalCacheAdapter.java
@@ -28,7 +28,9 @@
 import java.util.Map;
 import java.util.UUID;
 import org.apache.ignite.IgniteCheckedException;
+import org.apache.ignite.IgniteException;
 import org.apache.ignite.cluster.ClusterNode;
+import org.apache.ignite.cluster.ClusterTopologyException;
 import org.apache.ignite.internal.IgniteInternalFuture;
 import org.apache.ignite.internal.NodeStoppingException;
 import org.apache.ignite.internal.cluster.ClusterTopologyCheckedException;
@@ -55,8 +57,16 @@
 import org.apache.ignite.internal.processors.cache.distributed.near.GridNearLockResponse;
 import org.apache.ignite.internal.processors.cache.distributed.near.GridNearSingleGetRequest;
 import org.apache.ignite.internal.processors.cache.distributed.near.GridNearTransactionalCache;
+import org.apache.ignite.internal.processors.cache.distributed.near.GridNearTxQueryEnlistFuture;
+import org.apache.ignite.internal.processors.cache.distributed.near.GridNearTxQueryEnlistRequest;
+import org.apache.ignite.internal.processors.cache.distributed.near.GridNearTxQueryEnlistResponse;
+import org.apache.ignite.internal.processors.cache.distributed.near.GridNearTxQueryResultsEnlistFuture;
+import org.apache.ignite.internal.processors.cache.distributed.near.GridNearTxQueryResultsEnlistRequest;
+import org.apache.ignite.internal.processors.cache.distributed.near.GridNearTxQueryResultsEnlistResponse;
 import org.apache.ignite.internal.processors.cache.distributed.near.GridNearTxRemote;
 import org.apache.ignite.internal.processors.cache.distributed.near.GridNearUnlockRequest;
+import org.apache.ignite.internal.processors.cache.mvcc.MvccSnapshot;
+import org.apache.ignite.internal.processors.cache.mvcc.MvccSnapshotWithoutTxs;
 import org.apache.ignite.internal.processors.cache.transactions.IgniteTxEntry;
 import org.apache.ignite.internal.processors.cache.transactions.IgniteTxKey;
 import org.apache.ignite.internal.processors.cache.transactions.IgniteTxLocalEx;
@@ -76,6 +86,7 @@
 import org.apache.ignite.internal.util.typedef.X;
 import org.apache.ignite.internal.util.typedef.internal.CU;
 import org.apache.ignite.internal.util.typedef.internal.U;
+import org.apache.ignite.lang.IgniteUuid;
 import org.apache.ignite.thread.IgniteThread;
 import org.apache.ignite.transactions.TransactionIsolation;
 import org.jetbrains.annotations.Nullable;
@@ -83,7 +94,9 @@
 import static org.apache.ignite.cache.CacheWriteSynchronizationMode.FULL_SYNC;
 import static org.apache.ignite.internal.processors.cache.GridCacheOperation.NOOP;
 import static org.apache.ignite.internal.processors.cache.GridCacheUtils.isNearEnabled;
+import static org.apache.ignite.internal.processors.cache.mvcc.MvccUtils.MVCC_OP_COUNTER_NA;
 import static org.apache.ignite.transactions.TransactionConcurrency.PESSIMISTIC;
+import static org.apache.ignite.transactions.TransactionIsolation.REPEATABLE_READ;
 import static org.apache.ignite.transactions.TransactionState.COMMITTING;
 
 /**
@@ -164,6 +177,18 @@ protected GridDhtTransactionalCacheAdapter(GridCacheContext<K, V> ctx, GridCache
             }
         });
 
+        ctx.io().addCacheHandler(ctx.cacheId(), GridNearTxQueryEnlistRequest.class, new CI2<UUID, GridNearTxQueryEnlistRequest>() {
+            @Override public void apply(UUID nodeId, GridNearTxQueryEnlistRequest req) {
+                processNearTxQueryEnlistRequest(nodeId, req);
+            }
+        });
+
+        ctx.io().addCacheHandler(ctx.cacheId(), GridNearTxQueryEnlistResponse.class, new CI2<UUID, GridNearTxQueryEnlistResponse>() {
+            @Override public void apply(UUID nodeId, GridNearTxQueryEnlistResponse req) {
+                processNearEnlistResponse(nodeId, req);
+            }
+        });
+
         ctx.io().addCacheHandler(ctx.cacheId(), GridDhtForceKeysRequest.class,
             new MessageHandler<GridDhtForceKeysRequest>() {
                 @Override public void onMessage(ClusterNode node, GridDhtForceKeysRequest msg) {
@@ -177,6 +202,41 @@ protected GridDhtTransactionalCacheAdapter(GridCacheContext<K, V> ctx, GridCache
                     processForceKeyResponse(node, msg);
                 }
             });
+
+        ctx.io().addCacheHandler(ctx.cacheId(), GridNearTxQueryResultsEnlistRequest.class,
+            new CI2<UUID, GridNearTxQueryResultsEnlistRequest>() {
+                @Override public void apply(UUID nodeId, GridNearTxQueryResultsEnlistRequest req) {
+                    processNearTxQueryResultsEnlistRequest(nodeId, req);
+                }
+            });
+
+        ctx.io().addCacheHandler(ctx.cacheId(), GridNearTxQueryResultsEnlistResponse.class,
+            new CI2<UUID, GridNearTxQueryResultsEnlistResponse>() {
+                @Override public void apply(UUID nodeId, GridNearTxQueryResultsEnlistResponse req) {
+                    processNearTxEnlistResponse(nodeId, req);
+                }
+            });
+
+        ctx.io().addCacheHandler(ctx.cacheId(), GridDhtTxQueryEnlistRequest.class,
+            new CI2<UUID, GridDhtTxQueryEnlistRequest>() {
+                @Override public void apply(UUID nodeId, GridDhtTxQueryEnlistRequest msg) {
+                    processDhtTxQueryEnlistRequest(nodeId, msg, false);
+                }
+            });
+
+        ctx.io().addCacheHandler(ctx.cacheId(), GridDhtTxQueryFirstEnlistRequest.class,
+            new CI2<UUID, GridDhtTxQueryEnlistRequest>() {
+                @Override public void apply(UUID nodeId, GridDhtTxQueryEnlistRequest msg) {
+                    processDhtTxQueryEnlistRequest(nodeId, msg, true);
+                }
+            });
+
+        ctx.io().addCacheHandler(ctx.cacheId(), GridDhtTxQueryEnlistResponse.class,
+            new CI2<UUID, GridDhtTxQueryEnlistResponse>() {
+                @Override public void apply(UUID nodeId, GridDhtTxQueryEnlistResponse msg) {
+                    processDhtTxQueryEnlistResponse(nodeId, msg);
+                }
+            });
     }
 
     /** {@inheritDoc} */
@@ -623,6 +683,85 @@ private void processDhtUnlockRequest(UUID nodeId, GridDhtUnlockRequest req) {
             near().clearLocks(nodeId, req);
     }
 
+    /**
+     * @param nodeId Node ID.
+     * @param req Request.
+     */
+    private void processNearTxQueryEnlistRequest(UUID nodeId, final GridNearTxQueryEnlistRequest req) {
+        assert nodeId != null;
+        assert req != null;
+
+        ClusterNode nearNode = ctx.discovery().node(nodeId);
+
+        GridDhtTxLocal tx;
+
+        try {
+            tx = initTxTopologyVersion(nodeId,
+                nearNode,
+                req.version(),
+                req.futureId(),
+                req.miniId(),
+                req.firstClientRequest(),
+                req.topologyVersion(),
+                req.threadId(),
+                req.txTimeout(),
+                req.subjectId(),
+                req.taskNameHash());
+        }
+        catch (IgniteCheckedException | IgniteException ex) {
+            GridNearTxQueryEnlistResponse res = new GridNearTxQueryEnlistResponse(req.cacheId(),
+                req.futureId(),
+                req.miniId(),
+                req.version(),
+                ex);
+
+            try {
+                ctx.io().send(nearNode, res, ctx.ioPolicy());
+            }
+            catch (IgniteCheckedException e) {
+                U.error(log, "Failed to send near enlist response [" +
+                    "txId=" + req.version() +
+                    ", node=" + nodeId +
+                    ", res=" + res + ']', e);
+            }
+
+            return;
+        }
+
+        GridDhtTxQueryEnlistFuture fut = new GridDhtTxQueryEnlistFuture(
+            nodeId,
+            req.version(),
+            req.mvccSnapshot(),
+            req.threadId(),
+            req.futureId(),
+            req.miniId(),
+            tx,
+            req.cacheIds(),
+            req.partitions(),
+            req.schemaName(),
+            req.query(),
+            req.parameters(),
+            req.flags(),
+            req.pageSize(),
+            req.timeout(),
+            ctx);
+
+        fut.listen(NearTxQueryEnlistResultHandler.instance());
+
+        fut.init();
+    }
+
+    /**
+     * @param nodeId Node ID.
+     * @param res Response.
+     */
+    private void processNearEnlistResponse(UUID nodeId, final GridNearTxQueryEnlistResponse res) {
+        GridNearTxQueryEnlistFuture fut = (GridNearTxQueryEnlistFuture)ctx.mvcc().versionedFuture(res.version(), res.futureId());
+
+        if (fut != null)
+            fut.onResult(nodeId, res);
+    }
+
     /**
      * @param nodeId Node ID.
      * @param req Request.
@@ -1276,7 +1415,7 @@ private GridNearLockResponse createLockReply(
 
                                 CacheObject val = null;
 
-                                if (ret)
+                                if (ret) {
                                     val = e.innerGet(
                                         null,
                                         tx,
@@ -1287,7 +1426,9 @@ private GridNearLockResponse createLockReply(
                                         null,
                                         tx != null ? tx.resolveTaskName() : null,
                                         null,
-                                        req.keepBinary());
+                                        req.keepBinary(),
+                                        null); // TODO IGNITE-7371
+                                }
 
                                 assert e.lockedBy(mappedVer) || ctx.mvcc().isRemoved(e.context(), mappedVer) :
                                     "Entry does not own lock for tx [locNodeId=" + ctx.localNodeId() +
@@ -1380,6 +1521,7 @@ private void sendLockReply(
             U.error(log, "Failed to acquire lock for request: " + req, err);
 
         try {
+            // TODO Async rollback
             // Don't send reply message to this node or if lock was cancelled or tx was rolled back asynchronously.
             if (!nearNode.id().equals(ctx.nodeId()) && !X.hasCause(err, GridDistributedLockCancelledException.class) &&
                 !X.hasCause(err, IgniteTxRollbackCheckedException.class)) {
@@ -1576,7 +1718,7 @@ private void map(UUID nodeId,
      * @param nodes Nodes.
      * @param map Map.
      */
-    @SuppressWarnings( {"MismatchedQueryAndUpdateOfCollection"})
+    @SuppressWarnings({"MismatchedQueryAndUpdateOfCollection"})
     private void map(GridCacheEntryEx entry,
         @Nullable Iterable<? extends ClusterNode> nodes,
         Map<ClusterNode, List<KeyCacheObject>> map) {
@@ -1785,4 +1927,321 @@ private void obsoleteNearEntry(KeyCacheObject key) {
         if (nearEntry != null)
             nearEntry.markObsolete(ctx.versions().next());
     }
+
+    /**
+     * @param nodeId Node ID.
+     * @param req Request.
+     */
+    private void processNearTxQueryResultsEnlistRequest(UUID nodeId, final GridNearTxQueryResultsEnlistRequest req) {
+        assert nodeId != null;
+        assert req != null;
+
+        ClusterNode nearNode = ctx.discovery().node(nodeId);
+
+        GridDhtTxLocal tx;
+
+        try {
+            tx = initTxTopologyVersion(nodeId,
+                nearNode,
+                req.version(),
+                req.futureId(),
+                req.miniId(),
+                req.firstClientRequest(),
+                req.topologyVersion(),
+                req.threadId(),
+                req.txTimeout(),
+                req.subjectId(),
+                req.taskNameHash());
+        }
+        catch (IgniteCheckedException | IgniteException ex) {
+            GridNearTxQueryResultsEnlistResponse res = new GridNearTxQueryResultsEnlistResponse(req.cacheId(),
+                req.futureId(),
+                req.miniId(),
+                req.version(),
+                ex);
+
+            try {
+                ctx.io().send(nearNode, res, ctx.ioPolicy());
+            }
+            catch (IgniteCheckedException e) {
+                U.error(log, "Failed to send near enlist response [" +
+                    "txId=" + req.version() +
+                    ", node=" + nodeId +
+                    ", res=" + res + ']', e);
+            }
+
+            return;
+        }
+
+        GridDhtTxQueryResultsEnlistFuture fut = new GridDhtTxQueryResultsEnlistFuture(
+            nodeId,
+            req.version(),
+            req.mvccSnapshot(),
+            req.threadId(),
+            req.futureId(),
+            req.miniId(),
+            tx,
+            req.timeout(),
+            ctx,
+            req.rows(),
+            req.operation());
+
+        fut.listen(NearTxQueryEnlistResultHandler.instance());
+
+        fut.init();
+    }
+
+    /**
+     * @param nodeId Near node id.
+     * @param nearNode Near node.
+     * @param nearLockVer Near lock version.
+     * @param nearFutId Near future id.
+     * @param nearMiniId Near mini-future id.
+     * @param firstClientReq First client request flag.
+     * @param topVer Topology version.
+     * @param nearThreadId Near node thread id.
+     * @param timeout Timeout.
+     * @param txSubjectId Transaction subject id.
+     * @param txTaskNameHash Transaction task name hash.
+     * @return Transaction.
+     */
+    public GridDhtTxLocal initTxTopologyVersion(UUID nodeId,
+        ClusterNode nearNode,
+        GridCacheVersion nearLockVer,
+        IgniteUuid nearFutId,
+        int nearMiniId,
+        boolean firstClientReq,
+        AffinityTopologyVersion topVer,
+        long nearThreadId,
+        long timeout,
+        UUID txSubjectId,
+        int txTaskNameHash) throws IgniteException, IgniteCheckedException {
+
+        assert ctx.affinityNode();
+
+        if (txLockMsgLog.isDebugEnabled()) {
+            txLockMsgLog.debug("Received near enlist request [txId=" + nearLockVer +
+                ", node=" + nodeId + ']');
+        }
+
+        if (nearNode == null) {
+            U.warn(txLockMsgLog, "Received near enlist request from unknown node (will ignore) [txId=" + nearLockVer +
+                ", node=" + nodeId + ']');
+
+            return null;
+        }
+
+        GridDhtTxLocal tx = null;
+
+        GridCacheVersion dhtVer = ctx.tm().mappedVersion(nearLockVer);
+
+        if (dhtVer != null)
+            tx = ctx.tm().tx(dhtVer);
+
+        GridDhtPartitionTopology top = null;
+
+        if (tx == null) {
+            if (firstClientReq) {
+                assert nearNode.isClient();
+
+                top = topology();
+
+                top.readLock();
+
+                GridDhtTopologyFuture topFut = top.topologyVersionFuture();
+
+                if (!topFut.isDone() || !topFut.topologyVersion().equals(topVer)) {
+                    // TODO IGNITE-7164 Wait for topology change, remap client TX in case affinity was changed.
+                    top.readUnlock();
+
+                    throw new ClusterTopologyException("Topology was changed. Please retry on stable topology.");
+                }
+            }
+
+            try {
+                tx = new GridDhtTxLocal(
+                    ctx.shared(),
+                    topVer,
+                    nearNode.id(),
+                    nearLockVer,
+                    nearFutId,
+                    nearMiniId,
+                    nearThreadId,
+                    false,
+                    false,
+                    ctx.systemTx(),
+                    false,
+                    ctx.ioPolicy(),
+                    PESSIMISTIC,
+                    REPEATABLE_READ,
+                    timeout,
+                    false,
+                    false,
+                    false,
+                    -1,
+                    null,
+                    txSubjectId,
+                    txTaskNameHash);
+
+                // if (req.syncCommit())
+                tx.syncMode(FULL_SYNC);
+
+                tx = ctx.tm().onCreated(null, tx);
+
+                if (tx == null || !tx.init()) {
+                    String msg = "Failed to acquire lock (transaction has been completed): " +
+                        nearLockVer;
+
+                    U.warn(log, msg);
+
+                    try {
+                        if (tx != null)
+                            tx.rollbackDhtLocal();
+                    }
+                    catch (IgniteCheckedException ex) {
+                        U.error(log, "Failed to rollback the transaction: " + tx, ex);
+                    }
+
+                    throw new IgniteCheckedException(msg);
+                }
+
+                tx.topologyVersion(topVer);
+            }
+            finally {
+                if (top != null)
+                    top.readUnlock();
+            }
+        }
+
+        ctx.tm().txContext(tx);
+
+        return tx;
+    }
+
+    /**
+     * @param nodeId Node ID.
+     * @param res Response.
+     */
+    private void processNearTxEnlistResponse(UUID nodeId, final GridNearTxQueryResultsEnlistResponse res) {
+        GridNearTxQueryResultsEnlistFuture fut = (GridNearTxQueryResultsEnlistFuture)
+            ctx.mvcc().versionedFuture(res.version(), res.futureId());
+
+        if (fut != null)
+            fut.onResult(nodeId, res);
+    }
+
+    /**
+     * @param primary Primary node.
+     * @param req Request.
+     * @param e Error.
+     */
+    private void onError(UUID primary, GridDhtTxQueryEnlistRequest req, Throwable e) {
+        GridDhtTxQueryEnlistResponse res = new GridDhtTxQueryEnlistResponse(ctx.cacheId(),
+            req.dhtFutureId(),
+            req.batchId(),
+            e);
+
+        try {
+            ctx.io().send(primary, res, ctx.ioPolicy());
+        }
+        catch (IgniteCheckedException ioEx) {
+            U.error(log, "Failed to send DHT enlist reply to primary node [node: " + primary + ", req=" + req +
+                ']', ioEx);
+        }
+    }
+
+    /**
+     * @param primary Primary node.
+     * @param req Message.
+     * @param first Flag if this is a first request in current operation.
+     */
+    private void processDhtTxQueryEnlistRequest(UUID primary, GridDhtTxQueryEnlistRequest req, boolean first) {
+        try {
+            assert req.version() != null && req.op() != null;
+
+            GridDhtTxRemote tx = ctx.tm().tx(req.version());
+
+            if (tx == null) {
+                if (!first)
+                    throw new IgniteCheckedException("Can not find a transaction for version [version="
+                        + req.version() + ']');
+
+                GridDhtTxQueryFirstEnlistRequest req0 = (GridDhtTxQueryFirstEnlistRequest)req;
+
+                tx = new GridDhtTxRemote(ctx.shared(),
+                    req0.nearNodeId(),
+                    req0.dhtFutureId(),
+                    primary,
+                    req0.nearXidVersion(),
+                    req0.topologyVersion(),
+                    req0.version(),
+                    null,
+                    ctx.systemTx(),
+                    ctx.ioPolicy(),
+                    PESSIMISTIC,
+                    REPEATABLE_READ,
+                    false,
+                    req0.timeout(),
+                    -1,
+                    req0.subjectId(),
+                    req0.taskNameHash(),
+                    false);
+
+                tx.mvccSnapshot(new MvccSnapshotWithoutTxs(req0.coordinatorVersion(), req0.counter(),
+                    MVCC_OP_COUNTER_NA, req0.cleanupVersion()));
+
+                tx = ctx.tm().onCreated(null, tx);
+
+                if (tx == null || !ctx.tm().onStarted(tx)) {
+                    throw new IgniteTxRollbackCheckedException("Failed to update backup " +
+                        "(transaction has been completed): " + req0.version());
+                }
+            }
+
+            assert tx != null;
+
+            MvccSnapshot s0 = tx.mvccSnapshot();
+
+            MvccSnapshot snapshot = new MvccSnapshotWithoutTxs(s0.coordinatorVersion(), s0.counter(),
+                req.operationCounter(), s0.cleanupVersion());
+
+            tx.mvccEnlistBatch(ctx, req.op(), req.keys(), req.values(), snapshot, req.updateCounters());
+
+            GridDhtTxQueryEnlistResponse res = new GridDhtTxQueryEnlistResponse(req.cacheId(),
+                req.dhtFutureId(),
+                req.batchId(),
+                null);
+
+            try {
+                ctx.io().send(primary, res, ctx.ioPolicy());
+            }
+            catch (IgniteCheckedException ioEx) {
+                U.error(log, "Failed to send DHT enlist reply to primary node [node: " + primary + ", req=" +
+                    req + ']', ioEx);
+            }
+        }
+        catch (IgniteCheckedException e) {
+            onError(primary, req, e);
+        }
+    }
+
+    /**
+     * @param backup Backup node.
+     * @param res Response message.
+     */
+    private void processDhtTxQueryEnlistResponse(UUID backup, GridDhtTxQueryEnlistResponse res) {
+        GridDhtTxAbstractEnlistFuture fut = (GridDhtTxAbstractEnlistFuture)
+            ctx.mvcc().future(res.futureId());
+
+        if (fut == null) {
+            U.warn(log, "Received dht enlist response for unknown future [futId=" + res.futureId() +
+                ", batchId=" + res.batchId() +
+                ", node=" + backup + ']');
+
+            return;
+        }
+
+        fut.onResult(backup, res);
+    }
+
 }
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridDhtTxAbstractEnlistFuture.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridDhtTxAbstractEnlistFuture.java
new file mode 100644
index 000000000000..bb11df5590c6
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridDhtTxAbstractEnlistFuture.java
@@ -0,0 +1,1139 @@
+/*
+ * 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.cache.distributed.dht;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
+import org.apache.ignite.IgniteCheckedException;
+import org.apache.ignite.IgniteLogger;
+import org.apache.ignite.cluster.ClusterNode;
+import org.apache.ignite.internal.IgniteInternalFuture;
+import org.apache.ignite.internal.cluster.ClusterTopologyCheckedException;
+import org.apache.ignite.internal.pagemem.wal.WALPointer;
+import org.apache.ignite.internal.processors.affinity.AffinityTopologyVersion;
+import org.apache.ignite.internal.processors.cache.CacheEntryInfoCollection;
+import org.apache.ignite.internal.processors.cache.CacheObject;
+import org.apache.ignite.internal.processors.cache.GridCacheContext;
+import org.apache.ignite.internal.processors.cache.GridCacheEntryInfo;
+import org.apache.ignite.internal.processors.cache.GridCacheEntryRemovedException;
+import org.apache.ignite.internal.processors.cache.GridCacheFutureAdapter;
+import org.apache.ignite.internal.processors.cache.GridCacheMvccEntryInfo;
+import org.apache.ignite.internal.processors.cache.GridCacheUpdateTxResult;
+import org.apache.ignite.internal.processors.cache.KeyCacheObject;
+import org.apache.ignite.internal.processors.cache.distributed.GridDistributedTxMapping;
+import org.apache.ignite.internal.processors.cache.distributed.near.GridNearTxAbstractEnlistFuture;
+import org.apache.ignite.internal.processors.cache.distributed.near.GridNearTxSelectForUpdateFuture;
+import org.apache.ignite.internal.processors.cache.mvcc.MvccSnapshot;
+import org.apache.ignite.internal.processors.cache.mvcc.MvccUtils;
+import org.apache.ignite.internal.processors.cache.mvcc.txlog.TxState;
+import org.apache.ignite.internal.processors.cache.persistence.CacheDataRowAdapter;
+import org.apache.ignite.internal.processors.cache.query.IgniteQueryErrorCode;
+import org.apache.ignite.internal.processors.cache.tree.mvcc.data.MvccDataRow;
+import org.apache.ignite.internal.processors.cache.tree.mvcc.search.MvccLinkAwareSearchRow;
+import org.apache.ignite.internal.processors.cache.version.GridCacheVersion;
+import org.apache.ignite.internal.processors.query.EnlistOperation;
+import org.apache.ignite.internal.processors.query.IgniteSQLException;
+import org.apache.ignite.internal.processors.query.UpdateSourceIterator;
+import org.apache.ignite.internal.processors.timeout.GridTimeoutObjectAdapter;
+import org.apache.ignite.internal.transactions.IgniteTxTimeoutCheckedException;
+import org.apache.ignite.internal.util.GridLongList;
+import org.apache.ignite.internal.util.tostring.GridToStringExclude;
+import org.apache.ignite.internal.util.typedef.CI1;
+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;
+import org.apache.ignite.lang.IgniteBiTuple;
+import org.apache.ignite.lang.IgniteInClosure;
+import org.apache.ignite.lang.IgniteUuid;
+import org.apache.ignite.plugin.extensions.communication.Message;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Abstract future processing transaction enlisting and locking
+ * of entries produced with DML and SELECT FOR UPDATE queries.
+ */
+public abstract class GridDhtTxAbstractEnlistFuture extends GridCacheFutureAdapter<Long>
+    implements DhtLockFuture<Long> {
+    /** Done field updater. */
+    private static final AtomicIntegerFieldUpdater<GridDhtTxAbstractEnlistFuture> DONE_UPD =
+        AtomicIntegerFieldUpdater.newUpdater(GridDhtTxAbstractEnlistFuture.class, "done");
+
+    /** SkipCntr field updater. */
+    private static final AtomicIntegerFieldUpdater<GridDhtTxAbstractEnlistFuture> SKIP_UPD =
+        AtomicIntegerFieldUpdater.newUpdater(GridDhtTxAbstractEnlistFuture.class, "skipCntr");
+
+    /** Marker object. */
+    private static final Object FINISHED = new Object();
+
+    /** */
+    private static final int BATCH_SIZE = 1024;
+
+    /** In-flight batches per node limit. */
+    private static final int BATCHES_PER_NODE = 5;
+
+    /** */
+    private static final int FIRST_BATCH_ID = 0;
+
+    /** Future ID. */
+    protected final IgniteUuid futId;
+
+    /** Cache registry. */
+    @GridToStringExclude
+    protected final GridCacheContext<?, ?> cctx;
+
+    /** Logger. */
+    @GridToStringExclude
+    protected final IgniteLogger log;
+
+    /** Thread. */
+    protected final long threadId;
+
+    /** Future ID. */
+    protected final IgniteUuid nearFutId;
+
+    /** Future ID. */
+    protected final int nearMiniId;
+
+    /** Partitions. */
+    protected final int[] parts;
+
+    /** Transaction. */
+    protected final GridDhtTxLocalAdapter tx;
+
+    /** Lock version. */
+    protected final GridCacheVersion lockVer;
+
+    /** */
+    protected final MvccSnapshot mvccSnapshot;
+
+    /** Processed entries count. */
+    protected long cnt;
+
+    /** Near node ID. */
+    protected final UUID nearNodeId;
+
+    /** Near lock version. */
+    protected final GridCacheVersion nearLockVer;
+
+    /** Timeout object. */
+    @GridToStringExclude
+    protected LockTimeoutObject timeoutObj;
+
+    /** Lock timeout. */
+    protected final long timeout;
+
+    /** Query iterator */
+    private UpdateSourceIterator<?> it;
+
+    /** Row extracted from iterator but not yet used. */
+    private Object peek;
+
+    /** */
+    @SuppressWarnings({"FieldCanBeLocal"})
+    @GridToStringExclude
+    private volatile int skipCntr;
+
+    /** */
+    @SuppressWarnings("unused")
+    @GridToStringExclude
+    private volatile int done;
+
+    /** */
+    @GridToStringExclude
+    private int batchIdCntr;
+
+    /** Batches for sending to remote nodes. */
+    private Map<UUID, Batch> batches;
+
+    /** Batches already sent to remotes, but their acks are not received yet. */
+    private ConcurrentMap<UUID, ConcurrentMap<Integer, Batch>> pending;
+
+    /** */
+    private WALPointer walPtr;
+
+    /** Do not send DHT requests to near node. */
+    protected boolean skipNearNodeUpdates;
+
+    /** There are keys belonging to backup partitions on near node. */
+    protected boolean hasNearNodeUpdates;
+
+    /** Moving partitions. */
+    private Map<Integer, Boolean> movingParts;
+
+    /** Update counters to be sent to the near node in case it is a backup node also. */
+    protected GridLongList nearUpdCntrs;
+
+    /**
+     * @param nearNodeId Near node ID.
+     * @param nearLockVer Near lock version.
+     * @param mvccSnapshot Mvcc snapshot.
+     * @param threadId Thread ID.
+     * @param nearFutId Near future id.
+     * @param nearMiniId Near mini future id.
+     * @param parts Partitions.
+     * @param tx Transaction.
+     * @param timeout Lock acquisition timeout.
+     * @param cctx Cache context.
+     */
+    protected GridDhtTxAbstractEnlistFuture(UUID nearNodeId,
+        GridCacheVersion nearLockVer,
+        MvccSnapshot mvccSnapshot,
+        long threadId,
+        IgniteUuid nearFutId,
+        int nearMiniId,
+        @Nullable int[] parts,
+        GridDhtTxLocalAdapter tx,
+        long timeout,
+        GridCacheContext<?, ?> cctx) {
+        assert tx != null;
+        assert timeout >= 0;
+        assert nearNodeId != null;
+        assert nearLockVer != null;
+        assert threadId == tx.threadId();
+
+        this.threadId = threadId;
+        this.cctx = cctx;
+        this.nearNodeId = nearNodeId;
+        this.nearLockVer = nearLockVer;
+        this.nearFutId = nearFutId;
+        this.nearMiniId = nearMiniId;
+        this.mvccSnapshot = mvccSnapshot;
+        this.timeout = timeout;
+        this.tx = tx;
+        this.parts = parts;
+
+        lockVer = tx.xidVersion();
+
+        futId = IgniteUuid.randomUuid();
+
+        log = cctx.logger(GridDhtTxAbstractEnlistFuture.class);
+    }
+
+    /**
+     * @return iterator.
+     * @throws IgniteCheckedException If failed.
+     */
+    protected abstract UpdateSourceIterator<?> createIterator() throws IgniteCheckedException;
+
+    /**
+     *
+     */
+    public void init() {
+        if (timeout < 0) {
+            // Time is out.
+            onDone(timeoutException());
+
+            return;
+        }
+        else if (timeout > 0)
+            timeoutObj = new LockTimeoutObject();
+
+        while(true) {
+            IgniteInternalFuture<?> fut = tx.lockFut;
+
+            if (fut == GridDhtTxLocalAdapter.ROLLBACK_FUT) {
+                onDone(tx.timedOut() ? tx.timeoutException() : tx.rollbackException());
+
+                return;
+            }
+            else if (fut != null) {
+                // Wait for previous future.
+                assert fut instanceof GridNearTxAbstractEnlistFuture
+                    || fut instanceof GridDhtTxAbstractEnlistFuture
+                    || fut instanceof CompoundLockFuture
+                    || fut instanceof GridNearTxSelectForUpdateFuture : fut;
+
+                // Terminate this future if parent future is terminated by rollback.
+                if (!fut.isDone()) {
+                    fut.listen(new IgniteInClosure<IgniteInternalFuture>() {
+                        @Override public void apply(IgniteInternalFuture fut) {
+                            if (fut.error() != null)
+                                onDone(fut.error());
+                        }
+                    });
+                }
+                else if (fut.error() != null)
+                    onDone(fut.error());
+
+                break;
+            }
+            else if (tx.updateLockFuture(null, this))
+                break;
+        }
+
+        boolean added = cctx.mvcc().addFuture(this, futId);
+
+        assert added;
+
+        if (isDone()) {
+            cctx.mvcc().removeFuture(futId);
+
+            return;
+        }
+
+        if (timeoutObj != null)
+            cctx.time().addTimeoutObject(timeoutObj);
+
+        try {
+            checkPartitions(parts);
+
+            UpdateSourceIterator<?> it = createIterator();
+
+            if (!it.hasNext()) {
+                U.close(it, log);
+
+                onDone(0L);
+
+                return;
+            }
+
+            tx.addActiveCache(cctx, false);
+
+            this.it = it;
+        }
+        catch (Throwable e) {
+            onDone(e);
+
+            if (e instanceof Error)
+                throw (Error)e;
+
+            return;
+        }
+
+        continueLoop(false);
+    }
+
+    /**
+     * Clears lock future.
+     */
+    protected void clearLockFuture() {
+        tx.clearLockFuture(this);
+    }
+
+    /**
+     * Iterates over iterator, applies changes locally and sends it on backups.
+     *
+     * @param ignoreCntr {@code True} if need to ignore skip counter.
+     */
+    private void continueLoop(boolean ignoreCntr) {
+        if (isDone() || (!ignoreCntr && (SKIP_UPD.getAndIncrement(this) != 0)))
+            return;
+
+        GridDhtCacheAdapter cache = cctx.dhtCache();
+        EnlistOperation op = it.operation();
+        AffinityTopologyVersion topVer = tx.topologyVersionSnapshot();
+
+        try {
+            while (true) {
+                while (hasNext0()) {
+                    Object cur = next0();
+
+                    KeyCacheObject key = cctx.toCacheKeyObject(op.isDeleteOrLock() ? cur : ((IgniteBiTuple)cur).getKey());
+
+                    if (!ensureFreeSlot(key)) {
+                        // Can't advance further at the moment.
+                        peek = cur;
+
+                        it.beforeDetach();
+
+                        break;
+                    }
+
+                    GridDhtCacheEntry entry = cache.entryExx(key);
+
+                    if (log.isDebugEnabled())
+                        log.debug("Adding entry: " + entry);
+
+                    assert !entry.detached();
+
+                    CacheObject val = op.isDeleteOrLock() ? null : cctx.toCacheObject(((IgniteBiTuple)cur).getValue());
+
+                    tx.markQueryEnlisted(mvccSnapshot);
+
+                    GridCacheUpdateTxResult res;
+
+                    while (true) {
+                        cctx.shared().database().checkpointReadLock();
+
+                        try {
+                            switch (op) {
+                                case DELETE:
+                                    res = entry.mvccRemove(
+                                        tx,
+                                        cctx.localNodeId(),
+                                        topVer,
+                                        null,
+                                        mvccSnapshot,
+                                        isMoving(key.partition()));
+
+                                    break;
+
+                                case INSERT:
+                                case UPSERT:
+                                case UPDATE:
+                                    res = entry.mvccSet(
+                                        tx,
+                                        cctx.localNodeId(),
+                                        val,
+                                        0,
+                                        topVer,
+                                        null,
+                                        mvccSnapshot,
+                                        op.cacheOperation(),
+                                        isMoving(key.partition()),
+                                        op.noCreate());
+
+                                    break;
+
+                                case LOCK:
+                                    res = entry.mvccLock(
+                                        tx,
+                                        mvccSnapshot);
+
+                                    break;
+
+                                default:
+                                    throw new IgniteSQLException("Cannot acquire lock for operation [op= " + op + "]" +
+                                        "Operation is unsupported at the moment ", IgniteQueryErrorCode.UNSUPPORTED_OPERATION);
+                            }
+
+                            break;
+                        }
+                        catch (GridCacheEntryRemovedException ignored) {
+                            entry = cache.entryExx(entry.key(), topVer);
+                        }
+                        finally {
+                            cctx.shared().database().checkpointReadUnlock();
+                        }
+                    }
+
+                    IgniteInternalFuture<GridCacheUpdateTxResult> updateFut = res.updateFuture();
+
+                    if (updateFut != null) {
+                        if (updateFut.isDone())
+                            res = updateFut.get();
+                        else {
+                            CacheObject val0 = val;
+                            GridDhtCacheEntry entry0 = entry;
+
+                            it.beforeDetach();
+
+                            updateFut.listen(new CI1<IgniteInternalFuture<GridCacheUpdateTxResult>>() {
+                                @Override public void apply(IgniteInternalFuture<GridCacheUpdateTxResult> fut) {
+                                    try {
+                                        processEntry(entry0, op, fut.get(), val0);
+
+                                        continueLoop(true);
+                                    }
+                                    catch (Throwable e) {
+                                        onDone(e);
+                                    }
+                                }
+                            });
+
+                            // Can't move further. Exit loop without decrementing the counter.
+                            return;
+                        }
+                    }
+
+                    processEntry(entry, op, res, val);
+                }
+
+                if (!hasNext0()) {
+                    if (walPtr != null && !cctx.tm().logTxRecords()) {
+                        cctx.shared().wal().flush(walPtr, true);
+
+                        walPtr = null; // Avoid additional flushing.
+                    }
+
+                    if (!F.isEmpty(batches)) {
+                        // Flush incomplete batches.
+                        // Need to skip batches for nodes where first request (contains tx info) is still in-flight.
+                        // Otherwise, the regular enlist request (without tx info) may beat it to the primary node.
+                        Iterator<Map.Entry<UUID, Batch>> it = batches.entrySet().iterator();
+
+                        while (it.hasNext()) {
+                            Map.Entry<UUID, Batch> e = it.next();
+
+                            ConcurrentMap<Integer, Batch> pending0 =
+                                pending == null ? null : pending.get(e.getKey());
+
+                            if (pending0 == null || !pending0.containsKey(FIRST_BATCH_ID)) {
+                                it.remove();
+
+                                sendBatch(e.getValue());
+                            }
+                        }
+                    }
+
+                    if (noPendingRequests()) {
+                        onDone(cnt);
+
+                        return;
+                    }
+                }
+
+                if (SKIP_UPD.decrementAndGet(this) == 0)
+                    break;
+
+                skipCntr = 1;
+            }
+        }
+        catch (Throwable e) {
+            onDone(e);
+
+            if (e instanceof Error)
+                throw (Error)e;
+        }
+    }
+
+    /** */
+    private Object next0() {
+        if (!hasNext0())
+            throw new NoSuchElementException();
+
+        Object cur;
+
+        if ((cur = peek) != null)
+            peek = null;
+        else
+            cur = it.next();
+
+        return cur;
+    }
+
+    /** */
+    private boolean hasNext0() {
+        if (peek == null && !it.hasNext())
+            peek = FINISHED;
+
+        return peek != FINISHED;
+    }
+
+    /**
+     * @return {@code True} if in-flight batches map is empty.
+     */
+    private boolean noPendingRequests() {
+        if (F.isEmpty(pending))
+            return true;
+
+        for (ConcurrentMap<Integer, Batch> e : pending.values()) {
+            if (!e.isEmpty())
+                return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * @param entry Cache entry.
+     * @param op Operation.
+     * @param updRes Update result.
+     * @param val New value.
+     * @throws IgniteCheckedException If failed.
+     */
+    private void processEntry(GridDhtCacheEntry entry, EnlistOperation op,
+        GridCacheUpdateTxResult updRes, CacheObject val) throws IgniteCheckedException {
+        checkCompleted();
+
+        assert updRes != null && updRes.updateFuture() == null;
+
+        WALPointer ptr0 = updRes.loggedPointer();
+
+        if (ptr0 != null)
+            walPtr = ptr0;
+
+        if (!updRes.success())
+            return;
+
+        cnt++;
+
+        if (op != EnlistOperation.LOCK)
+            addToBatch(entry.key(), val, updRes.mvccHistory(), updRes.updateCounter(), entry.context().cacheId());
+    }
+
+    /**
+     * Adds row to batch.
+     * <b>IMPORTANT:</b> This method should be called from the critical section in {@link this.sendNextBatches()}
+     *
+     * @param key Key.
+     * @param val Value.
+     * @param hist History rows.
+     * @param updCntr Update counter.
+     */
+    private void addToBatch(KeyCacheObject key, CacheObject val, List<MvccLinkAwareSearchRow> hist, long updCntr,
+        int cacheId) throws IgniteCheckedException {
+        List<ClusterNode> backups = backupNodes(key);
+
+        int part = cctx.affinity().partition(key);
+
+        tx.addPartitionCountersMapping(cacheId, part);
+
+        if (F.isEmpty(backups))
+            return;
+
+        CacheEntryInfoCollection hist0 = null;
+
+        for (ClusterNode node : backups) {
+            assert !node.isLocal();
+
+            boolean moving = isMoving(node, part);
+
+            if (skipNearNodeUpdates && node.id().equals(nearNodeId) && !moving) {
+                updateMappings(node);
+
+                if (newRemoteTx(node))
+                    tx.addLockTransactionNode(node);
+
+                hasNearNodeUpdates = true;
+
+                if (nearUpdCntrs == null)
+                    nearUpdCntrs = new GridLongList();
+
+                nearUpdCntrs.add(updCntr);
+
+                continue;
+            }
+
+            Batch batch = null;
+
+            if (batches == null)
+                batches = new HashMap<>();
+            else
+                batch = batches.get(node.id());
+
+            if (batch == null)
+                batches.put(node.id(), batch = new Batch(node));
+
+            if (moving && hist0 == null) {
+                assert !F.isEmpty(hist);
+
+                hist0 = fetchHistoryInfo(key, hist);
+            }
+
+            batch.add(key, moving ? hist0 : val, updCntr);
+
+            if (batch.size() == BATCH_SIZE) {
+                assert batches != null;
+
+                batches.remove(node.id());
+
+                sendBatch(batch);
+            }
+        }
+    }
+
+    /**
+     *
+     * @param key Key.
+     * @param hist History rows.
+     * @return History entries.
+     * @throws IgniteCheckedException, if failed.
+     */
+    private CacheEntryInfoCollection fetchHistoryInfo(KeyCacheObject key, List<MvccLinkAwareSearchRow> hist)
+        throws IgniteCheckedException {
+        List<GridCacheEntryInfo> res = new ArrayList<>();
+
+        for (int i = 0; i < hist.size(); i++) {
+            MvccLinkAwareSearchRow row0 = hist.get(i);
+
+            MvccDataRow row = new MvccDataRow(cctx.group(),
+                row0.hash(),
+                row0.link(),
+                key.partition(),
+                CacheDataRowAdapter.RowData.NO_KEY,
+                row0.mvccCoordinatorVersion(),
+                row0.mvccCounter(),
+                row0.mvccOperationCounter());
+
+            GridCacheMvccEntryInfo entry = new GridCacheMvccEntryInfo();
+
+            entry.version(row.version());
+            entry.mvccVersion(row);
+            entry.newMvccVersion(row);
+            entry.value(row.value());
+            entry.expireTime(row.expireTime());
+
+            if (MvccUtils.compare(mvccSnapshot, row.mvccCoordinatorVersion(), row.mvccCounter()) != 0) {
+                entry.mvccTxState(row.mvccTxState() != TxState.NA ? row.mvccTxState() :
+                    MvccUtils.state(cctx, row.mvccCoordinatorVersion(), row.mvccCounter(), row.mvccOperationCounter()));
+            }
+
+            if (MvccUtils.compare(mvccSnapshot, row.newMvccCoordinatorVersion(), row.newMvccCounter()) != 0) {
+                entry.newMvccTxState(row.newMvccTxState() != TxState.NA ? row.newMvccTxState() :
+                    MvccUtils.state(cctx, row.newMvccCoordinatorVersion(), row.newMvccCounter(),
+                    row.newMvccOperationCounter()));
+            }
+
+            res.add(entry);
+        }
+
+        return new CacheEntryInfoCollection(res);
+    }
+
+    /** */
+    private boolean newRemoteTx(ClusterNode node) {
+        Set<ClusterNode> nodes = tx.lockTransactionNodes();
+
+        return nodes == null || !nodes.contains(node);
+    }
+
+    /**
+     * Checks if there free space in batches or free slot in in-flight batches is available for the given key.
+     *
+     * @param key Key.
+     * @return {@code True} if there is possible to add this key to batch or send ready batch.
+     */
+    @SuppressWarnings("ForLoopReplaceableByForEach")
+    private boolean ensureFreeSlot(KeyCacheObject key) {
+        if (F.isEmpty(batches) || F.isEmpty(pending))
+            return true;
+
+        // Check possibility of adding to batch and sending.
+        for (ClusterNode node : backupNodes(key)) {
+            if (skipNearNodeUpdates && node.id().equals(nearNodeId) && !isMoving(node, key.partition()))
+                continue;
+
+            Batch batch = batches.get(node.id());
+
+            // We can add key if batch is not full.
+            if (batch == null || batch.size() < BATCH_SIZE - 1)
+                continue;
+
+            ConcurrentMap<Integer, Batch> pending0 = pending.get(node.id());
+
+            assert pending0 == null || pending0.size() <= BATCHES_PER_NODE;
+
+            if (pending0 != null && (pending0.containsKey(FIRST_BATCH_ID) || pending0.size() == BATCHES_PER_NODE))
+                return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Send batch request to remote data node.
+     *
+     * @param batch Batch.
+     */
+    private void sendBatch(Batch batch) throws IgniteCheckedException {
+        assert batch != null && !batch.node().isLocal();
+
+        ClusterNode node = batch.node();
+
+        updateMappings(node);
+
+        GridDhtTxQueryEnlistRequest req;
+
+        if (newRemoteTx(node)) {
+            tx.addLockTransactionNode(node);
+
+            // If this is a first request to this node, send full info.
+            req = new GridDhtTxQueryFirstEnlistRequest(cctx.cacheId(),
+                futId,
+                cctx.localNodeId(),
+                tx.topologyVersionSnapshot(),
+                lockVer,
+                mvccSnapshot,
+                tx.remainingTime(),
+                tx.taskNameHash(),
+                nearNodeId,
+                nearLockVer,
+                it.operation(),
+                FIRST_BATCH_ID,
+                batch.keys(),
+                batch.values(),
+                batch.updateCounters());
+        }
+        else {
+            // Send only keys, values, LockVersion and batchId if this is not a first request to this backup.
+            req = new GridDhtTxQueryEnlistRequest(cctx.cacheId(),
+                futId,
+                lockVer,
+                it.operation(),
+                ++batchIdCntr,
+                mvccSnapshot.operationCounter(),
+                batch.keys(),
+                batch.values(),
+                batch.updateCounters());
+        }
+
+        ConcurrentMap<Integer, Batch> pending0 = null;
+
+        if (pending == null)
+            pending = new ConcurrentHashMap<>();
+        else
+            pending0 = pending.get(node.id());
+
+        if (pending0 == null)
+            pending.put(node.id(), pending0 = new ConcurrentHashMap<>());
+
+        Batch prev = pending0.put(req.batchId(), batch);
+
+        assert prev == null;
+
+        cctx.io().send(node, req, cctx.ioPolicy());
+    }
+
+    /** */
+    private synchronized void updateMappings(ClusterNode node) throws IgniteCheckedException {
+        checkCompleted();
+
+        Map<UUID, GridDistributedTxMapping> m = tx.dhtMap;
+
+        GridDistributedTxMapping mapping = m.get(node.id());
+
+        if (mapping == null)
+            m.put(node.id(), mapping = new GridDistributedTxMapping(node));
+
+        mapping.markQueryUpdate();
+    }
+
+    /**
+     * @param key Key.
+     * @return Backup nodes for the given key.
+     */
+    @NotNull private List<ClusterNode> backupNodes(KeyCacheObject key) {
+        List<ClusterNode> dhtNodes = cctx.affinity().nodesByKey(key, tx.topologyVersion());
+
+        assert !dhtNodes.isEmpty() && dhtNodes.get(0).id().equals(cctx.localNodeId()) :
+            "localNode = " + cctx.localNodeId() + ", dhtNodes = " + dhtNodes;
+
+        if (dhtNodes.size() == 1)
+            return Collections.emptyList();
+
+        return dhtNodes.subList(1, dhtNodes.size());
+    }
+
+    /**
+     * Checks whether all the necessary partitions are in {@link GridDhtPartitionState#OWNING} state.
+     *
+     * @param parts Partitions.
+     * @throws ClusterTopologyCheckedException If failed.
+     */
+    @SuppressWarnings("ForLoopReplaceableByForEach")
+    private void checkPartitions(@Nullable int[] parts) throws ClusterTopologyCheckedException {
+        if (cctx.isLocal() || !cctx.rebalanceEnabled())
+            return;
+
+        if (parts == null)
+            parts = U.toIntArray(
+                cctx.affinity()
+                    .primaryPartitions(cctx.localNodeId(), tx.topologyVersionSnapshot()));
+
+        GridDhtPartitionTopology top = cctx.topology();
+
+        try {
+            top.readLock();
+
+            for (int i = 0; i < parts.length; i++) {
+                GridDhtLocalPartition p = top.localPartition(parts[i]);
+
+                if (p == null || p.state() != GridDhtPartitionState.OWNING)
+                    throw new ClusterTopologyCheckedException("Cannot run update query. " +
+                        "Node must own all the necessary partitions."); // TODO IGNITE-7185 Send retry instead.
+            }
+        }
+        finally {
+            top.readUnlock();
+        }
+    }
+
+    /**
+     * @param part Partition.
+     * @return {@code true} if the given partition is rebalancing to any backup node.
+     */
+    private boolean isMoving(int part) {
+        if (movingParts == null)
+            movingParts = new HashMap<>();
+
+        Boolean res = movingParts.get(part);
+
+        if (res != null)
+            return res;
+
+        List<ClusterNode> dhtNodes = cctx.affinity().nodesByPartition(part, tx.topologyVersion());
+
+        for (int i = 1; i < dhtNodes.size(); i++) {
+            ClusterNode node = dhtNodes.get(i);
+            if (isMoving(node, part)) {
+                movingParts.put(part, Boolean.TRUE);
+
+                return true;
+            }
+        }
+
+        movingParts.put(part, Boolean.FALSE);
+
+        return false;
+    }
+
+    /**
+     * @param node Cluster node.
+     * @param part Partition.
+     * @return {@code true} if the given partition is rebalancing to the given node.
+     */
+    private boolean isMoving(ClusterNode node, int part) {
+        GridDhtPartitionState partState = cctx.topology().partitionState(node.id(), part);
+
+        return partState != GridDhtPartitionState.OWNING && partState != GridDhtPartitionState.EVICTED;
+    }
+
+    /** */
+    private void checkCompleted() throws IgniteCheckedException {
+        if (isDone())
+            throw new IgniteCheckedException("Future is done.");
+    }
+
+    /**
+     * Callback on backup response.
+     *
+     * @param nodeId Backup node.
+     * @param res Response.
+     */
+    public void onResult(UUID nodeId, GridDhtTxQueryEnlistResponse res) {
+        if (res.error() != null) {
+            onDone(new IgniteCheckedException("Failed to update backup node: [localNodeId=" + cctx.localNodeId() +
+                ", remoteNodeId=" + nodeId + ']', res.error()));
+
+            return;
+        }
+
+        assert pending != null;
+
+        ConcurrentMap<Integer, Batch> pending0 = pending.get(nodeId);
+
+        assert pending0 != null;
+
+        Batch rmv = pending0.remove(res.batchId());
+
+        assert rmv != null;
+
+        continueLoop(false);
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean trackable() {
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void markNotTrackable() {
+        // No-op.
+    }
+
+    /** {@inheritDoc} */
+    @Override public IgniteUuid futureId() {
+        return futId;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean onNodeLeft(UUID nodeId) {
+        boolean backupLeft = false;
+
+        Set<ClusterNode> nodes = tx.lockTransactionNodes();
+
+        if (!F.isEmpty(nodes)) {
+            for (ClusterNode node : nodes) {
+                if (node.id().equals(nodeId)) {
+                    backupLeft = true;
+
+                    break;
+                }
+            }
+        }
+
+        return (backupLeft || nearNodeId.equals(nodeId)) && onDone(
+            new ClusterTopologyCheckedException((backupLeft ? "Backup" : "Requesting") +
+                " node left the grid [nodeId=" + nodeId + ']'));
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean onDone(@Nullable Long res, @Nullable Throwable err) {
+        assert res != null || err != null;
+
+        if (!DONE_UPD.compareAndSet(this, 0, 1))
+            return false;
+
+        if (err == null)
+            clearLockFuture();
+
+        // To prevent new remote transactions creation
+        // after future is cancelled by rollback.
+        synchronized (this) {
+            boolean done = super.onDone(res, err);
+
+            assert done;
+
+            if (log.isDebugEnabled())
+                log.debug("Completing future: " + this);
+
+            // Clean up.
+            cctx.mvcc().removeFuture(futId);
+
+            if (timeoutObj != null)
+                cctx.time().removeTimeoutObject(timeoutObj);
+
+            U.close(it, log);
+
+            return true;
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override public void onError(Throwable error) {
+        onDone(error);
+    }
+
+    /**
+     * @return Timeout exception.
+     */
+    @NotNull protected IgniteTxTimeoutCheckedException timeoutException() {
+        return new IgniteTxTimeoutCheckedException("Failed to acquire lock within provided timeout for " +
+            "transaction [timeout=" + timeout + ", tx=" + tx + ']');
+    }
+
+    /**
+     * A batch of rows
+     */
+    private static class Batch {
+        /** Node ID. */
+        @GridToStringExclude
+        private final ClusterNode node;
+
+        /** */
+        private List<KeyCacheObject> keys;
+
+        /**
+         * Values collection.
+         * Items can be either {@link CacheObject} or preload entries collection {@link CacheEntryInfoCollection}.
+         */
+        private List<Message> vals;
+
+        /** Update counters. */
+        private GridLongList updCntrs;
+
+        /**
+         * @param node Cluster node.
+         */
+        private Batch(ClusterNode node) {
+            this.node = node;
+        }
+
+        /**
+         * @return Node.
+         */
+        public ClusterNode node() {
+            return node;
+        }
+
+        /**
+         * Adds a row to batch.
+         *
+         * @param key Key.
+         * @param val Value or preload entries collection.
+         */
+        public void add(KeyCacheObject key, Message val, long updCntr) {
+            assert val == null || val instanceof CacheObject || val instanceof CacheEntryInfoCollection;
+            assert updCntr > 0;
+
+            if (keys == null)
+                keys = new ArrayList<>();
+
+            keys.add(key);
+
+            if (val != null) {
+                if (vals == null)
+                    vals = new ArrayList<>();
+
+                vals.add(val);
+            }
+
+            if (updCntrs == null)
+                updCntrs = new GridLongList();
+
+            updCntrs.add(updCntr);
+
+            assert (vals == null) || keys.size() == vals.size();
+        }
+
+        /**
+         * @return number of rows.
+         */
+        public int size() {
+            return keys == null ? 0 : keys.size();
+        }
+
+        /**
+         * @return Collection of row keys.
+         */
+        public List<KeyCacheObject> keys() {
+            return keys;
+        }
+
+        /**
+         * @return Collection of row values.
+         */
+        public List<Message> values() {
+            return vals;
+        }
+
+        /**
+         * @return Update counters.
+         */
+        public GridLongList updateCounters() {
+            return updCntrs;
+        }
+    }
+
+    /**
+     * Lock request timeout object.
+     */
+    protected class LockTimeoutObject extends GridTimeoutObjectAdapter {
+        /**
+         * Default constructor.
+         */
+        LockTimeoutObject() {
+            super(timeout);
+        }
+
+        /** {@inheritDoc} */
+        @Override public void onTimeout() {
+            if (log.isDebugEnabled())
+                log.debug("Timed out waiting for lock response: " + this);
+
+            onDone(timeoutException());
+        }
+
+        /** {@inheritDoc} */
+        @Override public String toString() {
+            return S.toString(LockTimeoutObject.class, this);
+        }
+    }
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridDhtTxFinishFuture.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridDhtTxFinishFuture.java
index 2f36053b9d83..c0a38452b279 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridDhtTxFinishFuture.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridDhtTxFinishFuture.java
@@ -26,19 +26,23 @@
 import org.apache.ignite.IgniteCheckedException;
 import org.apache.ignite.IgniteLogger;
 import org.apache.ignite.cluster.ClusterNode;
-import org.apache.ignite.internal.InvalidEnvironmentException;
 import org.apache.ignite.internal.IgniteDiagnosticAware;
 import org.apache.ignite.internal.IgniteDiagnosticPrepareContext;
 import org.apache.ignite.internal.IgniteInternalFuture;
+import org.apache.ignite.internal.InvalidEnvironmentException;
 import org.apache.ignite.internal.NodeStoppingException;
 import org.apache.ignite.internal.cluster.ClusterTopologyCheckedException;
 import org.apache.ignite.internal.processors.cache.GridCacheCompoundIdentityFuture;
 import org.apache.ignite.internal.processors.cache.GridCacheFuture;
 import org.apache.ignite.internal.processors.cache.GridCacheSharedContext;
 import org.apache.ignite.internal.processors.cache.distributed.GridDistributedTxMapping;
+import org.apache.ignite.internal.processors.cache.mvcc.MvccCoordinator;
+import org.apache.ignite.internal.processors.cache.mvcc.MvccFuture;
+import org.apache.ignite.internal.processors.cache.mvcc.MvccSnapshot;
 import org.apache.ignite.internal.processors.cache.transactions.IgniteInternalTx;
 import org.apache.ignite.internal.processors.cache.transactions.IgniteTxEntry;
 import org.apache.ignite.internal.processors.cache.version.GridCacheVersion;
+import org.apache.ignite.internal.util.GridLongList;
 import org.apache.ignite.internal.util.future.GridFutureAdapter;
 import org.apache.ignite.internal.util.tostring.GridToStringExclude;
 import org.apache.ignite.internal.util.tostring.GridToStringInclude;
@@ -284,6 +288,8 @@ private void onComplete() {
     public void finish(boolean commit) {
         boolean sync;
 
+        assert !tx.queryEnlisted() || tx.mvccSnapshot() != null;
+
         if (!F.isEmpty(dhtMap) || !F.isEmpty(nearMap))
             sync = finish(commit, dhtMap, nearMap);
         else if (!commit && !F.isEmpty(tx.lockTransactionNodes()))
@@ -292,6 +298,22 @@ else if (!commit && !F.isEmpty(tx.lockTransactionNodes()))
             // No backup or near nodes to send commit message to (just complete then).
             sync = false;
 
+        GridLongList waitTxs = tx.mvccWaitTransactions();
+
+        if (waitTxs != null) {
+            MvccSnapshot snapshot = tx.mvccSnapshot();
+
+            assert snapshot != null;
+
+            MvccCoordinator crd = cctx.coordinators().currentCoordinator();
+
+            if (crd != null && crd.coordinatorVersion() == snapshot.coordinatorVersion()) {
+                add((IgniteInternalFuture)cctx.coordinators().waitTxsFuture(crd.nodeId(), waitTxs));
+
+                sync = true;
+            }
+        }
+
         markInitialized();
 
         if (!sync)
@@ -310,7 +332,7 @@ private boolean rollbackLockTransactions(Collection<ClusterNode> nodes) {
 
         boolean sync = tx.syncMode() == FULL_SYNC;
 
-        if (tx.explicitLock())
+        if (tx.explicitLock() || tx.queryEnlisted())
             sync = true;
 
         boolean res = false;
@@ -348,7 +370,8 @@ private boolean rollbackLockTransactions(Collection<ClusterNode> nodes) {
                 tx.taskNameHash(),
                 tx.activeCachesDeploymentEnabled(),
                 false,
-                false);
+                false,
+                tx.mvccSnapshot());
 
             try {
                 cctx.io().send(n, req, tx.ioPolicy());
@@ -396,15 +419,23 @@ private boolean finish(boolean commit,
         if (tx.onePhaseCommit())
             return false;
 
+        assert !commit || !tx.txState().mvccEnabled(cctx) || tx.mvccSnapshot() != null || F.isEmpty(tx.writeEntries());
+
         boolean sync = tx.syncMode() == FULL_SYNC;
 
-        if (tx.explicitLock())
+        if (tx.explicitLock() || tx.queryEnlisted())
             sync = true;
 
         boolean res = false;
 
         int miniId = 0;
 
+        // Do not need process active transactions on backups.
+        MvccSnapshot mvccSnapshot = tx.mvccSnapshot();
+
+        if (mvccSnapshot != null)
+            mvccSnapshot = mvccSnapshot.withoutActiveTransactions();
+
         // Create mini futures.
         for (GridDistributedTxMapping dhtMapping : dhtMap.values()) {
             ClusterNode n = dhtMapping.primary();
@@ -413,7 +444,7 @@ private boolean finish(boolean commit,
 
             GridDistributedTxMapping nearMapping = nearMap.get(n.id());
 
-            if (dhtMapping.empty() && nearMapping != null && nearMapping.empty())
+            if (!dhtMapping.queryUpdate() && dhtMapping.empty() && nearMapping != null && nearMapping.empty())
                 // Nothing to send.
                 continue;
 
@@ -426,6 +457,11 @@ private boolean finish(boolean commit,
             for (IgniteTxEntry e : dhtMapping.entries())
                 updCntrs.add(e.updateCounter());
 
+            Map<Integer, GridDhtPartitionsUpdateCountersMap> updCntrsMap = null;
+
+            if (dhtMapping.queryUpdate() && commit)
+                updCntrsMap = tx.updateCountersForNode(n);
+
             GridDhtTxFinishRequest req = new GridDhtTxFinishRequest(
                 tx.nearNodeId(),
                 futId,
@@ -451,7 +487,9 @@ private boolean finish(boolean commit,
                 tx.activeCachesDeploymentEnabled(),
                 updCntrs,
                 false,
-                false);
+                false,
+                mvccSnapshot,
+                updCntrsMap);
 
             req.writeVersion(tx.writeVersion() != null ? tx.writeVersion() : tx.xidVersion());
 
@@ -520,7 +558,8 @@ private boolean finish(boolean commit,
                     tx.taskNameHash(),
                     tx.activeCachesDeploymentEnabled(),
                     false,
-                    false);
+                    false,
+                    mvccSnapshot);
 
                 req.writeVersion(tx.writeVersion());
 
@@ -565,22 +604,35 @@ private boolean finish(boolean commit,
         if (!isDone()) {
             for (IgniteInternalFuture fut : futures()) {
                 if (!fut.isDone()) {
-                    MiniFuture f = (MiniFuture)fut;
-
-                    if (!f.node().isLocal()) {
-                        GridCacheVersion dhtVer = tx.xidVersion();
-                        GridCacheVersion nearVer = tx.nearXidVersion();
+                    if (MiniFuture.class.isInstance(fut)) {
+                        MiniFuture f = (MiniFuture)fut;
+
+                        if (!f.node().isLocal()) {
+                            GridCacheVersion dhtVer = tx.xidVersion();
+                            GridCacheVersion nearVer = tx.nearXidVersion();
+
+                            ctx.remoteTxInfo(f.node().id(), dhtVer, nearVer, "GridDhtTxFinishFuture " +
+                                "waiting for response [node=" + f.node().id() +
+                                ", topVer=" + tx.topologyVersion() +
+                                ", dhtVer=" + dhtVer +
+                                ", nearVer=" + nearVer +
+                                ", futId=" + futId +
+                                ", miniId=" + f.futId +
+                                ", tx=" + tx + ']');
+
+                            return;
+                        }
+                    }
+                    else if (fut instanceof MvccFuture) {
+                        MvccFuture f = (MvccFuture)fut;
 
-                        ctx.remoteTxInfo(f.node().id(), dhtVer, nearVer, "GridDhtTxFinishFuture " +
-                            "waiting for response [node=" + f.node().id() +
-                            ", topVer=" + tx.topologyVersion() +
-                            ", dhtVer=" + dhtVer +
-                            ", nearVer=" + nearVer +
-                            ", futId=" + futId +
-                            ", miniId=" + f.futId +
-                            ", tx=" + tx + ']');
+                        if (!cctx.localNodeId().equals(f.coordinatorNodeId())) {
+                            ctx.basicInfo(f.coordinatorNodeId(), "GridDhtTxFinishFuture " +
+                                "waiting for mvcc coordinator reply [mvccCrdNode=" + f.coordinatorNodeId() +
+                                ", loc=" + f.coordinatorNodeId().equals(cctx.localNodeId()) + ']');
 
-                        return;
+                            return;
+                        }
                     }
                 }
             }
@@ -592,9 +644,20 @@ private boolean finish(boolean commit,
         Collection<String> futs = F.viewReadOnly(futures(), new C1<IgniteInternalFuture<?>, String>() {
             @SuppressWarnings("unchecked")
             @Override public String apply(IgniteInternalFuture<?> f) {
-                return "[node=" + ((MiniFuture)f).node().id() +
-                    ", loc=" + ((MiniFuture)f).node().isLocal() +
-                    ", done=" + f.isDone() + "]";
+                if (f.getClass() == MiniFuture.class) {
+                    return "[node=" + ((MiniFuture)f).node().id() +
+                        ", loc=" + ((MiniFuture)f).node().isLocal() +
+                        ", done=" + f.isDone() + "]";
+                }
+                else if (f instanceof MvccFuture) {
+                    MvccFuture crdFut = (MvccFuture)f;
+
+                    return "[mvccCrdNode=" + crdFut.coordinatorNodeId() +
+                        ", loc=" + crdFut.coordinatorNodeId().equals(cctx.localNodeId()) +
+                        ", done=" + f.isDone() + "]";
+                }
+                else
+                    return f.toString();
             }
         });
 
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridDhtTxFinishRequest.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridDhtTxFinishRequest.java
index 823b5fee5ed9..2696c0e0621d 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridDhtTxFinishRequest.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridDhtTxFinishRequest.java
@@ -20,11 +20,14 @@
 import java.io.Externalizable;
 import java.nio.ByteBuffer;
 import java.util.Collection;
+import java.util.Map;
 import java.util.UUID;
 import org.apache.ignite.cache.CacheWriteSynchronizationMode;
 import org.apache.ignite.internal.GridDirectCollection;
+import org.apache.ignite.internal.GridDirectMap;
 import org.apache.ignite.internal.processors.affinity.AffinityTopologyVersion;
 import org.apache.ignite.internal.processors.cache.distributed.GridDistributedTxFinishRequest;
+import org.apache.ignite.internal.processors.cache.mvcc.MvccSnapshot;
 import org.apache.ignite.internal.processors.cache.version.GridCacheVersion;
 import org.apache.ignite.internal.util.GridLongList;
 import org.apache.ignite.internal.util.tostring.GridToStringInclude;
@@ -67,6 +70,12 @@
     /** One phase commit write version. */
     private GridCacheVersion writeVer;
 
+    /** */
+    private MvccSnapshot mvccSnapshot;
+
+    /** Map of update counters made by this tx. Mapping: cacheId -> partId -> updCntr. */
+    @GridDirectMap(keyType = Integer.class, valueType = GridDhtPartitionsUpdateCountersMap.class)
+    private Map<Integer, GridDhtPartitionsUpdateCountersMap> updCntrs;
     /**
      * Empty constructor required for {@link Externalizable}.
      */
@@ -122,7 +131,8 @@ public GridDhtTxFinishRequest(
         int taskNameHash,
         boolean addDepInfo,
         boolean retVal,
-        boolean waitRemoteTxs
+        boolean waitRemoteTxs,
+        MvccSnapshot mvccSnapshot
     ) {
         super(
             xidVer,
@@ -151,6 +161,7 @@ public GridDhtTxFinishRequest(
         this.nearNodeId = nearNodeId;
         this.isolation = isolation;
         this.miniId = miniId;
+        this.mvccSnapshot = mvccSnapshot;
 
         needReturnValue(retVal);
         waitRemoteTransactions(waitRemoteTxs);
@@ -207,7 +218,9 @@ public GridDhtTxFinishRequest(
         boolean addDepInfo,
         Collection<Long> updateIdxs,
         boolean retVal,
-        boolean waitRemoteTxs
+        boolean waitRemoteTxs,
+        MvccSnapshot mvccSnapshot,
+        Map<Integer, GridDhtPartitionsUpdateCountersMap> updCntrs
     ) {
         this(nearNodeId,
             futId,
@@ -232,7 +245,8 @@ public GridDhtTxFinishRequest(
             taskNameHash,
             addDepInfo,
             retVal,
-            waitRemoteTxs);
+            waitRemoteTxs,
+            mvccSnapshot);
 
         if (updateIdxs != null && !updateIdxs.isEmpty()) {
             partUpdateCnt = new GridLongList(updateIdxs.size());
@@ -240,6 +254,15 @@ public GridDhtTxFinishRequest(
             for (Long idx : updateIdxs)
                 partUpdateCnt.add(idx);
         }
+
+        this.updCntrs = updCntrs;
+    }
+
+    /**
+     * @return Counter.
+     */
+    public MvccSnapshot mvccSnapshot() {
+        return mvccSnapshot;
     }
 
     /**
@@ -339,6 +362,12 @@ public boolean needReturnValue() {
     public void needReturnValue(boolean retVal) {
         setFlag(retVal, NEED_RETURN_VALUE_FLAG_MASK);
     }
+    /**
+     * @return Partition update counters map.
+     */
+    public Map<Integer, GridDhtPartitionsUpdateCountersMap> updateCountersMap() {
+        return updCntrs;
+    }
 
     /** {@inheritDoc} */
     @Override public boolean writeTo(ByteBuffer buf, MessageWriter writer) {
@@ -368,24 +397,36 @@ public void needReturnValue(boolean retVal) {
                 writer.incrementState();
 
             case 23:
-                if (!writer.writeUuid("nearNodeId", nearNodeId))
+                if (!writer.writeMessage("mvccSnapshot", mvccSnapshot))
                     return false;
 
                 writer.incrementState();
 
             case 24:
-                if (!writer.writeMessage("partUpdateCnt", partUpdateCnt))
+                if (!writer.writeUuid("nearNodeId", nearNodeId))
                     return false;
 
                 writer.incrementState();
 
             case 25:
-                if (!writer.writeCollection("pendingVers", pendingVers, MessageCollectionItemType.MSG))
+                if (!writer.writeMessage("partUpdateCnt", partUpdateCnt))
                     return false;
 
                 writer.incrementState();
 
             case 26:
+                if (!writer.writeCollection("pendingVers", pendingVers, MessageCollectionItemType.MSG))
+                    return false;
+
+                writer.incrementState();
+
+            case 27:
+                if (!writer.writeMap("updCntrs", updCntrs, MessageCollectionItemType.INT, MessageCollectionItemType.MSG))
+                    return false;
+
+                writer.incrementState();
+
+            case 28:
                 if (!writer.writeMessage("writeVer", writeVer))
                     return false;
 
@@ -428,7 +469,7 @@ public void needReturnValue(boolean retVal) {
                 reader.incrementState();
 
             case 23:
-                nearNodeId = reader.readUuid("nearNodeId");
+                mvccSnapshot = reader.readMessage("mvccSnapshot");
 
                 if (!reader.isLastRead())
                     return false;
@@ -436,7 +477,7 @@ public void needReturnValue(boolean retVal) {
                 reader.incrementState();
 
             case 24:
-                partUpdateCnt = reader.readMessage("partUpdateCnt");
+                nearNodeId = reader.readUuid("nearNodeId");
 
                 if (!reader.isLastRead())
                     return false;
@@ -444,7 +485,7 @@ public void needReturnValue(boolean retVal) {
                 reader.incrementState();
 
             case 25:
-                pendingVers = reader.readCollection("pendingVers", MessageCollectionItemType.MSG);
+                partUpdateCnt = reader.readMessage("partUpdateCnt");
 
                 if (!reader.isLastRead())
                     return false;
@@ -452,6 +493,22 @@ public void needReturnValue(boolean retVal) {
                 reader.incrementState();
 
             case 26:
+                pendingVers = reader.readCollection("pendingVers", MessageCollectionItemType.MSG);
+
+                if (!reader.isLastRead())
+                    return false;
+
+                reader.incrementState();
+
+            case 27:
+                updCntrs = reader.readMap("updCntrs", MessageCollectionItemType.INT, MessageCollectionItemType.MSG, false);
+
+                if (!reader.isLastRead())
+                    return false;
+
+                reader.incrementState();
+
+            case 28:
                 writeVer = reader.readMessage("writeVer");
 
                 if (!reader.isLastRead())
@@ -471,7 +528,7 @@ public void needReturnValue(boolean retVal) {
 
     /** {@inheritDoc} */
     @Override public byte fieldsCount() {
-        return 27;
+        return 29;
     }
 
     /** {@inheritDoc} */
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridDhtTxLocal.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridDhtTxLocal.java
index 2e19df291150..a091d44ac423 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridDhtTxLocal.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridDhtTxLocal.java
@@ -160,7 +160,6 @@ public GridDhtTxLocal(
 
         assert nearNodeId != null;
         assert nearFutId != null;
-        assert nearMiniId != 0;
         assert nearXidVer != null;
 
         this.nearNodeId = nearNodeId;
@@ -424,10 +423,10 @@ private void finishTx(boolean commit, @Nullable IgniteInternalFuture prepFut, Gr
             final IgniteInternalFuture<?> lockFut = tryRollbackAsync();
 
             if (lockFut != null) {
-                if (lockFut instanceof GridDhtLockFuture)
-                    ((GridDhtLockFuture)lockFut).onError(rollbackException());
-                else {
-                    /**
+                if (lockFut instanceof DhtLockFuture)
+                    ((DhtLockFuture<?>)lockFut).onError(rollbackException());
+                else if (!lockFut.isDone()) {
+                    /*
                      * Prevents race with {@link GridDhtTransactionalCacheAdapter#lockAllAsync
                      * (GridCacheContext, ClusterNode, GridNearLockRequest, CacheEntryPredicate[])}
                      */
@@ -546,6 +545,8 @@ public void rollbackDhtLocal() throws IgniteCheckedException {
     public IgniteInternalFuture<IgniteInternalTx> rollbackDhtLocalAsync() {
         final GridDhtTxFinishFuture fut = new GridDhtTxFinishFuture<>(cctx, this, false);
 
+        rollbackFuture(fut);
+
         cctx.mvcc().addFuture(fut, fut.futureId());
 
         GridDhtTxPrepareFuture prepFut = this.prepFut;
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridDhtTxLocalAdapter.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridDhtTxLocalAdapter.java
index 604fe0655801..11b46a0f2fab 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridDhtTxLocalAdapter.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridDhtTxLocalAdapter.java
@@ -25,6 +25,7 @@
 import java.util.Map;
 import java.util.Set;
 import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
 import org.apache.ignite.IgniteCheckedException;
 import org.apache.ignite.cluster.ClusterNode;
@@ -59,7 +60,6 @@
 import org.apache.ignite.transactions.TransactionIsolation;
 import org.apache.ignite.transactions.TransactionState;
 import org.jetbrains.annotations.Nullable;
-import java.util.concurrent.ConcurrentHashMap;
 
 import static org.apache.ignite.internal.processors.cache.GridCacheOperation.NOOP;
 import static org.apache.ignite.transactions.TransactionState.COMMITTED;
@@ -78,7 +78,7 @@
     private static final long serialVersionUID = 0L;
 
     /** Asynchronous rollback marker for lock futures. */
-    protected static final IgniteInternalFuture<Boolean> ROLLBACK_FUT = new GridFutureAdapter<>();
+    public static final IgniteInternalFuture<Boolean> ROLLBACK_FUT = new GridFutureAdapter<>();
 
     /** Lock future updater. */
     private static final AtomicReferenceFieldUpdater<GridDhtTxLocalAdapter, IgniteInternalFuture> LOCK_FUT_UPD =
@@ -108,7 +108,7 @@
     /** Enlist or lock future what is currently in progress. */
     @SuppressWarnings("UnusedDeclaration")
     @GridToStringExclude
-    protected volatile IgniteInternalFuture<Boolean> lockFut;
+    protected volatile IgniteInternalFuture<?> lockFut;
 
     /**
      * Empty constructor required for {@link Externalizable}.
@@ -852,12 +852,18 @@ public final boolean commitOnPrepare() {
         return onePhaseCommit() && !near() && !nearOnOriginatingNode;
     }
 
+    /**
+     * @return Lock future.
+     */
+    public IgniteInternalFuture<?> lockFuture() {
+        return lockFut;
+    }
+
     /**
      * Atomically updates lock future.
      *
      * @param oldFut Old future.
      * @param newFut New future.
-     *
      * @return {@code true} If future was changed.
      */
     public boolean updateLockFuture(IgniteInternalFuture<?> oldFut, IgniteInternalFuture<?> newFut) {
@@ -870,20 +876,21 @@ public boolean updateLockFuture(IgniteInternalFuture<?> oldFut, IgniteInternalFu
      * @param cond Clear lock condition.
      */
     public void clearLockFuture(@Nullable IgniteInternalFuture cond) {
-        IgniteInternalFuture f = lockFut;
+        while (true) {
+            IgniteInternalFuture f = lockFut;
 
-        if (cond != null && f != cond)
-            return;
-
-        lockFut = null;
+            if (f == null
+                || f == ROLLBACK_FUT
+                || (cond != null && f != cond)
+                || updateLockFuture(f, null))
+                return;
+        }
     }
 
     /**
-     *
      * @param f Future to finish.
      * @param err Error.
      * @param clearLockFut {@code True} if need to clear lock future.
-     *
      * @return Finished future.
      */
     public <T> GridFutureAdapter<T> finishFuture(GridFutureAdapter<T> f, Throwable err, boolean clearLockFut) {
@@ -901,16 +908,13 @@ public void clearLockFuture(@Nullable IgniteInternalFuture cond) {
      * @return Current lock future or null if it's safe to roll back.
      */
     public @Nullable IgniteInternalFuture<?> tryRollbackAsync() {
-        IgniteInternalFuture<Boolean> fut;
-
-        while(true) {
-            fut = lockFut;
-
-            if (fut != null)
-                return fut == ROLLBACK_FUT ? null : fut;
+        while (true) {
+            final IgniteInternalFuture fut = lockFut;
 
-            if (updateLockFuture(null, ROLLBACK_FUT))
+            if (fut == ROLLBACK_FUT)
                 return null;
+            else if (updateLockFuture(fut, ROLLBACK_FUT))
+                return fut;
         }
     }
 
@@ -924,8 +928,7 @@ public void clearLockFuture(@Nullable IgniteInternalFuture cond) {
         if (commitOnPrepare()) {
             return finishFuture().chain(new CX1<IgniteInternalFuture<IgniteInternalTx>, GridNearTxPrepareResponse>() {
                 @Override public GridNearTxPrepareResponse applyx(IgniteInternalFuture<IgniteInternalTx> finishFut)
-                    throws IgniteCheckedException
-                {
+                    throws IgniteCheckedException {
                     return prepFut.get();
                 }
             });
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridDhtTxPrepareFuture.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridDhtTxPrepareFuture.java
index d8e204a15948..8ac5419b9f4e 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridDhtTxPrepareFuture.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridDhtTxPrepareFuture.java
@@ -51,16 +51,21 @@
 import org.apache.ignite.internal.processors.cache.GridCacheEntryInfo;
 import org.apache.ignite.internal.processors.cache.GridCacheEntryRemovedException;
 import org.apache.ignite.internal.processors.cache.GridCacheMvccCandidate;
-import org.apache.ignite.internal.processors.cache.GridCacheVersionedFuture;
 import org.apache.ignite.internal.processors.cache.GridCacheOperation;
 import org.apache.ignite.internal.processors.cache.GridCacheReturn;
 import org.apache.ignite.internal.processors.cache.GridCacheSharedContext;
+import org.apache.ignite.internal.processors.cache.GridCacheVersionedFuture;
 import org.apache.ignite.internal.processors.cache.KeyCacheObject;
 import org.apache.ignite.internal.processors.cache.distributed.GridDistributedCacheEntry;
 import org.apache.ignite.internal.processors.cache.distributed.GridDistributedTxMapping;
 import org.apache.ignite.internal.processors.cache.distributed.near.GridNearCacheAdapter;
 import org.apache.ignite.internal.processors.cache.distributed.near.GridNearTxPrepareRequest;
 import org.apache.ignite.internal.processors.cache.distributed.near.GridNearTxPrepareResponse;
+import org.apache.ignite.internal.processors.cache.mvcc.MvccCoordinator;
+import org.apache.ignite.internal.processors.cache.mvcc.MvccSnapshot;
+import org.apache.ignite.internal.processors.cache.mvcc.MvccUpdateVersionAware;
+import org.apache.ignite.internal.processors.cache.mvcc.MvccVersionAware;
+import org.apache.ignite.internal.processors.cache.mvcc.txlog.TxState;
 import org.apache.ignite.internal.processors.cache.transactions.IgniteInternalTx;
 import org.apache.ignite.internal.processors.cache.transactions.IgniteTxEntry;
 import org.apache.ignite.internal.processors.cache.transactions.IgniteTxKey;
@@ -85,6 +90,7 @@
 import org.apache.ignite.internal.util.typedef.internal.S;
 import org.apache.ignite.internal.util.typedef.internal.U;
 import org.apache.ignite.lang.IgniteFutureCancelledException;
+import org.apache.ignite.lang.IgniteInClosure;
 import org.apache.ignite.lang.IgniteReducer;
 import org.apache.ignite.lang.IgniteUuid;
 import org.apache.ignite.thread.IgniteThread;
@@ -255,6 +261,11 @@ public GridDhtTxPrepareFuture(
         timeoutObj = timeout > 0 ? new PrepareTimeoutObject(timeout) : null;
     }
 
+    /** {@inheritDoc} */
+    @Nullable @Override public IgniteLogger logger() {
+        return log;
+    }
+
     /** {@inheritDoc} */
     @Override public IgniteUuid futureId() {
         return futId;
@@ -394,7 +405,8 @@ private void onEntriesLocked() {
                         entryProc,
                         tx.resolveTaskName(),
                         null,
-                        keepBinary);
+                        keepBinary,
+                        null);  // TODO IGNITE-7371
 
                     if (retVal || txEntry.op() == TRANSFORM) {
                         if (!F.isEmpty(txEntry.entryProcessors())) {
@@ -507,7 +519,8 @@ else if (retVal)
                             /*transformClo*/null,
                             /*taskName*/null,
                             /*expiryPlc*/null,
-                            /*keepBinary*/true);
+                            /*keepBinary*/true,
+                            null); // TODO IGNITE-7371
                     }
 
                     if (oldVal != null)
@@ -886,6 +899,8 @@ private GridNearTxPrepareResponse createPrepareResponse(@Nullable Throwable prep
             tx.onePhaseCommit(),
             tx.activeCachesDeploymentEnabled());
 
+        res.mvccSnapshot(tx.mvccSnapshot());
+
         if (prepErr == null) {
             if (tx.needReturnValue() || tx.nearOnOriginatingNode() || tx.hasInterceptor())
                 addDhtValues(res);
@@ -1025,7 +1040,7 @@ public void complete() {
     public void prepare(GridNearTxPrepareRequest req) {
         assert req != null;
 
-        if (tx.empty()) {
+        if (tx.empty() && !req.queryUpdate()) {
             tx.setRollbackOnly();
 
             onDone((GridNearTxPrepareResponse)null);
@@ -1205,6 +1220,8 @@ private IgniteTxOptimisticCheckedException versionCheckError(IgniteTxEntry entry
      *
      */
     private void prepare0() {
+        boolean skipInit = false;
+
         try {
             if (tx.serializable() && tx.optimistic()) {
                 IgniteCheckedException err0;
@@ -1239,6 +1256,29 @@ private void prepare0() {
                 }
             }
 
+            IgniteInternalFuture<MvccSnapshot> waitCrdCntrFut = null;
+
+            if (req.requestMvccCounter()) {
+                assert last;
+
+                assert tx.txState().mvccEnabled(cctx);
+
+                try {
+                    // Request snapshot locally only because
+                    // Mvcc Coordinator is expected to be local.
+                    MvccSnapshot snapshot = cctx.coordinators().tryRequestSnapshotLocal(tx);
+
+                    assert snapshot != null : tx.topologyVersion();
+
+                    tx.mvccSnapshot(snapshot);
+                }
+                catch (ClusterTopologyCheckedException e) {
+                    onDone(e);
+
+                    return;
+                }
+            }
+
             onEntriesLocked();
 
             // We are holding transaction-level locks for entries here, so we can get next write version.
@@ -1258,11 +1298,37 @@ private void prepare0() {
             if (isDone())
                 return;
 
-            if (last)
-                sendPrepareRequests();
+            if (last) {
+                if (waitCrdCntrFut != null) {
+                    skipInit = true;
+
+                    waitCrdCntrFut.listen(new IgniteInClosure<IgniteInternalFuture<MvccSnapshot>>() {
+                        @Override public void apply(IgniteInternalFuture<MvccSnapshot> fut) {
+                            try {
+                                fut.get();
+
+                                sendPrepareRequests();
+
+                                markInitialized();
+                            }
+                            catch (Throwable e) {
+                                U.error(log, "Failed to get mvcc version for tx [txId=" + tx.nearXidVersion() +
+                                    ", err=" + e + ']', e);
+
+                                GridNearTxPrepareResponse res = createPrepareResponse(e);
+
+                                onDone(res, res.error());
+                            }
+                        }
+                    });
+                }
+                else
+                    sendPrepareRequests();
+            }
         }
         finally {
-            markInitialized();
+            if (!skipInit)
+                markInitialized();
         }
     }
 
@@ -1280,15 +1346,23 @@ private void sendPrepareRequests() {
             }
         }
 
+        assert !tx.txState().mvccEnabled(cctx) || !tx.onePhaseCommit() || tx.mvccSnapshot() != null;
+
         int miniId = 0;
 
         assert tx.transactionNodes() != null;
 
         final long timeout = timeoutObj != null ? timeoutObj.timeout : 0;
 
+        // Do not need process active transactions on backups.
+        MvccSnapshot mvccSnapshot = tx.mvccSnapshot();
+
+        if (mvccSnapshot != null)
+            mvccSnapshot = mvccSnapshot.withoutActiveTransactions();
+
         // Create mini futures.
         for (GridDistributedTxMapping dhtMapping : tx.dhtMap().values()) {
-            assert !dhtMapping.empty();
+            assert !dhtMapping.empty() || dhtMapping.queryUpdate();
 
             ClusterNode n = dhtMapping.primary();
 
@@ -1300,7 +1374,7 @@ private void sendPrepareRequests() {
 
             Collection<IgniteTxEntry> dhtWrites = dhtMapping.writes();
 
-            if (F.isEmpty(dhtWrites) && F.isEmpty(nearWrites))
+            if (!dhtMapping.queryUpdate() && F.isEmpty(dhtWrites) && F.isEmpty(nearWrites))
                 continue;
 
             MiniFuture fut = new MiniFuture(n.id(), ++miniId, dhtMapping, nearMapping);
@@ -1325,7 +1399,10 @@ private void sendPrepareRequests() {
                 tx.taskNameHash(),
                 tx.activeCachesDeploymentEnabled(),
                 tx.storeWriteThrough(),
-                retVal);
+                retVal,
+                mvccSnapshot);
+
+            req.queryUpdate(dhtMapping.queryUpdate());
 
             int idx = 0;
 
@@ -1428,7 +1505,8 @@ private void sendPrepareRequests() {
                     tx.taskNameHash(),
                     tx.activeCachesDeploymentEnabled(),
                     tx.storeWriteThrough(),
-                    retVal);
+                    retVal,
+                    mvccSnapshot);
 
                 for (IgniteTxEntry entry : nearMapping.entries()) {
                     if (CU.writes().apply(entry)) {
@@ -1799,7 +1877,7 @@ void onResult(GridDhtTxPrepareResponse res) {
                         }
                     }
 
-                    if (dhtMapping.empty()) {
+                    if (!dhtMapping.queryUpdate() && dhtMapping.empty()) {
                         dhtMap.remove(nodeId);
 
                         if (log.isDebugEnabled())
@@ -1830,6 +1908,10 @@ void onResult(GridDhtTxPrepareResponse res) {
                         try {
                             if (entry.initialValue(info.value(),
                                 info.version(),
+                                cacheCtx.mvccEnabled() ? ((MvccVersionAware)info).mvccVersion() : null,
+                                cacheCtx.mvccEnabled() ? ((MvccUpdateVersionAware)info).newMvccVersion() : null,
+                                cacheCtx.mvccEnabled() ? ((MvccVersionAware)info).mvccTxState() : TxState.NA,
+                                cacheCtx.mvccEnabled() ? ((MvccUpdateVersionAware)info).newMvccTxState() : TxState.NA,
                                 info.ttl(),
                                 info.expireTime(),
                                 true,
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridDhtTxPrepareRequest.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridDhtTxPrepareRequest.java
index 88da7b011ea2..a71fb6c96e65 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridDhtTxPrepareRequest.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridDhtTxPrepareRequest.java
@@ -34,6 +34,7 @@
 import org.apache.ignite.internal.processors.cache.GridCacheContext;
 import org.apache.ignite.internal.processors.cache.GridCacheSharedContext;
 import org.apache.ignite.internal.processors.cache.distributed.GridDistributedTxPrepareRequest;
+import org.apache.ignite.internal.processors.cache.mvcc.MvccSnapshot;
 import org.apache.ignite.internal.processors.cache.transactions.IgniteTxEntry;
 import org.apache.ignite.internal.processors.cache.transactions.IgniteTxKey;
 import org.apache.ignite.internal.processors.cache.version.GridCacheVersion;
@@ -103,6 +104,9 @@
     @GridDirectTransient
     private List<IgniteTxKey> nearWritesCacheMissed;
 
+    /** */
+    private MvccSnapshot mvccSnapshot;
+
     /** {@code True} if remote tx should skip adding itself to completed versions map on finish. */
     private boolean skipCompletedVers;
 
@@ -144,7 +148,8 @@ public GridDhtTxPrepareRequest(
         int taskNameHash,
         boolean addDepInfo,
         boolean storeWriteThrough,
-        boolean retVal) {
+        boolean retVal,
+        MvccSnapshot mvccInfo) {
         super(tx,
             timeout,
             null,
@@ -165,6 +170,7 @@ public GridDhtTxPrepareRequest(
         this.nearXidVer = nearXidVer;
         this.subjId = subjId;
         this.taskNameHash = taskNameHash;
+        this.mvccSnapshot = mvccInfo;
 
         storeWriteThrough(storeWriteThrough);
         needReturnValue(retVal);
@@ -176,6 +182,13 @@ public GridDhtTxPrepareRequest(
         skipCompletedVers = tx.xidVersion() == tx.nearXidVersion();
     }
 
+    /**
+     * @return Mvcc info.
+     */
+    public MvccSnapshot mvccSnapshot() {
+        return mvccSnapshot;
+    }
+
     /**
      * @return Near cache writes for which cache was not found (possible if client near cache was closed).
      */
@@ -414,60 +427,66 @@ public boolean skipCompletedVersion() {
                 writer.incrementState();
 
             case 23:
-                if (!writer.writeUuid("nearNodeId", nearNodeId))
+                if (!writer.writeMessage("mvccSnapshot", mvccSnapshot))
                     return false;
 
                 writer.incrementState();
 
             case 24:
-                if (!writer.writeCollection("nearWrites", nearWrites, MessageCollectionItemType.MSG))
+                if (!writer.writeUuid("nearNodeId", nearNodeId))
                     return false;
 
                 writer.incrementState();
 
             case 25:
-                if (!writer.writeMessage("nearXidVer", nearXidVer))
+                if (!writer.writeCollection("nearWrites", nearWrites, MessageCollectionItemType.MSG))
                     return false;
 
                 writer.incrementState();
 
             case 26:
-                if (!writer.writeCollection("ownedKeys", ownedKeys, MessageCollectionItemType.MSG))
+                if (!writer.writeMessage("nearXidVer", nearXidVer))
                     return false;
 
                 writer.incrementState();
 
             case 27:
-                if (!writer.writeCollection("ownedVals", ownedVals, MessageCollectionItemType.MSG))
+                if (!writer.writeCollection("ownedKeys", ownedKeys, MessageCollectionItemType.MSG))
                     return false;
 
                 writer.incrementState();
 
             case 28:
-                if (!writer.writeBitSet("preloadKeys", preloadKeys))
+                if (!writer.writeCollection("ownedVals", ownedVals, MessageCollectionItemType.MSG))
                     return false;
 
                 writer.incrementState();
 
             case 29:
-                if (!writer.writeBoolean("skipCompletedVers", skipCompletedVers))
+                if (!writer.writeBitSet("preloadKeys", preloadKeys))
                     return false;
 
                 writer.incrementState();
 
             case 30:
-                if (!writer.writeUuid("subjId", subjId))
+                if (!writer.writeBoolean("skipCompletedVers", skipCompletedVers))
                     return false;
 
                 writer.incrementState();
 
             case 31:
-                if (!writer.writeInt("taskNameHash", taskNameHash))
+                if (!writer.writeUuid("subjId", subjId))
                     return false;
 
                 writer.incrementState();
 
             case 32:
+                if (!writer.writeInt("taskNameHash", taskNameHash))
+                    return false;
+
+                writer.incrementState();
+
+            case 33:
                 if (!writer.writeMessage("topVer", topVer))
                     return false;
 
@@ -514,7 +533,7 @@ public boolean skipCompletedVersion() {
                 reader.incrementState();
 
             case 23:
-                nearNodeId = reader.readUuid("nearNodeId");
+                mvccSnapshot = reader.readMessage("mvccSnapshot");
 
                 if (!reader.isLastRead())
                     return false;
@@ -522,7 +541,7 @@ public boolean skipCompletedVersion() {
                 reader.incrementState();
 
             case 24:
-                nearWrites = reader.readCollection("nearWrites", MessageCollectionItemType.MSG);
+                nearNodeId = reader.readUuid("nearNodeId");
 
                 if (!reader.isLastRead())
                     return false;
@@ -530,7 +549,7 @@ public boolean skipCompletedVersion() {
                 reader.incrementState();
 
             case 25:
-                nearXidVer = reader.readMessage("nearXidVer");
+                nearWrites = reader.readCollection("nearWrites", MessageCollectionItemType.MSG);
 
                 if (!reader.isLastRead())
                     return false;
@@ -538,7 +557,7 @@ public boolean skipCompletedVersion() {
                 reader.incrementState();
 
             case 26:
-                ownedKeys = reader.readCollection("ownedKeys", MessageCollectionItemType.MSG);
+                nearXidVer = reader.readMessage("nearXidVer");
 
                 if (!reader.isLastRead())
                     return false;
@@ -546,7 +565,7 @@ public boolean skipCompletedVersion() {
                 reader.incrementState();
 
             case 27:
-                ownedVals = reader.readCollection("ownedVals", MessageCollectionItemType.MSG);
+                ownedKeys = reader.readCollection("ownedKeys", MessageCollectionItemType.MSG);
 
                 if (!reader.isLastRead())
                     return false;
@@ -554,7 +573,7 @@ public boolean skipCompletedVersion() {
                 reader.incrementState();
 
             case 28:
-                preloadKeys = reader.readBitSet("preloadKeys");
+                ownedVals = reader.readCollection("ownedVals", MessageCollectionItemType.MSG);
 
                 if (!reader.isLastRead())
                     return false;
@@ -562,7 +581,7 @@ public boolean skipCompletedVersion() {
                 reader.incrementState();
 
             case 29:
-                skipCompletedVers = reader.readBoolean("skipCompletedVers");
+                preloadKeys = reader.readBitSet("preloadKeys");
 
                 if (!reader.isLastRead())
                     return false;
@@ -570,7 +589,7 @@ public boolean skipCompletedVersion() {
                 reader.incrementState();
 
             case 30:
-                subjId = reader.readUuid("subjId");
+                skipCompletedVers = reader.readBoolean("skipCompletedVers");
 
                 if (!reader.isLastRead())
                     return false;
@@ -578,7 +597,7 @@ public boolean skipCompletedVersion() {
                 reader.incrementState();
 
             case 31:
-                taskNameHash = reader.readInt("taskNameHash");
+                subjId = reader.readUuid("subjId");
 
                 if (!reader.isLastRead())
                     return false;
@@ -586,6 +605,14 @@ public boolean skipCompletedVersion() {
                 reader.incrementState();
 
             case 32:
+                taskNameHash = reader.readInt("taskNameHash");
+
+                if (!reader.isLastRead())
+                    return false;
+
+                reader.incrementState();
+
+            case 33:
                 topVer = reader.readMessage("topVer");
 
                 if (!reader.isLastRead())
@@ -605,7 +632,7 @@ public boolean skipCompletedVersion() {
 
     /** {@inheritDoc} */
     @Override public byte fieldsCount() {
-        return 33;
+        return 34;
     }
 
     /** {@inheritDoc} */
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridDhtTxQueryEnlistFuture.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridDhtTxQueryEnlistFuture.java
new file mode 100644
index 000000000000..dd3085500e1e
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridDhtTxQueryEnlistFuture.java
@@ -0,0 +1,140 @@
+/*
+ * 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.cache.distributed.dht;
+
+import java.util.Objects;
+import java.util.UUID;
+import org.apache.ignite.IgniteCheckedException;
+import org.apache.ignite.internal.processors.cache.GridCacheContext;
+import org.apache.ignite.internal.processors.cache.mvcc.MvccSnapshot;
+import org.apache.ignite.internal.processors.cache.version.GridCacheVersion;
+import org.apache.ignite.internal.processors.query.GridQueryCancel;
+import org.apache.ignite.internal.processors.query.UpdateSourceIterator;
+import org.apache.ignite.internal.util.typedef.internal.S;
+import org.apache.ignite.lang.IgniteUuid;
+
+/**
+ * Cache lock future.
+ */
+public final class GridDhtTxQueryEnlistFuture extends GridDhtTxAbstractEnlistFuture {
+    /** Involved cache ids. */
+    private final int[] cacheIds;
+
+    /** Schema name. */
+    private final String schema;
+
+    /** Query string. */
+    private final String qry;
+
+    /** Query parameters. */
+    private final Object[] params;
+
+    /** Flags. */
+    private final int flags;
+
+    /** Fetch page size. */
+    private final int pageSize;
+
+    /**
+     * @param nearNodeId Near node ID.
+     * @param nearLockVer Near lock version.
+     * @param mvccSnapshot Mvcc snapshot.
+     * @param threadId Thread ID.
+     * @param nearFutId Near future id.
+     * @param nearMiniId Near mini future id.
+     * @param tx Transaction.
+     * @param cacheIds Involved cache ids.
+     * @param parts Partitions.
+     * @param schema Schema name.
+     * @param qry Query string.
+     * @param params Query parameters.
+     * @param flags Flags.
+     * @param pageSize Fetch page size.
+     * @param timeout Lock acquisition timeout.
+     * @param cctx Cache context.
+     */
+    public GridDhtTxQueryEnlistFuture(
+        UUID nearNodeId,
+        GridCacheVersion nearLockVer,
+        MvccSnapshot mvccSnapshot,
+        long threadId,
+        IgniteUuid nearFutId,
+        int nearMiniId,
+        GridDhtTxLocalAdapter tx,
+        int[] cacheIds,
+        int[] parts,
+        String schema,
+        String qry,
+        Object[] params,
+        int flags,
+        int pageSize,
+        long timeout,
+        GridCacheContext<?, ?> cctx) {
+        super(nearNodeId,
+            nearLockVer,
+            mvccSnapshot,
+            threadId,
+            nearFutId,
+            nearMiniId,
+            parts,
+            tx,
+            timeout,
+            cctx);
+
+        assert timeout >= 0;
+        assert nearNodeId != null;
+        assert nearLockVer != null;
+        assert threadId == tx.threadId();
+
+        this.cacheIds = cacheIds;
+        this.schema = schema;
+        this.qry = qry;
+        this.params = params;
+        this.flags = flags;
+        this.pageSize = pageSize;
+    }
+
+    /** {@inheritDoc} */
+    @Override protected UpdateSourceIterator<?> createIterator() throws IgniteCheckedException {
+        return cctx.kernalContext().query().prepareDistributedUpdate(cctx, cacheIds, parts, schema, qry,
+                params, flags, pageSize, 0, tx.topologyVersionSnapshot(), mvccSnapshot, new GridQueryCancel());
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean equals(Object o) {
+        if (this == o)
+            return true;
+
+        if (o == null || getClass() != o.getClass())
+            return false;
+
+        GridDhtTxQueryEnlistFuture future = (GridDhtTxQueryEnlistFuture)o;
+
+        return Objects.equals(futId, future.futId);
+    }
+
+    /** {@inheritDoc} */
+    @Override public int hashCode() {
+        return futId.hashCode();
+    }
+
+    /** {@inheritDoc} */
+    @Override public String toString() {
+        return S.toString(GridDhtTxQueryEnlistFuture.class, this);
+    }
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridDhtTxQueryEnlistRequest.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridDhtTxQueryEnlistRequest.java
new file mode 100644
index 000000000000..650e1dc73240
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridDhtTxQueryEnlistRequest.java
@@ -0,0 +1,404 @@
+/*
+ * 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.cache.distributed.dht;
+
+import java.nio.ByteBuffer;
+import java.util.List;
+import org.apache.ignite.IgniteCheckedException;
+import org.apache.ignite.internal.GridDirectCollection;
+import org.apache.ignite.internal.processors.cache.CacheEntryInfoCollection;
+import org.apache.ignite.internal.processors.cache.CacheObject;
+import org.apache.ignite.internal.processors.cache.CacheObjectContext;
+import org.apache.ignite.internal.processors.cache.GridCacheEntryInfo;
+import org.apache.ignite.internal.processors.cache.GridCacheIdMessage;
+import org.apache.ignite.internal.processors.cache.GridCacheSharedContext;
+import org.apache.ignite.internal.processors.cache.KeyCacheObject;
+import org.apache.ignite.internal.processors.cache.version.GridCacheVersion;
+import org.apache.ignite.internal.processors.query.EnlistOperation;
+import org.apache.ignite.internal.util.GridLongList;
+import org.apache.ignite.internal.util.typedef.internal.S;
+import org.apache.ignite.lang.IgniteUuid;
+import org.apache.ignite.plugin.extensions.communication.Message;
+import org.apache.ignite.plugin.extensions.communication.MessageCollectionItemType;
+import org.apache.ignite.plugin.extensions.communication.MessageReader;
+import org.apache.ignite.plugin.extensions.communication.MessageWriter;
+
+/**
+ *
+ */
+public class GridDhtTxQueryEnlistRequest extends GridCacheIdMessage {
+    /** */
+    private static final long serialVersionUID = 5103887309729425173L;
+
+    /** */
+    private IgniteUuid dhtFutId;
+
+    /** */
+    private int batchId;
+
+    /** DHT tx version. */
+    private GridCacheVersion lockVer;
+
+    /** */
+    private EnlistOperation op;
+
+    /** */
+    private int mvccOpCnt;
+
+    /** */
+    @GridDirectCollection(KeyCacheObject.class)
+    private List<KeyCacheObject> keys;
+
+    /** */
+    @GridDirectCollection(Message.class)
+    private List<Message> vals;
+
+    /** */
+    private GridLongList updCntrs;
+
+    /**
+     *
+     */
+    public GridDhtTxQueryEnlistRequest() {
+    }
+
+    /**
+     * @param cacheId Cache id.
+     * @param dhtFutId DHT future id.
+     * @param lockVer Lock version.
+     * @param op Operation.
+     * @param batchId Batch id.
+     * @param mvccOpCnt Mvcc operation counter.
+     * @param keys Keys.
+     * @param vals Values.
+     * @param updCntrs Update counters.
+     */
+    GridDhtTxQueryEnlistRequest(int cacheId,
+        IgniteUuid dhtFutId,
+        GridCacheVersion lockVer,
+        EnlistOperation op,
+        int batchId,
+        int mvccOpCnt,
+        List<KeyCacheObject> keys,
+        List<Message> vals,
+        GridLongList updCntrs) {
+        this.cacheId = cacheId;
+        this.dhtFutId = dhtFutId;
+        this.lockVer = lockVer;
+        this.op = op;
+        this.batchId = batchId;
+        this.mvccOpCnt = mvccOpCnt;
+        this.keys = keys;
+        this.vals = vals;
+        this.updCntrs = updCntrs;
+    }
+
+    /**
+     * Returns request rows number.
+     *
+     * @return Request rows number.
+     */
+    public int batchSize() {
+        return keys == null ? 0  : keys.size();
+    }
+
+    /**
+     * @return Dht future id.
+     */
+    public IgniteUuid dhtFutureId() {
+        return dhtFutId;
+    }
+
+    /**
+     * @return Lock version.
+     */
+    public GridCacheVersion version() {
+        return lockVer;
+    }
+
+    /**
+     * @return Mvcc operation counter.
+     */
+    public int operationCounter() {
+        return mvccOpCnt;
+    }
+
+    /**
+     * @return Operation.
+     */
+    public EnlistOperation op() {
+        return op;
+    }
+
+    /**
+     * @return Keys.
+     */
+    public List<KeyCacheObject> keys() {
+        return keys;
+    }
+
+    /**
+     * @return Values.
+     */
+    public List<Message> values() {
+        return vals;
+    }
+
+    /**
+     * @return Update counters.
+     */
+    public GridLongList updateCounters() {
+        return updCntrs;
+    }
+
+    /**
+     * @return Batch id.
+     */
+    public int batchId() {
+        return batchId;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean addDeploymentInfo() {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override public short directType() {
+        return 155;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void prepareMarshal(GridCacheSharedContext ctx) throws IgniteCheckedException {
+        super.prepareMarshal(ctx);
+
+        CacheObjectContext objCtx = ctx.cacheContext(cacheId).cacheObjectContext();
+
+        if (keys != null) {
+            for (int i = 0; i < keys.size(); i++) {
+
+                keys.get(i).prepareMarshal(objCtx);
+
+                if (vals != null) {
+                    Message val = vals.get(i);
+
+                    if (val instanceof CacheObject)
+                        ((CacheObject)val).prepareMarshal(objCtx);
+                    else if (val instanceof CacheEntryInfoCollection) {
+                        for (GridCacheEntryInfo entry : ((CacheEntryInfoCollection)val).infos()) {
+                            CacheObject entryVal = entry.value();
+
+                            if (entryVal != null)
+                                entryVal.prepareMarshal(objCtx);
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override public void finishUnmarshal(GridCacheSharedContext ctx, ClassLoader ldr) throws IgniteCheckedException {
+        super.finishUnmarshal(ctx, ldr);
+
+        CacheObjectContext objCtx = ctx.cacheContext(cacheId).cacheObjectContext();
+
+        if (keys != null) {
+            for (int i = 0; i < keys.size(); i++) {
+                keys.get(i).finishUnmarshal(objCtx, ldr);
+
+                if (vals != null) {
+                    Message val = vals.get(i);
+
+                    if (val instanceof CacheObject)
+                        ((CacheObject)val).finishUnmarshal(objCtx, ldr);
+                    else if (val instanceof CacheEntryInfoCollection) {
+                        for (GridCacheEntryInfo entry : ((CacheEntryInfoCollection)val).infos()) {
+                            CacheObject entryVal = entry.value();
+
+                            if (entryVal != null)
+                                entryVal.finishUnmarshal(objCtx, ldr);
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean writeTo(ByteBuffer buf, MessageWriter writer) {
+        writer.setBuffer(buf);
+
+        if (!super.writeTo(buf, writer))
+            return false;
+
+        if (!writer.isHeaderWritten()) {
+            if (!writer.writeHeader(directType(), fieldsCount()))
+                return false;
+
+            writer.onHeaderWritten();
+        }
+
+        switch (writer.state()) {
+            case 3:
+                if (!writer.writeInt("batchId", batchId))
+                    return false;
+
+                writer.incrementState();
+
+            case 4:
+                if (!writer.writeIgniteUuid("dhtFutId", dhtFutId))
+                    return false;
+
+                writer.incrementState();
+
+            case 5:
+                if (!writer.writeCollection("keys", keys, MessageCollectionItemType.MSG))
+                    return false;
+
+                writer.incrementState();
+
+            case 6:
+                if (!writer.writeMessage("lockVer", lockVer))
+                    return false;
+
+                writer.incrementState();
+
+            case 7:
+                if (!writer.writeInt("mvccOpCnt", mvccOpCnt))
+                    return false;
+
+                writer.incrementState();
+
+            case 8:
+                if (!writer.writeByte("op", op != null ? (byte)op.ordinal() : -1))
+                    return false;
+
+                writer.incrementState();
+
+            case 9:
+                if (!writer.writeMessage("updCntrs", updCntrs))
+                    return false;
+
+                writer.incrementState();
+
+            case 10:
+                if (!writer.writeCollection("vals", vals, MessageCollectionItemType.MSG))
+                    return false;
+
+                writer.incrementState();
+
+        }
+
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean readFrom(ByteBuffer buf, MessageReader reader) {
+        reader.setBuffer(buf);
+
+        if (!reader.beforeMessageRead())
+            return false;
+
+        if (!super.readFrom(buf, reader))
+            return false;
+
+        switch (reader.state()) {
+            case 3:
+                batchId = reader.readInt("batchId");
+
+                if (!reader.isLastRead())
+                    return false;
+
+                reader.incrementState();
+
+            case 4:
+                dhtFutId = reader.readIgniteUuid("dhtFutId");
+
+                if (!reader.isLastRead())
+                    return false;
+
+                reader.incrementState();
+
+            case 5:
+                keys = reader.readCollection("keys", MessageCollectionItemType.MSG);
+
+                if (!reader.isLastRead())
+                    return false;
+
+                reader.incrementState();
+
+            case 6:
+                lockVer = reader.readMessage("lockVer");
+
+                if (!reader.isLastRead())
+                    return false;
+
+                reader.incrementState();
+
+            case 7:
+                mvccOpCnt = reader.readInt("mvccOpCnt");
+
+                if (!reader.isLastRead())
+                    return false;
+
+                reader.incrementState();
+
+            case 8:
+                byte opOrd;
+
+                opOrd = reader.readByte("op");
+
+                if (!reader.isLastRead())
+                    return false;
+
+                op = EnlistOperation.fromOrdinal(opOrd);
+
+                reader.incrementState();
+
+            case 9:
+                updCntrs = reader.readMessage("updCntrs");
+
+                if (!reader.isLastRead())
+                    return false;
+
+                reader.incrementState();
+
+            case 10:
+                vals = reader.readCollection("vals", MessageCollectionItemType.MSG);
+
+                if (!reader.isLastRead())
+                    return false;
+
+                reader.incrementState();
+
+        }
+
+        return reader.afterMessageRead(GridDhtTxQueryEnlistRequest.class);
+    }
+
+    /** {@inheritDoc} */
+    @Override public byte fieldsCount() {
+        return 11;
+    }
+
+    /** {@inheritDoc} */
+    @Override public String toString() {
+        return S.toString(GridDhtTxQueryEnlistRequest.class, this);
+    }
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridDhtTxQueryEnlistResponse.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridDhtTxQueryEnlistResponse.java
new file mode 100644
index 000000000000..411848958d4f
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridDhtTxQueryEnlistResponse.java
@@ -0,0 +1,205 @@
+/*
+ * 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.cache.distributed.dht;
+
+import java.nio.ByteBuffer;
+import org.apache.ignite.IgniteCheckedException;
+import org.apache.ignite.internal.GridDirectTransient;
+import org.apache.ignite.internal.processors.cache.GridCacheIdMessage;
+import org.apache.ignite.internal.processors.cache.GridCacheSharedContext;
+import org.apache.ignite.internal.util.typedef.internal.S;
+import org.apache.ignite.internal.util.typedef.internal.U;
+import org.apache.ignite.lang.IgniteUuid;
+import org.apache.ignite.plugin.extensions.communication.MessageReader;
+import org.apache.ignite.plugin.extensions.communication.MessageWriter;
+
+/**
+ *
+ */
+public class GridDhtTxQueryEnlistResponse extends GridCacheIdMessage {
+    /** */
+    private static final long serialVersionUID = -1510546400896574705L;
+
+    /** Future ID. */
+    private IgniteUuid futId;
+
+    /** */
+    private int batchId;
+
+    /** Error. */
+    @GridDirectTransient
+    private Throwable err;
+
+    /** Serialized error. */
+    private byte[] errBytes;
+
+    /**
+     *
+     */
+    public GridDhtTxQueryEnlistResponse() {
+    }
+
+    /**
+     * @param cacheId Cache id.
+     * @param futId Future id.
+     * @param batchId Batch id.
+     * @param err Error.
+     */
+    GridDhtTxQueryEnlistResponse(int cacheId, IgniteUuid futId, int batchId,
+        Throwable err) {
+        this.cacheId = cacheId;
+        this.futId = futId;
+        this.batchId = batchId;
+        this.err = err;
+    }
+
+    /**
+     * @return Future id.
+     */
+    public IgniteUuid futureId() {
+        return futId;
+    }
+
+    /**
+     * @return Batch id.
+     */
+    public int batchId() {
+        return batchId;
+    }
+
+    /**
+     * @return Error.
+     */
+    public Throwable error() {
+        return err;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void prepareMarshal(GridCacheSharedContext ctx) throws IgniteCheckedException {
+        super.prepareMarshal(ctx);
+
+        if (err != null && errBytes == null)
+            errBytes = U.marshal(ctx.marshaller(), err);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void finishUnmarshal(GridCacheSharedContext ctx, ClassLoader ldr) throws IgniteCheckedException {
+        super.finishUnmarshal(ctx, ldr);
+
+        if (errBytes != null)
+            err = U.unmarshal(ctx, errBytes, U.resolveClassLoader(ldr, ctx.gridConfig()));
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean addDeploymentInfo() {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override public short directType() {
+        return 144;
+    }
+
+    /** {@inheritDoc} */
+    @Override public byte fieldsCount() {
+        return 6;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean writeTo(ByteBuffer buf, MessageWriter writer) {
+        writer.setBuffer(buf);
+
+        if (!super.writeTo(buf, writer))
+            return false;
+
+        if (!writer.isHeaderWritten()) {
+            if (!writer.writeHeader(directType(), fieldsCount()))
+                return false;
+
+            writer.onHeaderWritten();
+        }
+
+        switch (writer.state()) {
+            case 3:
+                if (!writer.writeInt("batchId", batchId))
+                    return false;
+
+                writer.incrementState();
+
+            case 4:
+                if (!writer.writeByteArray("errBytes", errBytes))
+                    return false;
+
+                writer.incrementState();
+
+            case 5:
+                if (!writer.writeIgniteUuid("futId", futId))
+                    return false;
+
+                writer.incrementState();
+
+        }
+
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean readFrom(ByteBuffer buf, MessageReader reader) {
+        reader.setBuffer(buf);
+
+        if (!reader.beforeMessageRead())
+            return false;
+
+        if (!super.readFrom(buf, reader))
+            return false;
+
+        switch (reader.state()) {
+            case 3:
+                batchId = reader.readInt("batchId");
+
+                if (!reader.isLastRead())
+                    return false;
+
+                reader.incrementState();
+
+            case 4:
+                errBytes = reader.readByteArray("errBytes");
+
+                if (!reader.isLastRead())
+                    return false;
+
+                reader.incrementState();
+
+            case 5:
+                futId = reader.readIgniteUuid("futId");
+
+                if (!reader.isLastRead())
+                    return false;
+
+                reader.incrementState();
+
+        }
+
+        return reader.afterMessageRead(GridDhtTxQueryEnlistResponse.class);
+    }
+
+    /** {@inheritDoc} */
+    @Override public String toString() {
+        return S.toString(GridDhtTxQueryEnlistResponse.class, this);
+    }
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridDhtTxQueryFirstEnlistRequest.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridDhtTxQueryFirstEnlistRequest.java
new file mode 100644
index 000000000000..5df1d5843662
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridDhtTxQueryFirstEnlistRequest.java
@@ -0,0 +1,370 @@
+/*
+ * 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.cache.distributed.dht;
+
+import java.nio.ByteBuffer;
+import java.util.List;
+import java.util.UUID;
+import org.apache.ignite.internal.processors.affinity.AffinityTopologyVersion;
+import org.apache.ignite.internal.processors.cache.KeyCacheObject;
+import org.apache.ignite.internal.processors.cache.mvcc.MvccSnapshot;
+import org.apache.ignite.internal.processors.cache.mvcc.MvccSnapshotWithoutTxs;
+import org.apache.ignite.internal.processors.cache.version.GridCacheVersion;
+import org.apache.ignite.internal.processors.query.EnlistOperation;
+import org.apache.ignite.internal.util.GridLongList;
+import org.apache.ignite.internal.util.typedef.internal.S;
+import org.apache.ignite.lang.IgniteUuid;
+import org.apache.ignite.plugin.extensions.communication.Message;
+import org.apache.ignite.plugin.extensions.communication.MessageReader;
+import org.apache.ignite.plugin.extensions.communication.MessageWriter;
+
+/**
+ * First enlist request.
+ */
+public class GridDhtTxQueryFirstEnlistRequest extends GridDhtTxQueryEnlistRequest {
+    /** */
+    private static final long serialVersionUID = -7494735627739420176L;
+
+    /** Tx initiator. Primary node in case of remote DHT tx. */
+    private UUID subjId;
+
+    /** */
+    private AffinityTopologyVersion topVer;
+
+    /** */
+    private long crdVer;
+
+    /** */
+    private long cntr;
+
+    /** */
+    private long cleanupVer;
+
+    /** */
+    private long timeout;
+
+    /** */
+    private int taskNameHash;
+
+    /** */
+    private UUID nearNodeId;
+
+    /** Near tx version. */
+    private GridCacheVersion nearXidVer;
+
+    /**
+     *
+     */
+    public GridDhtTxQueryFirstEnlistRequest() {
+    }
+
+    /**
+     * @param cacheId Cache id.
+     * @param dhtFutId DHT future id.
+     * @param subjId Subject id.
+     * @param topVer Topology version.
+     * @param lockVer Lock version.
+     * @param snapshot Mvcc snapshot.
+     * @param timeout Timeout.
+     * @param taskNameHash Task name hash.
+     * @param nearNodeId Near node id.
+     * @param nearXidVer Near xid version.
+     * @param op Operation.
+     * @param batchId Batch id.
+     * @param keys Keys.
+     * @param vals Values.
+     * @param updCntrs Update counters.
+     */
+    GridDhtTxQueryFirstEnlistRequest(int cacheId,
+        IgniteUuid dhtFutId,
+        UUID subjId,
+        AffinityTopologyVersion topVer,
+        GridCacheVersion lockVer,
+        MvccSnapshot snapshot,
+        long timeout,
+        int taskNameHash,
+        UUID nearNodeId,
+        GridCacheVersion nearXidVer,
+        EnlistOperation op,
+        int batchId,
+        List<KeyCacheObject> keys,
+        List<Message> vals,
+        GridLongList updCntrs) {
+        super(cacheId, dhtFutId, lockVer, op, batchId, snapshot.operationCounter(), keys, vals, updCntrs);
+        this.cacheId = cacheId;
+        this.subjId = subjId;
+        this.topVer = topVer;
+        this.crdVer = snapshot.coordinatorVersion();
+        this.cntr = snapshot.counter();
+        this.cleanupVer = snapshot.cleanupVersion();
+        this.timeout = timeout;
+        this.taskNameHash = taskNameHash;
+        this.nearNodeId = nearNodeId;
+        this.nearXidVer = nearXidVer;
+    }
+
+    /** {@inheritDoc} */
+    @Override public AffinityTopologyVersion topologyVersion() {
+        return topVer;
+    }
+
+    /**
+     * @return Near node id.
+     */
+    public UUID nearNodeId() {
+        return nearNodeId;
+    }
+
+    /**
+     * @return Near transaction ID.
+     */
+    public GridCacheVersion nearXidVersion() {
+        return nearXidVer;
+    }
+
+    /**
+     * @return Max lock wait time.
+     */
+    public long timeout() {
+        return timeout;
+    }
+
+    /**
+     * @return Subject id.
+     */
+    public UUID subjectId() {
+        return subjId;
+    }
+
+    /**
+     * @return Task name hash.
+     */
+    public int taskNameHash() {
+        return taskNameHash;
+    }
+
+    /**
+     * @return MVCC snapshot.
+     */
+    public MvccSnapshot mvccSnapshot() {
+        return new MvccSnapshotWithoutTxs(crdVer, cntr, operationCounter(), cleanupVer);
+    }
+
+    /**
+     * @return Coordinator version.
+     */
+    public long coordinatorVersion() {
+        return crdVer;
+    }
+
+    /**
+     * @return Counter.
+     */
+    public long counter() {
+        return cntr;
+    }
+
+    /**
+     * @return Cleanup version.
+     */
+    public long cleanupVersion() {
+        return cleanupVer;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean addDeploymentInfo() {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override public short directType() {
+        return 156;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean writeTo(ByteBuffer buf, MessageWriter writer) {
+        writer.setBuffer(buf);
+
+        if (!super.writeTo(buf, writer))
+            return false;
+
+        if (!writer.isHeaderWritten()) {
+            if (!writer.writeHeader(directType(), fieldsCount()))
+                return false;
+
+            writer.onHeaderWritten();
+        }
+
+        switch (writer.state()) {
+            case 11:
+                if (!writer.writeLong("cleanupVer", cleanupVer))
+                    return false;
+
+                writer.incrementState();
+
+            case 12:
+                if (!writer.writeLong("cntr", cntr))
+                    return false;
+
+                writer.incrementState();
+
+            case 13:
+                if (!writer.writeLong("crdVer", crdVer))
+                    return false;
+
+                writer.incrementState();
+
+            case 14:
+                if (!writer.writeUuid("nearNodeId", nearNodeId))
+                    return false;
+
+                writer.incrementState();
+
+            case 15:
+                if (!writer.writeMessage("nearXidVer", nearXidVer))
+                    return false;
+
+                writer.incrementState();
+
+            case 16:
+                if (!writer.writeUuid("subjId", subjId))
+                    return false;
+
+                writer.incrementState();
+
+            case 17:
+                if (!writer.writeInt("taskNameHash", taskNameHash))
+                    return false;
+
+                writer.incrementState();
+
+            case 18:
+                if (!writer.writeLong("timeout", timeout))
+                    return false;
+
+                writer.incrementState();
+
+            case 19:
+                if (!writer.writeMessage("topVer", topVer))
+                    return false;
+
+                writer.incrementState();
+
+        }
+
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean readFrom(ByteBuffer buf, MessageReader reader) {
+        reader.setBuffer(buf);
+
+        if (!reader.beforeMessageRead())
+            return false;
+
+        if (!super.readFrom(buf, reader))
+            return false;
+
+        switch (reader.state()) {
+            case 11:
+                cleanupVer = reader.readLong("cleanupVer");
+
+                if (!reader.isLastRead())
+                    return false;
+
+                reader.incrementState();
+
+            case 12:
+                cntr = reader.readLong("cntr");
+
+                if (!reader.isLastRead())
+                    return false;
+
+                reader.incrementState();
+
+            case 13:
+                crdVer = reader.readLong("crdVer");
+
+                if (!reader.isLastRead())
+                    return false;
+
+                reader.incrementState();
+
+            case 14:
+                nearNodeId = reader.readUuid("nearNodeId");
+
+                if (!reader.isLastRead())
+                    return false;
+
+                reader.incrementState();
+
+            case 15:
+                nearXidVer = reader.readMessage("nearXidVer");
+
+                if (!reader.isLastRead())
+                    return false;
+
+                reader.incrementState();
+
+            case 16:
+                subjId = reader.readUuid("subjId");
+
+                if (!reader.isLastRead())
+                    return false;
+
+                reader.incrementState();
+
+            case 17:
+                taskNameHash = reader.readInt("taskNameHash");
+
+                if (!reader.isLastRead())
+                    return false;
+
+                reader.incrementState();
+
+            case 18:
+                timeout = reader.readLong("timeout");
+
+                if (!reader.isLastRead())
+                    return false;
+
+                reader.incrementState();
+
+            case 19:
+                topVer = reader.readMessage("topVer");
+
+                if (!reader.isLastRead())
+                    return false;
+
+                reader.incrementState();
+
+        }
+
+        return reader.afterMessageRead(GridDhtTxQueryFirstEnlistRequest.class);
+    }
+
+    /** {@inheritDoc} */
+    @Override public byte fieldsCount() {
+        return 20;
+    }
+
+    /** {@inheritDoc} */
+    @Override public String toString() {
+        return S.toString(GridDhtTxQueryFirstEnlistRequest.class, this);
+    }
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridDhtTxQueryResultsEnlistFuture.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridDhtTxQueryResultsEnlistFuture.java
new file mode 100644
index 000000000000..6169e1fe7fc3
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridDhtTxQueryResultsEnlistFuture.java
@@ -0,0 +1,130 @@
+/*
+ * 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.cache.distributed.dht;
+
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.Objects;
+import java.util.UUID;
+import org.apache.ignite.IgniteCheckedException;
+import org.apache.ignite.internal.processors.cache.GridCacheContext;
+import org.apache.ignite.internal.processors.cache.mvcc.MvccSnapshot;
+import org.apache.ignite.internal.processors.cache.version.GridCacheVersion;
+import org.apache.ignite.internal.processors.query.EnlistOperation;
+import org.apache.ignite.internal.processors.query.UpdateSourceIterator;
+import org.apache.ignite.internal.util.typedef.internal.S;
+import org.apache.ignite.lang.IgniteUuid;
+
+/**
+ * Future processing transaction enlisting and locking of entries
+ * produces by complex DML queries with reduce step.
+ */
+public final class GridDhtTxQueryResultsEnlistFuture extends GridDhtTxAbstractEnlistFuture implements UpdateSourceIterator<Object> {
+    /** */
+    private static final long serialVersionUID = -4933550335145438798L;
+    /** */
+    private EnlistOperation op;
+
+    /** */
+    private Iterator<Object> it;
+
+    /**
+     * @param nearNodeId Near node ID.
+     * @param nearLockVer Near lock version.
+     * @param mvccSnapshot Mvcc snapshot.
+     * @param threadId Thread ID.
+     * @param nearFutId Near future id.
+     * @param nearMiniId Near mini future id.
+     * @param tx Transaction.
+     * @param timeout Lock acquisition timeout.
+     * @param cctx Cache context.
+     * @param rows Collection of rows.
+     * @param op Operation.
+     */
+    public GridDhtTxQueryResultsEnlistFuture(UUID nearNodeId,
+        GridCacheVersion nearLockVer,
+        MvccSnapshot mvccSnapshot,
+        long threadId,
+        IgniteUuid nearFutId,
+        int nearMiniId,
+        GridDhtTxLocalAdapter tx,
+        long timeout,
+        GridCacheContext<?, ?> cctx,
+        Collection<Object> rows,
+        EnlistOperation op) {
+        super(nearNodeId,
+            nearLockVer,
+            mvccSnapshot,
+            threadId,
+            nearFutId,
+            nearMiniId,
+            null,
+            tx,
+            timeout,
+            cctx);
+
+        this.op = op;
+
+        it = rows.iterator();
+
+        skipNearNodeUpdates = true;
+    }
+
+    /** {@inheritDoc} */
+    @Override protected UpdateSourceIterator<?> createIterator() throws IgniteCheckedException {
+        return this;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean equals(Object o) {
+        if (this == o)
+            return true;
+
+        if (o == null || getClass() != o.getClass())
+            return false;
+
+        GridDhtTxQueryResultsEnlistFuture future = (GridDhtTxQueryResultsEnlistFuture)o;
+
+        return Objects.equals(futId, future.futId);
+    }
+
+    /** {@inheritDoc} */
+    @Override public int hashCode() {
+        return futId.hashCode();
+    }
+
+    /** {@inheritDoc} */
+    @Override public String toString() {
+        return S.toString(GridDhtTxQueryResultsEnlistFuture.class, this);
+    }
+
+    /** {@inheritDoc} */
+    @Override public EnlistOperation operation() {
+        return op;
+    }
+
+    /** {@inheritDoc} */
+    public boolean hasNextX() {
+        return it.hasNext();
+    }
+
+    /** {@inheritDoc} */
+    public Object nextX() {
+        return it.next();
+    }
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridDhtTxRemote.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridDhtTxRemote.java
index 746eb387a9be..08ecf28e3a3b 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridDhtTxRemote.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridDhtTxRemote.java
@@ -21,26 +21,38 @@
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.List;
 import java.util.Map;
 import java.util.UUID;
 import javax.cache.processor.EntryProcessor;
 import org.apache.ignite.IgniteCheckedException;
+import org.apache.ignite.cluster.ClusterTopologyException;
+import org.apache.ignite.internal.pagemem.wal.WALPointer;
 import org.apache.ignite.internal.processors.affinity.AffinityTopologyVersion;
+import org.apache.ignite.internal.processors.cache.CacheEntryInfoCollection;
 import org.apache.ignite.internal.processors.cache.CacheObject;
 import org.apache.ignite.internal.processors.cache.GridCacheContext;
+import org.apache.ignite.internal.processors.cache.GridCacheEntryRemovedException;
 import org.apache.ignite.internal.processors.cache.GridCacheOperation;
 import org.apache.ignite.internal.processors.cache.GridCacheSharedContext;
+import org.apache.ignite.internal.processors.cache.GridCacheUpdateTxResult;
 import org.apache.ignite.internal.processors.cache.KeyCacheObject;
 import org.apache.ignite.internal.processors.cache.distributed.GridDistributedTxRemoteAdapter;
+import org.apache.ignite.internal.processors.cache.mvcc.MvccSnapshot;
+import org.apache.ignite.internal.processors.cache.query.IgniteQueryErrorCode;
 import org.apache.ignite.internal.processors.cache.transactions.IgniteTxEntry;
 import org.apache.ignite.internal.processors.cache.transactions.IgniteTxKey;
 import org.apache.ignite.internal.processors.cache.transactions.IgniteTxRemoteSingleStateImpl;
 import org.apache.ignite.internal.processors.cache.transactions.IgniteTxRemoteStateImpl;
 import org.apache.ignite.internal.processors.cache.version.GridCacheVersion;
+import org.apache.ignite.internal.processors.query.EnlistOperation;
+import org.apache.ignite.internal.processors.query.IgniteSQLException;
+import org.apache.ignite.internal.util.GridLongList;
 import org.apache.ignite.internal.util.tostring.GridToStringBuilder;
 import org.apache.ignite.internal.util.typedef.T2;
 import org.apache.ignite.internal.util.typedef.internal.U;
 import org.apache.ignite.lang.IgniteUuid;
+import org.apache.ignite.plugin.extensions.communication.Message;
 import org.apache.ignite.transactions.TransactionConcurrency;
 import org.apache.ignite.transactions.TransactionIsolation;
 import org.jetbrains.annotations.Nullable;
@@ -67,6 +79,8 @@
     /** Store write through flag. */
     private boolean storeWriteThrough;
 
+    /** Map of update counters made by this tx. Mapping: cacheId -> partId -> updCntr. */
+    private Map<Integer, GridDhtPartitionsUpdateCountersMap> updCntrs;
     /**
      * Empty constructor required for {@link Externalizable}.
      */
@@ -368,6 +382,136 @@ public void addWrite(GridCacheContext cacheCtx,
         txState.addWriteEntry(key, txEntry);
     }
 
+    /**
+     *
+     * @param ctx Cache context.
+     * @param op Operation.
+     * @param keys Keys.
+     * @param vals Values.
+     * @param snapshot Mvcc snapshot.
+     * @param updCntrs Update counters.
+     * @throws IgniteCheckedException If failed.
+     */
+    public void mvccEnlistBatch(GridCacheContext ctx, EnlistOperation op, List<KeyCacheObject> keys,
+        List<Message> vals, MvccSnapshot snapshot, GridLongList updCntrs) throws IgniteCheckedException {
+        assert keys != null && updCntrs != null && keys.size() == updCntrs.size();
+
+        WALPointer ptr = null;
+
+        GridDhtCacheAdapter dht = ctx.dht();
+
+        addActiveCache(ctx, false);
+
+        for (int i = 0; i < keys.size(); i++) {
+            KeyCacheObject key = keys.get(i);
+
+            assert key != null;
+
+            int part = ctx.affinity().partition(key);
+
+            GridDhtLocalPartition locPart = ctx.topology().localPartition(part, topologyVersion(), false);
+
+            if (locPart == null || !locPart.reserve())
+                throw new ClusterTopologyException("Can not reserve partition. Please retry on stable topology.");
+
+            try {
+                CacheObject val = null;
+
+                Message val0 = vals != null ? vals.get(i) : null;
+
+                CacheEntryInfoCollection entries =
+                    val0 instanceof CacheEntryInfoCollection ? (CacheEntryInfoCollection)val0 : null;
+
+                if (entries == null && !op.isDeleteOrLock())
+                    val = (val0 instanceof CacheObject) ? (CacheObject)val0 : null;
+
+                GridDhtCacheEntry entry = dht.entryExx(key, topologyVersion());
+
+                GridCacheUpdateTxResult updRes;
+
+                while (true) {
+                    ctx.shared().database().checkpointReadLock();
+
+                    try {
+                        if (entries == null) {
+                            switch (op) {
+                                case DELETE:
+                                    updRes = entry.mvccRemove(
+                                        this,
+                                        ctx.localNodeId(),
+                                        topologyVersion(),
+                                        updCntrs.get(i),
+                                        snapshot,
+                                        false);
+
+                                    break;
+
+                                case INSERT:
+                                case UPSERT:
+                                case UPDATE:
+                                    updRes = entry.mvccSet(
+                                        this,
+                                        ctx.localNodeId(),
+                                        val,
+                                        0,
+                                        topologyVersion(),
+                                        updCntrs.get(i),
+                                        snapshot,
+                                        op.cacheOperation(),
+                                        false,
+                                        false);
+
+                                    break;
+
+                                default:
+                                    throw new IgniteSQLException("Cannot acquire lock for operation [op= "
+                                        + op + "]" + "Operation is unsupported at the moment ",
+                                        IgniteQueryErrorCode.UNSUPPORTED_OPERATION);
+                            }
+                        }
+                        else {
+                            updRes = entry.mvccUpdateRowsWithPreloadInfo(this,
+                                ctx.localNodeId(),
+                                topologyVersion(),
+                                updCntrs.get(i),
+                                entries.infos(),
+                                op.cacheOperation(),
+                                snapshot);
+                        }
+
+                        break;
+                    }
+                    catch (GridCacheEntryRemovedException ignore) {
+                        entry = dht.entryExx(key);
+                    }
+                    finally {
+                        ctx.shared().database().checkpointReadUnlock();
+                    }
+                }
+
+                assert updRes.updateFuture() == null : "Entry should not be locked on the backup";
+
+                ptr = updRes.loggedPointer();
+            }
+            finally {
+                locPart.release();
+            }
+        }
+
+        if (ptr != null && !ctx.tm().logTxRecords())
+            ctx.shared().wal().flush(ptr, true);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void updateCountersMap(Map<Integer, GridDhtPartitionsUpdateCountersMap> updCntrsMap) {
+       this.updCntrs = updCntrsMap;
+    }
+
+    /** {@inheritDoc} */
+    @Override public Map<Integer, GridDhtPartitionsUpdateCountersMap> updateCountersMap() {
+        return updCntrs;
+    }
+
     /** {@inheritDoc} */
     @Override public String toString() {
         return GridToStringBuilder.toString(GridDhtTxRemote.class, this, "super", super.toString());
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridPartitionedGetFuture.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridPartitionedGetFuture.java
index 30d36e862fa8..a76844ae6168 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridPartitionedGetFuture.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridPartitionedGetFuture.java
@@ -43,6 +43,10 @@
 import org.apache.ignite.internal.processors.cache.KeyCacheObject;
 import org.apache.ignite.internal.processors.cache.distributed.near.GridNearGetRequest;
 import org.apache.ignite.internal.processors.cache.distributed.near.GridNearGetResponse;
+import org.apache.ignite.internal.processors.cache.mvcc.MvccQueryTrackerImpl;
+import org.apache.ignite.internal.processors.cache.mvcc.MvccQueryTracker;
+import org.apache.ignite.internal.processors.cache.mvcc.MvccSnapshot;
+import org.apache.ignite.internal.processors.cache.mvcc.MvccSnapshotResponseListener;
 import org.apache.ignite.internal.processors.cache.persistence.CacheDataRow;
 import org.apache.ignite.internal.processors.cache.version.GridCacheVersion;
 import org.apache.ignite.internal.util.GridLeanMap;
@@ -64,7 +68,8 @@
 /**
  * Colocated get future.
  */
-public class GridPartitionedGetFuture<K, V> extends CacheDistributedGetFutureAdapter<K, V> {
+public class GridPartitionedGetFuture<K, V> extends CacheDistributedGetFutureAdapter<K, V>
+    implements MvccSnapshotResponseListener {
     /** */
     private static final long serialVersionUID = 0L;
 
@@ -74,6 +79,12 @@
     /** Logger. */
     private static IgniteLogger log;
 
+    /** */
+    protected final MvccSnapshot mvccSnapshot;
+
+    /** */
+    private MvccQueryTracker mvccTracker;
+
     /**
      * @param cctx Context.
      * @param keys Keys.
@@ -88,6 +99,7 @@
      * @param skipVals Skip values flag.
      * @param needVer If {@code true} returns values as tuples containing value and version.
      * @param keepCacheObjects Keep cache objects flag.
+     * @param mvccSnapshot Mvcc snapshot.
      */
     public GridPartitionedGetFuture(
         GridCacheContext<K, V> cctx,
@@ -101,7 +113,8 @@ public GridPartitionedGetFuture(
         @Nullable IgniteCacheExpiryPolicy expiryPlc,
         boolean skipVals,
         boolean needVer,
-        boolean keepCacheObjects
+        boolean keepCacheObjects,
+        @Nullable MvccSnapshot mvccSnapshot
     ) {
         super(cctx,
             keys,
@@ -115,11 +128,31 @@ public GridPartitionedGetFuture(
             needVer,
             keepCacheObjects,
             recovery);
+        assert mvccSnapshot == null || cctx.mvccEnabled();
+
+        this.mvccSnapshot = mvccSnapshot;
 
         if (log == null)
             log = U.logger(cctx.kernalContext(), logRef, GridPartitionedGetFuture.class);
     }
 
+    /**
+     * @return Mvcc snapshot if mvcc is enabled for cache.
+     */
+    @Nullable private MvccSnapshot mvccSnapshot() {
+        if (!cctx.mvccEnabled())
+            return null;
+
+        if (mvccSnapshot != null)
+            return mvccSnapshot;
+
+        MvccSnapshot snapshot = mvccTracker.snapshot();
+
+        assert snapshot != null : "[fut=" + this + ", mvccTracker=" + mvccTracker + "]";
+
+        return snapshot;
+    }
+
     /**
      * Initializes future.
      *
@@ -129,16 +162,45 @@ public void init(AffinityTopologyVersion topVer) {
         AffinityTopologyVersion lockedTopVer = cctx.shared().lockedTopologyVersion(null);
 
         if (lockedTopVer != null) {
-            canRemap = false;
+            topVer = lockedTopVer;
 
-            map(keys, Collections.<ClusterNode, LinkedHashMap<KeyCacheObject, Boolean>>emptyMap(), lockedTopVer);
+            canRemap = false;
         }
         else {
             topVer = topVer.topologyVersion() > 0 ? topVer :
                 canRemap ? cctx.affinity().affinityTopologyVersion() : cctx.shared().exchange().readyAffinityVersion();
+        }
 
-            map(keys, Collections.<ClusterNode, LinkedHashMap<KeyCacheObject, Boolean>>emptyMap(), topVer);
+        if (!cctx.mvccEnabled() || mvccSnapshot != null)
+            initialMap(topVer);
+        else {
+            mvccTracker = new MvccQueryTrackerImpl(cctx, canRemap);
+
+            trackable = true;
+
+            cctx.mvcc().addFuture(this, futId);
+
+            mvccTracker.requestSnapshot(topVer, this);
         }
+    }
+
+    @Override public void onResponse(MvccSnapshot res) {
+        AffinityTopologyVersion topVer = mvccTracker.topologyVersion();
+
+        assert topVer != null;
+
+        initialMap(topVer);
+    }
+
+    @Override public void onError(IgniteCheckedException e) {
+        onDone(e);
+    }
+
+    /**
+     * @param topVer Topology version.
+     */
+    private void initialMap(AffinityTopologyVersion topVer) {
+        map(keys, Collections.<ClusterNode, LinkedHashMap<KeyCacheObject, Boolean>>emptyMap(), topVer);
 
         markInitialized();
     }
@@ -197,10 +259,12 @@ public void init(AffinityTopologyVersion topVer) {
     /** {@inheritDoc} */
     @Override public boolean onDone(Map<K, V> res, Throwable err) {
         if (super.onDone(res, err)) {
-            // Don't forget to clean up.
             if (trackable)
                 cctx.mvcc().removeFuture(futId);
 
+            if (mvccTracker != null)
+                mvccTracker.onDone();
+
             cache().sendTtlUpdateRequest(expiryPlc);
 
             return true;
@@ -293,7 +357,8 @@ private void map(
                         taskName == null ? 0 : taskName.hashCode(),
                         expiryPlc,
                         skipVals,
-                        recovery);
+                        recovery,
+                        mvccSnapshot());
 
                 final Collection<Integer> invalidParts = fut.invalidPartitions();
 
@@ -350,7 +415,8 @@ private void map(
                     false,
                     skipVals,
                     cctx.deploymentEnabled(),
-                    recovery);
+                    recovery,
+                    mvccSnapshot());
 
                 add(fut); // Append new future.
 
@@ -463,7 +529,9 @@ private boolean localGet(AffinityTopologyVersion topVer, KeyCacheObject key, int
                 GridCacheVersion ver = null;
 
                 if (readNoEntry) {
-                    CacheDataRow row = cctx.offheap().read(cctx, key);
+                    CacheDataRow row = cctx.mvccEnabled() ?
+                        cctx.offheap().mvccRead(cctx, key, mvccSnapshot()) :
+                        cctx.offheap().read(cctx, key);
 
                     if (row != null) {
                         long expireTime = row.expireTime();
@@ -506,6 +574,7 @@ private boolean localGet(AffinityTopologyVersion topVer, KeyCacheObject key, int
                                 taskName,
                                 expiryPlc,
                                 !deserializeBinary,
+                                mvccSnapshot(),
                                 null);
 
                             if (getRes != null) {
@@ -524,7 +593,8 @@ private boolean localGet(AffinityTopologyVersion topVer, KeyCacheObject key, int
                                 null,
                                 taskName,
                                 expiryPlc,
-                                !deserializeBinary);
+                                !deserializeBinary,
+                                mvccSnapshot());
                         }
 
                         entry.touch(topVer);
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridPartitionedSingleGetFuture.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridPartitionedSingleGetFuture.java
index 83e46ab81749..fd9bc77ba05c 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridPartitionedSingleGetFuture.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/GridPartitionedSingleGetFuture.java
@@ -46,6 +46,7 @@
 import org.apache.ignite.internal.processors.cache.distributed.near.GridNearGetResponse;
 import org.apache.ignite.internal.processors.cache.distributed.near.GridNearSingleGetRequest;
 import org.apache.ignite.internal.processors.cache.distributed.near.GridNearSingleGetResponse;
+import org.apache.ignite.internal.processors.cache.mvcc.MvccSnapshot;
 import org.apache.ignite.internal.processors.cache.persistence.CacheDataRow;
 import org.apache.ignite.internal.processors.cache.version.GridCacheVersion;
 import org.apache.ignite.internal.util.tostring.GridToStringInclude;
@@ -124,6 +125,9 @@
     @GridToStringInclude
     private ClusterNode node;
 
+    /** */
+    protected final MvccSnapshot mvccSnapshot;
+
     /** Post processing closure. */
     private volatile BackupPostProcessingClosure postProcessingClos;
 
@@ -154,9 +158,11 @@ public GridPartitionedSingleGetFuture(
         boolean skipVals,
         boolean needVer,
         boolean keepCacheObjects,
-        boolean recovery
+        boolean recovery,
+        @Nullable MvccSnapshot mvccSnapshot
     ) {
         assert key != null;
+        assert mvccSnapshot == null || cctx.mvccEnabled();
 
         AffinityTopologyVersion lockedTopVer = cctx.shared().lockedTopologyVersion(null);
 
@@ -181,6 +187,7 @@ public GridPartitionedSingleGetFuture(
         this.keepCacheObjects = keepCacheObjects;
         this.recovery = recovery;
         this.topVer = topVer;
+        this.mvccSnapshot = mvccSnapshot;
 
         futId = IgniteUuid.randomUuid();
 
@@ -235,7 +242,8 @@ private void map(final AffinityTopologyVersion topVer) {
                 taskName == null ? 0 : taskName.hashCode(),
                 expiryPlc,
                 skipVals,
-                recovery);
+                recovery,
+                mvccSnapshot);
 
             final Collection<Integer> invalidParts = fut.invalidPartitions();
 
@@ -305,7 +313,8 @@ private void map(final AffinityTopologyVersion topVer) {
                 /*add reader*/false,
                 needVer,
                 cctx.deploymentEnabled(),
-                recovery);
+                recovery,
+                mvccSnapshot);
 
             try {
                 cctx.io().send(node, req, cctx.ioPolicy());
@@ -379,7 +388,8 @@ private boolean localGet(AffinityTopologyVersion topVer, int part) {
                 boolean skipEntry = readNoEntry;
 
                 if (readNoEntry) {
-                    CacheDataRow row = cctx.offheap().read(cctx, key);
+                    CacheDataRow row = mvccSnapshot != null ? cctx.offheap().mvccRead(cctx, key, mvccSnapshot) :
+                        cctx.offheap().read(cctx, key);
 
                     if (row != null) {
                         long expireTime = row.expireTime();
@@ -422,6 +432,7 @@ private boolean localGet(AffinityTopologyVersion topVer, int part) {
                                 taskName,
                                 expiryPlc,
                                 true,
+                                mvccSnapshot,
                                 null);
 
                             if (res != null) {
@@ -440,7 +451,8 @@ private boolean localGet(AffinityTopologyVersion topVer, int part) {
                                 null,
                                 taskName,
                                 expiryPlc,
-                                true);
+                                true,
+                                mvccSnapshot);
                         }
 
                         entry.touch(topVer);
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/NearTxQueryEnlistResultHandler.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/NearTxQueryEnlistResultHandler.java
new file mode 100644
index 000000000000..bb863fb85eb6
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/NearTxQueryEnlistResultHandler.java
@@ -0,0 +1,169 @@
+/*
+ * 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.cache.distributed.dht;
+
+import java.util.UUID;
+import org.apache.ignite.IgniteCheckedException;
+import org.apache.ignite.internal.IgniteInternalFuture;
+import org.apache.ignite.internal.processors.cache.GridCacheContext;
+import org.apache.ignite.internal.processors.cache.distributed.near.GridNearTxQueryEnlistResponse;
+import org.apache.ignite.internal.processors.cache.distributed.near.GridNearTxQueryResultsEnlistResponse;
+import org.apache.ignite.internal.processors.cache.transactions.IgniteInternalTx;
+import org.apache.ignite.internal.processors.cache.version.GridCacheVersion;
+import org.apache.ignite.internal.util.GridLongList;
+import org.apache.ignite.internal.util.lang.GridClosureException;
+import org.apache.ignite.internal.util.typedef.CI1;
+import org.apache.ignite.internal.util.typedef.internal.CU;
+import org.apache.ignite.internal.util.typedef.internal.U;
+import org.apache.ignite.lang.IgniteUuid;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ *
+ */
+public final class NearTxQueryEnlistResultHandler implements CI1<IgniteInternalFuture<Long>> {
+    /** */
+    private static final long serialVersionUID = 5189735824793607906L;
+
+    /** */
+    private static final NearTxQueryEnlistResultHandler INSTANCE = new NearTxQueryEnlistResultHandler();
+
+    /** */
+    private NearTxQueryEnlistResultHandler() {}
+
+    /**
+     * @return Handler instance.
+     */
+    public static NearTxQueryEnlistResultHandler instance() {
+        return INSTANCE;
+    }
+
+    /**
+     * @param future Enlist future.
+     * @return Enlist response.
+     */
+    @SuppressWarnings("unchecked")
+    public static <T extends GridNearTxQueryEnlistResponse> T createResponse(IgniteInternalFuture<?> future) {
+        assert future != null;
+
+        Class<?> clazz = future.getClass();
+
+        if (clazz == GridDhtTxQueryResultsEnlistFuture.class)
+            return (T)createResponse((GridDhtTxQueryResultsEnlistFuture)future);
+        else if (clazz == GridDhtTxQueryEnlistFuture.class)
+            return (T)createResponse((GridDhtTxQueryEnlistFuture)future);
+        else
+            throw new IllegalStateException();
+    }
+
+    /**
+     * @param future Enlist future.
+     * @return Enlist response.
+     */
+    @NotNull private static GridNearTxQueryEnlistResponse createResponse(GridDhtTxQueryEnlistFuture future) {
+        try {
+            future.get();
+
+            assert future.tx.queryEnlisted() || future.cnt == 0;
+
+            return new GridNearTxQueryEnlistResponse(future.cctx.cacheId(), future.nearFutId, future.nearMiniId,
+                future.nearLockVer, future.cnt, future.tx.empty() && !future.tx.queryEnlisted());
+        }
+        catch (IgniteCheckedException e) {
+            return new GridNearTxQueryEnlistResponse(future.cctx.cacheId(), future.nearFutId, future.nearMiniId, future.nearLockVer, e);
+        }
+    }
+
+    /**
+     * @param future Enlist future.
+     * @return Enlist response.
+     */
+    @NotNull private static GridNearTxQueryResultsEnlistResponse createResponse(GridDhtTxQueryResultsEnlistFuture future) {
+        try {
+            future.get();
+
+            GridCacheVersion ver = null;
+            IgniteUuid id = null;
+            GridLongList updCntrs = null;
+
+            if (future.hasNearNodeUpdates) {
+                ver = future.cctx.tm().mappedVersion(future.nearLockVer);
+                id = future.futId;
+                updCntrs = future.nearUpdCntrs;
+            }
+
+            return new GridNearTxQueryResultsEnlistResponse(future.cctx.cacheId(), future.nearFutId, future.nearMiniId,
+                future.nearLockVer, future.cnt, ver, id, updCntrs);
+        }
+        catch (IgniteCheckedException e) {
+            return new GridNearTxQueryResultsEnlistResponse(future.cctx.cacheId(), future.nearFutId, future.nearMiniId,
+                future.nearLockVer, e);
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override public void apply(IgniteInternalFuture<Long> fut0) {
+        GridDhtTxAbstractEnlistFuture fut = (GridDhtTxAbstractEnlistFuture)fut0;
+
+        GridCacheContext<?, ?> cctx = fut.cctx;
+        GridDhtTxLocal tx = (GridDhtTxLocal)fut.tx;
+        UUID nearNodeId = fut.nearNodeId;
+
+        GridNearTxQueryEnlistResponse res = createResponse(fut);
+
+        if (res.removeMapping()) {
+            // TODO IGNITE-9133
+            tx.rollbackDhtLocalAsync().listen(new CI1<IgniteInternalFuture<IgniteInternalTx>>() {
+                @Override public void apply(IgniteInternalFuture<IgniteInternalTx> fut0) {
+                    try {
+                        cctx.io().send(nearNodeId, res, cctx.ioPolicy());
+                    }
+                    catch (IgniteCheckedException e) {
+                        U.error(fut.log, "Failed to send near enlist response [" +
+                            "tx=" + CU.txString(tx) +
+                            ", node=" + nearNodeId +
+                            ", res=" + res + ']', e);
+
+                        throw new GridClosureException(e);
+                    }
+                }
+            });
+
+            return;
+        }
+
+        try {
+            cctx.io().send(nearNodeId, res, cctx.ioPolicy());
+        }
+        catch (IgniteCheckedException e) {
+            U.error(fut.log, "Failed to send near enlist response (will rollback transaction) [" +
+                "tx=" + CU.txString(tx) +
+                ", node=" + nearNodeId +
+                ", res=" + res + ']', e);
+
+            try {
+                tx.rollbackDhtLocalAsync();
+            }
+            catch (Throwable e1) {
+                e.addSuppressed(e1);
+            }
+
+            throw new GridClosureException(e);
+        }
+    }
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/atomic/GridDhtAtomicCache.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/atomic/GridDhtAtomicCache.java
index 2e77b5243b6e..0b46e0503b4f 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/atomic/GridDhtAtomicCache.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/atomic/GridDhtAtomicCache.java
@@ -65,7 +65,6 @@
 import org.apache.ignite.internal.processors.cache.IgniteCacheExpiryPolicy;
 import org.apache.ignite.internal.processors.cache.KeyCacheObject;
 import org.apache.ignite.internal.processors.cache.binary.CacheObjectBinaryProcessorImpl;
-import org.apache.ignite.internal.processors.cache.persistence.CacheDataRow;
 import org.apache.ignite.internal.processors.cache.distributed.dht.GridDhtCacheAdapter;
 import org.apache.ignite.internal.processors.cache.distributed.dht.GridDhtCacheEntry;
 import org.apache.ignite.internal.processors.cache.distributed.dht.GridDhtFuture;
@@ -83,6 +82,7 @@
 import org.apache.ignite.internal.processors.cache.distributed.near.GridNearSingleGetResponse;
 import org.apache.ignite.internal.processors.cache.dr.GridCacheDrExpirationInfo;
 import org.apache.ignite.internal.processors.cache.dr.GridCacheDrInfo;
+import org.apache.ignite.internal.processors.cache.persistence.CacheDataRow;
 import org.apache.ignite.internal.processors.cache.transactions.IgniteTxLocalEx;
 import org.apache.ignite.internal.processors.cache.version.GridCacheVersion;
 import org.apache.ignite.internal.processors.cache.version.GridCacheVersionConflictContext;
@@ -1422,7 +1422,8 @@ private IgniteInternalFuture removeAllAsync0(
             skipVals,
             needVer,
             false,
-            recovery);
+            recovery,
+            null);
 
         fut.init();
 
@@ -1531,6 +1532,7 @@ private IgniteInternalFuture removeAllAsync0(
                                             taskName,
                                             expiry,
                                             true,
+                                            null,
                                             null);
 
                                         if (getRes != null) {
@@ -1549,7 +1551,8 @@ private IgniteInternalFuture removeAllAsync0(
                                             null,
                                             taskName,
                                             expiry,
-                                            !deserializeBinary);
+                                            !deserializeBinary,
+                                            null);
                                     }
 
                                     // Entry was not in memory or in swap, so we remove it from cache.
@@ -1626,7 +1629,8 @@ else if (!skipVals && ctx.statisticsEnabled())
             expiry,
             skipVals,
             needVer,
-            false);
+            false,
+            null);
 
         fut.init(topVer);
 
@@ -2093,7 +2097,8 @@ private void updateWithBatch(
                         entryProcessor,
                         taskName,
                         null,
-                        req.keepBinary());
+                        req.keepBinary(),
+                        null);
 
                     Object oldVal = null;
                     Object updatedVal = null;
@@ -2273,7 +2278,8 @@ else if (op == UPDATE) {
                             null,
                             taskName,
                             null,
-                            req.keepBinary());
+                            req.keepBinary(),
+                            null);
 
                         Object val = ctx.config().getInterceptor().onBeforePut(
                             new CacheLazyEntry(
@@ -2318,7 +2324,8 @@ else if (op == UPDATE) {
                             null,
                             taskName,
                             null,
-                            req.keepBinary());
+                            req.keepBinary(),
+                            null);
 
                         IgniteBiTuple<Boolean, ?> interceptorRes = ctx.config().getInterceptor()
                             .onBeforeRemove(new CacheLazyEntry(ctx, entry.key(), old, req.keepBinary()));
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/colocated/GridDhtColocatedCache.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/colocated/GridDhtColocatedCache.java
index eaa93aea14e5..f6de594a04cf 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/colocated/GridDhtColocatedCache.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/colocated/GridDhtColocatedCache.java
@@ -42,6 +42,7 @@
 import org.apache.ignite.internal.processors.cache.GridCacheReturn;
 import org.apache.ignite.internal.processors.cache.IgniteCacheExpiryPolicy;
 import org.apache.ignite.internal.processors.cache.KeyCacheObject;
+import org.apache.ignite.internal.processors.cache.mvcc.MvccSnapshot;
 import org.apache.ignite.internal.processors.cache.persistence.CacheDataRow;
 import org.apache.ignite.internal.processors.cache.distributed.GridDistributedCacheEntry;
 import org.apache.ignite.internal.processors.cache.distributed.GridDistributedLockCancelledException;
@@ -241,7 +242,8 @@ public GridDistributedCacheEntry entryExx(
             skipVals,
             needVer,
             /*keepCacheObjects*/false,
-            opCtx != null && opCtx.recovery());
+            opCtx != null && opCtx.recovery(),
+            null);
 
         fut.init();
 
@@ -319,7 +321,7 @@ public GridDistributedCacheEntry entryExx(
      * @param needVer Need version.
      * @return Loaded values.
      */
-    public IgniteInternalFuture<Map<K, V>> loadAsync(
+    private IgniteInternalFuture<Map<K, V>> loadAsync(
         @Nullable Collection<KeyCacheObject> keys,
         boolean readThrough,
         boolean forcePrimary,
@@ -341,7 +343,8 @@ public GridDistributedCacheEntry entryExx(
             expiryPlc,
             skipVals,
             needVer,
-            false);
+            false,
+            null);
     }
 
     /**
@@ -370,7 +373,8 @@ public GridDistributedCacheEntry entryExx(
         boolean skipVals,
         boolean needVer,
         boolean keepCacheObj,
-        boolean recovery
+        boolean recovery,
+        @Nullable MvccSnapshot mvccSnapshot
     ) {
         GridPartitionedSingleGetFuture fut = new GridPartitionedSingleGetFuture(ctx,
             ctx.toCacheKeyObject(key),
@@ -384,7 +388,8 @@ public GridDistributedCacheEntry entryExx(
             skipVals,
             needVer,
             keepCacheObj,
-            recovery);
+            recovery,
+            mvccSnapshot);
 
         fut.init();
 
@@ -403,6 +408,7 @@ public GridDistributedCacheEntry entryExx(
      * @param skipVals Skip values flag.
      * @param needVer If {@code true} returns values as tuples containing value and version.
      * @param keepCacheObj Keep cache objects flag.
+     * @param mvccSnapshot Mvcc snapshot.
      * @return Load future.
      */
     public final IgniteInternalFuture<Map<K, V>> loadAsync(
@@ -417,8 +423,11 @@ public GridDistributedCacheEntry entryExx(
         @Nullable IgniteCacheExpiryPolicy expiryPlc,
         boolean skipVals,
         boolean needVer,
-        boolean keepCacheObj
+        boolean keepCacheObj,
+        @Nullable MvccSnapshot mvccSnapshot
     ) {
+        assert mvccSnapshot == null || ctx.mvccEnabled();
+
         if (keys == null || keys.isEmpty())
             return new GridFinishedFuture<>(Collections.<K, V>emptyMap());
 
@@ -426,7 +435,7 @@ public GridDistributedCacheEntry entryExx(
             expiryPlc = expiryPolicy(null);
 
         // Optimization: try to resolve value locally and escape 'get future' creation.
-        if (!forcePrimary && ctx.affinityNode()) {
+        if (!forcePrimary && ctx.affinityNode() && (!ctx.mvccEnabled() || mvccSnapshot != null)) {
             try {
                 Map<K, V> locVals = null;
 
@@ -499,6 +508,7 @@ public GridDistributedCacheEntry entryExx(
                                             taskName,
                                             expiryPlc,
                                             !deserializeBinary,
+                                            mvccSnapshot,
                                             null);
 
                                         if (getRes != null) {
@@ -517,7 +527,8 @@ public GridDistributedCacheEntry entryExx(
                                             null,
                                             taskName,
                                             expiryPlc,
-                                            !deserializeBinary);
+                                            !deserializeBinary,
+                                            mvccSnapshot);
                                     }
 
                                     // Entry was not in memory or in swap, so we remove it from cache.
@@ -600,7 +611,8 @@ else if (!skipVals && ctx.statisticsEnabled())
             expiryPlc,
             skipVals,
             needVer,
-            keepCacheObj);
+            keepCacheObj,
+            mvccSnapshot);
 
         fut.init(topVer);
 
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/preloader/GridDhtForceKeysFuture.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/preloader/GridDhtForceKeysFuture.java
index fe216a00379a..52691f02521b 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/preloader/GridDhtForceKeysFuture.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/preloader/GridDhtForceKeysFuture.java
@@ -42,6 +42,9 @@
 import org.apache.ignite.internal.processors.cache.distributed.dht.GridDhtFuture;
 import org.apache.ignite.internal.processors.cache.distributed.dht.GridDhtLocalPartition;
 import org.apache.ignite.internal.processors.cache.distributed.dht.GridDhtPartitionTopology;
+import org.apache.ignite.internal.processors.cache.mvcc.MvccUpdateVersionAware;
+import org.apache.ignite.internal.processors.cache.mvcc.MvccVersionAware;
+import org.apache.ignite.internal.processors.cache.mvcc.txlog.TxState;
 import org.apache.ignite.internal.util.F0;
 import org.apache.ignite.internal.util.GridLeanSet;
 import org.apache.ignite.internal.util.future.GridCompoundFuture;
@@ -537,6 +540,10 @@ void onResult(GridDhtForceKeysResponse res) {
                         if (entry.initialValue(
                             info.value(),
                             info.version(),
+                            cctx.mvccEnabled() ? ((MvccVersionAware)info).mvccVersion() : null,
+                            cctx.mvccEnabled() ? ((MvccUpdateVersionAware)info).newMvccVersion() : null,
+                            cctx.mvccEnabled() ? ((MvccVersionAware)entry).mvccTxState() : TxState.NA,
+                            cctx.mvccEnabled() ? ((MvccUpdateVersionAware)entry).newMvccTxState() : TxState.NA,
                             info.ttl(),
                             info.expireTime(),
                             true,
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/preloader/GridDhtPartitionDemander.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/preloader/GridDhtPartitionDemander.java
index 5b5f3444027e..a0f82eab217d 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/preloader/GridDhtPartitionDemander.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/preloader/GridDhtPartitionDemander.java
@@ -52,6 +52,9 @@
 import org.apache.ignite.internal.processors.cache.distributed.dht.GridDhtInvalidPartitionException;
 import org.apache.ignite.internal.processors.cache.distributed.dht.GridDhtLocalPartition;
 import org.apache.ignite.internal.processors.cache.distributed.dht.GridDhtPartitionTopology;
+import org.apache.ignite.internal.processors.cache.mvcc.MvccUpdateVersionAware;
+import org.apache.ignite.internal.processors.cache.mvcc.MvccVersionAware;
+import org.apache.ignite.internal.processors.cache.mvcc.txlog.TxState;
 import org.apache.ignite.internal.processors.timeout.GridTimeoutObject;
 import org.apache.ignite.internal.processors.timeout.GridTimeoutObjectAdapter;
 import org.apache.ignite.internal.util.future.GridCompoundFuture;
@@ -881,6 +884,10 @@ private boolean preloadEntry(
                     if (cached.initialValue(
                         entry.value(),
                         entry.version(),
+                        cctx.mvccEnabled() ? ((MvccVersionAware)entry).mvccVersion() : null,
+                        cctx.mvccEnabled() ? ((MvccUpdateVersionAware)entry).newMvccVersion() : null,
+                        cctx.mvccEnabled() ? ((MvccVersionAware)entry).mvccTxState() : TxState.NA,
+                        cctx.mvccEnabled() ? ((MvccUpdateVersionAware)entry).newMvccTxState() : TxState.NA,
                         entry.ttl(),
                         entry.expireTime(),
                         true,
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/preloader/GridDhtPartitionSupplier.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/preloader/GridDhtPartitionSupplier.java
index ea7f4c9b4aac..7ce4f7e7db49 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/preloader/GridDhtPartitionSupplier.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/preloader/GridDhtPartitionSupplier.java
@@ -33,10 +33,15 @@
 import org.apache.ignite.internal.processors.affinity.AffinityTopologyVersion;
 import org.apache.ignite.internal.processors.cache.CacheGroupContext;
 import org.apache.ignite.internal.processors.cache.GridCacheEntryInfo;
+import org.apache.ignite.internal.processors.cache.GridCacheMvccEntryInfo;
 import org.apache.ignite.internal.processors.cache.IgniteRebalanceIterator;
 import org.apache.ignite.internal.processors.cache.distributed.dht.GridDhtLocalPartition;
 import org.apache.ignite.internal.processors.cache.distributed.dht.GridDhtPartitionState;
 import org.apache.ignite.internal.processors.cache.distributed.dht.GridDhtPartitionTopology;
+import org.apache.ignite.internal.processors.cache.mvcc.MvccUpdateVersionAware;
+import org.apache.ignite.internal.processors.cache.mvcc.MvccUtils;
+import org.apache.ignite.internal.processors.cache.mvcc.MvccVersionAware;
+import org.apache.ignite.internal.processors.cache.mvcc.txlog.TxState;
 import org.apache.ignite.internal.processors.cache.persistence.CacheDataRow;
 import org.apache.ignite.internal.util.tostring.GridToStringExclude;
 import org.apache.ignite.internal.util.typedef.T3;
@@ -361,14 +366,39 @@ public void handleDemandMessage(int topicId, UUID nodeId, GridDhtPartitionDemand
                 if (!remainingParts.contains(part))
                     continue;
 
-                GridCacheEntryInfo info = new GridCacheEntryInfo();
+                GridCacheEntryInfo info = grp.mvccEnabled() ?
+                    new GridCacheMvccEntryInfo() : new GridCacheEntryInfo();
 
                 info.key(row.key());
-                info.expireTime(row.expireTime());
-                info.version(row.version());
-                info.value(row.value());
                 info.cacheId(row.cacheId());
 
+                if (grp.mvccEnabled()) {
+                    byte txState = row.mvccTxState() != TxState.NA ? row.mvccTxState() :
+                        MvccUtils.state(grp, row.mvccCoordinatorVersion(), row.mvccCounter(),
+                        row.mvccOperationCounter());
+
+                    if (txState != TxState.COMMITTED)
+                        continue;
+
+                    ((MvccVersionAware)info).mvccVersion(row);
+                    ((GridCacheMvccEntryInfo)info).mvccTxState(TxState.COMMITTED);
+
+                    byte newTxState = row.newMvccTxState() != TxState.NA ? row.newMvccTxState() :
+                        MvccUtils.state(grp, row.newMvccCoordinatorVersion(), row.newMvccCounter(),
+                        row.newMvccOperationCounter());
+
+                    if (newTxState != TxState.ABORTED) {
+                        ((MvccUpdateVersionAware)info).newMvccVersion(row);
+
+                        if (newTxState == TxState.COMMITTED)
+                            ((GridCacheMvccEntryInfo)info).newMvccTxState(TxState.COMMITTED);
+                    }
+                }
+
+                info.value(row.value());
+                info.version(row.version());
+                info.expireTime(row.expireTime());
+
                 if (preloadPred == null || preloadPred.apply(info))
                     s.addEntry0(part, iter.historical(part), info, grp.shared(), grp.cacheObjectContext());
                 else {
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/preloader/GridDhtPartitionsExchangeFuture.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/preloader/GridDhtPartitionsExchangeFuture.java
index 516dfb93f014..357f3d78d0c0 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/preloader/GridDhtPartitionsExchangeFuture.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/preloader/GridDhtPartitionsExchangeFuture.java
@@ -90,6 +90,7 @@
 import org.apache.ignite.internal.processors.cache.distributed.dht.GridDhtPartitionsStateValidator;
 import org.apache.ignite.internal.processors.cache.distributed.dht.GridDhtTopologyFutureAdapter;
 import org.apache.ignite.internal.processors.cache.distributed.dht.preloader.latch.Latch;
+import org.apache.ignite.internal.processors.cache.mvcc.MvccCoordinator;
 import org.apache.ignite.internal.processors.cache.persistence.snapshot.SnapshotDiscoveryMessage;
 import org.apache.ignite.internal.processors.cache.transactions.IgniteTxKey;
 import org.apache.ignite.internal.processors.cache.version.GridCacheVersion;
@@ -97,6 +98,7 @@
 import org.apache.ignite.internal.processors.cluster.ChangeGlobalStateFinishMessage;
 import org.apache.ignite.internal.processors.cluster.ChangeGlobalStateMessage;
 import org.apache.ignite.internal.processors.cluster.DiscoveryDataClusterState;
+import org.apache.ignite.internal.util.GridLongList;
 import org.apache.ignite.internal.util.IgniteUtils;
 import org.apache.ignite.internal.util.future.GridFutureAdapter;
 import org.apache.ignite.internal.util.tostring.GridToStringExclude;
@@ -647,7 +649,17 @@ public void init(boolean newCrd) throws IgniteInterruptedCheckedException {
 
             boolean crdNode = crd != null && crd.isLocal();
 
-            exchCtx = new ExchangeContext(crdNode, this);
+            MvccCoordinator mvccCrd = firstEvtDiscoCache.mvccCoordinator();
+
+            boolean mvccCrdChange = mvccCrd != null &&
+                (initialVersion().equals(mvccCrd.topologyVersion()) || activateCluster());
+
+            // Mvcc coordinator should has been initialized before exchange context is created.
+            cctx.kernalContext().coordinators().updateCoordinator(mvccCrd);
+
+            exchCtx = new ExchangeContext(crdNode, mvccCrdChange, this);
+
+            cctx.kernalContext().coordinators().onExchangeStart(mvccCrd, exchCtx, crd);
 
             assert state == null : state;
 
@@ -658,6 +670,8 @@ public void init(boolean newCrd) throws IgniteInterruptedCheckedException {
 
             if (exchLog.isInfoEnabled()) {
                 exchLog.info("Started exchange init [topVer=" + topVer +
+                    ", mvccCrd=" + mvccCrd +
+                    ", mvccCrdChange=" + mvccCrdChange +
                     ", crd=" + crdNode +
                     ", evt=" + IgniteUtils.gridEventName(firstDiscoEvt.type()) +
                     ", evtNode=" + firstDiscoEvt.eventNode().id() +
@@ -745,7 +759,7 @@ else if (msg instanceof WalStateAbstractMessage)
                 }
             }
 
-            updateTopologies(crdNode);
+            updateTopologies(crd, crdNode, cctx.coordinators().currentCoordinator());
 
             switch (exchange) {
                 case ALL: {
@@ -895,10 +909,12 @@ private void initTopologies() throws IgniteCheckedException {
     /**
      * Updates topology versions and discovery caches on all topologies.
      *
+     * @param exchCrd Exchange coordinator node.
      * @param crd Coordinator flag.
+     * @param mvccCrd Mvcc coordinator.
      * @throws IgniteCheckedException If failed.
      */
-    private void updateTopologies(boolean crd) throws IgniteCheckedException {
+    private void updateTopologies(ClusterNode exchCrd, boolean crd, MvccCoordinator mvccCrd) throws IgniteCheckedException {
         for (CacheGroupContext grp : cctx.cache().cacheGroups()) {
             if (grp.isLocal())
                 continue;
@@ -925,12 +941,18 @@ private void updateTopologies(boolean crd) throws IgniteCheckedException {
             top.updateTopologyVersion(
                 this,
                 events().discoveryCache(),
+                mvccCrd,
                 updSeq,
                 cacheGroupStopping(grp.groupId()));
         }
 
-        for (GridClientPartitionTopology top : cctx.exchange().clientTopologies())
-            top.updateTopologyVersion(this, events().discoveryCache(), -1, cacheGroupStopping(top.groupId()));
+        for (GridClientPartitionTopology top : cctx.exchange().clientTopologies()) {
+            top.updateTopologyVersion(this,
+                events().discoveryCache(),
+                mvccCrd,
+                -1,
+                cacheGroupStopping(top.groupId()));
+        }
     }
 
     /**
@@ -1570,6 +1592,12 @@ private void sendLocalPartitions(ClusterNode node) throws IgniteCheckedException
                 msg.partitionHistoryCounters(partHistReserved0);
         }
 
+        if (exchCtx.newMvccCoordinator() && cctx.coordinators().currentCoordinatorId().equals(node.id())) {
+            Map<UUID, GridLongList> activeQueries = exchCtx.activeQueries();
+
+            msg.activeQueries(activeQueries != null ? activeQueries.get(cctx.localNodeId()) : null);
+        }
+
         if ((stateChangeExchange() || dynamicCacheStartExchange()) && exchangeLocE != null)
             msg.setError(exchangeLocE);
         else if (localJoinExchange())
@@ -1813,6 +1841,10 @@ public void finishMerged() {
         if (!cctx.localNode().isClient())
             tryToPerformLocalSnapshotOperation();
 
+        if (err == null)
+            cctx.coordinators().onExchangeDone(exchCtx.newMvccCoordinator(), exchCtx.events().discoveryCache(),
+                exchCtx.activeQueries());
+
         cctx.cache().onExchangeDone(initialVersion(), exchActions, err);
 
         cctx.exchange().onExchangeDone(res, initialVersion(), err);
@@ -2319,6 +2351,9 @@ public void waitAndReplyToNode(final UUID nodeId, final GridDhtPartitionsSingleM
      */
     private void processSingleMessage(UUID nodeId, GridDhtPartitionsSingleMessage msg) {
         if (msg.client()) {
+            if (msg.activeQueries() != null)
+                cctx.coordinators().processClientActiveQueries(nodeId, msg.activeQueries());
+
             waitAndReplyToNode(nodeId, msg);
 
             return;
@@ -2856,6 +2891,9 @@ private void finishExchangeOnCoordinator(@Nullable Collection<ClusterNode> sndRe
             for (Map.Entry<UUID, GridDhtPartitionsSingleMessage> e : msgs.entrySet()) {
                 GridDhtPartitionsSingleMessage msg = e.getValue();
 
+                if (exchCtx.newMvccCoordinator())
+                    exchCtx.addActiveQueries(e.getKey(), msg.activeQueries());
+
                 // Apply update counters after all single messages are received.
                 for (Map.Entry<Integer, GridDhtPartitionMap> entry : msg.partitions().entrySet()) {
                     Integer grpId = entry.getKey();
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/preloader/GridDhtPartitionsSingleMessage.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/preloader/GridDhtPartitionsSingleMessage.java
index 804cc030489f..77d7eabfc780 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/preloader/GridDhtPartitionsSingleMessage.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/preloader/GridDhtPartitionsSingleMessage.java
@@ -30,6 +30,7 @@
 import org.apache.ignite.internal.processors.cache.GridCacheSharedContext;
 import org.apache.ignite.internal.processors.cache.distributed.dht.GridDhtPartitionState;
 import org.apache.ignite.internal.processors.cache.version.GridCacheVersion;
+import org.apache.ignite.internal.util.GridLongList;
 import org.apache.ignite.internal.util.tostring.GridToStringInclude;
 import org.apache.ignite.internal.util.typedef.F;
 import org.apache.ignite.internal.util.typedef.T2;
@@ -108,6 +109,9 @@
      */
     private GridDhtPartitionsFullMessage finishMsg;
 
+    /** */
+    private GridLongList activeQryTrackers;
+
     /**
      * Required by {@link Externalizable}.
      */
@@ -131,6 +135,20 @@ public GridDhtPartitionsSingleMessage(GridDhtPartitionExchangeId exchId,
         this.compress = compress;
     }
 
+    /**
+     * @return Active queries started with previous coordinator.
+     */
+    GridLongList activeQueries() {
+        return activeQryTrackers;
+    }
+
+    /**
+     * @param activeQrys Active queries started with previous coordinator.
+     */
+    void activeQueries(GridLongList activeQrys) {
+        this.activeQryTrackers = activeQrys;
+    }
+
     /**
      * @param finishMsg Exchange finish message (used to restore exchange state on new coordinator).
      */
@@ -331,7 +349,7 @@ public void setError(Exception ex) {
             byte[] partsBytes0 = null;
             byte[] partCntrsBytes0 = null;
             byte[] partHistCntrsBytes0 = null;
-            byte[] partSizesBytes0 = null;
+            byte[] partsSizesBytes0 = null;
             byte[] errBytes0 = null;
 
             if (parts != null && partsBytes == null)
@@ -344,7 +362,7 @@ public void setError(Exception ex) {
                 partHistCntrsBytes0 = U.marshal(ctx, partHistCntrs);
 
             if (partsSizes != null && partsSizesBytes == null)
-                partSizesBytes0 = U.marshal(ctx, partsSizes);
+                partsSizesBytes0 = U.marshal(ctx, partsSizes);
 
             if (err != null && errBytes == null)
                 errBytes0 = U.marshal(ctx, err);
@@ -356,13 +374,13 @@ public void setError(Exception ex) {
                     byte[] partsBytesZip = U.zip(partsBytes0);
                     byte[] partCntrsBytesZip = U.zip(partCntrsBytes0);
                     byte[] partHistCntrsBytesZip = U.zip(partHistCntrsBytes0);
-                    byte[] partSizesBytesZip = U.zip(partSizesBytes0);
+                    byte[] partsSizesBytesZip = U.zip(partsSizesBytes0);
                     byte[] exBytesZip = U.zip(errBytes0);
 
                     partsBytes0 = partsBytesZip;
                     partCntrsBytes0 = partCntrsBytesZip;
                     partHistCntrsBytes0 = partHistCntrsBytesZip;
-                    partSizesBytes0 = partSizesBytesZip;
+                    partsSizesBytes0 = partsSizesBytesZip;
                     errBytes0 = exBytesZip;
 
                     compressed(true);
@@ -375,7 +393,7 @@ public void setError(Exception ex) {
             partsBytes = partsBytes0;
             partCntrsBytes = partCntrsBytes0;
             partHistCntrsBytes = partHistCntrsBytes0;
-            partsSizesBytes = partSizesBytes0;
+            partsSizesBytes = partsSizesBytes0;
             errBytes = errBytes0;
         }
     }
@@ -456,54 +474,60 @@ public void setError(Exception ex) {
 
         switch (writer.state()) {
             case 5:
-                if (!writer.writeBoolean("client", client))
+                if (!writer.writeMessage("activeQryTrackers", activeQryTrackers))
                     return false;
 
                 writer.incrementState();
 
             case 6:
-                if (!writer.writeMap("dupPartsData", dupPartsData, MessageCollectionItemType.INT, MessageCollectionItemType.INT))
+                if (!writer.writeBoolean("client", client))
                     return false;
 
                 writer.incrementState();
 
             case 7:
-                if (!writer.writeByteArray("errBytes", errBytes))
+                if (!writer.writeMap("dupPartsData", dupPartsData, MessageCollectionItemType.INT, MessageCollectionItemType.INT))
                     return false;
 
                 writer.incrementState();
 
             case 8:
-                if (!writer.writeMessage("finishMsg", finishMsg))
+                if (!writer.writeByteArray("errBytes", errBytes))
                     return false;
 
                 writer.incrementState();
 
             case 9:
-                if (!writer.writeCollection("grpsAffRequest", grpsAffRequest, MessageCollectionItemType.INT))
+                if (!writer.writeMessage("finishMsg", finishMsg))
                     return false;
 
                 writer.incrementState();
 
             case 10:
-                if (!writer.writeByteArray("partCntrsBytes", partCntrsBytes))
+                if (!writer.writeCollection("grpsAffRequest", grpsAffRequest, MessageCollectionItemType.INT))
                     return false;
 
                 writer.incrementState();
 
             case 11:
-                if (!writer.writeByteArray("partHistCntrsBytes", partHistCntrsBytes))
+                if (!writer.writeByteArray("partCntrsBytes", partCntrsBytes))
                     return false;
 
                 writer.incrementState();
 
             case 12:
-                if (!writer.writeByteArray("partsBytes", partsBytes))
+                if (!writer.writeByteArray("partHistCntrsBytes", partHistCntrsBytes))
                     return false;
 
                 writer.incrementState();
 
             case 13:
+                if (!writer.writeByteArray("partsBytes", partsBytes))
+                    return false;
+
+                writer.incrementState();
+
+            case 14:
                 if (!writer.writeByteArray("partsSizesBytes", partsSizesBytes))
                     return false;
 
@@ -525,7 +549,7 @@ public void setError(Exception ex) {
 
         switch (reader.state()) {
             case 5:
-                client = reader.readBoolean("client");
+                activeQryTrackers = reader.readMessage("activeQryTrackers");
 
                 if (!reader.isLastRead())
                     return false;
@@ -533,7 +557,7 @@ public void setError(Exception ex) {
                 reader.incrementState();
 
             case 6:
-                dupPartsData = reader.readMap("dupPartsData", MessageCollectionItemType.INT, MessageCollectionItemType.INT, false);
+                client = reader.readBoolean("client");
 
                 if (!reader.isLastRead())
                     return false;
@@ -541,7 +565,7 @@ public void setError(Exception ex) {
                 reader.incrementState();
 
             case 7:
-                errBytes = reader.readByteArray("errBytes");
+                dupPartsData = reader.readMap("dupPartsData", MessageCollectionItemType.INT, MessageCollectionItemType.INT, false);
 
                 if (!reader.isLastRead())
                     return false;
@@ -549,7 +573,7 @@ public void setError(Exception ex) {
                 reader.incrementState();
 
             case 8:
-                finishMsg = reader.readMessage("finishMsg");
+                errBytes = reader.readByteArray("errBytes");
 
                 if (!reader.isLastRead())
                     return false;
@@ -557,7 +581,7 @@ public void setError(Exception ex) {
                 reader.incrementState();
 
             case 9:
-                grpsAffRequest = reader.readCollection("grpsAffRequest", MessageCollectionItemType.INT);
+                finishMsg = reader.readMessage("finishMsg");
 
                 if (!reader.isLastRead())
                     return false;
@@ -565,7 +589,7 @@ public void setError(Exception ex) {
                 reader.incrementState();
 
             case 10:
-                partCntrsBytes = reader.readByteArray("partCntrsBytes");
+                grpsAffRequest = reader.readCollection("grpsAffRequest", MessageCollectionItemType.INT);
 
                 if (!reader.isLastRead())
                     return false;
@@ -573,7 +597,7 @@ public void setError(Exception ex) {
                 reader.incrementState();
 
             case 11:
-                partHistCntrsBytes = reader.readByteArray("partHistCntrsBytes");
+                partCntrsBytes = reader.readByteArray("partCntrsBytes");
 
                 if (!reader.isLastRead())
                     return false;
@@ -581,7 +605,7 @@ public void setError(Exception ex) {
                 reader.incrementState();
 
             case 12:
-                partsBytes = reader.readByteArray("partsBytes");
+                partHistCntrsBytes = reader.readByteArray("partHistCntrsBytes");
 
                 if (!reader.isLastRead())
                     return false;
@@ -589,6 +613,14 @@ public void setError(Exception ex) {
                 reader.incrementState();
 
             case 13:
+                partsBytes = reader.readByteArray("partsBytes");
+
+                if (!reader.isLastRead())
+                    return false;
+
+                reader.incrementState();
+
+            case 14:
                 partsSizesBytes = reader.readByteArray("partsSizesBytes");
 
                 if (!reader.isLastRead())
@@ -607,7 +639,7 @@ public void setError(Exception ex) {
 
     /** {@inheritDoc} */
     @Override public byte fieldsCount() {
-        return 14;
+        return 15;
     }
 
     /** {@inheritDoc} */
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/near/AckCoordinatorOnRollback.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/near/AckCoordinatorOnRollback.java
new file mode 100644
index 000000000000..1648da926c55
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/near/AckCoordinatorOnRollback.java
@@ -0,0 +1,54 @@
+/*
+ * 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.cache.distributed.near;
+
+import org.apache.ignite.IgniteCheckedException;
+import org.apache.ignite.internal.IgniteInternalFuture;
+import org.apache.ignite.internal.processors.cache.mvcc.MvccQueryTracker;
+import org.apache.ignite.internal.processors.cache.mvcc.MvccSnapshot;
+import org.apache.ignite.internal.processors.cache.transactions.IgniteInternalTx;
+import org.apache.ignite.internal.util.typedef.CIX1;
+
+/** */
+public class AckCoordinatorOnRollback extends CIX1<IgniteInternalFuture<IgniteInternalTx>> {
+    /** */
+    private static final long serialVersionUID = 8172699207968328284L;
+
+    /** */
+    private final GridNearTxLocal tx;
+
+    /**
+     * @param tx Transaction.
+     */
+    public AckCoordinatorOnRollback(GridNearTxLocal tx) {
+        this.tx = tx;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void applyx(IgniteInternalFuture<IgniteInternalTx> fut) throws IgniteCheckedException {
+        assert fut.isDone();
+
+        MvccQueryTracker tracker = tx.mvccQueryTracker();
+        MvccSnapshot mvccSnapshot = tx.mvccSnapshot();
+
+        if (tracker != null) // Optimistic tx.
+            tracker.onDone(tx, false);
+        else if (mvccSnapshot != null)// Pessimistic tx.
+            tx.context().coordinators().ackTxRollback(mvccSnapshot);
+    }
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/near/GridNearGetFuture.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/near/GridNearGetFuture.java
index ee0651814b30..7cf5a6b31940 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/near/GridNearGetFuture.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/near/GridNearGetFuture.java
@@ -319,7 +319,8 @@ private void map(
                         taskName == null ? 0 : taskName.hashCode(),
                         expiryPlc,
                         skipVals,
-                        recovery);
+                        recovery,
+                        null); // TODO IGNITE-7371
 
                 final Collection<Integer> invalidParts = fut.invalidPartitions();
 
@@ -382,7 +383,8 @@ private void map(
                     true,
                     skipVals,
                     cctx.deploymentEnabled(),
-                    recovery);
+                    recovery,
+                    null); // TODO IGNITE-7371
 
                 add(fut); // Append new future.
 
@@ -455,7 +457,8 @@ private void map(
                             taskName,
                             expiryPlc,
                             !deserializeBinary,
-                            null);
+                            null,
+                            null); // TODO IGNITE-7371
 
                         if (res != null) {
                             v = res.value();
@@ -473,7 +476,8 @@ private void map(
                             null,
                             taskName,
                             expiryPlc,
-                            !deserializeBinary);
+                            !deserializeBinary,
+                            null); // TODO IGNITE-7371
                     }
                 }
 
@@ -599,7 +603,8 @@ private boolean localDhtGet(KeyCacheObject key,
                             taskName,
                             expiryPlc,
                             !deserializeBinary,
-                            null);
+                            null,
+                            null); // TODO IGNITE-7371
 
                         if (res != null) {
                             v = res.value();
@@ -617,7 +622,8 @@ private boolean localDhtGet(KeyCacheObject key,
                             null,
                             taskName,
                             expiryPlc,
-                            !deserializeBinary);
+                            !deserializeBinary,
+                            null); // TODO IGNITE-7371
                     }
 
                     // Entry was not in memory or in swap, so we remove it from cache.
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/near/GridNearGetRequest.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/near/GridNearGetRequest.java
index dcb167d81a89..c891bfb7dc3c 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/near/GridNearGetRequest.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/near/GridNearGetRequest.java
@@ -34,6 +34,7 @@
 import org.apache.ignite.internal.processors.cache.GridCacheIdMessage;
 import org.apache.ignite.internal.processors.cache.GridCacheSharedContext;
 import org.apache.ignite.internal.processors.cache.KeyCacheObject;
+import org.apache.ignite.internal.processors.cache.mvcc.MvccSnapshot;
 import org.apache.ignite.internal.processors.cache.version.GridCacheVersion;
 import org.apache.ignite.internal.processors.cache.version.GridCacheVersionable;
 import org.apache.ignite.internal.util.tostring.GridToStringInclude;
@@ -45,6 +46,7 @@
 import org.apache.ignite.plugin.extensions.communication.MessageReader;
 import org.apache.ignite.plugin.extensions.communication.MessageWriter;
 import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
 
 /**
  * Get request. Responsible for obtaining entry from primary node. 'Near' means 'Initiating node' here, not 'Near Cache'.
@@ -106,6 +108,9 @@
     /** TTL for read operation. */
     private long accessTtl;
 
+    /** */
+    private MvccSnapshot mvccSnapshot;
+
     /**
      * Empty constructor required for {@link Externalizable}.
      */
@@ -128,6 +133,7 @@ public GridNearGetRequest() {
      * @param createTtl New TTL to set after entry is created, -1 to leave unchanged.
      * @param accessTtl New TTL to set after entry is accessed, -1 to leave unchanged.
      * @param addDepInfo Deployment info.
+     * @param mvccSnapshot Mvcc snapshot.
      */
     public GridNearGetRequest(
         int cacheId,
@@ -144,7 +150,8 @@ public GridNearGetRequest(
         boolean addReader,
         boolean skipVals,
         boolean addDepInfo,
-        boolean recovery
+        boolean recovery,
+        @Nullable MvccSnapshot mvccSnapshot
     ) {
         assert futId != null;
         assert miniId != null;
@@ -173,6 +180,7 @@ public GridNearGetRequest(
         this.createTtl = createTtl;
         this.accessTtl = accessTtl;
         this.addDepInfo = addDepInfo;
+        this.mvccSnapshot = mvccSnapshot;
 
         if (readThrough)
             flags |= READ_THROUGH_FLAG_MASK;
@@ -187,6 +195,13 @@ public GridNearGetRequest(
             flags |= RECOVERY_FLAG_MASK;
     }
 
+    /**
+     * @return Mvcc version.
+     */
+    @Nullable public MvccSnapshot mvccSnapshot() {
+        return mvccSnapshot;
+    }
+
     /**
      * @return Future ID.
      */
@@ -382,30 +397,36 @@ public long accessTtl() {
                 writer.incrementState();
 
             case 9:
-                if (!writer.writeCollection("readersFlags", readersFlags, MessageCollectionItemType.BOOLEAN))
+                if (!writer.writeMessage("mvccSnapshot", mvccSnapshot))
                     return false;
 
                 writer.incrementState();
 
             case 10:
-                if (!writer.writeUuid("subjId", subjId))
+                if (!writer.writeCollection("readersFlags", readersFlags, MessageCollectionItemType.BOOLEAN))
                     return false;
 
                 writer.incrementState();
 
             case 11:
-                if (!writer.writeInt("taskNameHash", taskNameHash))
+                if (!writer.writeUuid("subjId", subjId))
                     return false;
 
                 writer.incrementState();
 
             case 12:
-                if (!writer.writeMessage("topVer", topVer))
+                if (!writer.writeInt("taskNameHash", taskNameHash))
                     return false;
 
                 writer.incrementState();
 
             case 13:
+                if (!writer.writeMessage("topVer", topVer))
+                    return false;
+
+                writer.incrementState();
+
+            case 14:
                 if (!writer.writeMessage("ver", ver))
                     return false;
 
@@ -476,7 +497,7 @@ public long accessTtl() {
                 reader.incrementState();
 
             case 9:
-                readersFlags = reader.readCollection("readersFlags", MessageCollectionItemType.BOOLEAN);
+                mvccSnapshot = reader.readMessage("mvccSnapshot");
 
                 if (!reader.isLastRead())
                     return false;
@@ -484,7 +505,7 @@ public long accessTtl() {
                 reader.incrementState();
 
             case 10:
-                subjId = reader.readUuid("subjId");
+                readersFlags = reader.readCollection("readersFlags", MessageCollectionItemType.BOOLEAN);
 
                 if (!reader.isLastRead())
                     return false;
@@ -492,7 +513,7 @@ public long accessTtl() {
                 reader.incrementState();
 
             case 11:
-                taskNameHash = reader.readInt("taskNameHash");
+                subjId = reader.readUuid("subjId");
 
                 if (!reader.isLastRead())
                     return false;
@@ -500,7 +521,7 @@ public long accessTtl() {
                 reader.incrementState();
 
             case 12:
-                topVer = reader.readMessage("topVer");
+                taskNameHash = reader.readInt("taskNameHash");
 
                 if (!reader.isLastRead())
                     return false;
@@ -508,6 +529,14 @@ public long accessTtl() {
                 reader.incrementState();
 
             case 13:
+                topVer = reader.readMessage("topVer");
+
+                if (!reader.isLastRead())
+                    return false;
+
+                reader.incrementState();
+
+            case 14:
                 ver = reader.readMessage("ver");
 
                 if (!reader.isLastRead())
@@ -527,7 +556,7 @@ public long accessTtl() {
 
     /** {@inheritDoc} */
     @Override public byte fieldsCount() {
-        return 14;
+        return 15;
     }
 
     /** {@inheritDoc} */
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/near/GridNearOptimisticSerializableTxPrepareFuture.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/near/GridNearOptimisticSerializableTxPrepareFuture.java
index a997c45ec439..140c1d5fdbb7 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/near/GridNearOptimisticSerializableTxPrepareFuture.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/near/GridNearOptimisticSerializableTxPrepareFuture.java
@@ -38,6 +38,7 @@
 import org.apache.ignite.internal.processors.cache.GridCacheSharedContext;
 import org.apache.ignite.internal.processors.cache.distributed.GridDistributedTxMapping;
 import org.apache.ignite.internal.processors.cache.distributed.dht.GridDhtTxMapping;
+import org.apache.ignite.internal.processors.cache.mvcc.MvccCoordinator;
 import org.apache.ignite.internal.processors.cache.transactions.IgniteInternalTx;
 import org.apache.ignite.internal.processors.cache.transactions.IgniteTxEntry;
 import org.apache.ignite.internal.processors.cache.version.GridCacheVersion;
@@ -50,7 +51,6 @@
 import org.apache.ignite.internal.util.typedef.C1;
 import org.apache.ignite.internal.util.typedef.CI1;
 import org.apache.ignite.internal.util.typedef.F;
-import org.apache.ignite.internal.util.typedef.P1;
 import org.apache.ignite.internal.util.typedef.X;
 import org.apache.ignite.internal.util.typedef.internal.S;
 import org.apache.ignite.internal.util.typedef.internal.U;
@@ -59,6 +59,7 @@
 import org.jetbrains.annotations.Nullable;
 
 import static org.apache.ignite.internal.processors.cache.GridCacheOperation.TRANSFORM;
+import static org.apache.ignite.internal.processors.cache.mvcc.MvccUtils.noCoordinatorError;
 import static org.apache.ignite.transactions.TransactionState.PREPARED;
 import static org.apache.ignite.transactions.TransactionState.PREPARING;
 
@@ -184,10 +185,20 @@ private void onError(@Nullable GridDistributedTxMapping m, Throwable e) {
                 tx.removeMapping(m.primary().id());
         }
 
+        prepareError(e);
+    }
+
+    /**
+     * @param e Error.
+     */
+    private void prepareError(Throwable e) {
         ERR_UPD.compareAndSet(this, null, e);
 
         if (keyLockFut != null)
             keyLockFut.onDone(e);
+
+        if (mvccVerFut != null)
+            mvccVerFut.onDone();
     }
 
     /** {@inheritDoc} */
@@ -229,7 +240,7 @@ private MiniFuture miniFuture(int miniId) {
 
             // Avoid iterator creation.
             for (int i = 0; i < size; i++) {
-                IgniteInternalFuture<GridNearTxPrepareResponse> fut = future(i);
+                IgniteInternalFuture fut = future(i);
 
                 if (!isMini(fut))
                     continue;
@@ -338,11 +349,25 @@ private void prepare(
 
         boolean hasNearCache = false;
 
+        MvccCoordinator mvccCrd = null;
+
         for (IgniteTxEntry write : writes) {
             map(write, topVer, mappings, txMapping, remap, topLocked);
 
-            if (write.context().isNear())
+            GridCacheContext cctx = write.context();
+
+            if (cctx.isNear())
                 hasNearCache = true;
+
+            if (cctx.mvccEnabled() && mvccCrd == null) {
+                mvccCrd = cctx.affinity().mvccCoordinator(topVer);
+
+                if (mvccCrd == null) {
+                    onDone(noCoordinatorError(topVer));
+
+                    return;
+                }
+            }
         }
 
         for (IgniteTxEntry read : reads)
@@ -358,6 +383,8 @@ private void prepare(
             return;
         }
 
+        assert !tx.txState().mvccEnabled(cctx) || mvccCrd != null || F.isEmpty(writes);
+
         tx.addEntryMapping(mappings.values());
 
         cctx.mvcc().recheckPendingLocks();
@@ -369,23 +396,32 @@ private void prepare(
 
         MiniFuture locNearEntriesFut = null;
 
+        int lockCnt = keyLockFut != null ? 1 : 0;
+
         // Create futures in advance to have all futures when process {@link GridNearTxPrepareResponse#clientRemapVersion}.
         for (GridDistributedTxMapping m : mappings.values()) {
             assert !m.empty();
 
             MiniFuture fut = new MiniFuture(this, m, ++miniId);
 
-            add(fut);
+            lockCnt++;
+
+            add((IgniteInternalFuture)fut);
 
             if (m.primary().isLocal() && m.hasNearCacheEntries() && m.hasColocatedCacheEntries()) {
                 assert locNearEntriesFut == null;
 
                 locNearEntriesFut = fut;
 
-                add(new MiniFuture(this, m, ++miniId));
+                add((IgniteInternalFuture)new MiniFuture(this, m, ++miniId));
+
+                lockCnt++;
             }
         }
 
+        if (mvccCrd != null)
+            initMvccVersionFuture(lockCnt, remap);
+
         Collection<IgniteInternalFuture<?>> futs = (Collection)futures();
 
         Iterator<IgniteInternalFuture<?>> it = futs.iterator();
@@ -639,7 +675,7 @@ else if (!cacheCtx.isLocal())
                 if (keyLockFut == null) {
                     keyLockFut = new KeyLockFuture();
 
-                    add(keyLockFut);
+                    add((IgniteInternalFuture)keyLockFut);
                 }
 
                 keyLockFut.addLockKey(entry.txKey());
@@ -696,20 +732,20 @@ else if (cacheCtx.isColocated())
         Collection<String> futs = F.viewReadOnly(futures(),
             new C1<IgniteInternalFuture<?>, String>() {
                 @Override public String apply(IgniteInternalFuture<?> f) {
-                    return "[node=" + ((MiniFuture)f).primary().id() +
-                        ", loc=" + ((MiniFuture)f).primary().isLocal() +
-                        ", done=" + f.isDone() + "]";
-                }
-            },
-            new P1<IgniteInternalFuture<?>>() {
-                @Override public boolean apply(IgniteInternalFuture<?> f) {
-                    return isMini(f);
+                    if (isMini(f)) {
+                        return "[node=" + ((MiniFuture)f).primary().id() +
+                            ", loc=" + ((MiniFuture)f).primary().isLocal() +
+                            ", done=" + f.isDone() +
+                            ", err=" + f.error() + "]";
+                    }
+                    else
+                        return f.toString();
                 }
             });
 
         return S.toString(GridNearOptimisticSerializableTxPrepareFuture.class, this,
             "innerFuts", futs,
-            "keyLockFut", keyLockFut,
+            "remap", remapFut != null,
             "tx", tx,
             "super", super.toString());
     }
@@ -760,7 +796,7 @@ else if (cacheCtx.isColocated())
      */
     private static class MiniFuture extends GridFutureAdapter<GridNearTxPrepareResponse> {
         /** Receive result flag updater. */
-        private static AtomicIntegerFieldUpdater<MiniFuture> RCV_RES_UPD =
+        private static final AtomicIntegerFieldUpdater<MiniFuture> RCV_RES_UPD =
             AtomicIntegerFieldUpdater.newUpdater(MiniFuture.class, "rcvRes");
 
         /** */
@@ -924,7 +960,7 @@ void onResult(final GridNearTxPrepareResponse res, boolean updateMapping) {
 
                                             err0.retryReadyFuture(affFut);
 
-                                            ERR_UPD.compareAndSet(parent, null, err0);
+                                            parent.prepareError(err0);
 
                                             onDone(err0);
                                         }
@@ -935,7 +971,7 @@ void onResult(final GridNearTxPrepareResponse res, boolean updateMapping) {
                                                 parent);
                                         }
 
-                                        ERR_UPD.compareAndSet(parent, null, e);
+                                        parent.prepareError(e);
 
                                         onDone(e);
                                     }
@@ -950,6 +986,9 @@ void onResult(final GridNearTxPrepareResponse res, boolean updateMapping) {
 
                         // Finish this mini future (need result only on client node).
                         onDone(parent.cctx.kernalContext().clientNode() ? res : null);
+
+                        if (parent.mvccVerFut != null)
+                            parent.mvccVerFut.onLockReceived();
                     }
                 }
             }
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/near/GridNearOptimisticTxPrepareFuture.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/near/GridNearOptimisticTxPrepareFuture.java
index 566789c17d92..06d7a8c2cdf0 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/near/GridNearOptimisticTxPrepareFuture.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/near/GridNearOptimisticTxPrepareFuture.java
@@ -43,6 +43,7 @@
 import org.apache.ignite.internal.processors.cache.GridCacheSharedContext;
 import org.apache.ignite.internal.processors.cache.distributed.GridDistributedTxMapping;
 import org.apache.ignite.internal.processors.cache.distributed.dht.GridDhtTxMapping;
+import org.apache.ignite.internal.processors.cache.mvcc.MvccCoordinator;
 import org.apache.ignite.internal.processors.cache.transactions.IgniteInternalTx;
 import org.apache.ignite.internal.processors.cache.transactions.IgniteTxEntry;
 import org.apache.ignite.internal.processors.cache.transactions.IgniteTxKey;
@@ -68,6 +69,7 @@
 import org.jetbrains.annotations.Nullable;
 
 import static org.apache.ignite.internal.processors.cache.GridCacheOperation.TRANSFORM;
+import static org.apache.ignite.internal.processors.cache.mvcc.MvccUtils.noCoordinatorError;
 import static org.apache.ignite.transactions.TransactionState.PREPARED;
 import static org.apache.ignite.transactions.TransactionState.PREPARING;
 
@@ -220,7 +222,7 @@ private void onError(Throwable e, boolean discoThread) {
             int size = futuresCountNoLock();
 
             for (int i = 0; i < size; i++) {
-                IgniteInternalFuture<GridNearTxPrepareResponse> fut = future(i);
+                IgniteInternalFuture fut = future(i);
 
                 if (isMini(fut) && !fut.isDone()) {
                     MiniFuture miniFut = (MiniFuture)fut;
@@ -254,7 +256,7 @@ private MiniFuture miniFuture(int miniId) {
 
             // Avoid iterator creation.
             for (int i = size - 1; i >= 0; i--) {
-                IgniteInternalFuture<GridNearTxPrepareResponse> fut = future(i);
+                IgniteInternalFuture fut = future(i);
 
                 if (!isMini(fut))
                     continue;
@@ -380,6 +382,18 @@ else if (write.context().isColocated())
                 tx.colocatedLocallyMapped(true);
         }
 
+        if (write.context().mvccEnabled()) {
+            MvccCoordinator mvccCrd = write.context().affinity().mvccCoordinator(topVer);
+
+            if (mvccCrd == null) {
+                onDone(noCoordinatorError(topVer));
+
+                return;
+            }
+
+            initMvccVersionFuture(keyLockFut != null ? 2 : 1, remap);
+        }
+
         if (keyLockFut != null)
             keyLockFut.onAllKeysAdded();
 
@@ -424,6 +438,8 @@ private void prepare(
 
         boolean hasNearCache = false;
 
+        MvccCoordinator mvccCrd = null;
+
         for (IgniteTxEntry write : writes) {
             write.clearEntryReadVersion();
 
@@ -433,6 +449,16 @@ private void prepare(
                 // an exception occurred while transaction mapping, stop further processing
                 break;
 
+            if (write.context().mvccEnabled() && mvccCrd == null) {
+                mvccCrd = write.context().affinity().mvccCoordinator(topVer);
+
+                if (mvccCrd == null) {
+                    onDone(noCoordinatorError(topVer));
+
+                    break;
+                }
+            }
+
             if (write.context().isNear())
                 hasNearCache = true;
 
@@ -472,6 +498,11 @@ else if (write.context().isColocated())
             return;
         }
 
+        assert !tx.txState().mvccEnabled(cctx) || mvccCrd != null;
+
+        if (mvccCrd != null)
+            initMvccVersionFuture(keyLockFut != null ? 2 : 1, remap);
+
         if (keyLockFut != null)
             keyLockFut.onAllKeysAdded();
 
@@ -495,8 +526,12 @@ else if (write.context().isColocated())
     private void proceedPrepare(final Queue<GridDistributedTxMapping> mappings) {
         final GridDistributedTxMapping m = mappings.poll();
 
-        if (m == null)
+        if (m == null) {
+            if (mvccVerFut != null)
+                mvccVerFut.onLockReceived();
+
             return;
+        }
 
         proceedPrepare(m, mappings);
     }
@@ -562,7 +597,7 @@ private void proceedPrepare(GridDistributedTxMapping m, @Nullable final Queue<Gr
 
                 req.miniId(fut.futureId());
 
-                add(fut); // Append new future.
+                add((IgniteInternalFuture)fut); // Append new future.
 
                 if (n.isLocal()) {
                     assert !(m.hasColocatedCacheEntries() && m.hasNearCacheEntries()) : m;
@@ -679,7 +714,7 @@ else if (!cacheCtx.isLocal())
                 if (keyLockFut == null) {
                     keyLockFut = new KeyLockFuture();
 
-                    add(keyLockFut);
+                    add((IgniteInternalFuture)keyLockFut);
                 }
 
                 keyLockFut.addLockKey(entry.txKey());
@@ -738,7 +773,7 @@ private void onTimeout() {
                     int size = futuresCountNoLock();
 
                     for (int i = 0; i < size; i++) {
-                        IgniteInternalFuture<GridNearTxPrepareResponse> fut = future(i);
+                        IgniteInternalFuture fut = future(i);
 
                         if (isMini(fut) && !fut.isDone()) {
                             MiniFuture miniFut = (MiniFuture)fut;
@@ -756,7 +791,7 @@ private void onTimeout() {
                 }
             }
 
-            add(new GridEmbeddedFuture<>(new IgniteBiClosure<TxDeadlock, Exception, GridNearTxPrepareResponse>() {
+            add(new GridEmbeddedFuture<>(new IgniteBiClosure<TxDeadlock, Exception, Object>() {
                 @Override public GridNearTxPrepareResponse apply(TxDeadlock deadlock, Exception e) {
                     if (e != null)
                         U.warn(log, "Failed to detect deadlock.", e);
@@ -787,7 +822,7 @@ private void onTimeout() {
     /** {@inheritDoc} */
     @Override public void addDiagnosticRequest(IgniteDiagnosticPrepareContext ctx) {
         if (!isDone()) {
-            for (IgniteInternalFuture<GridNearTxPrepareResponse> fut : futures()) {
+            for (IgniteInternalFuture fut : futures()) {
                 if (!fut.isDone()) {
                     if (fut instanceof MiniFuture) {
                         MiniFuture miniFut = (MiniFuture)fut;
@@ -843,19 +878,22 @@ else if (fut instanceof KeyLockFuture) {
     @Override public String toString() {
         Collection<String> futs = F.viewReadOnly(futures(), new C1<IgniteInternalFuture<?>, String>() {
             @Override public String apply(IgniteInternalFuture<?> f) {
-                return "[node=" + ((MiniFuture)f).node().id() +
-                    ", loc=" + ((MiniFuture)f).node().isLocal() +
-                    ", done=" + f.isDone() + "]";
+                if (isMini(f)) {
+                    return "[node=" + ((MiniFuture)f).node().id() +
+                        ", loc=" + ((MiniFuture)f).node().isLocal() +
+                        ", done=" + f.isDone() + "]";
+                }
+                else
+                    return f.toString();
             }
-        }, new P1<IgniteInternalFuture<GridNearTxPrepareResponse>>() {
-            @Override public boolean apply(IgniteInternalFuture<GridNearTxPrepareResponse> fut) {
+        }, new P1<IgniteInternalFuture<Object>>() {
+            @Override public boolean apply(IgniteInternalFuture<Object> fut) {
                 return isMini(fut);
             }
         });
 
         return S.toString(GridNearOptimisticTxPrepareFuture.class, this,
             "innerFuts", futs,
-            "keyLockFut", keyLockFut,
             "tx", tx,
             "super", super.toString());
     }
@@ -1001,6 +1039,8 @@ void onResult(final GridNearTxPrepareResponse res) {
                         // Proceed prepare before finishing mini future.
                         if (mappings != null)
                             parent.proceedPrepare(mappings);
+                        else if (parent.mvccVerFut != null)
+                            parent.mvccVerFut.onLockReceived();
 
                         // Finish this mini future.
                         onDone((GridNearTxPrepareResponse)null);
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/near/GridNearOptimisticTxPrepareFutureAdapter.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/near/GridNearOptimisticTxPrepareFutureAdapter.java
index 45f89416b877..6f541d330b5a 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/near/GridNearOptimisticTxPrepareFutureAdapter.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/near/GridNearOptimisticTxPrepareFutureAdapter.java
@@ -18,19 +18,20 @@
 package org.apache.ignite.internal.processors.cache.distributed.near;
 
 import java.util.Collection;
-import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
 import org.apache.ignite.IgniteCheckedException;
 import org.apache.ignite.internal.IgniteInternalFuture;
+import org.apache.ignite.internal.cluster.ClusterTopologyCheckedException;
 import org.apache.ignite.internal.processors.affinity.AffinityTopologyVersion;
 import org.apache.ignite.internal.processors.cache.GridCacheSharedContext;
 import org.apache.ignite.internal.processors.cache.distributed.dht.GridDhtTopologyFuture;
+import org.apache.ignite.internal.processors.cache.mvcc.MvccSnapshot;
+import org.apache.ignite.internal.processors.cache.mvcc.MvccSnapshotResponseListener;
 import org.apache.ignite.internal.processors.cache.transactions.IgniteTxEntry;
 import org.apache.ignite.internal.processors.cache.transactions.IgniteTxKey;
-import org.apache.ignite.internal.processors.timeout.GridTimeoutObjectAdapter;
 import org.apache.ignite.internal.transactions.IgniteTxTimeoutCheckedException;
 import org.apache.ignite.internal.util.GridConcurrentHashSet;
 import org.apache.ignite.internal.util.future.GridFutureAdapter;
-import org.apache.ignite.internal.util.lang.GridAbsClosureX;
 import org.apache.ignite.internal.util.tostring.GridToStringExclude;
 import org.apache.ignite.internal.util.tostring.GridToStringInclude;
 import org.apache.ignite.internal.util.typedef.internal.S;
@@ -41,10 +42,21 @@
  *
  */
 public abstract class GridNearOptimisticTxPrepareFutureAdapter extends GridNearTxPrepareFutureAdapter {
+    /** */
+    private static final long serialVersionUID = 7460376140787916619L;
+
+    /** */
+    private static final AtomicIntegerFieldUpdater<MvccSnapshotFutureExt> LOCK_CNT_UPD =
+        AtomicIntegerFieldUpdater.newUpdater(MvccSnapshotFutureExt.class, "lockCnt");
+
     /** */
     @GridToStringExclude
     protected KeyLockFuture keyLockFut;
 
+    /** */
+    @GridToStringExclude
+    protected MvccSnapshotFutureExt mvccVerFut;
+
     /**
      * @param cctx Context.
      * @param tx Transaction.
@@ -73,7 +85,7 @@ protected GridNearOptimisticTxPrepareFutureAdapter(GridCacheSharedContext cctx,
             }
 
             if (keyLockFut != null)
-                add(keyLockFut);
+                add((IgniteInternalFuture)keyLockFut);
         }
     }
 
@@ -201,6 +213,29 @@ protected final void prepareOnTopology(final boolean remap, @Nullable final Runn
      */
     protected abstract void prepare0(boolean remap, boolean topLocked);
 
+    /**
+     * @param lockCnt Expected number of lock responses.
+     * @param remap Remap flag.
+     */
+    @SuppressWarnings("unchecked")
+    final void initMvccVersionFuture(int lockCnt, boolean remap) {
+        if (!remap) {
+            mvccVerFut = new MvccSnapshotFutureExt();
+
+            mvccVerFut.init(lockCnt);
+
+            if (keyLockFut != null)
+                keyLockFut.listen(mvccVerFut);
+
+            add((IgniteInternalFuture)mvccVerFut);
+        }
+        else {
+            assert mvccVerFut != null;
+
+            mvccVerFut.init(lockCnt);
+        }
+    }
+
     /**
      * @param e Exception.
      * @param timedOut {@code True} if timed out.
@@ -223,7 +258,7 @@ protected boolean errorOrTimeoutOnTopologyVersion(IgniteCheckedException e, bool
     /**
      * Keys lock future.
      */
-    protected static class KeyLockFuture extends GridFutureAdapter<GridNearTxPrepareResponse> {
+    protected static class KeyLockFuture extends GridFutureAdapter<Void> {
         /** */
         @GridToStringInclude
         protected Collection<IgniteTxKey> lockKeys = new GridConcurrentHashSet<>();
@@ -258,24 +293,20 @@ protected void onAllKeysAdded() {
             checkLocks();
         }
 
-        /**
-         * @return {@code True} if all locks are owned.
-         */
-        private boolean checkLocks() {
+        /** */
+        private void checkLocks() {
             boolean locked = lockKeys.isEmpty();
 
             if (locked && allKeysAdded) {
                 if (log.isDebugEnabled())
                     log.debug("All locks are acquired for near prepare future: " + this);
 
-                onDone((GridNearTxPrepareResponse)null);
+                onDone((Void)null);
             }
             else {
                 if (log.isDebugEnabled())
                     log.debug("Still waiting for locks [fut=" + this + ", keys=" + lockKeys + ']');
             }
-
-            return locked;
         }
 
         /** {@inheritDoc} */
@@ -283,4 +314,82 @@ private boolean checkLocks() {
             return S.toString(KeyLockFuture.class, this, super.toString());
         }
     }
+
+    /**
+     *
+     */
+    class MvccSnapshotFutureExt extends GridFutureAdapter<Void> implements MvccSnapshotResponseListener, IgniteInClosure<IgniteInternalFuture<Void>> {
+        /** */
+        private static final long serialVersionUID = 5883078648683911226L;
+
+        /** */
+        volatile int lockCnt;
+
+        /** {@inheritDoc} */
+        @Override public void apply(IgniteInternalFuture<Void> keyLockFut) {
+            try {
+                keyLockFut.get();
+
+                onLockReceived();
+            }
+            catch (IgniteCheckedException e) {
+                if (log.isDebugEnabled())
+                    log.debug("MvccSnapshotFutureExt ignores key lock future failure: " + e);
+            }
+        }
+
+        /**
+         * @param lockCnt Expected number of lock responses.
+         */
+        void init(int lockCnt) {
+            assert lockCnt > 0;
+
+            this.lockCnt = lockCnt;
+
+            assert !isDone();
+        }
+
+        /** */
+        void onLockReceived() {
+            int remaining = LOCK_CNT_UPD.decrementAndGet(this);
+
+            assert remaining >= 0 : remaining;
+
+            if (remaining == 0) {
+                try {
+                    MvccSnapshot snapshot = cctx.coordinators().tryRequestSnapshotLocal(tx);
+
+                    if (snapshot != null)
+                        onResponse(snapshot);
+                    else
+                        cctx.coordinators().requestSnapshotAsync(tx, this);
+                }
+                catch (ClusterTopologyCheckedException e) {
+                    onError(e);
+                }
+            }
+        }
+
+        /** {@inheritDoc} */
+        @Override public void onResponse(MvccSnapshot res) {
+            tx.mvccSnapshot(res);
+
+            onDone();
+        }
+
+        /** {@inheritDoc} */
+        @Override public void onError(IgniteCheckedException e) {
+            if (e instanceof ClusterTopologyCheckedException)
+                ((ClusterTopologyCheckedException)e).retryReadyFuture(cctx.nextAffinityReadyFuture(tx.topologyVersion()));
+
+            ERR_UPD.compareAndSet(GridNearOptimisticTxPrepareFutureAdapter.this, null, e);
+
+            onDone();
+        }
+
+        /** {@inheritDoc} */
+        @Override public String toString() {
+            return S.toString(MvccSnapshotFutureExt.class, this, super.toString());
+        }
+    }
 }
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/near/GridNearPessimisticTxPrepareFuture.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/near/GridNearPessimisticTxPrepareFuture.java
index ffde0f39b023..dbf54c23af3e 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/near/GridNearPessimisticTxPrepareFuture.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/near/GridNearPessimisticTxPrepareFuture.java
@@ -36,6 +36,9 @@
 import org.apache.ignite.internal.processors.cache.distributed.GridDistributedTxMapping;
 import org.apache.ignite.internal.processors.cache.distributed.dht.GridDhtPartitionTopology;
 import org.apache.ignite.internal.processors.cache.distributed.dht.GridDhtTxMapping;
+import org.apache.ignite.internal.processors.cache.mvcc.MvccCoordinator;
+import org.apache.ignite.internal.processors.cache.mvcc.MvccSnapshot;
+import org.apache.ignite.internal.processors.cache.mvcc.MvccSnapshotResponseListener;
 import org.apache.ignite.internal.processors.cache.transactions.IgniteInternalTx;
 import org.apache.ignite.internal.processors.cache.transactions.IgniteTxEntry;
 import org.apache.ignite.internal.transactions.IgniteTxTimeoutCheckedException;
@@ -43,11 +46,13 @@
 import org.apache.ignite.internal.util.typedef.C1;
 import org.apache.ignite.internal.util.typedef.CI1;
 import org.apache.ignite.internal.util.typedef.F;
+import org.apache.ignite.internal.util.typedef.internal.CU;
 import org.apache.ignite.internal.util.typedef.internal.S;
 import org.apache.ignite.internal.util.typedef.internal.U;
 import org.jetbrains.annotations.Nullable;
 
 import static org.apache.ignite.internal.processors.cache.GridCacheOperation.TRANSFORM;
+import static org.apache.ignite.internal.processors.cache.mvcc.MvccUtils.noCoordinatorError;
 import static org.apache.ignite.transactions.TransactionState.PREPARED;
 import static org.apache.ignite.transactions.TransactionState.PREPARING;
 
@@ -55,6 +60,9 @@
  *
  */
 public class GridNearPessimisticTxPrepareFuture extends GridNearTxPrepareFutureAdapter {
+    /** */
+    private static final long serialVersionUID = 4014479758215810181L;
+
     /**
      * @param cctx Context.
      * @param tx Transaction.
@@ -80,17 +88,19 @@ public GridNearPessimisticTxPrepareFuture(GridCacheSharedContext cctx, GridNearT
         boolean found = false;
 
         for (IgniteInternalFuture<?> fut : futures()) {
-            MiniFuture f = (MiniFuture)fut;
+            if (fut instanceof MiniFuture) {
+                MiniFuture f = (MiniFuture)fut;
 
-            if (f.primary().id().equals(nodeId)) {
-                ClusterTopologyCheckedException e = new ClusterTopologyCheckedException("Remote node left grid: " +
-                    nodeId);
+                if (f.primary().id().equals(nodeId)) {
+                    ClusterTopologyCheckedException e = new ClusterTopologyCheckedException("Remote node left grid: " +
+                        nodeId);
 
-                e.retryReadyFuture(cctx.nextAffinityReadyFuture(tx.topologyVersion()));
+                    e.retryReadyFuture(cctx.nextAffinityReadyFuture(tx.topologyVersion()));
 
-                f.onNodeLeft(e);
+                    f.onNodeLeft(e);
 
-                found = true;
+                    found = true;
+                }
             }
         }
 
@@ -142,13 +152,17 @@ private MiniFuture miniFuture(int miniId) {
 
             // Avoid iterator creation.
             for (int i = 0; i < size; i++) {
-                MiniFuture mini = (MiniFuture)future(i);
+                IgniteInternalFuture fut = future(i);
 
-                if (mini.futureId() == miniId) {
-                    if (!mini.isDone())
-                        return mini;
-                    else
-                        return null;
+                if (fut instanceof MiniFuture) {
+                    MiniFuture mini = (MiniFuture)fut;
+
+                    if (mini.futureId() == miniId) {
+                        if (!mini.isDone())
+                            return mini;
+                        else
+                            return null;
+                    }
                 }
             }
         }
@@ -217,6 +231,8 @@ private GridNearTxPrepareRequest createRequest(Map<UUID, Collection<UUID>> txNod
             true,
             tx.activeCachesDeploymentEnabled());
 
+        req.queryUpdate(m.queryUpdate());
+
         for (IgniteTxEntry txEntry : writes) {
             if (txEntry.op() == TRANSFORM)
                 req.addDhtVersion(txEntry.txKey(), null);
@@ -231,6 +247,7 @@ private GridNearTxPrepareRequest createRequest(Map<UUID, Collection<UUID>> txNod
      * @param miniId Mini future ID.
      * @param nearEntries {@code True} if prepare near cache entries.
      */
+    @SuppressWarnings("unchecked")
     private void prepareLocal(GridNearTxPrepareRequest req,
         GridDistributedTxMapping m,
         int miniId,
@@ -239,7 +256,7 @@ private void prepareLocal(GridNearTxPrepareRequest req,
 
         req.miniId(fut.futureId());
 
-        add(fut);
+        add((IgniteInternalFuture)fut);
 
         IgniteInternalFuture<GridNearTxPrepareResponse> prepFut = nearEntries ?
             cctx.tm().txHandler().prepareNearTxLocal(req) :
@@ -260,6 +277,7 @@ private void prepareLocal(GridNearTxPrepareRequest req,
     /**
      *
      */
+    @SuppressWarnings("unchecked")
     private void preparePessimistic() {
         Map<UUID, GridDistributedTxMapping> mappings = new HashMap<>();
 
@@ -267,48 +285,77 @@ private void preparePessimistic() {
 
         GridDhtTxMapping txMapping = new GridDhtTxMapping();
 
+        boolean queryMapped = false;
+
+        for (GridDistributedTxMapping m : F.view(tx.mappings().mappings(), CU.FILTER_QUERY_MAPPING)) {
+            GridDistributedTxMapping nodeMapping = mappings.get(m.primary().id());
+
+            if(nodeMapping == null)
+                mappings.put(m.primary().id(), m);
+
+            txMapping.addMapping(F.asList(m.primary()));
+
+            queryMapped = true;
+        }
+
+        MvccCoordinator mvccCrd = null;
+
         boolean hasNearCache = false;
 
-        for (IgniteTxEntry txEntry : tx.allEntries()) {
-            txEntry.clearEntryReadVersion();
+        if (!queryMapped) {
+            for (IgniteTxEntry txEntry : tx.allEntries()) {
+                txEntry.clearEntryReadVersion();
 
-            GridCacheContext cacheCtx = txEntry.context();
+                GridCacheContext cacheCtx = txEntry.context();
 
-            if (cacheCtx.isNear())
-                hasNearCache = true;
+                if (cacheCtx.isNear())
+                    hasNearCache = true;
 
-            List<ClusterNode> nodes;
+                List<ClusterNode> nodes;
 
-            if (!cacheCtx.isLocal()) {
-                GridDhtPartitionTopology top = cacheCtx.topology();
+                if (!cacheCtx.isLocal()) {
+                    GridDhtPartitionTopology top = cacheCtx.topology();
 
-                nodes = top.nodes(cacheCtx.affinity().partition(txEntry.key()), topVer);
-            }
-            else
-                nodes = cacheCtx.affinity().nodesByKey(txEntry.key(), topVer);
+                    nodes = top.nodes(cacheCtx.affinity().partition(txEntry.key()), topVer);
+                }
+                else
+                    nodes = cacheCtx.affinity().nodesByKey(txEntry.key(), topVer);
 
-            if (F.isEmpty(nodes)) {
-                onDone(new ClusterTopologyServerNotFoundException("Failed to map keys to nodes (partition " +
-                    "is not mapped to any node) [key=" + txEntry.key() +
-                    ", partition=" + cacheCtx.affinity().partition(txEntry.key()) + ", topVer=" + topVer + ']'));
+                if (tx.mvccSnapshot() == null && mvccCrd == null && cacheCtx.mvccEnabled()) {
+                    mvccCrd = cacheCtx.affinity().mvccCoordinator(topVer);
 
-                return;
-            }
+                    if (mvccCrd == null) {
+                        onDone(noCoordinatorError(topVer));
+
+                        return;
+                    }
+                }
+
+                if (F.isEmpty(nodes)) {
+                    onDone(new ClusterTopologyServerNotFoundException("Failed to map keys to nodes (partition " +
+                        "is not mapped to any node) [key=" + txEntry.key() +
+                        ", partition=" + cacheCtx.affinity().partition(txEntry.key()) + ", topVer=" + topVer + ']'));
+
+                    return;
+                }
 
-            ClusterNode primary = nodes.get(0);
+                ClusterNode primary = nodes.get(0);
 
-            GridDistributedTxMapping nodeMapping = mappings.get(primary.id());
+                GridDistributedTxMapping nodeMapping = mappings.get(primary.id());
 
-            if (nodeMapping == null)
-                mappings.put(primary.id(), nodeMapping = new GridDistributedTxMapping(primary));
+                if (nodeMapping == null)
+                    mappings.put(primary.id(), nodeMapping = new GridDistributedTxMapping(primary));
 
-            txEntry.nodeId(primary.id());
+                txEntry.nodeId(primary.id());
 
-            nodeMapping.add(txEntry);
+                nodeMapping.add(txEntry);
 
-            txMapping.addMapping(nodes);
+                txMapping.addMapping(nodes);
+            }
         }
 
+        assert !tx.txState().mvccEnabled(cctx) || tx.mvccSnapshot() != null || mvccCrd != null;
+
         tx.transactionNodes(txMapping.transactionNodes());
 
         if (!hasNearCache)
@@ -329,6 +376,16 @@ private void preparePessimistic() {
         for (final GridDistributedTxMapping m : mappings.values()) {
             final ClusterNode primary = m.primary();
 
+            boolean needCntr = false;
+
+            if (mvccCrd != null) {
+                if (tx.onePhaseCommit() || mvccCrd.nodeId().equals(primary.id())) {
+                    needCntr = true;
+
+                    mvccCrd = null;
+                }
+            }
+
             if (primary.isLocal()) {
                 if (m.hasNearCacheEntries() && m.hasColocatedCacheEntries()) {
                     GridNearTxPrepareRequest nearReq = createRequest(txMapping.transactionNodes(),
@@ -337,6 +394,8 @@ private void preparePessimistic() {
                         m.nearEntriesReads(),
                         m.nearEntriesWrites());
 
+                    nearReq.requestMvccCounter(needCntr);
+
                     prepareLocal(nearReq, m, ++miniId, true);
 
                     GridNearTxPrepareRequest colocatedReq = createRequest(txNodes,
@@ -350,6 +409,8 @@ private void preparePessimistic() {
                 else {
                     GridNearTxPrepareRequest req = createRequest(txNodes, m, timeout, m.reads(), m.writes());
 
+                    req.requestMvccCounter(needCntr);
+
                     prepareLocal(req, m, ++miniId, m.hasNearCacheEntries());
                 }
             }
@@ -360,11 +421,13 @@ private void preparePessimistic() {
                     m.reads(),
                     m.writes());
 
+                req.requestMvccCounter(needCntr);
+
                 final MiniFuture fut = new MiniFuture(m, ++miniId);
 
                 req.miniId(fut.futureId());
 
-                add(fut);
+                add((IgniteInternalFuture)fut);
 
                 try {
                     cctx.io().send(primary, req, tx.ioPolicy());
@@ -392,6 +455,16 @@ private void preparePessimistic() {
             }
         }
 
+        if (mvccCrd != null) {
+            assert !tx.onePhaseCommit();
+
+            MvccSnapshotFutureExt fut = new MvccSnapshotFutureExt();
+
+            cctx.coordinators().requestSnapshotAsync(tx, fut);
+
+            add((IgniteInternalFuture)fut);
+        }
+
         markInitialized();
     }
 
@@ -423,20 +496,52 @@ private void preparePessimistic() {
     @Override public String toString() {
         Collection<String> futs = F.viewReadOnly(futures(), new C1<IgniteInternalFuture<?>, String>() {
             @Override public String apply(IgniteInternalFuture<?> f) {
-                return "[node=" + ((MiniFuture)f).primary().id() +
-                    ", loc=" + ((MiniFuture)f).primary().isLocal() +
-                    ", done=" + f.isDone() + "]";
+                if (f instanceof MiniFuture) {
+                    return "[node=" + ((MiniFuture)f).primary().id() +
+                        ", loc=" + ((MiniFuture)f).primary().isLocal() +
+                        ", done=" + f.isDone() + "]";
+                }
+                else
+                    return f.toString();
             }
         });
 
         return S.toString(GridNearPessimisticTxPrepareFuture.class, this,
             "innerFuts", futs,
+            "txId", tx.nearXidVersion(),
             "super", super.toString());
     }
 
     /**
      *
      */
+    private class MvccSnapshotFutureExt extends GridFutureAdapter<Void> implements MvccSnapshotResponseListener {
+        /** {@inheritDoc} */
+        @Override public void onResponse(MvccSnapshot res) {
+            tx.mvccSnapshot(res);
+
+            onDone();
+        }
+
+        /** {@inheritDoc} */
+        @Override public void onError(IgniteCheckedException e) {
+            if (log.isDebugEnabled())
+                log.debug("Error on tx prepare [fut=" + this + ", err=" + e + ", tx=" + tx +  ']');
+
+            if (ERR_UPD.compareAndSet(GridNearPessimisticTxPrepareFuture.this, null, e))
+                tx.setRollbackOnly();
+
+            onDone(e);
+        }
+
+        /** {@inheritDoc} */
+        @Override public String toString() {
+            return S.toString(MvccSnapshotFutureExt.class, this, super.toString());
+        }
+    }
+
+
+    /** */
     private class MiniFuture extends GridFutureAdapter<GridNearTxPrepareResponse> {
         /** */
         private final int futId;
@@ -475,6 +580,9 @@ void onResult(GridNearTxPrepareResponse res, boolean updateMapping) {
             if (res.error() != null)
                 onError(res.error());
             else {
+                if (res.mvccSnapshot() != null)
+                    tx.mvccSnapshot(res.mvccSnapshot());
+
                 onPrepareResponse(m, res, updateMapping);
 
                 onDone(res);
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/near/GridNearSingleGetRequest.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/near/GridNearSingleGetRequest.java
index 00ff4bb60bf9..d1aa2e96b949 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/near/GridNearSingleGetRequest.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/near/GridNearSingleGetRequest.java
@@ -26,11 +26,13 @@
 import org.apache.ignite.internal.processors.cache.GridCacheIdMessage;
 import org.apache.ignite.internal.processors.cache.GridCacheSharedContext;
 import org.apache.ignite.internal.processors.cache.KeyCacheObject;
+import org.apache.ignite.internal.processors.cache.mvcc.MvccSnapshot;
 import org.apache.ignite.internal.util.typedef.internal.S;
 import org.apache.ignite.plugin.extensions.communication.Message;
 import org.apache.ignite.plugin.extensions.communication.MessageReader;
 import org.apache.ignite.plugin.extensions.communication.MessageWriter;
 import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
 
 /**
  *
@@ -81,6 +83,9 @@
     /** TTL for read operation. */
     private long accessTtl;
 
+    /** */
+    private MvccSnapshot mvccSnapshot;
+
     /**
      * Empty constructor required for {@link Message}.
      */
@@ -103,6 +108,7 @@ public GridNearSingleGetRequest() {
      * @param addReader Add reader flag.
      * @param needVer {@code True} if entry version is needed.
      * @param addDepInfo Deployment info.
+     * @param mvccSnapshot MVCC snapshot.
      */
     public GridNearSingleGetRequest(
         int cacheId,
@@ -118,7 +124,8 @@ public GridNearSingleGetRequest(
         boolean addReader,
         boolean needVer,
         boolean addDepInfo,
-        boolean recovery
+        boolean recovery,
+        MvccSnapshot mvccSnapshot
     ) {
         assert key != null;
 
@@ -131,6 +138,7 @@ public GridNearSingleGetRequest(
         this.createTtl = createTtl;
         this.accessTtl = accessTtl;
         this.addDepInfo = addDepInfo;
+        this.mvccSnapshot = mvccSnapshot;
 
         if (readThrough)
             flags |= READ_THROUGH_FLAG_MASK;
@@ -148,6 +156,13 @@ public GridNearSingleGetRequest(
             flags |= RECOVERY_FLAG_MASK;
     }
 
+    /**
+     * @return Mvcc version.
+     */
+    @Nullable public MvccSnapshot mvccSnapshot() {
+        return mvccSnapshot;
+    }
+
     /**
      * @return Key.
      */
@@ -322,7 +337,7 @@ public boolean recovery() {
                 reader.incrementState();
 
             case 8:
-                subjId = reader.readUuid("subjId");
+                mvccSnapshot = reader.readMessage("mvccSnapshot");
 
                 if (!reader.isLastRead())
                     return false;
@@ -330,7 +345,7 @@ public boolean recovery() {
                 reader.incrementState();
 
             case 9:
-                taskNameHash = reader.readInt("taskNameHash");
+                subjId = reader.readUuid("subjId");
 
                 if (!reader.isLastRead())
                     return false;
@@ -338,6 +353,14 @@ public boolean recovery() {
                 reader.incrementState();
 
             case 10:
+                taskNameHash = reader.readInt("taskNameHash");
+
+                if (!reader.isLastRead())
+                    return false;
+
+                reader.incrementState();
+
+            case 11:
                 topVer = reader.readMessage("topVer");
 
                 if (!reader.isLastRead())
@@ -396,18 +419,24 @@ public boolean recovery() {
                 writer.incrementState();
 
             case 8:
-                if (!writer.writeUuid("subjId", subjId))
+                if (!writer.writeMessage("mvccSnapshot", mvccSnapshot))
                     return false;
 
                 writer.incrementState();
 
             case 9:
-                if (!writer.writeInt("taskNameHash", taskNameHash))
+                if (!writer.writeUuid("subjId", subjId))
                     return false;
 
                 writer.incrementState();
 
             case 10:
+                if (!writer.writeInt("taskNameHash", taskNameHash))
+                    return false;
+
+                writer.incrementState();
+
+            case 11:
                 if (!writer.writeMessage("topVer", topVer))
                     return false;
 
@@ -430,7 +459,7 @@ public boolean recovery() {
 
     /** {@inheritDoc} */
     @Override public byte fieldsCount() {
-        return 11;
+        return 12;
     }
 
     /** {@inheritDoc} */
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/near/GridNearTxAbstractEnlistFuture.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/near/GridNearTxAbstractEnlistFuture.java
new file mode 100644
index 000000000000..f484bd6ec345
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/near/GridNearTxAbstractEnlistFuture.java
@@ -0,0 +1,499 @@
+/*
+ * 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.cache.distributed.near;
+
+import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
+import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
+import org.apache.ignite.IgniteCheckedException;
+import org.apache.ignite.IgniteLogger;
+import org.apache.ignite.cluster.ClusterNode;
+import org.apache.ignite.internal.IgniteFutureCancelledCheckedException;
+import org.apache.ignite.internal.IgniteInternalFuture;
+import org.apache.ignite.internal.processors.affinity.AffinityTopologyVersion;
+import org.apache.ignite.internal.processors.cache.CacheStoppedException;
+import org.apache.ignite.internal.processors.cache.GridCacheCompoundIdentityFuture;
+import org.apache.ignite.internal.processors.cache.GridCacheContext;
+import org.apache.ignite.internal.processors.cache.GridCacheEntryEx;
+import org.apache.ignite.internal.processors.cache.GridCacheMvccCandidate;
+import org.apache.ignite.internal.processors.cache.GridCacheVersionedFuture;
+import org.apache.ignite.internal.processors.cache.distributed.GridDistributedTxMapping;
+import org.apache.ignite.internal.processors.cache.distributed.dht.CompoundLockFuture;
+import org.apache.ignite.internal.processors.cache.distributed.dht.GridDhtTopologyFuture;
+import org.apache.ignite.internal.processors.cache.distributed.dht.GridDhtTxAbstractEnlistFuture;
+import org.apache.ignite.internal.processors.cache.distributed.dht.GridDhtTxLocalAdapter;
+import org.apache.ignite.internal.processors.cache.mvcc.MvccSnapshot;
+import org.apache.ignite.internal.processors.cache.version.GridCacheVersion;
+import org.apache.ignite.internal.processors.timeout.GridTimeoutObjectAdapter;
+import org.apache.ignite.internal.transactions.IgniteTxTimeoutCheckedException;
+import org.apache.ignite.internal.util.tostring.GridToStringExclude;
+import org.apache.ignite.internal.util.typedef.CI1;
+import org.apache.ignite.internal.util.typedef.internal.CU;
+import org.apache.ignite.internal.util.typedef.internal.S;
+import org.apache.ignite.internal.util.typedef.internal.U;
+import org.apache.ignite.lang.IgniteInClosure;
+import org.apache.ignite.lang.IgniteUuid;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ *
+ */
+public abstract class GridNearTxAbstractEnlistFuture extends GridCacheCompoundIdentityFuture<Long> implements
+    GridCacheVersionedFuture<Long> {
+    /** */
+    private static final long serialVersionUID = -6069985059301497282L;
+
+    /** Done field updater. */
+    private static final AtomicIntegerFieldUpdater<GridNearTxAbstractEnlistFuture> DONE_UPD =
+        AtomicIntegerFieldUpdater.newUpdater(GridNearTxAbstractEnlistFuture.class, "done");
+
+    /** Done field updater. */
+    private static final AtomicReferenceFieldUpdater<GridNearTxAbstractEnlistFuture, Throwable> EX_UPD =
+        AtomicReferenceFieldUpdater.newUpdater(GridNearTxAbstractEnlistFuture.class, Throwable.class, "ex");
+
+    /** Cache context. */
+    @GridToStringExclude
+    protected final GridCacheContext<?, ?> cctx;
+
+    /** Transaction. */
+    protected final GridNearTxLocal tx;
+
+    /** */
+    protected AffinityTopologyVersion topVer;
+
+    /** MVCC snapshot. */
+    protected MvccSnapshot mvccSnapshot;
+
+    /** Logger. */
+    @GridToStringExclude
+    protected final IgniteLogger log;
+
+    /** */
+    protected long timeout;
+
+    /** Initiated thread id. */
+    protected final long threadId;
+
+    /** Mvcc future id. */
+    protected final IgniteUuid futId;
+
+    /** Lock version. */
+    protected final GridCacheVersion lockVer;
+
+    /** */
+    @GridToStringExclude
+    private GridDhtTxAbstractEnlistFuture localEnlistFuture;
+
+    /** */
+    @SuppressWarnings("unused")
+    @GridToStringExclude
+    protected volatile Throwable ex;
+
+    /** */
+    @SuppressWarnings("unused")
+    @GridToStringExclude
+    private volatile int done;
+
+    /** Timeout object. */
+    @GridToStringExclude
+    protected LockTimeoutObject timeoutObj;
+
+    /**
+     * @param cctx Cache context.
+     * @param tx Transaction.
+     * @param timeout Timeout.
+     */
+    public GridNearTxAbstractEnlistFuture(
+        GridCacheContext<?, ?> cctx, GridNearTxLocal tx, long timeout) {
+        super(CU.longReducer());
+
+        assert cctx != null;
+        assert tx != null;
+
+        this.cctx = cctx;
+        this.tx = tx;
+        this.timeout = timeout;
+
+        threadId = tx.threadId();
+        lockVer = tx.xidVersion();
+        futId = IgniteUuid.randomUuid();
+
+        mvccSnapshot = tx.mvccSnapshot();
+
+        assert mvccSnapshot != null;
+
+        log = cctx.logger(getClass());
+    }
+
+    /**
+     *
+     */
+    public void init() {
+        if (timeout < 0) {
+            // Time is out.
+            onDone(timeoutException());
+
+            return;
+        }
+        else if (timeout > 0)
+            timeoutObj = new LockTimeoutObject();
+
+        while(true) {
+            IgniteInternalFuture<?> fut = tx.lockFuture();
+
+            if (fut == GridDhtTxLocalAdapter.ROLLBACK_FUT) {
+                onDone(tx.timedOut() ? tx.timeoutException() : tx.rollbackException());
+
+                return;
+            }
+            else if (fut != null) {
+                // Wait for previous future.
+                assert fut instanceof GridNearTxAbstractEnlistFuture
+                    || fut instanceof GridDhtTxAbstractEnlistFuture
+                    || fut instanceof CompoundLockFuture
+                    || fut instanceof GridNearTxSelectForUpdateFuture : fut;
+
+                // Terminate this future if parent future is terminated by rollback.
+                if (!fut.isDone()) {
+                    fut.listen(new IgniteInClosure<IgniteInternalFuture>() {
+                        @Override public void apply(IgniteInternalFuture fut) {
+                            if (fut.error() != null)
+                                onDone(fut.error());
+                        }
+                    });
+                }
+                else if (fut.error() != null)
+                    onDone(fut.error());
+
+                break;
+            }
+            else if (tx.updateLockFuture(null, this))
+                break;
+        }
+
+        boolean added = cctx.mvcc().addFuture(this);
+
+        assert added : this;
+
+        if (isDone()) {
+            cctx.mvcc().removeFuture(futId);
+
+            return;
+        }
+
+        try {
+            tx.addActiveCache(cctx, false);
+        }
+        catch (IgniteCheckedException e) {
+            onDone(e);
+
+            return;
+        }
+
+        if (timeoutObj != null)
+            cctx.time().addTimeoutObject(timeoutObj);
+
+        // Obtain the topology version to use.
+        long threadId = Thread.currentThread().getId();
+
+        AffinityTopologyVersion topVer = cctx.mvcc().lastExplicitLockTopologyVersion(threadId);
+
+        // If there is another system transaction in progress, use it's topology version to prevent deadlock.
+        if (topVer == null && tx.system())
+            topVer = cctx.tm().lockedTopologyVersion(threadId, tx);
+
+        if (topVer != null)
+            tx.topologyVersion(topVer);
+
+        if (topVer == null)
+            topVer = tx.topologyVersionSnapshot();
+
+        if (topVer != null) {
+            for (GridDhtTopologyFuture fut : cctx.shared().exchange().exchangeFutures()) {
+                if (fut.exchangeDone() && fut.topologyVersion().equals(topVer)) {
+                    Throwable err = fut.validateCache(cctx, false, false, null, null);
+
+                    if (err != null) {
+                        onDone(err);
+
+                        return;
+                    }
+
+                    break;
+                }
+            }
+
+            if (this.topVer == null)
+                this.topVer = topVer;
+
+            map(true);
+
+            return;
+        }
+
+        mapOnTopology();
+    }
+
+    /**
+     * @param node Primary node.
+     * @throws IgniteCheckedException if future is already completed.
+     */
+    protected synchronized void updateMappings(ClusterNode node) throws IgniteCheckedException {
+        checkCompleted();
+
+        IgniteTxMappings m = tx.mappings();
+
+        GridDistributedTxMapping mapping = m.get(node.id());
+
+        if (mapping == null)
+            m.put(mapping = new GridDistributedTxMapping(node));
+
+        mapping.markQueryUpdate();
+
+        if (node.isLocal())
+            tx.colocatedLocallyMapped(true);
+    }
+
+    /**
+     * @param fut Local enlist future.
+     * @throws IgniteCheckedException if future is already completed.
+     */
+    protected synchronized void updateLocalFuture(GridDhtTxAbstractEnlistFuture fut) throws IgniteCheckedException {
+        checkCompleted();
+
+        assert localEnlistFuture == null;
+
+        localEnlistFuture = fut;
+    }
+
+    /**
+     * @param fut Local enlist future.
+     * @throws IgniteCheckedException if future is already completed.
+     */
+    protected synchronized void clearLocalFuture(GridDhtTxAbstractEnlistFuture fut) throws IgniteCheckedException {
+        checkCompleted();
+
+        if (localEnlistFuture == fut)
+            localEnlistFuture = null;
+    }
+
+    /**
+     * @throws IgniteCheckedException if future is already completed.
+     */
+    protected void checkCompleted() throws IgniteCheckedException {
+        if (isDone())
+            throw new IgniteCheckedException("Future is done.");
+    }
+
+
+
+    /**
+     */
+    private void mapOnTopology() {
+        cctx.topology().readLock();
+
+        try {
+            if (cctx.topology().stopping()) {
+                onDone(new CacheStoppedException(cctx.name()));
+
+                return;
+            }
+
+            GridDhtTopologyFuture fut = cctx.topologyVersionFuture();
+
+            if (fut.isDone()) {
+                Throwable err = fut.validateCache(cctx, false, false, null, null);
+
+                if (err != null) {
+                    onDone(err);
+
+                    return;
+                }
+
+                AffinityTopologyVersion topVer = fut.topologyVersion();
+
+                if (tx != null)
+                    tx.topologyVersion(topVer);
+
+                if (this.topVer == null)
+                    this.topVer = topVer;
+
+                map(false);
+            }
+            else {
+                fut.listen(new CI1<IgniteInternalFuture<AffinityTopologyVersion>>() {
+                    @Override public void apply(IgniteInternalFuture<AffinityTopologyVersion> fut) {
+                        try {
+                            fut.get();
+
+                            mapOnTopology();
+                        }
+                        catch (IgniteCheckedException e) {
+                            onDone(e);
+                        }
+                        finally {
+                            cctx.shared().txContextReset();
+                        }
+                    }
+                });
+            }
+        }
+        finally {
+            cctx.topology().readUnlock();
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override protected boolean processFailure(Throwable err, IgniteInternalFuture<Long> fut) {
+        if (ex != null || !EX_UPD.compareAndSet(this, null, err))
+            ex.addSuppressed(err);
+
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean onDone(@Nullable Long res, @Nullable Throwable err, boolean cancelled) {
+        if (!DONE_UPD.compareAndSet(this, 0, 1))
+            return false;
+
+        cctx.tm().txContext(tx);
+
+        Throwable ex0 = ex;
+
+        if (ex0 != null) {
+            if (err != null)
+                ex0.addSuppressed(err);
+
+            err = ex0;
+        }
+
+        if (!cancelled && err == null)
+            tx.clearLockFuture(this);
+        else
+            tx.setRollbackOnly();
+
+        synchronized (this) {
+            boolean done = super.onDone(res, err, cancelled);
+
+            assert done;
+
+            GridDhtTxAbstractEnlistFuture localFuture0 = localEnlistFuture;
+
+            if (localFuture0 != null && (err != null || cancelled))
+                localFuture0.onDone(cancelled ? new IgniteFutureCancelledCheckedException("Future was cancelled: " + localFuture0) : err);
+
+            // Clean up.
+            cctx.mvcc().removeVersionedFuture(this);
+
+            if (timeoutObj != null)
+                cctx.time().removeTimeoutObject(timeoutObj);
+
+            return true;
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void logError(IgniteLogger log, String msg, Throwable e) {
+        // no-op
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void logDebug(IgniteLogger log, String msg) {
+        // no-op
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean trackable() {
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void markNotTrackable() {
+        // No-op.
+    }
+
+    @Override public GridCacheVersion version() {
+        return lockVer;
+    }
+
+    @Override public boolean onOwnerChanged(GridCacheEntryEx entry, GridCacheMvccCandidate owner) {
+        return false;
+    }
+
+    @Override public IgniteUuid futureId() {
+        return futId;
+    }
+
+    /**
+     * Gets remaining allowed time.
+     *
+     * @return Remaining time. {@code 0} if timeout isn't specified. {@code -1} if time is out.
+     * @throws IgniteTxTimeoutCheckedException If timed out.
+     */
+    protected long remainingTime() throws IgniteTxTimeoutCheckedException {
+        if (timeout <= 0)
+            return 0;
+
+        long timeLeft = timeout - (U.currentTimeMillis() - startTime());
+
+        if (timeLeft <= 0)
+            throw timeoutException();
+
+        return timeLeft;
+    }
+
+    /**
+     * @return Timeout exception.
+     */
+    @NotNull protected IgniteTxTimeoutCheckedException timeoutException() {
+        return new IgniteTxTimeoutCheckedException("Failed to acquire lock within provided timeout for " +
+            "transaction [timeout=" + timeout + ", tx=" + tx + ']');
+    }
+
+    /**
+     * Start iterating the data rows and form batches.
+     *
+     * @param topLocked Whether topology was already locked.
+     */
+    protected abstract void map(boolean topLocked);
+
+    /**
+     * Lock request timeout object.
+     */
+    private class LockTimeoutObject extends GridTimeoutObjectAdapter {
+        /**
+         * Default constructor.
+         */
+        LockTimeoutObject() {
+            super(timeout);
+        }
+
+        /** {@inheritDoc} */
+        @Override public void onTimeout() {
+            if (log.isDebugEnabled())
+                log.debug("Timed out waiting for lock response: " + this);
+
+            onDone(timeoutException());
+        }
+
+        /** {@inheritDoc} */
+        @Override public String toString() {
+            return S.toString(LockTimeoutObject.class, this);
+        }
+    }
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/near/GridNearTxFastFinishFuture.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/near/GridNearTxFastFinishFuture.java
index 53d901aafb93..435f2814af84 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/near/GridNearTxFastFinishFuture.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/near/GridNearTxFastFinishFuture.java
@@ -17,6 +17,7 @@
 
 package org.apache.ignite.internal.processors.cache.distributed.near;
 
+import org.apache.ignite.IgniteCheckedException;
 import org.apache.ignite.internal.processors.cache.transactions.IgniteInternalTx;
 import org.apache.ignite.internal.util.future.GridFutureAdapter;
 
@@ -51,10 +52,15 @@
         return commit;
     }
 
+    /** {@inheritDoc} */
+    @Override public GridNearTxLocal tx() {
+        return tx;
+    }
+
     /**
      * @param clearThreadMap {@code True} if need remove tx from thread map.
      */
-    public void finish(boolean clearThreadMap) {
+    public void finish(boolean commit, boolean clearThreadMap, boolean onTimeout) {
         try {
             if (commit) {
                 tx.state(PREPARING);
@@ -79,4 +85,9 @@ public void finish(boolean clearThreadMap) {
             onDone(tx);
         }
     }
+
+    /** {@inheritDoc} */
+    @Override public void onNodeStop(IgniteCheckedException e) {
+        onDone(tx, e);
+    }
 }
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/near/GridNearTxFinishAndAckFuture.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/near/GridNearTxFinishAndAckFuture.java
new file mode 100644
index 000000000000..011b2ec3faa2
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/near/GridNearTxFinishAndAckFuture.java
@@ -0,0 +1,144 @@
+/*
+ * 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.cache.distributed.near;
+
+import org.apache.ignite.IgniteCheckedException;
+import org.apache.ignite.internal.IgniteInternalFuture;
+import org.apache.ignite.internal.processors.cache.mvcc.MvccQueryTracker;
+import org.apache.ignite.internal.processors.cache.mvcc.MvccSnapshot;
+import org.apache.ignite.internal.processors.cache.transactions.IgniteInternalTx;
+import org.apache.ignite.internal.util.future.GridFutureAdapter;
+import org.apache.ignite.internal.util.typedef.internal.S;
+import org.apache.ignite.lang.IgniteInClosure;
+
+/**
+ *
+ */
+public class GridNearTxFinishAndAckFuture extends GridFutureAdapter<IgniteInternalTx> implements NearTxFinishFuture {
+    /** */
+    private final NearTxFinishFuture finishFut;
+
+    /**
+     * @param finishFut Finish future.
+     */
+    GridNearTxFinishAndAckFuture(NearTxFinishFuture finishFut) {
+        this.finishFut = finishFut;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean commit() {
+        return finishFut.commit();
+    }
+
+    /** {@inheritDoc} */
+    @Override public GridNearTxLocal tx() {
+        return finishFut.tx();
+    }
+
+    /** {@inheritDoc} */
+    @SuppressWarnings("unchecked")
+    public void finish(boolean commit, boolean clearThreadMap, boolean onTimeout) {
+        finishFut.finish(commit, clearThreadMap, onTimeout);
+
+        if (finishFut.commit()) {
+            finishFut.listen((IgniteInClosure)new IgniteInClosure<NearTxFinishFuture>() {
+                @Override public void apply(final NearTxFinishFuture fut) {
+                    GridNearTxLocal tx = fut.tx();
+
+                    IgniteInternalFuture<Void> ackFut = null;
+
+                    MvccQueryTracker tracker = tx.mvccQueryTracker();
+
+                    MvccSnapshot mvccSnapshot = tx.mvccSnapshot();
+
+                    if (tracker != null)
+                        ackFut = tracker.onDone(tx, commit);
+                    else if (mvccSnapshot != null) {
+                        if (commit)
+                            ackFut = tx.context().coordinators().ackTxCommit(mvccSnapshot);
+                        else
+                            tx.context().coordinators().ackTxRollback(mvccSnapshot);
+                    }
+
+                    if (ackFut != null) {
+                        ackFut.listen(new IgniteInClosure<IgniteInternalFuture<Void>>() {
+                            @Override public void apply(IgniteInternalFuture<Void> ackFut) {
+                                Exception err = null;
+
+                                try {
+                                    fut.get();
+
+                                    ackFut.get();
+                                }
+                                catch (Exception e) {
+                                    err = e;
+                                }
+                                catch (Error e) {
+                                    onDone(e);
+
+                                    throw e;
+                                }
+
+                                if (err != null)
+                                    onDone(err);
+                                else
+                                    onDone(fut.tx());
+                            }
+                        });
+                    }
+                    else
+                        finishWithFutureResult(fut);
+                }
+            });
+        }
+        else {
+            finishFut.listen(new IgniteInClosure<IgniteInternalFuture>() {
+                @Override public void apply(IgniteInternalFuture fut) {
+                    finishWithFutureResult(fut);
+                }
+            });
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override public void onNodeStop(IgniteCheckedException e) {
+        super.onDone(finishFut.tx(), e);
+    }
+
+    /**
+     * @param fut Future.
+     */
+    private void finishWithFutureResult(IgniteInternalFuture<IgniteInternalTx> fut) {
+        try {
+            onDone(fut.get());
+        }
+        catch (IgniteCheckedException | RuntimeException e) {
+            onDone(e);
+        }
+        catch (Error e) {
+            onDone(e);
+
+            throw e;
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override public String toString() {
+        return S.toString(GridNearTxFinishAndAckFuture.class, this);
+    }
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/near/GridNearTxFinishFuture.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/near/GridNearTxFinishFuture.java
index 297456350dff..447d899ba54d 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/near/GridNearTxFinishFuture.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/near/GridNearTxFinishFuture.java
@@ -43,11 +43,15 @@
 import org.apache.ignite.internal.processors.cache.distributed.GridDistributedTxMapping;
 import org.apache.ignite.internal.processors.cache.distributed.dht.GridDhtTxFinishRequest;
 import org.apache.ignite.internal.processors.cache.distributed.dht.GridDhtTxFinishResponse;
+import org.apache.ignite.internal.processors.cache.mvcc.MvccCoordinator;
+import org.apache.ignite.internal.processors.cache.mvcc.MvccFuture;
+import org.apache.ignite.internal.processors.cache.mvcc.MvccSnapshot;
 import org.apache.ignite.internal.processors.cache.transactions.IgniteInternalTx;
 import org.apache.ignite.internal.processors.cache.transactions.IgniteTxEntry;
 import org.apache.ignite.internal.processors.cache.version.GridCacheVersion;
 import org.apache.ignite.internal.transactions.IgniteTxHeuristicCheckedException;
 import org.apache.ignite.internal.transactions.IgniteTxRollbackCheckedException;
+import org.apache.ignite.internal.util.GridLongList;
 import org.apache.ignite.internal.util.future.GridFutureAdapter;
 import org.apache.ignite.internal.util.tostring.GridToStringInclude;
 import org.apache.ignite.internal.util.typedef.C1;
@@ -136,6 +140,13 @@ public GridNearTxFinishFuture(GridCacheSharedContext<K, V> cctx, GridNearTxLocal
         return commit;
     }
 
+    /**
+     * @return Cache context.
+     */
+    GridCacheSharedContext<K, V> context() {
+        return cctx;
+    }
+
     /** {@inheritDoc} */
     @Override public IgniteUuid futureId() {
         return futId;
@@ -390,15 +401,12 @@ private boolean isMini(IgniteInternalFuture<?> fut) {
             fut.getClass() == CheckRemoteTxMiniFuture.class;
     }
 
-    /**
-     * Initializes future.
-     *
-     * @param commit Commit flag.
-     * @param clearThreadMap If {@code true} removes {@link GridNearTxLocal} from thread map.
-     * @param onTimeout If {@code true} called from timeout handler.
-     */
+    /** {@inheritDoc} */
     @SuppressWarnings("ForLoopReplaceableByForEach")
     public void finish(final boolean commit, final boolean clearThreadMap, final boolean onTimeout) {
+        if (!cctx.mvcc().addFuture(this, futureId()))
+            return;
+
         if (tx.onNeedCheckBackup()) {
             assert tx.onePhaseCommit();
 
@@ -465,18 +473,38 @@ private void rollbackAsyncSafe(boolean onTimeout) {
     private void doFinish(boolean commit, boolean clearThreadMap) {
         try {
             if (tx.localFinish(commit, clearThreadMap) || (!commit && tx.state() == UNKNOWN)) {
+                GridLongList waitTxs = tx.mvccWaitTransactions();
+
+                if (waitTxs != null) {
+                    MvccSnapshot snapshot = tx.mvccSnapshot();
+
+                    MvccCoordinator crd = cctx.coordinators().currentCoordinator();
+
+                    assert snapshot != null;
+
+                    if (snapshot.coordinatorVersion() == crd.coordinatorVersion()) {
+                        IgniteInternalFuture fut = cctx.coordinators()
+                            .waitTxsFuture(cctx.coordinators().currentCoordinatorId(), waitTxs);
+
+                        add(fut);
+                    }
+                }
+
                 if ((tx.onePhaseCommit() && needFinishOnePhase(commit)) || (!tx.onePhaseCommit() && mappings != null)) {
                     if (mappings.single()) {
                         GridDistributedTxMapping mapping = mappings.singleMapping();
 
                         if (mapping != null) {
-                            assert !hasFutures() : futures();
+                            assert !hasFutures() || waitTxs != null : futures();
 
                             finish(1, mapping, commit, !clearThreadMap);
                         }
                     }
-                    else
+                    else {
+                        assert !hasFutures() || waitTxs != null : futures();
+
                         finish(mappings.mappings(), commit, !clearThreadMap);
+                    }
                 }
 
                 markInitialized();
@@ -501,6 +529,11 @@ private void doFinish(boolean commit, boolean clearThreadMap) {
         }
     }
 
+    /** {@inheritDoc} */
+    @Override public void onNodeStop(IgniteCheckedException e) {
+        super.onDone(tx, e);
+    }
+
     /**
      *
      */
@@ -732,8 +765,6 @@ private void readyNearMappingFromBackup(GridDistributedTxMapping mapping) {
      * @param {@code true} If need to add completed version on finish.
      */
     private void finish(Iterable<GridDistributedTxMapping> mappings, boolean commit, boolean useCompletedVer) {
-        assert !hasFutures() : futures();
-
         int miniId = 0;
 
         // Create mini futures.
@@ -750,11 +781,11 @@ private void finish(Iterable<GridDistributedTxMapping> mappings, boolean commit,
     private void finish(int miniId, GridDistributedTxMapping m, boolean commit, boolean useCompletedVer) {
         ClusterNode n = m.primary();
 
-        assert !m.empty() : m + " " + tx.state();
+        assert !m.empty() || m.queryUpdate() : m + " " + tx.state();
 
         CacheWriteSynchronizationMode syncMode = tx.syncMode();
 
-        if (m.explicitLock())
+        if (m.explicitLock() || m.queryUpdate())
             syncMode = FULL_SYNC;
 
         GridNearTxFinishRequest req = new GridNearTxFinishRequest(
@@ -775,6 +806,7 @@ private void finish(int miniId, GridDistributedTxMapping m, boolean commit, bool
             tx.size(),
             tx.subjectId(),
             tx.taskNameHash(),
+            tx.mvccSnapshot(),
             tx.activeCachesDeploymentEnabled()
         );
 
@@ -869,6 +901,11 @@ else if (f.getClass() == CheckRemoteTxMiniFuture.class) {
 
                     return "CheckRemoteTxMiniFuture[nodes=" + fut.nodes() + ", done=" + f.isDone() + "]";
                 }
+                else if (f instanceof MvccFuture) {
+                    MvccFuture fut = (MvccFuture)f;
+
+                    return "WaitPreviousTxsFut[mvccCrd=" + fut.coordinatorNodeId() + ", done=" + f.isDone() + "]";
+                }
                 else
                     return "[loc=true, done=" + f.isDone() + "]";
             }
@@ -909,7 +946,8 @@ private GridDhtTxFinishRequest checkCommittedRequest(int miniId, boolean waitRem
             0,
             tx.activeCachesDeploymentEnabled(),
             !waitRemoteTxs && (tx.needReturnValue() && tx.implicit()),
-            waitRemoteTxs);
+            waitRemoteTxs,
+            null);
 
         finishReq.checkCommitted(true);
 
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/near/GridNearTxFinishRequest.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/near/GridNearTxFinishRequest.java
index 00c29e5f7b5c..6b5aa90e3f5f 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/near/GridNearTxFinishRequest.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/near/GridNearTxFinishRequest.java
@@ -24,6 +24,7 @@
 import org.apache.ignite.cache.CacheWriteSynchronizationMode;
 import org.apache.ignite.internal.processors.affinity.AffinityTopologyVersion;
 import org.apache.ignite.internal.processors.cache.distributed.GridDistributedTxFinishRequest;
+import org.apache.ignite.internal.processors.cache.mvcc.MvccSnapshot;
 import org.apache.ignite.internal.processors.cache.version.GridCacheVersion;
 import org.apache.ignite.internal.util.tostring.GridToStringBuilder;
 import org.apache.ignite.internal.util.typedef.internal.U;
@@ -43,6 +44,9 @@
     /** Mini future ID. */
     private int miniId;
 
+    /** */
+    private MvccSnapshot mvccSnapshot;
+
     /**
      * Empty constructor required for {@link Externalizable}.
      */
@@ -88,6 +92,7 @@ public GridNearTxFinishRequest(
         int txSize,
         @Nullable UUID subjId,
         int taskNameHash,
+        MvccSnapshot mvccSnapshot,
         boolean addDepInfo) {
         super(
             xidVer,
@@ -111,6 +116,15 @@ public GridNearTxFinishRequest(
 
         explicitLock(explicitLock);
         storeEnabled(storeEnabled);
+
+        this.mvccSnapshot = mvccSnapshot;
+    }
+
+    /**
+     * @return Mvcc info.
+     */
+    @Nullable public MvccSnapshot mvccSnapshot() {
+        return mvccSnapshot;
     }
 
     /**
@@ -178,6 +192,12 @@ public void miniId(int miniId) {
 
                 writer.incrementState();
 
+            case 22:
+                if (!writer.writeMessage("mvccSnapshot", mvccSnapshot))
+                    return false;
+
+                writer.incrementState();
+
         }
 
         return true;
@@ -202,6 +222,14 @@ public void miniId(int miniId) {
 
                 reader.incrementState();
 
+            case 22:
+                mvccSnapshot = reader.readMessage("mvccSnapshot");
+
+                if (!reader.isLastRead())
+                    return false;
+
+                reader.incrementState();
+
         }
 
         return reader.afterMessageRead(GridNearTxFinishRequest.class);
@@ -214,7 +242,7 @@ public void miniId(int miniId) {
 
     /** {@inheritDoc} */
     @Override public byte fieldsCount() {
-        return 22;
+        return 23;
     }
 
     /** {@inheritDoc} */
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/near/GridNearTxLocal.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/near/GridNearTxLocal.java
index a4acfa6c1f5a..d03d0d3aaeff 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/near/GridNearTxLocal.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/near/GridNearTxLocal.java
@@ -47,10 +47,10 @@
 import org.apache.ignite.internal.processors.cache.GridCacheEntryEx;
 import org.apache.ignite.internal.processors.cache.GridCacheEntryRemovedException;
 import org.apache.ignite.internal.processors.cache.GridCacheMvccCandidate;
-import org.apache.ignite.internal.processors.cache.GridCacheVersionedFuture;
 import org.apache.ignite.internal.processors.cache.GridCacheOperation;
 import org.apache.ignite.internal.processors.cache.GridCacheReturn;
 import org.apache.ignite.internal.processors.cache.GridCacheSharedContext;
+import org.apache.ignite.internal.processors.cache.GridCacheVersionedFuture;
 import org.apache.ignite.internal.processors.cache.IgniteCacheExpiryPolicy;
 import org.apache.ignite.internal.processors.cache.KeyCacheObject;
 import org.apache.ignite.internal.processors.cache.distributed.GridDistributedCacheEntry;
@@ -61,6 +61,9 @@
 import org.apache.ignite.internal.processors.cache.distributed.dht.GridDhtTxPrepareFuture;
 import org.apache.ignite.internal.processors.cache.distributed.dht.colocated.GridDhtDetachedCacheEntry;
 import org.apache.ignite.internal.processors.cache.dr.GridCacheDrInfo;
+import org.apache.ignite.internal.processors.cache.mvcc.MvccQueryTracker;
+import org.apache.ignite.internal.processors.cache.mvcc.MvccQueryTrackerImpl;
+import org.apache.ignite.internal.processors.cache.mvcc.MvccSnapshot;
 import org.apache.ignite.internal.processors.cache.transactions.IgniteInternalTx;
 import org.apache.ignite.internal.processors.cache.transactions.IgniteTxEntry;
 import org.apache.ignite.internal.processors.cache.transactions.IgniteTxKey;
@@ -68,6 +71,7 @@
 import org.apache.ignite.internal.processors.cache.transactions.TransactionProxyImpl;
 import org.apache.ignite.internal.processors.cache.transactions.TransactionProxyRollbackOnlyImpl;
 import org.apache.ignite.internal.processors.cache.version.GridCacheVersion;
+import org.apache.ignite.internal.processors.query.UpdateSourceIterator;
 import org.apache.ignite.internal.processors.timeout.GridTimeoutObject;
 import org.apache.ignite.internal.transactions.IgniteTxOptimisticCheckedException;
 import org.apache.ignite.internal.transactions.IgniteTxRollbackCheckedException;
@@ -134,6 +138,10 @@
     private static final AtomicReferenceFieldUpdater<GridNearTxLocal, NearTxFinishFuture> FINISH_FUT_UPD =
         AtomicReferenceFieldUpdater.newUpdater(GridNearTxLocal.class, NearTxFinishFuture.class, "finishFut");
 
+    /** */
+    private static final String TX_TYPE_MISMATCH_ERR_MSG =
+        "SQL queries and cache operations may not be used in the same transaction.";
+
     /** DHT mappings. */
     private IgniteTxMappings mappings;
 
@@ -179,6 +187,15 @@
     /** Tx label. */
     private @Nullable String lb;
 
+    /** */
+    private MvccQueryTracker mvccTracker;
+
+    /** Whether this transaction is for SQL operations or not.<p>
+     * {@code null} means there haven't been any calls made on this transaction, and first operation will give this
+     * field actual value.
+     */
+    private Boolean sql;
+
     /**
      * Empty constructor required for {@link Externalizable}.
      */
@@ -196,6 +213,7 @@ public GridNearTxLocal() {
      * @param isolation Isolation.
      * @param timeout Timeout.
      * @param storeEnabled Store enabled flag.
+     * @param sql Whether this transaction was started via SQL API or not, or {@code null} if unknown.
      * @param txSize Transaction size.
      * @param subjId Subject ID.
      * @param taskNameHash Task name hash code.
@@ -211,6 +229,7 @@ public GridNearTxLocal(
         TransactionIsolation isolation,
         long timeout,
         boolean storeEnabled,
+        Boolean sql,
         int txSize,
         @Nullable UUID subjId,
         int taskNameHash,
@@ -237,11 +256,20 @@ public GridNearTxLocal(
 
         mappings = implicitSingle ? new IgniteTxMappingsSingleImpl() : new IgniteTxMappingsImpl();
 
+        this.sql = sql;
+
         initResult();
 
         trackTimeout = timeout() > 0 && !implicit() && cctx.time().addTimeoutObject(this);
     }
 
+    /**
+     * @return Mvcc query version tracker.
+     */
+    public MvccQueryTracker mvccQueryTracker() {
+        return mvccTracker;
+    }
+
     /** {@inheritDoc} */
     @Override public boolean near() {
         return true;
@@ -546,7 +574,7 @@ public void colocatedLocallyMapped(boolean colocatedLocallyMapped) {
         assert key != null;
 
         try {
-            beforePut(cacheCtx, retval);
+            beforePut(cacheCtx, retval, false);
 
             final GridCacheReturn ret = new GridCacheReturn(localResult(), false);
 
@@ -689,7 +717,7 @@ public void colocatedLocallyMapped(boolean colocatedLocallyMapped) {
         final boolean retval
     ) {
         try {
-            beforePut(cacheCtx, retval);
+            beforePut(cacheCtx, retval, false);
         }
         catch (IgniteCheckedException e) {
             return new GridFinishedFuture(e);
@@ -855,6 +883,8 @@ public void colocatedLocallyMapped(boolean colocatedLocallyMapped) {
      * @param ret Return value.
      * @param skipStore Skip store flag.
      * @param singleRmv {@code True} for single key remove operation ({@link Cache#remove(Object)}.
+     * @param recovery Recovery flag.
+     * @param dataCenterId Optional data center Id.
      * @return Future for entry values loading.
      */
     private <K, V> IgniteInternalFuture<Void> enlistWrite(
@@ -978,6 +1008,7 @@ public void colocatedLocallyMapped(boolean colocatedLocallyMapped) {
      * @param skipStore Skip store flag.
      * @param singleRmv {@code True} for single key remove operation ({@link Cache#remove(Object)}.
      * @param keepBinary Keep binary flag.
+     * @param recovery Recovery flag.
      * @param dataCenterId Optional data center ID.
      * @return Future for enlisting writes.
      */
@@ -1245,6 +1276,7 @@ private boolean enlistWriteEntry(GridCacheContext cacheCtx,
                                         resolveTaskName(),
                                         null,
                                         keepBinary,
+                                        null, // TODO IGNITE-7371
                                         null) : null;
 
                                 if (res != null) {
@@ -1263,7 +1295,8 @@ private boolean enlistWriteEntry(GridCacheContext cacheCtx,
                                     entryProcessor,
                                     resolveTaskName(),
                                     null,
-                                    keepBinary);
+                                    keepBinary,
+                                    null); // TODO IGNITE-7371
                             }
                         }
                         catch (ClusterTopologyCheckedException e) {
@@ -1524,6 +1557,9 @@ private boolean enlistWriteEntry(GridCacheContext cacheCtx,
 
         cacheCtx.checkSecurity(SecurityPermission.CACHE_REMOVE);
 
+        if (cacheCtx.mvccEnabled() && !isOperationAllowed(false))
+            return txTypeMismatchFinishFuture();
+
         if (retval)
             needReturnValue(true);
 
@@ -1731,6 +1767,109 @@ private boolean enlistWriteEntry(GridCacheContext cacheCtx,
         }
     }
 
+    /**
+     * @param cctx Cache context.
+     * @return Mvcc snapshot for read inside tx (initialized once for OPTIMISTIC SERIALIZABLE and REPEATABLE_READ txs).
+     */
+    private MvccSnapshot mvccReadSnapshot(GridCacheContext cctx) {
+        if (!cctx.mvccEnabled() || mvccTracker == null)
+            return null;
+
+        return mvccTracker.snapshot();
+    }
+
+    /**
+     * @param cacheCtx Cache context.
+     * @param cacheIds Involved cache ids.
+     * @param parts Partitions.
+     * @param schema Schema name.
+     * @param qry Query string.
+     * @param params Query parameters.
+     * @param flags Flags.
+     * @param pageSize Fetch page size.
+     * @param timeout Timeout.
+     * @return Operation future.
+     */
+    public IgniteInternalFuture<Long> updateAsync(GridCacheContext cacheCtx,
+        int[] cacheIds, int[] parts, String schema, String qry, Object[] params,
+        int flags, int pageSize, long timeout) {
+        try {
+            beforePut(cacheCtx, false, true);
+
+            return updateAsync(new GridNearTxQueryEnlistFuture(
+                cacheCtx,
+                this,
+                cacheIds,
+                parts,
+                schema,
+                qry,
+                params,
+                flags,
+                pageSize,
+                timeout));
+        }
+        catch (IgniteCheckedException e) {
+            return new GridFinishedFuture(e);
+        }
+        catch (RuntimeException e) {
+            onException();
+
+            throw e;
+        }
+    }
+
+    /**
+     * @param cacheCtx Cache context.
+     * @param it Entries iterator.
+     * @param pageSize Page size.
+     * @param timeout Timeout.
+     * @param sequential Sequential locking flag.
+     * @return Operation future.
+     */
+    public IgniteInternalFuture<Long> updateAsync(GridCacheContext cacheCtx,
+        UpdateSourceIterator<?> it, int pageSize, long timeout, boolean sequential) {
+        try {
+            beforePut(cacheCtx, false, true);
+
+            return updateAsync(new GridNearTxQueryResultsEnlistFuture(cacheCtx, this,
+                timeout, it, pageSize, sequential));
+        }
+        catch (IgniteCheckedException e) {
+            return new GridFinishedFuture(e);
+        }
+        catch (RuntimeException e) {
+            onException();
+
+            throw e;
+        }
+    }
+
+    /**
+     * @param fut Enlist future.
+     * @return Operation future.
+     */
+    public IgniteInternalFuture<Long> updateAsync(GridNearTxAbstractEnlistFuture fut) {
+        fut.init();
+
+        return nonInterruptable(new GridEmbeddedFuture<>(fut.chain(new CX1<IgniteInternalFuture<Long>, Boolean>() {
+            @Override public Boolean applyx(IgniteInternalFuture<Long> fut0) throws IgniteCheckedException {
+                return fut0.get() != null;
+            }
+        }), new PLC1<Long>(null) {
+            @Override protected Long postLock(Long val) throws IgniteCheckedException {
+                Long res = fut.get();
+
+                assert mvccSnapshot != null;
+                assert res != null;
+
+                if (res > 0)
+                    mvccSnapshot.incrementOperationCounter();
+
+                return res;
+            }
+        }));
+    }
+
     /**
      * @param cacheCtx Cache context.
      * @param keys Keys to get.
@@ -1744,7 +1883,7 @@ private boolean enlistWriteEntry(GridCacheContext cacheCtx,
     public <K, V> IgniteInternalFuture<Map<K, V>> getAllAsync(
         final GridCacheContext cacheCtx,
         @Nullable final AffinityTopologyVersion entryTopVer,
-        Collection<KeyCacheObject> keys,
+        final Collection<KeyCacheObject> keys,
         final boolean deserializeBinary,
         final boolean skipVals,
         final boolean keepCacheObjects,
@@ -1754,8 +1893,36 @@ private boolean enlistWriteEntry(GridCacheContext cacheCtx,
         if (F.isEmpty(keys))
             return new GridFinishedFuture<>(Collections.<K, V>emptyMap());
 
+        if (cacheCtx.mvccEnabled() && !isOperationAllowed(false))
+            return txTypeMismatchFinishFuture();
+
         init();
 
+        if (cacheCtx.mvccEnabled() && (optimistic() && !readCommitted()) && mvccTracker == null) {
+            // TODO IGNITE-7388: support async tx rollback (e.g. on timeout).
+            boolean canRemap = cctx.lockedTopologyVersion(null) == null;
+
+            mvccTracker = new MvccQueryTrackerImpl(cacheCtx, canRemap);
+
+            return new GridEmbeddedFuture<>(mvccTracker.requestSnapshot(topologyVersion()),
+                new IgniteBiClosure<MvccSnapshot, Exception, IgniteInternalFuture<Map<K, V>>>() {
+                @Override public IgniteInternalFuture<Map<K, V>> apply(MvccSnapshot snapshot, Exception e) {
+                    if (e != null)
+                        return new GridFinishedFuture<>(e);
+
+                    return getAllAsync(cacheCtx,
+                        entryTopVer,
+                        keys,
+                        deserializeBinary,
+                        skipVals,
+                        keepCacheObjects,
+                        skipStore,
+                        recovery,
+                        needVer);
+                }
+            });
+        }
+
         int keysCnt = keys.size();
 
         boolean single = keysCnt == 1;
@@ -1878,6 +2045,7 @@ private boolean enlistWriteEntry(GridCacheContext cacheCtx,
                                             resolveTaskName(),
                                             null,
                                             txEntry.keepBinary(),
+                                            null, // TODO IGNITE-7371
                                             null);
 
                                         if (getRes != null) {
@@ -1896,7 +2064,8 @@ private boolean enlistWriteEntry(GridCacheContext cacheCtx,
                                             transformClo,
                                             resolveTaskName(),
                                             null,
-                                            txEntry.keepBinary());
+                                            txEntry.keepBinary(),
+                                            null); // TODO IGNITE-7371
                                     }
 
                                     // If value is in cache and passed the filter.
@@ -2057,6 +2226,7 @@ private boolean enlistWriteEntry(GridCacheContext cacheCtx,
      * @param skipVals Skip values flag.
      * @param keepCacheObjects Keep cache objects flag.
      * @param skipStore Skip store flag.
+     * @param recovery Recovery flag..
      * @throws IgniteCheckedException If failed.
      * @return Enlisted keys.
      */
@@ -2175,7 +2345,8 @@ private boolean enlistWriteEntry(GridCacheContext cacheCtx,
                                     resolveTaskName(),
                                     null,
                                     txEntry.keepBinary(),
-                                    null);
+                                    null,
+                                    null); // TODO IGNITE-7371
 
                                 if (getRes != null) {
                                     val = getRes.value();
@@ -2193,7 +2364,8 @@ private boolean enlistWriteEntry(GridCacheContext cacheCtx,
                                     transformClo,
                                     resolveTaskName(),
                                     null,
-                                    txEntry.keepBinary());
+                                    txEntry.keepBinary(),
+                                    null); // TODO IGNITE-7371
                             }
 
                             if (val != null) {
@@ -2261,6 +2433,7 @@ private boolean enlistWriteEntry(GridCacheContext cacheCtx,
                                         resolveTaskName(),
                                         accessPlc,
                                         !deserializeBinary,
+                                        mvccReadSnapshot(cacheCtx), // TODO IGNITE-7371
                                         null) : null;
 
                                 if (getRes != null) {
@@ -2279,7 +2452,8 @@ private boolean enlistWriteEntry(GridCacheContext cacheCtx,
                                     null,
                                     resolveTaskName(),
                                     accessPlc,
-                                    !deserializeBinary);
+                                    !deserializeBinary,
+                                    mvccReadSnapshot(cacheCtx)); // TODO IGNITE-7371
                             }
 
                             if (val != null) {
@@ -2465,6 +2639,13 @@ private boolean enlistWriteEntry(GridCacheContext cacheCtx,
             c);
     }
 
+    /**
+     * @return Finished future with error message about tx type mismatch.
+     */
+    private static IgniteInternalFuture txTypeMismatchFinishFuture() {
+        return new GridFinishedFuture(new IgniteCheckedException(TX_TYPE_MISMATCH_ERR_MSG));
+    }
+
     /**
      * @param cacheCtx Cache context.
      * @param loadFut Missing keys load future.
@@ -2551,7 +2732,7 @@ private void onException() {
      * @param expiryPlc Expiry policy.
      * @return Future with {@code True} value if loading took place.
      */
-    public IgniteInternalFuture<Void> loadMissing(
+    private IgniteInternalFuture<Void> loadMissing(
         final GridCacheContext cacheCtx,
         AffinityTopologyVersion topVer,
         boolean readThrough,
@@ -2610,7 +2791,8 @@ else if (cacheCtx.isColocated()) {
                     skipVals,
                     needVer,
                     /*keepCacheObject*/true,
-                    recovery
+                    recovery,
+                    mvccReadSnapshot(cacheCtx)
                 ).chain(new C1<IgniteInternalFuture<Object>, Void>() {
                     @Override public Void apply(IgniteInternalFuture<Object> f) {
                         try {
@@ -2641,7 +2823,8 @@ else if (cacheCtx.isColocated()) {
                     expiryPlc0,
                     skipVals,
                     needVer,
-                    /*keepCacheObject*/true
+                    /*keepCacheObject*/true,
+                    mvccReadSnapshot(cacheCtx)
                 ).chain(new C1<IgniteInternalFuture<Map<Object, Object>>, Void>() {
                     @Override public Void apply(IgniteInternalFuture<Map<Object, Object>> f) {
                         try {
@@ -2738,7 +2921,8 @@ else if (cacheCtx.isColocated()) {
                             resolveTaskName(),
                             expiryPlc0,
                             txEntry == null ? keepBinary : txEntry.keepBinary(),
-                            null);
+                            null,
+                            null); // TODO IGNITE-7371
 
                         if (res == null) {
                             if (misses == null)
@@ -3158,7 +3342,7 @@ private void readyNearLock(IgniteTxEntry txEntry,
                 if (state != COMMITTING && state != COMMITTED)
                     throw isRollbackOnly() ? timedOut() ? timeoutException() : rollbackException() :
                         new IgniteCheckedException("Invalid transaction state for commit [state=" + state() +
-                        ", tx=" + this + ']');
+                            ", tx=" + this + ']');
                 else {
                     if (log.isDebugEnabled())
                         log.debug("Invalid transaction state for commit (another thread is committing): " + this);
@@ -3307,56 +3491,44 @@ public void commit() throws IgniteCheckedException {
         if (log.isDebugEnabled())
             log.debug("Committing near local tx: " + this);
 
-        NearTxFinishFuture fut = finishFut;
-
-        if (fut != null)
-            return chainFinishFuture(fut, true, true, false);
-
-        if (fastFinish()) {
-            GridNearTxFastFinishFuture fut0;
+        final NearTxFinishFuture fut, fut0 = finishFut; boolean fastFinish;
 
-            if (!FINISH_FUT_UPD.compareAndSet(this, null, fut0 = new GridNearTxFastFinishFuture(this, true)))
-                return chainFinishFuture(finishFut, true, true, false);
-
-            fut0.finish(false);
-
-            return fut0;
-        }
-
-        final GridNearTxFinishFuture fut0;
-
-        if (!FINISH_FUT_UPD.compareAndSet(this, null, fut0 = new GridNearTxFinishFuture<>(cctx, this, true)))
+        if (fut0 != null || !FINISH_FUT_UPD.compareAndSet(this, null, fut = finishFuture(fastFinish = fastFinish(), true)))
             return chainFinishFuture(finishFut, true, true, false);
 
-        cctx.mvcc().addFuture(fut0, fut0.futureId());
-
-        final IgniteInternalFuture<?> prepareFut = prepareNearTxLocal();
+        if (!fastFinish) {
+            final IgniteInternalFuture<?> prepareFut = prepareNearTxLocal();
 
-        prepareFut.listen(new CI1<IgniteInternalFuture<?>>() {
-            @Override public void apply(IgniteInternalFuture<?> f) {
-                try {
-                    // Make sure that here are no exceptions.
-                    prepareFut.get();
+            prepareFut.listen(new CI1<IgniteInternalFuture<?>>() {
+                @Override public void apply(IgniteInternalFuture<?> f) {
+                    try {
+                        // Make sure that here are no exceptions.
+                        prepareFut.get();
 
-                    fut0.finish(true, true, false);
-                }
-                catch (Error | RuntimeException e) {
-                    COMMIT_ERR_UPD.compareAndSet(GridNearTxLocal.this, null, e);
+                        fut.finish(true, true, false);
+                    }
+                    catch (Error | RuntimeException e) {
+                        COMMIT_ERR_UPD.compareAndSet(GridNearTxLocal.this, null, e);
 
-                    fut0.finish(false, true, false);
+                        fut.finish(false, true, false);
 
-                    throw e;
-                }
-                catch (IgniteCheckedException e) {
-                    COMMIT_ERR_UPD.compareAndSet(GridNearTxLocal.this, null, e);
+                        throw e;
+                    }
+                    catch (IgniteCheckedException e) {
+                        COMMIT_ERR_UPD.compareAndSet(GridNearTxLocal.this, null, e);
 
-                    if (!(e instanceof NodeStoppingException))
-                        fut0.finish(false, true, true);
+                        if (!(e instanceof NodeStoppingException))
+                            fut.finish(false, true, true);
+                        else
+                            fut.onNodeStop(e);
+                    }
                 }
-            }
-        });
+            });
+        }
+        else
+            fut.finish(true, false, false);
 
-        return fut0;
+        return fut;
     }
 
     /** {@inheritDoc} */
@@ -3400,61 +3572,70 @@ public void rollback() throws IgniteCheckedException {
         if (onTimeout && prepFut instanceof GridNearTxPrepareFutureAdapter && !prepFut.isDone())
             ((GridNearTxPrepareFutureAdapter) prepFut).onNearTxLocalTimeout();
 
-        NearTxFinishFuture fut = finishFut;
+        final NearTxFinishFuture fut, fut0 = finishFut; boolean fastFinish;
 
-        if (fut != null)
+        if (fut0 != null)
             return chainFinishFuture(finishFut, false, clearThreadMap, onTimeout);
 
-        // Enable fast finish only from tx thread.
-        if (clearThreadMap && fastFinish()) {
-            GridNearTxFastFinishFuture fut0;
-
-            if (!FINISH_FUT_UPD.compareAndSet(this, null, fut0 = new GridNearTxFastFinishFuture(this, false)))
-                return chainFinishFuture(finishFut, false, true, onTimeout);
-
-            fut0.finish(true);
-
-            return fut0;
-        }
-
-        final GridNearTxFinishFuture fut0;
-
-        if (!FINISH_FUT_UPD.compareAndSet(this, null, fut0 = new GridNearTxFinishFuture<>(cctx, this, false)))
+        if (!FINISH_FUT_UPD.compareAndSet(this, null, fut = finishFuture(fastFinish = clearThreadMap && fastFinish(), false)))
             return chainFinishFuture(finishFut, false, clearThreadMap, onTimeout);
 
-        cctx.mvcc().addFuture(fut0, fut0.futureId());
+        rollbackFuture(fut);
 
-        if (prepFut == null || prepFut.isDone()) {
-            try {
-                // Check for errors in prepare future.
-                if (prepFut != null)
-                    prepFut.get();
-            }
-            catch (IgniteCheckedException e) {
-                if (log.isDebugEnabled())
-                    log.debug("Got optimistic tx failure [tx=" + this + ", err=" + e + ']');
+        if (!fastFinish) {
+            if (prepFut == null || prepFut.isDone()) {
+                try {
+                    // Check for errors in prepare future.
+                    if (prepFut != null)
+                        prepFut.get();
+                }
+                catch (IgniteCheckedException e) {
+                    if (log.isDebugEnabled())
+                        log.debug("Got optimistic tx failure [tx=" + this + ", err=" + e + ']');
+                }
+
+                fut.finish(false, clearThreadMap, onTimeout);
             }
+            else {
+                prepFut.listen(new CI1<IgniteInternalFuture<?>>() {
+                    @Override public void apply(IgniteInternalFuture<?> f) {
+                        try {
+                            // Check for errors in prepare future.
+                            f.get();
+                        }
+                        catch (IgniteCheckedException e) {
+                            if (log.isDebugEnabled())
+                                log.debug("Got optimistic tx failure [tx=" + this + ", err=" + e + ']');
+                        }
 
-            fut0.finish(false, clearThreadMap, onTimeout);
-        }
-        else {
-            prepFut.listen(new CI1<IgniteInternalFuture<?>>() {
-                @Override public void apply(IgniteInternalFuture<?> f) {
-                    try {
-                        // Check for errors in prepare future.
-                        f.get();
-                    }
-                    catch (IgniteCheckedException e) {
-                        if (log.isDebugEnabled())
-                            log.debug("Got optimistic tx failure [tx=" + this + ", err=" + e + ']');
+                        fut.finish(false, clearThreadMap, onTimeout);
                     }
+                });
+            }
+        }
+        else
+            fut.finish(false, true, onTimeout);
 
-                    fut0.finish(false, clearThreadMap, onTimeout);
-                }
-            });
+        return fut;
+    }
+
+    /**
+     * @return Transaction commit future.
+     * @param fast {@code True} in case of fast finish.
+     * @param commit {@code True} if commit.
+     */
+    private NearTxFinishFuture finishFuture(boolean fast, boolean commit) {
+        NearTxFinishFuture fut = fast ? new GridNearTxFastFinishFuture(this, commit) :
+            new GridNearTxFinishFuture<>(cctx, this, commit);
+
+        if (mvccQueryTracker() != null || mvccSnapshot != null || txState.mvccEnabled(cctx)) {
+            if (commit)
+                fut = new GridNearTxFinishAndAckFuture(fut);
+            else
+                fut.listen(new AckCoordinatorOnRollback(this));
         }
 
-        return fut0;
+        return fut;
     }
 
     /** {@inheritDoc} */
@@ -3489,12 +3670,8 @@ public void rollback() throws IgniteCheckedException {
 
                                 assert rollbackFut.isDone() : rollbackFut;
                             }
-                            else {
-                                if (!cctx.mvcc().addFuture(rollbackFut, rollbackFut.futureId()))
-                                    return;
-
+                            else
                                 rollbackFut.finish(false, clearThreadMap, onTimeout);
-                            }
                         }
                         else {
                             finishFut.listen(new IgniteInClosure<IgniteInternalFuture<IgniteInternalTx>>() {
@@ -3540,7 +3717,9 @@ public void rollback() throws IgniteCheckedException {
      * @return {@code True} if 'fast finish' path can be used for transaction completion.
      */
     private boolean fastFinish() {
-        return writeMap().isEmpty() && ((optimistic() && !serializable()) || readMap().isEmpty());
+        return writeMap().isEmpty()
+            && ((optimistic() && !serializable()) || readMap().isEmpty())
+            && (mappings.single() || F.view(mappings.mappings(), CU.FILTER_QUERY_MAPPING).isEmpty());
     }
 
     /**
@@ -3984,6 +4163,20 @@ public boolean hasRemoteLocks() {
         return hasRemoteLocks;
     }
 
+    /**
+     * @return {@code true} if this transaction does not have type flag set or it matches invoking operation,
+     * {@code false} otherwise.
+     */
+    public boolean isOperationAllowed(boolean sqlOp) {
+        if (sql == null) {
+            sql = sqlOp;
+
+            return true;
+        }
+
+        return sql == sqlOp;
+    }
+
     /**
      * @return Public API proxy.
      */
@@ -4185,13 +4378,19 @@ private boolean isAll(GridCacheContext cctx,
     /**
      * @param cacheCtx Cache context.
      * @param retval Return value flag.
+     * @param sql SQL operation flag.
      * @throws IgniteCheckedException If failed.
      */
-    private void beforePut(GridCacheContext cacheCtx, boolean retval) throws IgniteCheckedException {
+    private void beforePut(GridCacheContext cacheCtx, boolean retval, boolean sql) throws IgniteCheckedException {
+        assert !sql || cacheCtx.mvccEnabled();
+
         checkUpdatesAllowed(cacheCtx);
 
         cacheCtx.checkSecurity(SecurityPermission.CACHE_PUT);
 
+        if (cacheCtx.mvccEnabled() && !isOperationAllowed(sql))
+            throw new IgniteCheckedException(TX_TYPE_MISMATCH_ERR_MSG);
+
         if (retval)
             needReturnValue(true);
 
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/near/GridNearTxPrepareFutureAdapter.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/near/GridNearTxPrepareFutureAdapter.java
index 5c0fcec8a0dd..a9b1848bb6d6 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/near/GridNearTxPrepareFutureAdapter.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/near/GridNearTxPrepareFutureAdapter.java
@@ -49,7 +49,7 @@
  * Common code for tx prepare in optimistic and pessimistic modes.
  */
 public abstract class GridNearTxPrepareFutureAdapter extends
-    GridCacheCompoundFuture<GridNearTxPrepareResponse, IgniteInternalTx> implements GridCacheVersionedFuture<IgniteInternalTx> {
+    GridCacheCompoundFuture<Object, IgniteInternalTx> implements GridCacheVersionedFuture<IgniteInternalTx> {
     /** Logger reference. */
     protected static final AtomicReference<IgniteLogger> logRef = new AtomicReference<>();
 
@@ -58,9 +58,9 @@
         AtomicReferenceFieldUpdater.newUpdater(GridNearTxPrepareFutureAdapter.class, Throwable.class, "err");
 
     /** */
-    private static final IgniteReducer<GridNearTxPrepareResponse, IgniteInternalTx> REDUCER =
-        new IgniteReducer<GridNearTxPrepareResponse, IgniteInternalTx>() {
-            @Override public boolean collect(GridNearTxPrepareResponse e) {
+    private static final IgniteReducer<Object, IgniteInternalTx> REDUCER =
+        new IgniteReducer<Object, IgniteInternalTx>() {
+            @Override public boolean collect(Object e) {
                 return true;
             }
 
@@ -165,7 +165,7 @@ public IgniteInternalTx tx() {
      * @param txMapping Transaction mapping.
      */
     final void checkOnePhase(GridDhtTxMapping txMapping) {
-        if (tx.storeWriteThrough())
+        if (tx.storeWriteThrough() || tx.txState().mvccEnabled(cctx)) // TODO IGNITE-3479 (onePhase + mvcc)
             return;
 
         Map<UUID, Collection<UUID>> map = txMapping.transactionNodes();
@@ -255,7 +255,7 @@ else if (txEntry.cached().detached()) {
                 txEntry.ttl(CU.toTtl(expiry.getExpiryForAccess()));
         }
 
-        if (!m.empty()) {
+        if (m.queryUpdate() || !m.empty()) {
             // This step is very important as near and DHT versions grow separately.
             cctx.versions().onReceived(nodeId, res.dhtVersion());
 
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/near/GridNearTxPrepareRequest.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/near/GridNearTxPrepareRequest.java
index a6706093b11c..55c809d6f189 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/near/GridNearTxPrepareRequest.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/near/GridNearTxPrepareRequest.java
@@ -58,6 +58,9 @@
     /** */
     private static final int ALLOW_WAIT_TOP_FUT_FLAG_MASK = 0x10;
 
+    /** */
+    private static final int REQUEST_MVCC_CNTR_FLAG_MASK = 0x20;
+
     /** Future ID. */
     private IgniteUuid futId;
 
@@ -149,6 +152,20 @@ public GridNearTxPrepareRequest(
         setFlag(allowWaitTopFut, ALLOW_WAIT_TOP_FUT_FLAG_MASK);
     }
 
+    /**
+     * @return {@code True} if need request MVCC counter on primary node on prepare step.
+     */
+    public boolean requestMvccCounter() {
+        return isFlag(REQUEST_MVCC_CNTR_FLAG_MASK);
+    }
+
+    /**
+     * @param val {@code True} if need request MVCC counter on primary node on prepare step.
+     */
+    public void requestMvccCounter(boolean val) {
+        setFlag(val, REQUEST_MVCC_CNTR_FLAG_MASK);
+    }
+
     /**
      * @return {@code True} if it is safe for first client request to wait for topology future
      *      completion.
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/near/GridNearTxPrepareResponse.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/near/GridNearTxPrepareResponse.java
index 8162168136d9..92db4633b732 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/near/GridNearTxPrepareResponse.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/near/GridNearTxPrepareResponse.java
@@ -33,6 +33,7 @@
 import org.apache.ignite.internal.processors.cache.GridCacheReturn;
 import org.apache.ignite.internal.processors.cache.GridCacheSharedContext;
 import org.apache.ignite.internal.processors.cache.distributed.GridDistributedTxPrepareResponse;
+import org.apache.ignite.internal.processors.cache.mvcc.MvccSnapshot;
 import org.apache.ignite.internal.processors.cache.transactions.IgniteTxKey;
 import org.apache.ignite.internal.processors.cache.version.GridCacheVersion;
 import org.apache.ignite.internal.util.tostring.GridToStringExclude;
@@ -97,6 +98,9 @@
     /** Not {@code null} if client node should remap transaction. */
     private AffinityTopologyVersion clientRemapVer;
 
+    /** */
+    private MvccSnapshot mvccSnapshot;
+
     /**
      * Empty constructor required by {@link Externalizable}.
      */
@@ -145,6 +149,20 @@ public GridNearTxPrepareResponse(
             flags |= NEAR_PREPARE_ONE_PHASE_COMMIT_FLAG_MASK;
     }
 
+    /**
+     * @param mvccSnapshot Mvcc info.
+     */
+    public void mvccSnapshot(MvccSnapshot mvccSnapshot) {
+        this.mvccSnapshot = mvccSnapshot;
+    }
+
+    /**
+     * @return Mvcc info.
+     */
+    @Nullable public MvccSnapshot mvccSnapshot() {
+        return mvccSnapshot;
+    }
+
     /**
      * @return One-phase commit state on primary node.
      */
@@ -389,30 +407,36 @@ public boolean hasOwnedValue(IgniteTxKey key) {
                 writer.incrementState();
 
             case 15:
-                if (!writer.writeCollection("ownedValKeys", ownedValKeys, MessageCollectionItemType.MSG))
+                if (!writer.writeMessage("mvccSnapshot", mvccSnapshot))
                     return false;
 
                 writer.incrementState();
 
             case 16:
-                if (!writer.writeCollection("ownedValVals", ownedValVals, MessageCollectionItemType.MSG))
+                if (!writer.writeCollection("ownedValKeys", ownedValKeys, MessageCollectionItemType.MSG))
                     return false;
 
                 writer.incrementState();
 
             case 17:
-                if (!writer.writeCollection("pending", pending, MessageCollectionItemType.MSG))
+                if (!writer.writeCollection("ownedValVals", ownedValVals, MessageCollectionItemType.MSG))
                     return false;
 
                 writer.incrementState();
 
             case 18:
-                if (!writer.writeMessage("retVal", retVal))
+                if (!writer.writeCollection("pending", pending, MessageCollectionItemType.MSG))
                     return false;
 
                 writer.incrementState();
 
             case 19:
+                if (!writer.writeMessage("retVal", retVal))
+                    return false;
+
+                writer.incrementState();
+
+            case 20:
                 if (!writer.writeMessage("writeVer", writeVer))
                     return false;
 
@@ -475,7 +499,7 @@ public boolean hasOwnedValue(IgniteTxKey key) {
                 reader.incrementState();
 
             case 15:
-                ownedValKeys = reader.readCollection("ownedValKeys", MessageCollectionItemType.MSG);
+                mvccSnapshot = reader.readMessage("mvccSnapshot");
 
                 if (!reader.isLastRead())
                     return false;
@@ -483,7 +507,7 @@ public boolean hasOwnedValue(IgniteTxKey key) {
                 reader.incrementState();
 
             case 16:
-                ownedValVals = reader.readCollection("ownedValVals", MessageCollectionItemType.MSG);
+                ownedValKeys = reader.readCollection("ownedValKeys", MessageCollectionItemType.MSG);
 
                 if (!reader.isLastRead())
                     return false;
@@ -491,7 +515,7 @@ public boolean hasOwnedValue(IgniteTxKey key) {
                 reader.incrementState();
 
             case 17:
-                pending = reader.readCollection("pending", MessageCollectionItemType.MSG);
+                ownedValVals = reader.readCollection("ownedValVals", MessageCollectionItemType.MSG);
 
                 if (!reader.isLastRead())
                     return false;
@@ -499,7 +523,7 @@ public boolean hasOwnedValue(IgniteTxKey key) {
                 reader.incrementState();
 
             case 18:
-                retVal = reader.readMessage("retVal");
+                pending = reader.readCollection("pending", MessageCollectionItemType.MSG);
 
                 if (!reader.isLastRead())
                     return false;
@@ -507,6 +531,14 @@ public boolean hasOwnedValue(IgniteTxKey key) {
                 reader.incrementState();
 
             case 19:
+                retVal = reader.readMessage("retVal");
+
+                if (!reader.isLastRead())
+                    return false;
+
+                reader.incrementState();
+
+            case 20:
                 writeVer = reader.readMessage("writeVer");
 
                 if (!reader.isLastRead())
@@ -526,7 +558,7 @@ public boolean hasOwnedValue(IgniteTxKey key) {
 
     /** {@inheritDoc} */
     @Override public byte fieldsCount() {
-        return 20;
+        return 21;
     }
 
     /** {@inheritDoc} */
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/near/GridNearTxQueryEnlistFuture.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/near/GridNearTxQueryEnlistFuture.java
new file mode 100644
index 000000000000..21967d714946
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/near/GridNearTxQueryEnlistFuture.java
@@ -0,0 +1,382 @@

  (This diff was longer than 20,000 lines, and has been truncated...)


 

----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
users@infra.apache.org


With regards,
Apache Git Services