You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by sa...@apache.org on 2022/10/25 19:20:31 UTC

[ignite-3] branch ignite-3.0.0-beta1 updated: IGNITE-17260 IgniteTransactions and Transaction interfaces enriched with RO related methods (#1248)

This is an automated email from the ASF dual-hosted git repository.

sanpwc pushed a commit to branch ignite-3.0.0-beta1
in repository https://gitbox.apache.org/repos/asf/ignite-3.git


The following commit(s) were added to refs/heads/ignite-3.0.0-beta1 by this push:
     new 3d5771c5ba IGNITE-17260 IgniteTransactions and Transaction interfaces enriched with RO related methods (#1248)
3d5771c5ba is described below

commit 3d5771c5ba0a7e5ac21f9d08b2b52cb7c781bfb0
Author: Alexander Lapin <la...@gmail.com>
AuthorDate: Tue Oct 25 22:20:25 2022 +0300

    IGNITE-17260 IgniteTransactions and Transaction interfaces enriched with RO related methods (#1248)
---
 .../org/apache/ignite/tx/IgniteTransactions.java   |   7 +
 .../java/org/apache/ignite/tx/Transaction.java     |  15 ++
 .../org/apache/ignite/tx/TransactionException.java |  11 +
 .../internal/client/tx/ClientTransaction.java      |  15 ++
 .../internal/client/tx/ClientTransactions.java     |   6 +
 .../org/apache/ignite/client/fakes/FakeIgnite.java |  17 ++
 .../ignite/client/fakes/FakeInternalTable.java     |  31 +++
 .../internal/cluster/management/MockNode.java      |   2 +-
 .../management/raft/ItCmgRaftServiceTest.java      |   2 +-
 .../ignite/{ => internal}/hlc/HybridClock.java     |   2 +-
 .../ignite/{ => internal}/hlc/HybridTimestamp.java |   4 +-
 .../java/org/apache/ignite/lang/ErrorGroups.java   |   7 +
 .../ignite/{hlc => internal}/HybridClockTest.java  |   4 +-
 .../{hlc => internal}/HybridClockTestUtils.java    |   2 +-
 .../{hlc => internal}/HybridTimestampTest.java     |   4 +-
 .../apache/ignite/internal/raft/ItLozaTest.java    |   2 +-
 .../internal/raft/ItRaftGroupServiceTest.java      |   2 +-
 .../apache/ignite/raft/jraft/core/ItNodeTest.java  |   2 +-
 .../ignite/raft/server/ItJraftHlcServerTest.java   |   2 +-
 .../java/org/apache/ignite/internal/raft/Loza.java |   2 +-
 .../apache/ignite/raft/jraft/core/NodeImpl.java    |   5 +-
 .../ignite/raft/jraft/option/NodeOptions.java      |   2 +-
 .../apache/ignite/raft/jraft/rpc/RpcRequests.java  |   2 +-
 .../org/apache/ignite/internal/raft/LozaTest.java  |   2 +-
 .../apache/ignite/raft/jraft/core/TestCluster.java |   2 +-
 .../ignite/internal/replicator/ReplicaManager.java |   4 +-
 .../ignite/internal/replicator/ReplicaService.java |   2 +-
 .../replicator/message/TimestampAware.java         |   2 +-
 .../ItDistributedConfigurationPropertiesTest.java  |   2 +-
 .../ItDistributedConfigurationStorageTest.java     |   2 +-
 .../storage/ItRebalanceDistributedTest.java        |   2 +-
 .../runner/app/ItIgniteNodeRestartTest.java        |   2 +-
 .../app/client/ItThinClientTransactionsTest.java   |  11 +
 .../org/apache/ignite/internal/app/IgniteImpl.java |   2 +-
 .../internal/sql/engine/IgniteSqlApiTest.java      |  11 +
 .../engine/exec/rel/TableScanExecutionTest.java    |   2 +-
 .../internal/storage/MvPartitionStorage.java       |   2 +-
 .../internal/storage/PartitionTimestampCursor.java |   2 +-
 .../apache/ignite/internal/storage/ReadResult.java |   2 +-
 .../storage/AbstractMvPartitionStorageTest.java    |   4 +-
 .../storage/AbstractMvTableStorageTest.java        |   2 +-
 .../storage/impl/TestMvPartitionStorage.java       |   2 +-
 .../mv/AbstractPageMemoryMvPartitionStorage.java   |   2 +-
 .../storage/pagememory/mv/HybridTimestamps.java    |   4 +-
 .../storage/pagememory/mv/ReadRowVersion.java      |   2 +-
 .../internal/storage/pagememory/mv/RowVersion.java |   4 +-
 .../storage/pagememory/mv/RowVersionFreeList.java  |   2 +-
 .../storage/pagememory/mv/io/RowVersionDataIo.java |   2 +-
 .../AbstractPageMemoryMvPartitionStorageTest.java  |   2 +-
 ...PersistentPageMemoryMvPartitionStorageTest.java |   2 +-
 .../storage/rocksdb/RocksDbMvPartitionStorage.java |   4 +-
 .../storage/rocksdb/RocksDbMvTableStorageTest.java |   2 +-
 ...t.java => ItAbstractInternalTableScanTest.java} |  41 ++--
 .../ItInternalTableReadOnlyOperationsTest.java     | 260 +++++++++++++++++++++
 .../ItInternalTableReadOnlyScanTest.java           |  51 ++++
 .../ItInternalTableReadWriteScanTest.java}         |  27 +--
 .../ignite/distributed/ItTablePersistenceTest.java |   4 +-
 .../distributed/ItTxDistributedTestSingleNode.java |  22 +-
 ...butedTestThreeNodesThreeReplicasCollocated.java |   4 +-
 .../ignite/internal/table/ItColocationTest.java    |   2 +-
 .../ignite/internal/table/InternalTable.java       |  50 ++++
 .../internal/table/distributed/TableManager.java   |   2 +-
 .../table/distributed/command/FinishTxCommand.java |   2 +-
 .../distributed/command/TxCleanupCommand.java      |   2 +-
 .../snapshot/message/SnapshotMvDataResponse.java   |   2 +-
 .../request/ReadOnlyReplicaRequest.java            |   7 +-
 .../ReadOnlyScanRetrieveBatchReplicaRequest.java   |   2 +
 .../replicator/PartitionReplicaListener.java       | 133 ++++++++---
 .../distributed/storage/InternalTableImpl.java     | 231 +++++++++++++++---
 .../ignite/internal/table/TxAbstractTest.java      | 120 ++++++++++
 .../apache/ignite/internal/table/TxLocalTest.java  |   2 +-
 .../PartitionRaftCommandsSerializationTest.java    |   2 +-
 .../raft/PartitionCommandListenerTest.java         |   4 +-
 .../replication/PartitionReplicaListenerTest.java  |  19 +-
 .../table/impl/DummyInternalTableImpl.java         |  10 +-
 .../ignite/internal/tx/InternalTransaction.java    |   1 +
 .../org/apache/ignite/internal/tx/TxManager.java   |  13 +-
 .../java/org/apache/ignite/internal/tx/TxMeta.java |   2 +-
 .../tx/impl/IgniteAbstractTransactionImpl.java     | 107 +++++++++
 .../internal/tx/impl/IgniteTransactionsImpl.java   |  23 +-
 .../internal/tx/impl/ReadOnlyTransactionImpl.java  | 101 ++++++++
 ...tionImpl.java => ReadWriteTransactionImpl.java} |  95 ++------
 .../ignite/internal/tx/impl/TxManagerImpl.java     |  12 +-
 .../tx/message/TxCleanupReplicaRequest.java        |   2 +-
 .../tx/message/TxFinishReplicaRequest.java         |   2 +-
 .../internal/tx/message/TxStateReplicaRequest.java |   2 +-
 .../apache/ignite/internal/tx/TxManagerTest.java   |   2 +-
 .../storage/state/TxStateStorageAbstractTest.java  |   2 +-
 88 files changed, 1326 insertions(+), 271 deletions(-)

diff --git a/modules/api/src/main/java/org/apache/ignite/tx/IgniteTransactions.java b/modules/api/src/main/java/org/apache/ignite/tx/IgniteTransactions.java
index de22c0273d..f4b90ea139 100644
--- a/modules/api/src/main/java/org/apache/ignite/tx/IgniteTransactions.java
+++ b/modules/api/src/main/java/org/apache/ignite/tx/IgniteTransactions.java
@@ -70,6 +70,13 @@ public interface IgniteTransactions {
      */
     IgniteTransactions withTimeout(long timeout);
 
+    /**
+     * Returns a decorated {@code IgniteTransactions} instance that will start read-only transactions.
+     *
+     * @return Decorated {@code IgniteTransactions} instance that will start read-only transactions.
+     */
+    IgniteTransactions readOnly();
+
     /**
      * Begins a transaction.
      *
diff --git a/modules/api/src/main/java/org/apache/ignite/tx/Transaction.java b/modules/api/src/main/java/org/apache/ignite/tx/Transaction.java
index 8086ae4fb3..3caa02de44 100644
--- a/modules/api/src/main/java/org/apache/ignite/tx/Transaction.java
+++ b/modules/api/src/main/java/org/apache/ignite/tx/Transaction.java
@@ -18,6 +18,7 @@
 package org.apache.ignite.tx;
 
 import java.util.concurrent.CompletableFuture;
+import org.apache.ignite.internal.hlc.HybridTimestamp;
 
 /**
  * The transaction.
@@ -50,4 +51,18 @@ public interface Transaction {
      * @return The future.
      */
     CompletableFuture<Void> rollbackAsync();
+
+    /**
+     * Returns {code true} if given transaction is a read-only, {@code false otherwise}.
+     *
+     * @return {code true} if given transaction is a read-only, {@code false otherwise}.
+     */
+    boolean isReadOnly();
+
+    /**
+     * Returns read timestamp for the given transaction if it is a read-only one or {code null} otherwise.
+     *
+     * @return Read timestamp for the given transaction if it is a read-only one or {code null} otherwise.
+     */
+    HybridTimestamp readTimestamp();
 }
diff --git a/modules/api/src/main/java/org/apache/ignite/tx/TransactionException.java b/modules/api/src/main/java/org/apache/ignite/tx/TransactionException.java
index 0f75c8a614..49895f2737 100644
--- a/modules/api/src/main/java/org/apache/ignite/tx/TransactionException.java
+++ b/modules/api/src/main/java/org/apache/ignite/tx/TransactionException.java
@@ -52,6 +52,17 @@ public class TransactionException extends IgniteException {
         super(code, cause);
     }
 
+    /**
+     * Creates a new transaction exception with the given error code and detail message.
+     *
+     * @param code Full error code.
+     * @param message Detail message.
+     */
+    public TransactionException(int code, String message) {
+        super(code, message);
+    }
+
+
     /**
      * Creates a new transaction exception with the given trace id, error code and cause.
      *
diff --git a/modules/client/src/main/java/org/apache/ignite/internal/client/tx/ClientTransaction.java b/modules/client/src/main/java/org/apache/ignite/internal/client/tx/ClientTransaction.java
index 10fc90e62f..74dce7fb19 100644
--- a/modules/client/src/main/java/org/apache/ignite/internal/client/tx/ClientTransaction.java
+++ b/modules/client/src/main/java/org/apache/ignite/internal/client/tx/ClientTransaction.java
@@ -23,6 +23,7 @@ import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.atomic.AtomicInteger;
 import org.apache.ignite.internal.client.ClientChannel;
 import org.apache.ignite.internal.client.proto.ClientOp;
+import org.apache.ignite.internal.hlc.HybridTimestamp;
 import org.apache.ignite.tx.Transaction;
 import org.apache.ignite.tx.TransactionException;
 
@@ -118,4 +119,18 @@ public class ClientTransaction implements Transaction {
 
         throw new TransactionException(message);
     }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean isReadOnly() {
+        // TODO: IGNITE-17929 Add read-only support to ClientTransactions
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public HybridTimestamp readTimestamp() {
+        // TODO: IGNITE-17929 Add read-only support to ClientTransactions
+        return null;
+    }
 }
diff --git a/modules/client/src/main/java/org/apache/ignite/internal/client/tx/ClientTransactions.java b/modules/client/src/main/java/org/apache/ignite/internal/client/tx/ClientTransactions.java
index 9bfe17c8fc..6572c51a19 100644
--- a/modules/client/src/main/java/org/apache/ignite/internal/client/tx/ClientTransactions.java
+++ b/modules/client/src/main/java/org/apache/ignite/internal/client/tx/ClientTransactions.java
@@ -59,4 +59,10 @@ public class ClientTransactions implements IgniteTransactions {
     public CompletableFuture<Transaction> beginAsync() {
         return ch.serviceAsync(ClientOp.TX_BEGIN, w -> {},  r -> new ClientTransaction(r.clientChannel(), r.in().unpackLong()));
     }
+
+    @Override
+    public IgniteTransactions readOnly() {
+        // TODO: IGNITE-17929 Add read-only support to ClientTransactions
+        return null;
+    }
 }
diff --git a/modules/client/src/test/java/org/apache/ignite/client/fakes/FakeIgnite.java b/modules/client/src/test/java/org/apache/ignite/client/fakes/FakeIgnite.java
index fcaa85853f..85b08b405a 100644
--- a/modules/client/src/test/java/org/apache/ignite/client/fakes/FakeIgnite.java
+++ b/modules/client/src/test/java/org/apache/ignite/client/fakes/FakeIgnite.java
@@ -22,6 +22,7 @@ import java.util.UUID;
 import java.util.concurrent.CompletableFuture;
 import org.apache.ignite.Ignite;
 import org.apache.ignite.compute.IgniteCompute;
+import org.apache.ignite.internal.hlc.HybridTimestamp;
 import org.apache.ignite.internal.replicator.ReplicationGroupId;
 import org.apache.ignite.internal.sql.engine.QueryProcessor;
 import org.apache.ignite.internal.tx.InternalTransaction;
@@ -143,8 +144,24 @@ public class FakeIgnite implements Ignite {
                     public CompletableFuture<Void> rollbackAsync() {
                         return CompletableFuture.completedFuture(null);
                     }
+
+                    @Override
+                    public boolean isReadOnly() {
+                        return false;
+                    }
+
+                    @Override
+                    public HybridTimestamp readTimestamp() {
+                        return null;
+                    }
                 });
             }
+
+            /** {@inheritDoc} */
+            @Override
+            public IgniteTransactions readOnly() {
+                throw new UnsupportedOperationException();
+            }
         };
     }
 
diff --git a/modules/client/src/test/java/org/apache/ignite/client/fakes/FakeInternalTable.java b/modules/client/src/test/java/org/apache/ignite/client/fakes/FakeInternalTable.java
index da457b78bf..bef9e1f75e 100644
--- a/modules/client/src/test/java/org/apache/ignite/client/fakes/FakeInternalTable.java
+++ b/modules/client/src/test/java/org/apache/ignite/client/fakes/FakeInternalTable.java
@@ -27,6 +27,7 @@ import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.Flow.Publisher;
 import java.util.function.BiConsumer;
 import javax.naming.OperationNotSupportedException;
+import org.apache.ignite.internal.hlc.HybridTimestamp;
 import org.apache.ignite.internal.schema.BinaryRow;
 import org.apache.ignite.internal.schema.BinaryRowEx;
 import org.apache.ignite.internal.storage.engine.MvTableStorage;
@@ -36,6 +37,7 @@ import org.apache.ignite.internal.tx.storage.state.TxStateTableStorage;
 import org.apache.ignite.lang.IgniteInternalException;
 import org.apache.ignite.network.ClusterNode;
 import org.apache.ignite.raft.client.service.RaftGroupService;
+import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
 /**
@@ -97,6 +99,15 @@ public class FakeInternalTable implements InternalTable {
         return CompletableFuture.completedFuture(data.get(keyRow.keySlice()));
     }
 
+    /** {@inheritDoc} */
+    @Override
+    public CompletableFuture<BinaryRow> get(
+            BinaryRowEx keyRow,
+            @NotNull HybridTimestamp readTimestamp,
+            @NotNull ClusterNode recipientNode) {
+        return null;
+    }
+
     /** {@inheritDoc} */
     @Override
     public CompletableFuture<Collection<BinaryRow>> getAll(Collection<BinaryRowEx> keyRows,
@@ -115,6 +126,16 @@ public class FakeInternalTable implements InternalTable {
         return CompletableFuture.completedFuture(res);
     }
 
+    /** {@inheritDoc} */
+    @Override
+    public CompletableFuture<Collection<BinaryRow>> getAll(
+            Collection<BinaryRowEx> keyRows,
+            @NotNull HybridTimestamp readTimestamp,
+            @NotNull ClusterNode recipientNode
+    ) {
+        return null;
+    }
+
     /** {@inheritDoc} */
     @Override
     public CompletableFuture<Void> upsert(BinaryRowEx row, @Nullable InternalTransaction tx) {
@@ -302,6 +323,16 @@ public class FakeInternalTable implements InternalTable {
         throw new IgniteInternalException(new OperationNotSupportedException());
     }
 
+    /** {@inheritDoc} */
+    @Override
+    public Publisher<BinaryRow> scan(
+            int p,
+            @NotNull HybridTimestamp readTimestamp,
+            @NotNull ClusterNode recipientNode
+    ) {
+        return null;
+    }
+
     /** {@inheritDoc} */
     @Override
     public List<String> assignments() {
diff --git a/modules/cluster-management/src/integrationTest/java/org/apache/ignite/internal/cluster/management/MockNode.java b/modules/cluster-management/src/integrationTest/java/org/apache/ignite/internal/cluster/management/MockNode.java
index cebb8067e0..28c95c33b1 100644
--- a/modules/cluster-management/src/integrationTest/java/org/apache/ignite/internal/cluster/management/MockNode.java
+++ b/modules/cluster-management/src/integrationTest/java/org/apache/ignite/internal/cluster/management/MockNode.java
@@ -24,8 +24,8 @@ import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.CompletableFuture;
-import org.apache.ignite.hlc.HybridClock;
 import org.apache.ignite.internal.cluster.management.raft.RocksDbClusterStateStorage;
+import org.apache.ignite.internal.hlc.HybridClock;
 import org.apache.ignite.internal.manager.IgniteComponent;
 import org.apache.ignite.internal.raft.Loza;
 import org.apache.ignite.internal.util.ReverseIterator;
diff --git a/modules/cluster-management/src/integrationTest/java/org/apache/ignite/internal/cluster/management/raft/ItCmgRaftServiceTest.java b/modules/cluster-management/src/integrationTest/java/org/apache/ignite/internal/cluster/management/raft/ItCmgRaftServiceTest.java
index abe50a0997..017abd973f 100644
--- a/modules/cluster-management/src/integrationTest/java/org/apache/ignite/internal/cluster/management/raft/ItCmgRaftServiceTest.java
+++ b/modules/cluster-management/src/integrationTest/java/org/apache/ignite/internal/cluster/management/raft/ItCmgRaftServiceTest.java
@@ -39,7 +39,6 @@ import java.util.List;
 import java.util.Set;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.TimeUnit;
-import org.apache.ignite.hlc.HybridClock;
 import org.apache.ignite.internal.cluster.management.ClusterState;
 import org.apache.ignite.internal.cluster.management.ClusterTag;
 import org.apache.ignite.internal.cluster.management.network.messages.CmgMessagesFactory;
@@ -47,6 +46,7 @@ import org.apache.ignite.internal.cluster.management.raft.commands.JoinReadyComm
 import org.apache.ignite.internal.cluster.management.raft.commands.JoinRequestCommand;
 import org.apache.ignite.internal.configuration.testframework.ConfigurationExtension;
 import org.apache.ignite.internal.configuration.testframework.InjectConfiguration;
+import org.apache.ignite.internal.hlc.HybridClock;
 import org.apache.ignite.internal.properties.IgniteProductVersion;
 import org.apache.ignite.internal.raft.Loza;
 import org.apache.ignite.internal.raft.configuration.RaftConfiguration;
diff --git a/modules/core/src/main/java/org/apache/ignite/hlc/HybridClock.java b/modules/core/src/main/java/org/apache/ignite/internal/hlc/HybridClock.java
similarity index 98%
rename from modules/core/src/main/java/org/apache/ignite/hlc/HybridClock.java
rename to modules/core/src/main/java/org/apache/ignite/internal/hlc/HybridClock.java
index f538239960..591c96c516 100644
--- a/modules/core/src/main/java/org/apache/ignite/hlc/HybridClock.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/hlc/HybridClock.java
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.hlc;
+package org.apache.ignite.internal.hlc;
 
 import java.lang.invoke.MethodHandles;
 import java.lang.invoke.VarHandle;
diff --git a/modules/core/src/main/java/org/apache/ignite/hlc/HybridTimestamp.java b/modules/core/src/main/java/org/apache/ignite/internal/hlc/HybridTimestamp.java
similarity index 95%
rename from modules/core/src/main/java/org/apache/ignite/hlc/HybridTimestamp.java
rename to modules/core/src/main/java/org/apache/ignite/internal/hlc/HybridTimestamp.java
index c086c65524..79495518bc 100644
--- a/modules/core/src/main/java/org/apache/ignite/hlc/HybridTimestamp.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/hlc/HybridTimestamp.java
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.hlc;
+package org.apache.ignite.internal.hlc;
 
 import java.io.Serializable;
 import org.apache.ignite.internal.tostring.S;
@@ -48,7 +48,7 @@ public final class HybridTimestamp implements Comparable<HybridTimestamp>, Seria
      */
     public HybridTimestamp(long physical, int logical) {
         assert physical > 0 : physical;
-        // Value -1 is used in "org.apache.ignite.hlc.HybridClock.update" to produce "0" after the increment.
+        // Value -1 is used in "org.apache.ignite.internal.hlc.HybridClock.update" to produce "0" after the increment.
         // Real usable value cannot be negative.
         assert logical >= -1 : logical;
 
diff --git a/modules/core/src/main/java/org/apache/ignite/lang/ErrorGroups.java b/modules/core/src/main/java/org/apache/ignite/lang/ErrorGroups.java
index 0d4a3d8c78..0edbbd3b69 100755
--- a/modules/core/src/main/java/org/apache/ignite/lang/ErrorGroups.java
+++ b/modules/core/src/main/java/org/apache/ignite/lang/ErrorGroups.java
@@ -276,6 +276,9 @@ public class ErrorGroups {
 
         /** Failed to rollback a transaction. */
         public static final int TX_ROLLBACK_ERR = TX_ERR_GROUP.registerErrorCode(8);
+
+        /** Failed to enlist read-write operation into read-only transaction. */
+        public static final int TX_INSUFFICIENT_READ_WRITE_OPERATION_ERR = TX_ERR_GROUP.registerErrorCode(9);
     }
 
     /** Replicator error group. */
@@ -300,6 +303,10 @@ public class ErrorGroups {
 
         /** The error happens when the replica is not the current primary replica. */
         public static final int REPLICA_MISS_ERR = REPLICATOR_ERR_GROUP.registerErrorCode(6);
+
+        /** Failed to close cursor. */
+        public static final int CURSOR_CLOSE_ERR = REPLICATOR_ERR_GROUP.registerErrorCode(7);
+
     }
 
     /** Storage error group. */
diff --git a/modules/core/src/test/java/org/apache/ignite/hlc/HybridClockTest.java b/modules/core/src/test/java/org/apache/ignite/internal/HybridClockTest.java
similarity index 96%
rename from modules/core/src/test/java/org/apache/ignite/hlc/HybridClockTest.java
rename to modules/core/src/test/java/org/apache/ignite/internal/HybridClockTest.java
index 3962b97e30..5442de531b 100644
--- a/modules/core/src/test/java/org/apache/ignite/hlc/HybridClockTest.java
+++ b/modules/core/src/test/java/org/apache/ignite/internal/HybridClockTest.java
@@ -15,9 +15,9 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.hlc;
+package org.apache.ignite.internal.hlc;
 
-import static org.apache.ignite.hlc.HybridClockTestUtils.mockToEpochMilli;
+import static org.apache.ignite.internal.hlc.HybridClockTestUtils.mockToEpochMilli;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 
 import java.time.Clock;
diff --git a/modules/core/src/test/java/org/apache/ignite/hlc/HybridClockTestUtils.java b/modules/core/src/test/java/org/apache/ignite/internal/HybridClockTestUtils.java
similarity index 97%
rename from modules/core/src/test/java/org/apache/ignite/hlc/HybridClockTestUtils.java
rename to modules/core/src/test/java/org/apache/ignite/internal/HybridClockTestUtils.java
index 8d244f4feb..cb30cd2686 100644
--- a/modules/core/src/test/java/org/apache/ignite/hlc/HybridClockTestUtils.java
+++ b/modules/core/src/test/java/org/apache/ignite/internal/HybridClockTestUtils.java
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.hlc;
+package org.apache.ignite.internal.hlc;
 
 import static org.mockito.Mockito.mockStatic;
 import static org.mockito.Mockito.spy;
diff --git a/modules/core/src/test/java/org/apache/ignite/hlc/HybridTimestampTest.java b/modules/core/src/test/java/org/apache/ignite/internal/HybridTimestampTest.java
similarity index 93%
rename from modules/core/src/test/java/org/apache/ignite/hlc/HybridTimestampTest.java
rename to modules/core/src/test/java/org/apache/ignite/internal/HybridTimestampTest.java
index bf111500c1..95a5429a00 100644
--- a/modules/core/src/test/java/org/apache/ignite/hlc/HybridTimestampTest.java
+++ b/modules/core/src/test/java/org/apache/ignite/internal/HybridTimestampTest.java
@@ -15,9 +15,9 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.hlc;
+package org.apache.ignite.internal.hlc;
 
-import static org.apache.ignite.hlc.HybridTimestamp.max;
+import static org.apache.ignite.internal.hlc.HybridTimestamp.max;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 
 import org.junit.jupiter.api.Test;
diff --git a/modules/raft/src/integrationTest/java/org/apache/ignite/internal/raft/ItLozaTest.java b/modules/raft/src/integrationTest/java/org/apache/ignite/internal/raft/ItLozaTest.java
index a100881f07..b4d247f228 100644
--- a/modules/raft/src/integrationTest/java/org/apache/ignite/internal/raft/ItLozaTest.java
+++ b/modules/raft/src/integrationTest/java/org/apache/ignite/internal/raft/ItLozaTest.java
@@ -36,9 +36,9 @@ import java.util.Objects;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.TimeUnit;
 import java.util.function.Supplier;
-import org.apache.ignite.hlc.HybridClock;
 import org.apache.ignite.internal.configuration.testframework.ConfigurationExtension;
 import org.apache.ignite.internal.configuration.testframework.InjectConfiguration;
+import org.apache.ignite.internal.hlc.HybridClock;
 import org.apache.ignite.internal.raft.configuration.RaftConfiguration;
 import org.apache.ignite.internal.replicator.ReplicationGroupId;
 import org.apache.ignite.internal.testframework.WorkDirectory;
diff --git a/modules/raft/src/integrationTest/java/org/apache/ignite/internal/raft/ItRaftGroupServiceTest.java b/modules/raft/src/integrationTest/java/org/apache/ignite/internal/raft/ItRaftGroupServiceTest.java
index f17cb31bd3..2ba4b40c49 100644
--- a/modules/raft/src/integrationTest/java/org/apache/ignite/internal/raft/ItRaftGroupServiceTest.java
+++ b/modules/raft/src/integrationTest/java/org/apache/ignite/internal/raft/ItRaftGroupServiceTest.java
@@ -32,9 +32,9 @@ import java.util.Map;
 import java.util.Objects;
 import java.util.concurrent.CompletableFuture;
 import java.util.stream.Collectors;
-import org.apache.ignite.hlc.HybridClock;
 import org.apache.ignite.internal.configuration.testframework.ConfigurationExtension;
 import org.apache.ignite.internal.configuration.testframework.InjectConfiguration;
+import org.apache.ignite.internal.hlc.HybridClock;
 import org.apache.ignite.internal.raft.configuration.RaftConfiguration;
 import org.apache.ignite.internal.replicator.ReplicationGroupId;
 import org.apache.ignite.internal.testframework.WorkDirectory;
diff --git a/modules/raft/src/integrationTest/java/org/apache/ignite/raft/jraft/core/ItNodeTest.java b/modules/raft/src/integrationTest/java/org/apache/ignite/raft/jraft/core/ItNodeTest.java
index 0357964a78..55210e4cd3 100644
--- a/modules/raft/src/integrationTest/java/org/apache/ignite/raft/jraft/core/ItNodeTest.java
+++ b/modules/raft/src/integrationTest/java/org/apache/ignite/raft/jraft/core/ItNodeTest.java
@@ -68,7 +68,7 @@ import java.util.function.BiPredicate;
 import java.util.function.BooleanSupplier;
 import java.util.stream.IntStream;
 import java.util.stream.Stream;
-import org.apache.ignite.hlc.HybridClock;
+import org.apache.ignite.internal.hlc.HybridClock;
 import org.apache.ignite.internal.logger.IgniteLogger;
 import org.apache.ignite.internal.logger.Loggers;
 import org.apache.ignite.internal.raft.server.RaftGroupEventsListener;
diff --git a/modules/raft/src/integrationTest/java/org/apache/ignite/raft/server/ItJraftHlcServerTest.java b/modules/raft/src/integrationTest/java/org/apache/ignite/raft/server/ItJraftHlcServerTest.java
index 2ae91d709d..6cf4cd74fd 100644
--- a/modules/raft/src/integrationTest/java/org/apache/ignite/raft/server/ItJraftHlcServerTest.java
+++ b/modules/raft/src/integrationTest/java/org/apache/ignite/raft/server/ItJraftHlcServerTest.java
@@ -32,7 +32,7 @@ import java.util.function.Consumer;
 import java.util.function.Supplier;
 import java.util.stream.Collectors;
 import java.util.stream.IntStream;
-import org.apache.ignite.hlc.HybridClock;
+import org.apache.ignite.internal.hlc.HybridClock;
 import org.apache.ignite.internal.logger.IgniteLogger;
 import org.apache.ignite.internal.logger.Loggers;
 import org.apache.ignite.internal.raft.server.RaftServer;
diff --git a/modules/raft/src/main/java/org/apache/ignite/internal/raft/Loza.java b/modules/raft/src/main/java/org/apache/ignite/internal/raft/Loza.java
index 387b8008fa..83238dd978 100644
--- a/modules/raft/src/main/java/org/apache/ignite/internal/raft/Loza.java
+++ b/modules/raft/src/main/java/org/apache/ignite/internal/raft/Loza.java
@@ -28,7 +28,7 @@ import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.function.Supplier;
 import java.util.stream.Collectors;
-import org.apache.ignite.hlc.HybridClock;
+import org.apache.ignite.internal.hlc.HybridClock;
 import org.apache.ignite.internal.logger.IgniteLogger;
 import org.apache.ignite.internal.logger.Loggers;
 import org.apache.ignite.internal.manager.IgniteComponent;
diff --git a/modules/raft/src/main/java/org/apache/ignite/raft/jraft/core/NodeImpl.java b/modules/raft/src/main/java/org/apache/ignite/raft/jraft/core/NodeImpl.java
index 1afcfe934e..064546cb0f 100644
--- a/modules/raft/src/main/java/org/apache/ignite/raft/jraft/core/NodeImpl.java
+++ b/modules/raft/src/main/java/org/apache/ignite/raft/jraft/core/NodeImpl.java
@@ -34,8 +34,8 @@ import java.util.concurrent.TimeUnit;
 import java.util.concurrent.locks.Lock;
 import java.util.concurrent.locks.ReadWriteLock;
 import java.util.stream.Collectors;
-import org.apache.ignite.hlc.HybridClock;
-import org.apache.ignite.hlc.HybridTimestamp;
+import org.apache.ignite.internal.hlc.HybridClock;
+import org.apache.ignite.internal.hlc.HybridTimestamp;
 import org.apache.ignite.internal.logger.IgniteLogger;
 import org.apache.ignite.internal.logger.Loggers;
 import org.apache.ignite.internal.thread.NamedThreadFactory;
@@ -90,7 +90,6 @@ import org.apache.ignite.raft.jraft.rpc.RaftClientService;
 import org.apache.ignite.raft.jraft.rpc.RaftRpcFactory;
 import org.apache.ignite.raft.jraft.rpc.RaftServerService;
 import org.apache.ignite.raft.jraft.rpc.ReadIndexResponseBuilder;
-import org.apache.ignite.raft.jraft.rpc.RequestVoteResponseBuilder;
 import org.apache.ignite.raft.jraft.rpc.RpcRequestClosure;
 import org.apache.ignite.raft.jraft.rpc.RpcRequests.AppendEntriesRequest;
 import org.apache.ignite.raft.jraft.rpc.RpcRequests.AppendEntriesResponse;
diff --git a/modules/raft/src/main/java/org/apache/ignite/raft/jraft/option/NodeOptions.java b/modules/raft/src/main/java/org/apache/ignite/raft/jraft/option/NodeOptions.java
index a56b1e9eaf..158efeeb95 100644
--- a/modules/raft/src/main/java/org/apache/ignite/raft/jraft/option/NodeOptions.java
+++ b/modules/raft/src/main/java/org/apache/ignite/raft/jraft/option/NodeOptions.java
@@ -18,7 +18,7 @@ package org.apache.ignite.raft.jraft.option;
 
 import java.util.List;
 import java.util.concurrent.ExecutorService;
-import org.apache.ignite.hlc.HybridClock;
+import org.apache.ignite.internal.hlc.HybridClock;
 import org.apache.ignite.internal.raft.server.RaftGroupEventsListener;
 import org.apache.ignite.raft.jraft.JRaftServiceFactory;
 import org.apache.ignite.raft.jraft.StateMachine;
diff --git a/modules/raft/src/main/java/org/apache/ignite/raft/jraft/rpc/RpcRequests.java b/modules/raft/src/main/java/org/apache/ignite/raft/jraft/rpc/RpcRequests.java
index f0d0571922..91dbde9cfb 100644
--- a/modules/raft/src/main/java/org/apache/ignite/raft/jraft/rpc/RpcRequests.java
+++ b/modules/raft/src/main/java/org/apache/ignite/raft/jraft/rpc/RpcRequests.java
@@ -19,7 +19,7 @@ package org.apache.ignite.raft.jraft.rpc;
 
 import java.util.Collection;
 import java.util.List;
-import org.apache.ignite.hlc.HybridTimestamp;
+import org.apache.ignite.internal.hlc.HybridTimestamp;
 import org.apache.ignite.network.annotations.Marshallable;
 import org.apache.ignite.network.annotations.Transferable;
 import org.apache.ignite.raft.jraft.RaftMessageGroup;
diff --git a/modules/raft/src/test/java/org/apache/ignite/internal/raft/LozaTest.java b/modules/raft/src/test/java/org/apache/ignite/internal/raft/LozaTest.java
index 3960ba2d57..6cc9758794 100644
--- a/modules/raft/src/test/java/org/apache/ignite/internal/raft/LozaTest.java
+++ b/modules/raft/src/test/java/org/apache/ignite/internal/raft/LozaTest.java
@@ -25,7 +25,7 @@ import java.util.List;
 import java.util.Objects;
 import java.util.UUID;
 import java.util.function.Supplier;
-import org.apache.ignite.hlc.HybridClock;
+import org.apache.ignite.internal.hlc.HybridClock;
 import org.apache.ignite.internal.raft.configuration.RaftConfiguration;
 import org.apache.ignite.internal.replicator.ReplicationGroupId;
 import org.apache.ignite.internal.testframework.IgniteAbstractTest;
diff --git a/modules/raft/src/test/java/org/apache/ignite/raft/jraft/core/TestCluster.java b/modules/raft/src/test/java/org/apache/ignite/raft/jraft/core/TestCluster.java
index 351ec74972..6cb5e9614c 100644
--- a/modules/raft/src/test/java/org/apache/ignite/raft/jraft/core/TestCluster.java
+++ b/modules/raft/src/test/java/org/apache/ignite/raft/jraft/core/TestCluster.java
@@ -39,7 +39,7 @@ import java.util.concurrent.locks.ReentrantLock;
 import java.util.function.Consumer;
 import java.util.function.Predicate;
 import java.util.stream.Stream;
-import org.apache.ignite.hlc.HybridClock;
+import org.apache.ignite.internal.hlc.HybridClock;
 import org.apache.ignite.internal.logger.IgniteLogger;
 import org.apache.ignite.internal.logger.Loggers;
 import org.apache.ignite.internal.raft.server.RaftGroupEventsListener;
diff --git a/modules/replicator/src/main/java/org/apache/ignite/internal/replicator/ReplicaManager.java b/modules/replicator/src/main/java/org/apache/ignite/internal/replicator/ReplicaManager.java
index e91c644f1e..a15fd2b7cc 100644
--- a/modules/replicator/src/main/java/org/apache/ignite/internal/replicator/ReplicaManager.java
+++ b/modules/replicator/src/main/java/org/apache/ignite/internal/replicator/ReplicaManager.java
@@ -22,8 +22,8 @@ import java.util.Set;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.atomic.AtomicBoolean;
-import org.apache.ignite.hlc.HybridClock;
-import org.apache.ignite.hlc.HybridTimestamp;
+import org.apache.ignite.internal.hlc.HybridClock;
+import org.apache.ignite.internal.hlc.HybridTimestamp;
 import org.apache.ignite.internal.logger.IgniteLogger;
 import org.apache.ignite.internal.logger.Loggers;
 import org.apache.ignite.internal.manager.IgniteComponent;
diff --git a/modules/replicator/src/main/java/org/apache/ignite/internal/replicator/ReplicaService.java b/modules/replicator/src/main/java/org/apache/ignite/internal/replicator/ReplicaService.java
index 26cf100d05..86e71be80a 100644
--- a/modules/replicator/src/main/java/org/apache/ignite/internal/replicator/ReplicaService.java
+++ b/modules/replicator/src/main/java/org/apache/ignite/internal/replicator/ReplicaService.java
@@ -23,7 +23,7 @@ import static org.apache.ignite.lang.ErrorGroups.Replicator.REPLICA_COMMON_ERR;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.CompletionException;
 import java.util.concurrent.TimeoutException;
-import org.apache.ignite.hlc.HybridClock;
+import org.apache.ignite.internal.hlc.HybridClock;
 import org.apache.ignite.internal.replicator.exception.ReplicaUnavailableException;
 import org.apache.ignite.internal.replicator.exception.ReplicationException;
 import org.apache.ignite.internal.replicator.exception.ReplicationTimeoutException;
diff --git a/modules/replicator/src/main/java/org/apache/ignite/internal/replicator/message/TimestampAware.java b/modules/replicator/src/main/java/org/apache/ignite/internal/replicator/message/TimestampAware.java
index f63712d651..60c31942b6 100644
--- a/modules/replicator/src/main/java/org/apache/ignite/internal/replicator/message/TimestampAware.java
+++ b/modules/replicator/src/main/java/org/apache/ignite/internal/replicator/message/TimestampAware.java
@@ -17,7 +17,7 @@
 
 package org.apache.ignite.internal.replicator.message;
 
-import org.apache.ignite.hlc.HybridTimestamp;
+import org.apache.ignite.internal.hlc.HybridTimestamp;
 import org.apache.ignite.network.NetworkMessage;
 import org.apache.ignite.network.annotations.Marshallable;
 import org.apache.ignite.network.annotations.Transferable;
diff --git a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/configuration/ItDistributedConfigurationPropertiesTest.java b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/configuration/ItDistributedConfigurationPropertiesTest.java
index cb9ff5c2ba..822a1604a0 100644
--- a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/configuration/ItDistributedConfigurationPropertiesTest.java
+++ b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/configuration/ItDistributedConfigurationPropertiesTest.java
@@ -37,13 +37,13 @@ import org.apache.ignite.configuration.ConfigurationValue;
 import org.apache.ignite.configuration.annotation.ConfigurationRoot;
 import org.apache.ignite.configuration.annotation.ConfigurationType;
 import org.apache.ignite.configuration.annotation.Value;
-import org.apache.ignite.hlc.HybridClock;
 import org.apache.ignite.internal.cluster.management.ClusterManagementGroupManager;
 import org.apache.ignite.internal.cluster.management.raft.TestClusterStateStorage;
 import org.apache.ignite.internal.configuration.storage.ConfigurationStorageListener;
 import org.apache.ignite.internal.configuration.storage.DistributedConfigurationStorage;
 import org.apache.ignite.internal.configuration.testframework.ConfigurationExtension;
 import org.apache.ignite.internal.configuration.testframework.InjectConfiguration;
+import org.apache.ignite.internal.hlc.HybridClock;
 import org.apache.ignite.internal.manager.IgniteComponent;
 import org.apache.ignite.internal.metastorage.MetaStorageManager;
 import org.apache.ignite.internal.metastorage.server.SimpleInMemoryKeyValueStorage;
diff --git a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/configuration/storage/ItDistributedConfigurationStorageTest.java b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/configuration/storage/ItDistributedConfigurationStorageTest.java
index e27b67e093..ce14c25d27 100644
--- a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/configuration/storage/ItDistributedConfigurationStorageTest.java
+++ b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/configuration/storage/ItDistributedConfigurationStorageTest.java
@@ -30,11 +30,11 @@ import java.util.Map;
 import java.util.Objects;
 import java.util.concurrent.CompletableFuture;
 import java.util.stream.Stream;
-import org.apache.ignite.hlc.HybridClock;
 import org.apache.ignite.internal.cluster.management.ClusterManagementGroupManager;
 import org.apache.ignite.internal.cluster.management.raft.TestClusterStateStorage;
 import org.apache.ignite.internal.configuration.testframework.ConfigurationExtension;
 import org.apache.ignite.internal.configuration.testframework.InjectConfiguration;
+import org.apache.ignite.internal.hlc.HybridClock;
 import org.apache.ignite.internal.manager.IgniteComponent;
 import org.apache.ignite.internal.metastorage.MetaStorageManager;
 import org.apache.ignite.internal.metastorage.server.SimpleInMemoryKeyValueStorage;
diff --git a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/configuration/storage/ItRebalanceDistributedTest.java b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/configuration/storage/ItRebalanceDistributedTest.java
index 83c9c25382..7c6a1c1190 100644
--- a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/configuration/storage/ItRebalanceDistributedTest.java
+++ b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/configuration/storage/ItRebalanceDistributedTest.java
@@ -39,13 +39,13 @@ import java.util.stream.Collectors;
 import java.util.stream.IntStream;
 import org.apache.ignite.client.handler.configuration.ClientConnectorConfiguration;
 import org.apache.ignite.configuration.schemas.network.NetworkConfiguration;
-import org.apache.ignite.hlc.HybridClock;
 import org.apache.ignite.internal.baseline.BaselineManager;
 import org.apache.ignite.internal.cluster.management.ClusterManagementGroupManager;
 import org.apache.ignite.internal.cluster.management.raft.TestClusterStateStorage;
 import org.apache.ignite.internal.configuration.ConfigurationManager;
 import org.apache.ignite.internal.configuration.testframework.ConfigurationExtension;
 import org.apache.ignite.internal.configuration.testframework.InjectConfiguration;
+import org.apache.ignite.internal.hlc.HybridClock;
 import org.apache.ignite.internal.logger.IgniteLogger;
 import org.apache.ignite.internal.logger.Loggers;
 import org.apache.ignite.internal.manager.IgniteComponent;
diff --git a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/ItIgniteNodeRestartTest.java b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/ItIgniteNodeRestartTest.java
index 87d1c17d27..25c353226f 100644
--- a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/ItIgniteNodeRestartTest.java
+++ b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/ItIgniteNodeRestartTest.java
@@ -49,7 +49,6 @@ import java.util.stream.IntStream;
 import org.apache.ignite.Ignite;
 import org.apache.ignite.IgnitionManager;
 import org.apache.ignite.configuration.schemas.network.NetworkConfiguration;
-import org.apache.ignite.hlc.HybridClock;
 import org.apache.ignite.internal.app.IgniteImpl;
 import org.apache.ignite.internal.baseline.BaselineManager;
 import org.apache.ignite.internal.cluster.management.ClusterManagementGroupManager;
@@ -64,6 +63,7 @@ import org.apache.ignite.internal.configuration.storage.DistributedConfiguration
 import org.apache.ignite.internal.configuration.storage.LocalConfigurationStorage;
 import org.apache.ignite.internal.configuration.testframework.ConfigurationExtension;
 import org.apache.ignite.internal.configuration.testframework.InjectConfiguration;
+import org.apache.ignite.internal.hlc.HybridClock;
 import org.apache.ignite.internal.logger.IgniteLogger;
 import org.apache.ignite.internal.manager.IgniteComponent;
 import org.apache.ignite.internal.metastorage.MetaStorageManager;
diff --git a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/client/ItThinClientTransactionsTest.java b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/client/ItThinClientTransactionsTest.java
index 4920f5bdd5..6824e411c0 100644
--- a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/client/ItThinClientTransactionsTest.java
+++ b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/client/ItThinClientTransactionsTest.java
@@ -29,6 +29,7 @@ import java.util.Map;
 import java.util.Objects;
 import java.util.concurrent.CompletableFuture;
 import org.apache.ignite.client.IgniteClient;
+import org.apache.ignite.internal.hlc.HybridTimestamp;
 import org.apache.ignite.lang.IgniteException;
 import org.apache.ignite.table.KeyValueView;
 import org.apache.ignite.table.RecordView;
@@ -233,6 +234,16 @@ public class ItThinClientTransactionsTest extends ItAbstractThinClientTest {
             public CompletableFuture<Void> rollbackAsync() {
                 return null;
             }
+
+            @Override
+            public boolean isReadOnly() {
+                return false;
+            }
+
+            @Override
+            public HybridTimestamp readTimestamp() {
+                return null;
+            }
         };
 
         var ex = assertThrows(IgniteException.class, () -> kvView().put(tx, 1, "1"));
diff --git a/modules/runner/src/main/java/org/apache/ignite/internal/app/IgniteImpl.java b/modules/runner/src/main/java/org/apache/ignite/internal/app/IgniteImpl.java
index 178c16f26a..4a9f9e97f3 100644
--- a/modules/runner/src/main/java/org/apache/ignite/internal/app/IgniteImpl.java
+++ b/modules/runner/src/main/java/org/apache/ignite/internal/app/IgniteImpl.java
@@ -34,7 +34,6 @@ import org.apache.ignite.IgnitionManager;
 import org.apache.ignite.client.handler.ClientHandlerModule;
 import org.apache.ignite.compute.IgniteCompute;
 import org.apache.ignite.configuration.schemas.network.NetworkConfiguration;
-import org.apache.ignite.hlc.HybridClock;
 import org.apache.ignite.internal.baseline.BaselineManager;
 import org.apache.ignite.internal.cluster.management.ClusterManagementGroupManager;
 import org.apache.ignite.internal.cluster.management.network.messages.CmgMessagesSerializationRegistryInitializer;
@@ -54,6 +53,7 @@ import org.apache.ignite.internal.configuration.ServiceLoaderModulesProvider;
 import org.apache.ignite.internal.configuration.storage.ConfigurationStorage;
 import org.apache.ignite.internal.configuration.storage.DistributedConfigurationStorage;
 import org.apache.ignite.internal.configuration.storage.LocalConfigurationStorage;
+import org.apache.ignite.internal.hlc.HybridClock;
 import org.apache.ignite.internal.index.IndexManager;
 import org.apache.ignite.internal.logger.IgniteLogger;
 import org.apache.ignite.internal.logger.Loggers;
diff --git a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/IgniteSqlApiTest.java b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/IgniteSqlApiTest.java
index 41bac8c4ce..3b8b3a1aa0 100644
--- a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/IgniteSqlApiTest.java
+++ b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/IgniteSqlApiTest.java
@@ -34,6 +34,7 @@ import java.util.concurrent.TimeUnit;
 import java.util.function.Consumer;
 import java.util.function.Function;
 import java.util.stream.Collectors;
+import org.apache.ignite.internal.hlc.HybridTimestamp;
 import org.apache.ignite.internal.schema.Column;
 import org.apache.ignite.internal.schema.NativeTypes;
 import org.apache.ignite.internal.schema.SchemaDescriptor;
@@ -664,5 +665,15 @@ public class IgniteSqlApiTest {
             rollback();
             return CompletableFuture.completedFuture(null);
         }
+
+        @Override
+        public boolean isReadOnly() {
+            return false;
+        }
+
+        @Override
+        public HybridTimestamp readTimestamp() {
+            return null;
+        }
     }
 }
diff --git a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/rel/TableScanExecutionTest.java b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/rel/TableScanExecutionTest.java
index b1ab1d1a35..47d70d6953 100644
--- a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/rel/TableScanExecutionTest.java
+++ b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/rel/TableScanExecutionTest.java
@@ -28,7 +28,7 @@ import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ThreadLocalRandom;
 import org.apache.calcite.rel.type.RelDataType;
 import org.apache.calcite.util.ImmutableBitSet;
-import org.apache.ignite.hlc.HybridClock;
+import org.apache.ignite.internal.hlc.HybridClock;
 import org.apache.ignite.internal.replicator.ReplicaService;
 import org.apache.ignite.internal.schema.BinaryRow;
 import org.apache.ignite.internal.schema.ByteBufferRow;
diff --git a/modules/storage-api/src/main/java/org/apache/ignite/internal/storage/MvPartitionStorage.java b/modules/storage-api/src/main/java/org/apache/ignite/internal/storage/MvPartitionStorage.java
index 8e742f54d3..7f87994f03 100644
--- a/modules/storage-api/src/main/java/org/apache/ignite/internal/storage/MvPartitionStorage.java
+++ b/modules/storage-api/src/main/java/org/apache/ignite/internal/storage/MvPartitionStorage.java
@@ -20,7 +20,7 @@ package org.apache.ignite.internal.storage;
 import java.util.UUID;
 import java.util.concurrent.CompletableFuture;
 import java.util.function.BiConsumer;
-import org.apache.ignite.hlc.HybridTimestamp;
+import org.apache.ignite.internal.hlc.HybridTimestamp;
 import org.apache.ignite.internal.schema.BinaryRow;
 import org.apache.ignite.internal.util.Cursor;
 import org.jetbrains.annotations.Nullable;
diff --git a/modules/storage-api/src/main/java/org/apache/ignite/internal/storage/PartitionTimestampCursor.java b/modules/storage-api/src/main/java/org/apache/ignite/internal/storage/PartitionTimestampCursor.java
index dd11f5d4e1..308f882541 100644
--- a/modules/storage-api/src/main/java/org/apache/ignite/internal/storage/PartitionTimestampCursor.java
+++ b/modules/storage-api/src/main/java/org/apache/ignite/internal/storage/PartitionTimestampCursor.java
@@ -17,7 +17,7 @@
 
 package org.apache.ignite.internal.storage;
 
-import org.apache.ignite.hlc.HybridTimestamp;
+import org.apache.ignite.internal.hlc.HybridTimestamp;
 import org.apache.ignite.internal.schema.BinaryRow;
 import org.apache.ignite.internal.util.Cursor;
 import org.jetbrains.annotations.Nullable;
diff --git a/modules/storage-api/src/main/java/org/apache/ignite/internal/storage/ReadResult.java b/modules/storage-api/src/main/java/org/apache/ignite/internal/storage/ReadResult.java
index 41819efe8e..735f7dfb11 100644
--- a/modules/storage-api/src/main/java/org/apache/ignite/internal/storage/ReadResult.java
+++ b/modules/storage-api/src/main/java/org/apache/ignite/internal/storage/ReadResult.java
@@ -18,7 +18,7 @@
 package org.apache.ignite.internal.storage;
 
 import java.util.UUID;
-import org.apache.ignite.hlc.HybridTimestamp;
+import org.apache.ignite.internal.hlc.HybridTimestamp;
 import org.apache.ignite.internal.schema.BinaryRow;
 import org.jetbrains.annotations.Nullable;
 
diff --git a/modules/storage-api/src/testFixtures/java/org/apache/ignite/internal/storage/AbstractMvPartitionStorageTest.java b/modules/storage-api/src/testFixtures/java/org/apache/ignite/internal/storage/AbstractMvPartitionStorageTest.java
index 09e1b2cc3a..98e25e1f2f 100644
--- a/modules/storage-api/src/testFixtures/java/org/apache/ignite/internal/storage/AbstractMvPartitionStorageTest.java
+++ b/modules/storage-api/src/testFixtures/java/org/apache/ignite/internal/storage/AbstractMvPartitionStorageTest.java
@@ -41,8 +41,8 @@ import java.util.NoSuchElementException;
 import java.util.UUID;
 import java.util.concurrent.CompletableFuture;
 import java.util.stream.Collectors;
-import org.apache.ignite.hlc.HybridClock;
-import org.apache.ignite.hlc.HybridTimestamp;
+import org.apache.ignite.internal.hlc.HybridClock;
+import org.apache.ignite.internal.hlc.HybridTimestamp;
 import org.apache.ignite.internal.schema.BinaryRow;
 import org.apache.ignite.internal.tx.Timestamp;
 import org.apache.ignite.internal.util.Cursor;
diff --git a/modules/storage-api/src/testFixtures/java/org/apache/ignite/internal/storage/AbstractMvTableStorageTest.java b/modules/storage-api/src/testFixtures/java/org/apache/ignite/internal/storage/AbstractMvTableStorageTest.java
index 995a856848..13f251f9be 100644
--- a/modules/storage-api/src/testFixtures/java/org/apache/ignite/internal/storage/AbstractMvTableStorageTest.java
+++ b/modules/storage-api/src/testFixtures/java/org/apache/ignite/internal/storage/AbstractMvTableStorageTest.java
@@ -33,9 +33,9 @@ import java.util.List;
 import java.util.UUID;
 import java.util.concurrent.CompletableFuture;
 import java.util.stream.Collectors;
-import org.apache.ignite.hlc.HybridTimestamp;
 import org.apache.ignite.internal.binarytuple.BinaryTupleBuilder;
 import org.apache.ignite.internal.configuration.util.ConfigurationUtil;
+import org.apache.ignite.internal.hlc.HybridTimestamp;
 import org.apache.ignite.internal.schema.BinaryTuple;
 import org.apache.ignite.internal.schema.BinaryTupleSchema;
 import org.apache.ignite.internal.schema.BinaryTupleSchema.Element;
diff --git a/modules/storage-api/src/testFixtures/java/org/apache/ignite/internal/storage/impl/TestMvPartitionStorage.java b/modules/storage-api/src/testFixtures/java/org/apache/ignite/internal/storage/impl/TestMvPartitionStorage.java
index 846cfe78f2..f6043ae9a8 100644
--- a/modules/storage-api/src/testFixtures/java/org/apache/ignite/internal/storage/impl/TestMvPartitionStorage.java
+++ b/modules/storage-api/src/testFixtures/java/org/apache/ignite/internal/storage/impl/TestMvPartitionStorage.java
@@ -27,7 +27,7 @@ import java.util.concurrent.ConcurrentNavigableMap;
 import java.util.concurrent.ConcurrentSkipListMap;
 import java.util.function.BiConsumer;
 import java.util.stream.Stream;
-import org.apache.ignite.hlc.HybridTimestamp;
+import org.apache.ignite.internal.hlc.HybridTimestamp;
 import org.apache.ignite.internal.schema.BinaryRow;
 import org.apache.ignite.internal.storage.MvPartitionStorage;
 import org.apache.ignite.internal.storage.PartitionTimestampCursor;
diff --git a/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/mv/AbstractPageMemoryMvPartitionStorage.java b/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/mv/AbstractPageMemoryMvPartitionStorage.java
index eaf33041c3..60dccdc65e 100644
--- a/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/mv/AbstractPageMemoryMvPartitionStorage.java
+++ b/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/mv/AbstractPageMemoryMvPartitionStorage.java
@@ -31,7 +31,7 @@ import java.util.function.BiConsumer;
 import java.util.function.Predicate;
 import java.util.stream.Stream;
 import org.apache.ignite.configuration.NamedListView;
-import org.apache.ignite.hlc.HybridTimestamp;
+import org.apache.ignite.internal.hlc.HybridTimestamp;
 import org.apache.ignite.internal.pagememory.PageIdAllocator;
 import org.apache.ignite.internal.pagememory.PageMemory;
 import org.apache.ignite.internal.pagememory.datapage.DataPageReader;
diff --git a/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/mv/HybridTimestamps.java b/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/mv/HybridTimestamps.java
index 542c0aa182..e04848be7b 100644
--- a/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/mv/HybridTimestamps.java
+++ b/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/mv/HybridTimestamps.java
@@ -17,14 +17,14 @@
 
 package org.apache.ignite.internal.storage.pagememory.mv;
 
-import static org.apache.ignite.hlc.HybridTimestamp.HYBRID_TIMESTAMP_SIZE;
+import static org.apache.ignite.internal.hlc.HybridTimestamp.HYBRID_TIMESTAMP_SIZE;
 import static org.apache.ignite.internal.pagememory.util.PageUtils.getInt;
 import static org.apache.ignite.internal.pagememory.util.PageUtils.getLong;
 import static org.apache.ignite.internal.pagememory.util.PageUtils.putInt;
 import static org.apache.ignite.internal.pagememory.util.PageUtils.putLong;
 
 import java.nio.ByteBuffer;
-import org.apache.ignite.hlc.HybridTimestamp;
+import org.apache.ignite.internal.hlc.HybridTimestamp;
 import org.jetbrains.annotations.Nullable;
 
 /**
diff --git a/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/mv/ReadRowVersion.java b/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/mv/ReadRowVersion.java
index 5d60a27b15..e5f83b0650 100644
--- a/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/mv/ReadRowVersion.java
+++ b/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/mv/ReadRowVersion.java
@@ -21,7 +21,7 @@ import static org.apache.ignite.internal.pagememory.util.PartitionlessLinks.read
 
 import java.nio.ByteBuffer;
 import java.util.function.Predicate;
-import org.apache.ignite.hlc.HybridTimestamp;
+import org.apache.ignite.internal.hlc.HybridTimestamp;
 import org.apache.ignite.internal.pagememory.datapage.PageMemoryTraversal;
 import org.apache.ignite.internal.pagememory.io.DataPagePayload;
 import org.apache.ignite.internal.pagememory.util.PageIdUtils;
diff --git a/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/mv/RowVersion.java b/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/mv/RowVersion.java
index c51d8b888b..200ddee03f 100644
--- a/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/mv/RowVersion.java
+++ b/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/mv/RowVersion.java
@@ -17,12 +17,12 @@
 
 package org.apache.ignite.internal.storage.pagememory.mv;
 
-import static org.apache.ignite.hlc.HybridTimestamp.HYBRID_TIMESTAMP_SIZE;
+import static org.apache.ignite.internal.hlc.HybridTimestamp.HYBRID_TIMESTAMP_SIZE;
 import static org.apache.ignite.internal.pagememory.util.PageIdUtils.NULL_LINK;
 
 import java.nio.ByteBuffer;
 import java.util.Objects;
-import org.apache.ignite.hlc.HybridTimestamp;
+import org.apache.ignite.internal.hlc.HybridTimestamp;
 import org.apache.ignite.internal.pagememory.Storable;
 import org.apache.ignite.internal.pagememory.io.AbstractDataPageIo;
 import org.apache.ignite.internal.pagememory.io.IoVersions;
diff --git a/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/mv/RowVersionFreeList.java b/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/mv/RowVersionFreeList.java
index 56546140fd..3bc3cac725 100644
--- a/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/mv/RowVersionFreeList.java
+++ b/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/mv/RowVersionFreeList.java
@@ -18,7 +18,7 @@
 package org.apache.ignite.internal.storage.pagememory.mv;
 
 import java.util.concurrent.atomic.AtomicLong;
-import org.apache.ignite.hlc.HybridTimestamp;
+import org.apache.ignite.internal.hlc.HybridTimestamp;
 import org.apache.ignite.internal.logger.IgniteLogger;
 import org.apache.ignite.internal.logger.Loggers;
 import org.apache.ignite.internal.pagememory.PageMemory;
diff --git a/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/mv/io/RowVersionDataIo.java b/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/mv/io/RowVersionDataIo.java
index 5b0ec438df..fd1913792a 100644
--- a/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/mv/io/RowVersionDataIo.java
+++ b/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/mv/io/RowVersionDataIo.java
@@ -23,7 +23,7 @@ import static org.apache.ignite.internal.pagememory.util.PageUtils.putShort;
 import static org.apache.ignite.internal.pagememory.util.PartitionlessLinks.writePartitionless;
 
 import java.nio.ByteBuffer;
-import org.apache.ignite.hlc.HybridTimestamp;
+import org.apache.ignite.internal.hlc.HybridTimestamp;
 import org.apache.ignite.internal.pagememory.io.AbstractDataPageIo;
 import org.apache.ignite.internal.pagememory.io.IoVersions;
 import org.apache.ignite.internal.pagememory.util.PartitionlessLinks;
diff --git a/modules/storage-page-memory/src/test/java/org/apache/ignite/internal/storage/pagememory/mv/AbstractPageMemoryMvPartitionStorageTest.java b/modules/storage-page-memory/src/test/java/org/apache/ignite/internal/storage/pagememory/mv/AbstractPageMemoryMvPartitionStorageTest.java
index 42c22c02fa..7d6cf962b0 100644
--- a/modules/storage-page-memory/src/test/java/org/apache/ignite/internal/storage/pagememory/mv/AbstractPageMemoryMvPartitionStorageTest.java
+++ b/modules/storage-page-memory/src/test/java/org/apache/ignite/internal/storage/pagememory/mv/AbstractPageMemoryMvPartitionStorageTest.java
@@ -20,7 +20,7 @@ package org.apache.ignite.internal.storage.pagememory.mv;
 import static java.util.stream.Collectors.joining;
 
 import java.util.stream.IntStream;
-import org.apache.ignite.hlc.HybridTimestamp;
+import org.apache.ignite.internal.hlc.HybridTimestamp;
 import org.apache.ignite.internal.pagememory.io.PageIoRegistry;
 import org.apache.ignite.internal.schema.BinaryRow;
 import org.apache.ignite.internal.storage.AbstractMvPartitionStorageTest;
diff --git a/modules/storage-page-memory/src/test/java/org/apache/ignite/internal/storage/pagememory/mv/PersistentPageMemoryMvPartitionStorageTest.java b/modules/storage-page-memory/src/test/java/org/apache/ignite/internal/storage/pagememory/mv/PersistentPageMemoryMvPartitionStorageTest.java
index 9290509941..d6eed10be8 100644
--- a/modules/storage-page-memory/src/test/java/org/apache/ignite/internal/storage/pagememory/mv/PersistentPageMemoryMvPartitionStorageTest.java
+++ b/modules/storage-page-memory/src/test/java/org/apache/ignite/internal/storage/pagememory/mv/PersistentPageMemoryMvPartitionStorageTest.java
@@ -21,10 +21,10 @@ import static org.apache.ignite.internal.pagememory.persistence.checkpoint.Check
 
 import java.nio.file.Path;
 import java.util.concurrent.TimeUnit;
-import org.apache.ignite.hlc.HybridTimestamp;
 import org.apache.ignite.internal.components.LongJvmPauseDetector;
 import org.apache.ignite.internal.configuration.testframework.ConfigurationExtension;
 import org.apache.ignite.internal.configuration.testframework.InjectConfiguration;
+import org.apache.ignite.internal.hlc.HybridTimestamp;
 import org.apache.ignite.internal.logger.Loggers;
 import org.apache.ignite.internal.schema.configuration.TablesConfiguration;
 import org.apache.ignite.internal.storage.RowId;
diff --git a/modules/storage-rocksdb/src/main/java/org/apache/ignite/internal/storage/rocksdb/RocksDbMvPartitionStorage.java b/modules/storage-rocksdb/src/main/java/org/apache/ignite/internal/storage/rocksdb/RocksDbMvPartitionStorage.java
index cebb731a4a..85375c72ac 100644
--- a/modules/storage-rocksdb/src/main/java/org/apache/ignite/internal/storage/rocksdb/RocksDbMvPartitionStorage.java
+++ b/modules/storage-rocksdb/src/main/java/org/apache/ignite/internal/storage/rocksdb/RocksDbMvPartitionStorage.java
@@ -21,7 +21,7 @@ import static java.lang.ThreadLocal.withInitial;
 import static java.nio.ByteBuffer.allocateDirect;
 import static java.util.Arrays.copyOf;
 import static java.util.Arrays.copyOfRange;
-import static org.apache.ignite.hlc.HybridTimestamp.HYBRID_TIMESTAMP_SIZE;
+import static org.apache.ignite.internal.hlc.HybridTimestamp.HYBRID_TIMESTAMP_SIZE;
 import static org.apache.ignite.internal.util.ByteUtils.bytesToLong;
 import static org.apache.ignite.internal.util.ByteUtils.bytesToUuid;
 import static org.apache.ignite.internal.util.ByteUtils.putUuidToBytes;
@@ -35,7 +35,7 @@ import java.util.UUID;
 import java.util.concurrent.CompletableFuture;
 import java.util.function.BiConsumer;
 import java.util.function.Predicate;
-import org.apache.ignite.hlc.HybridTimestamp;
+import org.apache.ignite.internal.hlc.HybridTimestamp;
 import org.apache.ignite.internal.rocksdb.RocksIteratorAdapter;
 import org.apache.ignite.internal.rocksdb.RocksUtils;
 import org.apache.ignite.internal.schema.BinaryRow;
diff --git a/modules/storage-rocksdb/src/test/java/org/apache/ignite/internal/storage/rocksdb/RocksDbMvTableStorageTest.java b/modules/storage-rocksdb/src/test/java/org/apache/ignite/internal/storage/rocksdb/RocksDbMvTableStorageTest.java
index d7920537bc..038404585b 100644
--- a/modules/storage-rocksdb/src/test/java/org/apache/ignite/internal/storage/rocksdb/RocksDbMvTableStorageTest.java
+++ b/modules/storage-rocksdb/src/test/java/org/apache/ignite/internal/storage/rocksdb/RocksDbMvTableStorageTest.java
@@ -28,9 +28,9 @@ import static org.hamcrest.MatcherAssert.assertThat;
 import java.nio.file.Path;
 import java.util.UUID;
 import java.util.concurrent.CompletableFuture;
-import org.apache.ignite.hlc.HybridTimestamp;
 import org.apache.ignite.internal.configuration.testframework.ConfigurationExtension;
 import org.apache.ignite.internal.configuration.testframework.InjectConfiguration;
+import org.apache.ignite.internal.hlc.HybridTimestamp;
 import org.apache.ignite.internal.schema.configuration.TablesConfiguration;
 import org.apache.ignite.internal.storage.AbstractMvTableStorageTest;
 import org.apache.ignite.internal.storage.MvPartitionStorage;
diff --git a/modules/table/src/integrationTest/java/org/apache/ignite/distributed/ItInternalTableScanTest.java b/modules/table/src/integrationTest/java/org/apache/ignite/distributed/ItAbstractInternalTableScanTest.java
similarity index 92%
rename from modules/table/src/integrationTest/java/org/apache/ignite/distributed/ItInternalTableScanTest.java
rename to modules/table/src/integrationTest/java/org/apache/ignite/distributed/ItAbstractInternalTableScanTest.java
index 714b50818e..47218e7f57 100644
--- a/modules/table/src/integrationTest/java/org/apache/ignite/distributed/ItInternalTableScanTest.java
+++ b/modules/table/src/integrationTest/java/org/apache/ignite/distributed/ItAbstractInternalTableScanTest.java
@@ -36,13 +36,14 @@ import java.util.List;
 import java.util.NoSuchElementException;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Flow;
+import java.util.concurrent.Flow.Publisher;
 import java.util.concurrent.Flow.Subscriber;
 import java.util.concurrent.Flow.Subscription;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.stream.Collectors;
-import org.apache.ignite.hlc.HybridClock;
-import org.apache.ignite.hlc.HybridTimestamp;
+import org.apache.ignite.internal.hlc.HybridClock;
+import org.apache.ignite.internal.hlc.HybridTimestamp;
 import org.apache.ignite.internal.replicator.ReplicaService;
 import org.apache.ignite.internal.schema.BinaryRow;
 import org.apache.ignite.internal.schema.ByteBufferRow;
@@ -53,6 +54,8 @@ import org.apache.ignite.internal.storage.ReadResult;
 import org.apache.ignite.internal.storage.StorageException;
 import org.apache.ignite.internal.table.InternalTable;
 import org.apache.ignite.internal.table.impl.DummyInternalTableImpl;
+import org.apache.ignite.internal.testframework.IgniteAbstractTest;
+import org.apache.ignite.internal.tx.InternalTransaction;
 import org.apache.ignite.internal.util.ByteUtils;
 import org.jetbrains.annotations.NotNull;
 import org.junit.jupiter.api.BeforeEach;
@@ -67,13 +70,13 @@ import org.mockito.junit.jupiter.MockitoExtension;
  * Tests for {@link InternalTable#scan(int, org.apache.ignite.internal.tx.InternalTransaction)}.
  */
 @ExtendWith(MockitoExtension.class)
-public class ItInternalTableScanTest {
+public abstract class ItAbstractInternalTableScanTest extends IgniteAbstractTest {
     /** Mock partition storage. */
     @Mock
     private MvPartitionStorage mockStorage;
 
     /** Internal table to test. */
-    private InternalTable internalTbl;
+    protected InternalTable internalTbl;
 
     private final HybridClock clock = new HybridClock();
 
@@ -182,7 +185,7 @@ public class ItInternalTableScanTest {
             return cursor;
         });
 
-        internalTbl.scan(0, null).subscribe(new Subscriber<>() {
+        scan(0, null).subscribe(new Subscriber<>() {
 
             @Override
             public void onSubscribe(Subscription subscription) {
@@ -223,7 +226,7 @@ public class ItInternalTableScanTest {
 
         when(mockStorage.scan(any(HybridTimestamp.class))).thenThrow(new StorageException("Some storage exception"));
 
-        internalTbl.scan(0, null).subscribe(new Subscriber<>() {
+        scan(0, null).subscribe(new Subscriber<>() {
 
             @Override
             public void onSubscribe(Subscription subscription) {
@@ -260,12 +263,12 @@ public class ItInternalTableScanTest {
     public void testInvalidPartitionParameterScan() {
         assertThrows(
                 IllegalArgumentException.class,
-                () -> internalTbl.scan(-1, null)
+                () -> scan(-1, null)
         );
 
         assertThrows(
                 IllegalArgumentException.class,
-                () -> internalTbl.scan(1, null)
+                () -> scan(1, null)
         );
     }
 
@@ -276,7 +279,7 @@ public class ItInternalTableScanTest {
      */
     @Test
     public void testSecondSubscriptionFiresIllegalStateException() throws Exception {
-        Flow.Publisher<BinaryRow> scan = internalTbl.scan(0, null);
+        Flow.Publisher<BinaryRow> scan = scan(0, null);
 
         scan.subscribe(new Subscriber<>() {
             @Override
@@ -340,7 +343,7 @@ public class ItInternalTableScanTest {
     public void testNullPointerExceptionIsThrownInCaseOfNullSubscription() {
         assertThrows(
                 NullPointerException.class,
-                () -> internalTbl.scan(0, null).subscribe(null)
+                () -> scan(0, null).subscribe(null)
         );
     }
 
@@ -352,8 +355,7 @@ public class ItInternalTableScanTest {
      * @return {@link DataRow} based on given key and value.
      * @throws java.io.IOException If failed to close output stream that was used to convertation.
      */
-    private static @NotNull BinaryRow prepareRow(@NotNull String entryKey,
-            @NotNull String entryVal) throws IOException {
+    private static @NotNull BinaryRow prepareRow(@NotNull String entryKey, @NotNull String entryVal) throws IOException {
         byte[] keyBytes = ByteUtils.toBytes(entryKey);
 
         try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
@@ -381,7 +383,7 @@ public class ItInternalTableScanTest {
 
             when(cursor.hasNext()).thenAnswer(hnInvocation -> cursorTouchCnt.get() < submittedItems.size());
 
-            when(cursor.next()).thenAnswer(ninvocation ->
+            lenient().when(cursor.next()).thenAnswer(ninvocation ->
                     ReadResult.createFromCommitted(submittedItems.get(cursorTouchCnt.getAndIncrement()), clock.now()));
 
             return cursor;
@@ -390,7 +392,7 @@ public class ItInternalTableScanTest {
         // The latch that allows to await Subscriber.onError() before asserting test invariants.
         CountDownLatch subscriberAllDataAwaitLatch = new CountDownLatch(1);
 
-        internalTbl.scan(0, null).subscribe(new Subscriber<>() {
+        scan(0, null).subscribe(new Subscriber<>() {
             private Subscription subscription;
 
             @Override
@@ -463,7 +465,7 @@ public class ItInternalTableScanTest {
 
         AtomicReference<Throwable> gotException = new AtomicReference<>();
 
-        internalTbl.scan(0, null).subscribe(new Subscriber<>() {
+        scan(0, null).subscribe(new Subscriber<>() {
             @Override
             public void onSubscribe(Subscription subscription) {
                 subscription.request(reqAmount);
@@ -495,4 +497,13 @@ public class ItInternalTableScanTest {
                 }
         );
     }
+
+    /**
+     * Either read-write or read-only publisher producer.
+     *
+     * @param part  The partition.
+     * @param tx The transaction.
+     * @return {@link Publisher} that reactively notifies about partition rows.
+     */
+    protected abstract Publisher<BinaryRow> scan(int part, InternalTransaction tx);
 }
diff --git a/modules/table/src/integrationTest/java/org/apache/ignite/distributed/ItInternalTableReadOnlyOperationsTest.java b/modules/table/src/integrationTest/java/org/apache/ignite/distributed/ItInternalTableReadOnlyOperationsTest.java
new file mode 100644
index 0000000000..4f3920c9de
--- /dev/null
+++ b/modules/table/src/integrationTest/java/org/apache/ignite/distributed/ItInternalTableReadOnlyOperationsTest.java
@@ -0,0 +1,260 @@
+/*
+ * 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.distributed;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.instanceOf;
+import static org.hamcrest.Matchers.is;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.lenient;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.atomic.AtomicInteger;
+import org.apache.ignite.internal.hlc.HybridClock;
+import org.apache.ignite.internal.hlc.HybridTimestamp;
+import org.apache.ignite.internal.replicator.ReplicaService;
+import org.apache.ignite.internal.schema.BinaryRow;
+import org.apache.ignite.internal.schema.ByteBufferRow;
+import org.apache.ignite.internal.schema.Column;
+import org.apache.ignite.internal.schema.NativeTypes;
+import org.apache.ignite.internal.schema.SchemaDescriptor;
+import org.apache.ignite.internal.schema.row.Row;
+import org.apache.ignite.internal.schema.row.RowAssembler;
+import org.apache.ignite.internal.storage.MvPartitionStorage;
+import org.apache.ignite.internal.storage.PartitionTimestampCursor;
+import org.apache.ignite.internal.storage.ReadResult;
+import org.apache.ignite.internal.table.InternalTable;
+import org.apache.ignite.internal.table.impl.DummyInternalTableImpl;
+import org.apache.ignite.internal.testframework.IgniteAbstractTest;
+import org.apache.ignite.internal.tx.InternalTransaction;
+import org.apache.ignite.network.ClusterNode;
+import org.apache.ignite.tx.TransactionException;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestInfo;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.api.function.Executable;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+/**
+ * Tests for {@link InternalTable} read-only operations.
+ */
+@ExtendWith(MockitoExtension.class)
+public class ItInternalTableReadOnlyOperationsTest extends IgniteAbstractTest {
+    private static final SchemaDescriptor SCHEMA = new SchemaDescriptor(
+            1,
+            new Column[]{new Column("key", NativeTypes.INT64, false)},
+            new Column[]{new Column("value", NativeTypes.INT64, false)}
+    );
+
+    private static final HybridClock CLOCK = new HybridClock();
+
+    private static final Row ROW_1 = createKeyValueRow(1, 1001);
+
+    private static final Row ROW_2 = createKeyValueRow(2, 1002);
+
+
+    /** Mock partition storage. */
+    @Mock
+    private MvPartitionStorage mockStorage;
+
+    /** Transaction mock. */
+    @Mock
+    private InternalTransaction readOnlyTx;
+
+    /** Internal table to test. */
+    private InternalTable internalTbl;
+
+    /**
+     * Prepare test environment using DummyInternalTableImpl and Mocked storage.
+     */
+    @BeforeEach
+    public void setUp(TestInfo testInfo) {
+        internalTbl = new DummyInternalTableImpl(Mockito.mock(ReplicaService.class), mockStorage);
+
+        mockStorage(List.of(ROW_1, ROW_2));
+
+        lenient().when(readOnlyTx.isReadOnly()).thenReturn(true);
+        lenient().when(readOnlyTx.readTimestamp()).thenReturn(CLOCK.now());
+    }
+
+    @Test
+    public void testReadOnlyGetNonExistingKeyWithReadTimestamp() {
+        assertNull(internalTbl.get(createKeyRow(0), CLOCK.now(), mock(ClusterNode.class)).join());
+    }
+
+    @Test
+    public void testReadOnlyGetNonExistingKeyWithTx() {
+        assertNull(internalTbl.get(createKeyRow(0), readOnlyTx).join());
+    }
+
+    @Test
+    public void testReadOnlyGetExistingKeyWithReadTimestamp() {
+        assertEquals(ROW_2, internalTbl.get(createKeyRow(2), CLOCK.now(), mock(ClusterNode.class)).join());
+    }
+
+    @Test
+    public void testReadOnlyGetExistingKeyWithTx() {
+        assertEquals(ROW_2, internalTbl.get(createKeyRow(2), readOnlyTx).join());
+    }
+
+
+    @Test
+    public void testReadOnlyGetAllNonExistingKeysWithReadTimestamp() {
+        assertEquals(0,
+                internalTbl.getAll(Collections.singleton(createKeyRow(0)), CLOCK.now(), mock(ClusterNode.class)).join().size()
+        );
+    }
+
+    @Test
+    public void testReadOnlyGetAllNonExistingKeysWithTx() {
+        assertEquals(0,
+                internalTbl.getAll(Collections.singleton(createKeyRow(0)), readOnlyTx).join().size()
+        );
+    }
+
+    @Test
+    public void testReadOnlyGetAllPartiallyExistingKeysWithReadTimestamp() {
+        assertEquals(
+                Collections.singletonList(ROW_2),
+                internalTbl.getAll(Collections.singleton(createKeyRow(2)), CLOCK.now(), mock(ClusterNode.class)).join()
+        );
+    }
+
+    @Test
+    public void testReadOnlyGetAllPartiallyExistingKeysWithTx() {
+        assertEquals(
+                Collections.singletonList(ROW_2),
+                internalTbl.getAll(Collections.singleton(createKeyRow(2)), readOnlyTx).join()
+        );
+    }
+
+    @Test
+    public void testReadOnlyGetAllExistingKeysWithReadTimestamp() {
+        assertEquals(
+                List.of(ROW_1, ROW_2),
+                internalTbl.getAll(List.of(createKeyRow(1), createKeyRow(2)), CLOCK.now(), mock(ClusterNode.class)).join()
+        );
+    }
+
+    @Test
+    public void testReadOnlyGetAllExistingKeysWithTx() {
+        assertEquals(
+                List.of(ROW_1, ROW_2),
+                internalTbl.getAll(List.of(createKeyRow(1), createKeyRow(2)), readOnlyTx).join()
+        );
+    }
+
+    @Test()
+    public void testEnlistingReadWriteOperationIntoReadOnlyTransactionThrowsAnException() {
+        InternalTransaction tx = mock(InternalTransaction.class);
+        when(tx.isReadOnly()).thenReturn(true);
+
+        List<Executable> executables = List.of(
+                () -> internalTbl.delete(null, tx).get(),
+                () -> internalTbl.deleteAll(null, tx).get(),
+                () -> internalTbl.deleteExact(null, tx).get(),
+                () -> internalTbl.deleteAllExact(null, tx).get(),
+                () -> internalTbl.getAndDelete(null, tx).get(),
+                () -> internalTbl.getAndReplace(null, tx).get(),
+                () -> internalTbl.getAndUpsert(null, tx).get(),
+                () -> internalTbl.upsert(null, tx).get(),
+                () -> internalTbl.upsertAll(null, tx).get(),
+                () -> internalTbl.insert(null, tx).get(),
+                () -> internalTbl.insertAll(null, tx).get(),
+                () -> internalTbl.replace(null, tx).get(),
+                () -> internalTbl.replace(null, null, tx).get()
+        );
+
+        executables.forEach(executable -> {
+            ExecutionException ex = assertThrows(ExecutionException.class, executable);
+
+            assertThat(ex.getCause(), is(instanceOf(TransactionException.class)));
+            assertThat(
+                    ex.getCause().getMessage(),
+                    containsString("Failed to enlist read-write operation into read-only transaction"));
+        });
+
+        TransactionException ex = assertThrows(TransactionException.class, () -> internalTbl.scan(0, tx));
+        assertThat(
+                ex.getMessage(),
+                containsString("Failed to enlist read-write operation into read-only transaction"));
+    }
+
+    private void mockStorage(List<BinaryRow> submittedItems) {
+        // TODO: IGNITE-17859 After index integration get and getAll methods should be used instead of scan.
+        AtomicInteger cursorTouchCnt = new AtomicInteger(0);
+
+        lenient().when(mockStorage.scan(any(HybridTimestamp.class))).thenAnswer(invocation -> {
+            var cursor = mock(PartitionTimestampCursor.class);
+
+            lenient().when(cursor.hasNext()).thenAnswer(hnInvocation -> cursorTouchCnt.get() < submittedItems.size());
+
+            lenient().when(cursor.next()).thenAnswer(
+                    ninvocation ->
+                            ReadResult.createFromCommitted(submittedItems.get(
+                                    cursorTouchCnt.getAndIncrement()),
+                                    new HybridTimestamp(1, 0)
+                            )
+            );
+
+            return cursor;
+        });
+
+    }
+
+    /**
+     * Creates a {@link Row} with the supplied key.
+     *
+     * @param id Key.
+     * @return Row.
+     */
+    private static Row createKeyRow(long id) {
+        RowAssembler rowBuilder = new RowAssembler(SCHEMA, 0, 0);
+
+        rowBuilder.appendLong(id);
+
+        return new Row(SCHEMA, new ByteBufferRow(rowBuilder.toBytes()));
+    }
+
+    /**
+     * Creates a {@link Row} with the supplied key and value.
+     *
+     * @param id    Key.
+     * @param value Value.
+     * @return Row.
+     */
+    private static Row createKeyValueRow(long id, long value) {
+        RowAssembler rowBuilder = new RowAssembler(SCHEMA, 0, 0);
+
+        rowBuilder.appendLong(id);
+        rowBuilder.appendLong(value);
+
+        return new Row(SCHEMA, new ByteBufferRow(rowBuilder.toBytes()));
+    }
+}
diff --git a/modules/table/src/integrationTest/java/org/apache/ignite/distributed/ItInternalTableReadOnlyScanTest.java b/modules/table/src/integrationTest/java/org/apache/ignite/distributed/ItInternalTableReadOnlyScanTest.java
new file mode 100644
index 0000000000..80871da334
--- /dev/null
+++ b/modules/table/src/integrationTest/java/org/apache/ignite/distributed/ItInternalTableReadOnlyScanTest.java
@@ -0,0 +1,51 @@
+/*
+ * 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.distributed;
+
+import static org.mockito.Mockito.mock;
+
+import java.util.concurrent.Flow.Publisher;
+import org.apache.ignite.internal.hlc.HybridClock;
+import org.apache.ignite.internal.schema.BinaryRow;
+import org.apache.ignite.internal.table.InternalTable;
+import org.apache.ignite.internal.tx.InternalTransaction;
+import org.apache.ignite.network.ClusterNode;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+/**
+ * Tests for {@link InternalTable#scan(int, org.apache.ignite.internal.tx.InternalTransaction)}.
+ */
+@ExtendWith(MockitoExtension.class)
+public class ItInternalTableReadOnlyScanTest extends ItAbstractInternalTableScanTest {
+    private static final HybridClock CLOCK = new HybridClock();
+
+    /** {@inheritDoc} */
+    @Override
+    protected Publisher<BinaryRow> scan(int part, InternalTransaction tx) {
+        return internalTbl.scan(part, CLOCK.now(), mock(ClusterNode.class));
+    }
+
+    // TODO: IGNITE-17666 Use super test as is.
+    @Disabled("https://issues.apache.org/jira/browse/IGNITE-17666")
+    @Override
+    public void testExceptionRowScanCursorHasNext() throws Exception {
+        super.testExceptionRowScanCursorHasNext();
+    }
+}
diff --git a/modules/replicator/src/main/java/org/apache/ignite/internal/replicator/message/TimestampAware.java b/modules/table/src/integrationTest/java/org/apache/ignite/distributed/ItInternalTableReadWriteScanTest.java
similarity index 57%
copy from modules/replicator/src/main/java/org/apache/ignite/internal/replicator/message/TimestampAware.java
copy to modules/table/src/integrationTest/java/org/apache/ignite/distributed/ItInternalTableReadWriteScanTest.java
index f63712d651..3f6f15cf13 100644
--- a/modules/replicator/src/main/java/org/apache/ignite/internal/replicator/message/TimestampAware.java
+++ b/modules/table/src/integrationTest/java/org/apache/ignite/distributed/ItInternalTableReadWriteScanTest.java
@@ -15,23 +15,20 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.internal.replicator.message;
+package org.apache.ignite.distributed;
 
-import org.apache.ignite.hlc.HybridTimestamp;
-import org.apache.ignite.network.NetworkMessage;
-import org.apache.ignite.network.annotations.Marshallable;
-import org.apache.ignite.network.annotations.Transferable;
+import java.util.concurrent.Flow.Publisher;
+import org.apache.ignite.internal.schema.BinaryRow;
+import org.apache.ignite.internal.table.InternalTable;
+import org.apache.ignite.internal.tx.InternalTransaction;
 
 /**
- * Message with a timestamp to adjust a hybrid logical clock.
+ * Tests for {@link InternalTable#scan(int, org.apache.ignite.internal.tx.InternalTransaction)}.
  */
-@Transferable(ReplicaMessageGroup.TIMESTAMP_AWARE)
-public interface TimestampAware extends NetworkMessage {
-    /**
-     * Gets a hybrid timestamp.
-     *
-     * @return Gets a hybrid timestamp.
-     */
-    @Marshallable
-    HybridTimestamp timestamp();
+public class ItInternalTableReadWriteScanTest extends ItAbstractInternalTableScanTest {
+    /** {@inheritDoc} */
+    @Override
+    protected Publisher<BinaryRow> scan(int part, InternalTransaction tx) {
+        return internalTbl.scan(part, tx);
+    }
 }
diff --git a/modules/table/src/integrationTest/java/org/apache/ignite/distributed/ItTablePersistenceTest.java b/modules/table/src/integrationTest/java/org/apache/ignite/distributed/ItTablePersistenceTest.java
index 70e27682d7..5900c51822 100644
--- a/modules/table/src/integrationTest/java/org/apache/ignite/distributed/ItTablePersistenceTest.java
+++ b/modules/table/src/integrationTest/java/org/apache/ignite/distributed/ItTablePersistenceTest.java
@@ -31,8 +31,8 @@ import java.util.UUID;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.function.BooleanSupplier;
 import java.util.function.Function;
-import org.apache.ignite.hlc.HybridClock;
-import org.apache.ignite.hlc.HybridTimestamp;
+import org.apache.ignite.internal.hlc.HybridClock;
+import org.apache.ignite.internal.hlc.HybridTimestamp;
 import org.apache.ignite.internal.raft.server.impl.JraftServerImpl;
 import org.apache.ignite.internal.replicator.ReplicaService;
 import org.apache.ignite.internal.schema.ByteBufferRow;
diff --git a/modules/table/src/integrationTest/java/org/apache/ignite/distributed/ItTxDistributedTestSingleNode.java b/modules/table/src/integrationTest/java/org/apache/ignite/distributed/ItTxDistributedTestSingleNode.java
index 658737a72d..93bbb5afaf 100644
--- a/modules/table/src/integrationTest/java/org/apache/ignite/distributed/ItTxDistributedTestSingleNode.java
+++ b/modules/table/src/integrationTest/java/org/apache/ignite/distributed/ItTxDistributedTestSingleNode.java
@@ -19,7 +19,9 @@ package org.apache.ignite.distributed;
 
 import static org.apache.ignite.raft.jraft.test.TestUtils.waitForTopology;
 import static org.apache.ignite.utils.ClusterServiceTestUtils.findLocalAddresses;
+import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.junit.jupiter.api.Assertions.fail;
 
@@ -39,10 +41,10 @@ import java.util.concurrent.ScheduledThreadPoolExecutor;
 import java.util.concurrent.TimeUnit;
 import java.util.function.Function;
 import java.util.stream.Collectors;
-import org.apache.ignite.hlc.HybridClock;
 import org.apache.ignite.internal.affinity.RendezvousAffinityFunction;
 import org.apache.ignite.internal.configuration.testframework.ConfigurationExtension;
 import org.apache.ignite.internal.configuration.testframework.InjectConfiguration;
+import org.apache.ignite.internal.hlc.HybridClock;
 import org.apache.ignite.internal.logger.IgniteLogger;
 import org.apache.ignite.internal.logger.Loggers;
 import org.apache.ignite.internal.raft.Loza;
@@ -86,9 +88,11 @@ import org.apache.ignite.raft.client.service.RaftGroupService;
 import org.apache.ignite.raft.jraft.RaftMessagesFactory;
 import org.apache.ignite.raft.jraft.rpc.impl.RaftGroupServiceImpl;
 import org.apache.ignite.table.Table;
+import org.apache.ignite.tx.Transaction;
 import org.apache.ignite.utils.ClusterServiceTestUtils;
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.TestInfo;
 import org.junit.jupiter.api.extension.ExtendWith;
 import org.mockito.Mockito;
@@ -585,4 +589,20 @@ public class ItTxDistributedTestSingleNode extends TxAbstractTest {
 
         return true;
     }
+
+    @Test
+    public void testIgniteTransactionsAndReadTimestamp() {
+        Transaction readWriteTx = igniteTransactions.begin();
+        assertFalse(readWriteTx.isReadOnly());
+        assertNull(readWriteTx.readTimestamp());
+
+        Transaction readOnlyTx = igniteTransactions.readOnly().begin();
+        assertTrue(readOnlyTx.isReadOnly());
+        assertNotNull(readOnlyTx.readTimestamp());
+
+        readWriteTx.commit();
+
+        Transaction readOnlyTx2 = igniteTransactions.readOnly().begin();
+        readOnlyTx2.rollback();
+    }
 }
diff --git a/modules/table/src/integrationTest/java/org/apache/ignite/distributed/ItTxDistributedTestThreeNodesThreeReplicasCollocated.java b/modules/table/src/integrationTest/java/org/apache/ignite/distributed/ItTxDistributedTestThreeNodesThreeReplicasCollocated.java
index 8def19ea7b..254eb50cd8 100644
--- a/modules/table/src/integrationTest/java/org/apache/ignite/distributed/ItTxDistributedTestThreeNodesThreeReplicasCollocated.java
+++ b/modules/table/src/integrationTest/java/org/apache/ignite/distributed/ItTxDistributedTestThreeNodesThreeReplicasCollocated.java
@@ -23,7 +23,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
 import java.util.UUID;
 import java.util.stream.Collectors;
 import org.apache.ignite.internal.tx.TxState;
-import org.apache.ignite.internal.tx.impl.TransactionImpl;
+import org.apache.ignite.internal.tx.impl.ReadWriteTransactionImpl;
 import org.apache.ignite.raft.jraft.test.TestUtils;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
@@ -57,7 +57,7 @@ public class ItTxDistributedTestThreeNodesThreeReplicasCollocated extends ItTxDi
 
     @Test
     public void testTxStateReplication() {
-        TransactionImpl tx = (TransactionImpl) igniteTransactions.begin();
+        ReadWriteTransactionImpl tx = (ReadWriteTransactionImpl) igniteTransactions.begin();
 
         UUID txId = tx.id();
 
diff --git a/modules/table/src/integrationTest/java/org/apache/ignite/internal/table/ItColocationTest.java b/modules/table/src/integrationTest/java/org/apache/ignite/internal/table/ItColocationTest.java
index 1b20a55077..78a59e7fd2 100644
--- a/modules/table/src/integrationTest/java/org/apache/ignite/internal/table/ItColocationTest.java
+++ b/modules/table/src/integrationTest/java/org/apache/ignite/internal/table/ItColocationTest.java
@@ -51,7 +51,7 @@ import java.util.concurrent.CompletableFuture;
 import java.util.stream.Collectors;
 import java.util.stream.IntStream;
 import java.util.stream.Stream;
-import org.apache.ignite.hlc.HybridClock;
+import org.apache.ignite.internal.hlc.HybridClock;
 import org.apache.ignite.internal.replicator.ReplicaService;
 import org.apache.ignite.internal.replicator.ReplicationGroupId;
 import org.apache.ignite.internal.replicator.message.ReplicaRequest;
diff --git a/modules/table/src/main/java/org/apache/ignite/internal/table/InternalTable.java b/modules/table/src/main/java/org/apache/ignite/internal/table/InternalTable.java
index 78ee0022fc..fc00c816d6 100644
--- a/modules/table/src/main/java/org/apache/ignite/internal/table/InternalTable.java
+++ b/modules/table/src/main/java/org/apache/ignite/internal/table/InternalTable.java
@@ -22,6 +22,7 @@ import java.util.List;
 import java.util.UUID;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.Flow.Publisher;
+import org.apache.ignite.internal.hlc.HybridTimestamp;
 import org.apache.ignite.internal.schema.BinaryRow;
 import org.apache.ignite.internal.schema.BinaryRowEx;
 import org.apache.ignite.internal.storage.engine.MvTableStorage;
@@ -30,6 +31,8 @@ import org.apache.ignite.internal.tx.LockException;
 import org.apache.ignite.internal.tx.storage.state.TxStateTableStorage;
 import org.apache.ignite.network.ClusterNode;
 import org.apache.ignite.raft.client.service.RaftGroupService;
+import org.apache.ignite.tx.TransactionException;
+import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
 /**
@@ -68,6 +71,21 @@ public interface InternalTable extends AutoCloseable {
      */
     CompletableFuture<BinaryRow> get(BinaryRowEx keyRow, @Nullable InternalTransaction tx);
 
+    /**
+     * Asynchronously gets a row with same key columns values as given one from the table on a specific node for the proposed readTimestamp.
+     *
+     * @param keyRow        Row with key columns set.
+     * @param readTimestamp Read timestamp.
+     * @param recipientNode Cluster node that will handle given get request.
+     * @return Future representing pending completion of the operation.
+     * @throws LockException If a lock can't be acquired by some reason.
+     */
+    CompletableFuture<BinaryRow> get(
+            BinaryRowEx keyRow,
+            @NotNull HybridTimestamp readTimestamp,
+            @NotNull ClusterNode recipientNode
+    );
+
     /**
      * Asynchronously get rows from the table.
      *
@@ -77,6 +95,21 @@ public interface InternalTable extends AutoCloseable {
      */
     CompletableFuture<Collection<BinaryRow>> getAll(Collection<BinaryRowEx> keyRows, @Nullable InternalTransaction tx);
 
+    /**
+     * Asynchronously get rows from the table for the proposed read timestamp.
+     *
+     * @param keyRows       Rows with key columns set.
+     * @param readTimestamp Read timestamp.
+     * @param recipientNode Cluster node that will handle given get request.
+     * @return Future representing pending completion of the operation.
+     */
+    CompletableFuture<Collection<BinaryRow>> getAll(
+            Collection<BinaryRowEx> keyRows,
+            @NotNull HybridTimestamp readTimestamp,
+            @NotNull ClusterNode recipientNode
+    );
+
+
     /**
      * Asynchronously inserts a row into the table if does not exist or replaces the existed one.
      *
@@ -210,9 +243,26 @@ public interface InternalTable extends AutoCloseable {
      * @param p  The partition.
      * @param tx The transaction.
      * @return {@link Publisher} that reactively notifies about partition rows.
+     * @throws IllegalArgumentException If proposed partition index {@code p} is out of bounds.
      */
     Publisher<BinaryRow> scan(int p, @Nullable InternalTransaction tx);
 
+    /**
+     * Scans given partition with the proposed read timestamp, providing {@link Publisher} that reactively notifies about partition rows.
+     *
+     * @param p             The partition.
+     * @param readTimestamp Read timestamp.
+     * @param recipientNode Cluster node that will handle given get request.
+     * @return {@link Publisher} that reactively notifies about partition rows.
+     * @throws IllegalArgumentException If proposed partition index {@code p} is out of bounds.
+     * @throws TransactionException If proposed {@code tx} is read-write. Transaction itself won't be automatically rolled back.
+     */
+    Publisher<BinaryRow> scan(
+            int p,
+            @NotNull HybridTimestamp readTimestamp,
+            @NotNull ClusterNode recipientNode
+    );
+
     /**
      * Gets a count of partitions of the table.
      *
diff --git a/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/TableManager.java b/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/TableManager.java
index 42fc63b908..6bd629ffaf 100644
--- a/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/TableManager.java
+++ b/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/TableManager.java
@@ -74,11 +74,11 @@ import org.apache.ignite.configuration.ConfigurationProperty;
 import org.apache.ignite.configuration.notifications.ConfigurationNamedListListener;
 import org.apache.ignite.configuration.notifications.ConfigurationNotificationEvent;
 import org.apache.ignite.configuration.validation.ConfigurationValidationException;
-import org.apache.ignite.hlc.HybridClock;
 import org.apache.ignite.internal.affinity.AffinityUtils;
 import org.apache.ignite.internal.baseline.BaselineManager;
 import org.apache.ignite.internal.causality.VersionedValue;
 import org.apache.ignite.internal.configuration.util.ConfigurationUtil;
+import org.apache.ignite.internal.hlc.HybridClock;
 import org.apache.ignite.internal.logger.IgniteLogger;
 import org.apache.ignite.internal.logger.Loggers;
 import org.apache.ignite.internal.manager.EventListener;
diff --git a/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/command/FinishTxCommand.java b/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/command/FinishTxCommand.java
index 750f359924..f640f2c33f 100644
--- a/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/command/FinishTxCommand.java
+++ b/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/command/FinishTxCommand.java
@@ -19,7 +19,7 @@ package org.apache.ignite.internal.table.distributed.command;
 
 import java.util.List;
 import java.util.UUID;
-import org.apache.ignite.hlc.HybridTimestamp;
+import org.apache.ignite.internal.hlc.HybridTimestamp;
 import org.apache.ignite.internal.replicator.ReplicationGroupId;
 
 /**
diff --git a/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/command/TxCleanupCommand.java b/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/command/TxCleanupCommand.java
index 345f78e3ac..9a8af04bd5 100644
--- a/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/command/TxCleanupCommand.java
+++ b/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/command/TxCleanupCommand.java
@@ -18,7 +18,7 @@
 package org.apache.ignite.internal.table.distributed.command;
 
 import java.util.UUID;
-import org.apache.ignite.hlc.HybridTimestamp;
+import org.apache.ignite.internal.hlc.HybridTimestamp;
 
 /**
  * State machine command to cleanup on a transaction commit.
diff --git a/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/raft/snapshot/message/SnapshotMvDataResponse.java b/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/raft/snapshot/message/SnapshotMvDataResponse.java
index 3bf4004504..5d4c9418cc 100644
--- a/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/raft/snapshot/message/SnapshotMvDataResponse.java
+++ b/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/raft/snapshot/message/SnapshotMvDataResponse.java
@@ -20,7 +20,7 @@ package org.apache.ignite.internal.table.distributed.raft.snapshot.message;
 import java.nio.ByteBuffer;
 import java.util.List;
 import java.util.UUID;
-import org.apache.ignite.hlc.HybridTimestamp;
+import org.apache.ignite.internal.hlc.HybridTimestamp;
 import org.apache.ignite.internal.schema.BinaryRow;
 import org.apache.ignite.internal.table.distributed.TableMessageGroup;
 import org.apache.ignite.network.NetworkMessage;
diff --git a/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/replication/request/ReadOnlyReplicaRequest.java b/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/replication/request/ReadOnlyReplicaRequest.java
index 72623a317d..5a61b2e23e 100644
--- a/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/replication/request/ReadOnlyReplicaRequest.java
+++ b/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/replication/request/ReadOnlyReplicaRequest.java
@@ -17,8 +17,7 @@
 
 package org.apache.ignite.internal.table.distributed.replication.request;
 
-import java.util.UUID;
-import org.apache.ignite.hlc.HybridTimestamp;
+import org.apache.ignite.internal.hlc.HybridTimestamp;
 import org.apache.ignite.internal.replicator.message.ReplicaRequest;
 import org.apache.ignite.network.annotations.Marshallable;
 
@@ -26,8 +25,6 @@ import org.apache.ignite.network.annotations.Marshallable;
  * Read only replica request.
  */
 public interface ReadOnlyReplicaRequest extends ReplicaRequest {
-    UUID transactionId();
-
     @Marshallable
-    HybridTimestamp timestamp();
+    HybridTimestamp readTimestamp();
 }
diff --git a/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/replication/request/ReadOnlyScanRetrieveBatchReplicaRequest.java b/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/replication/request/ReadOnlyScanRetrieveBatchReplicaRequest.java
index 37c1d37239..b0465e0b06 100644
--- a/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/replication/request/ReadOnlyScanRetrieveBatchReplicaRequest.java
+++ b/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/replication/request/ReadOnlyScanRetrieveBatchReplicaRequest.java
@@ -17,6 +17,7 @@
 
 package org.apache.ignite.internal.table.distributed.replication.request;
 
+import java.util.UUID;
 import org.apache.ignite.internal.table.distributed.TableMessageGroup;
 import org.apache.ignite.network.annotations.Transferable;
 
@@ -25,4 +26,5 @@ import org.apache.ignite.network.annotations.Transferable;
  */
 @Transferable(TableMessageGroup.RO_SCAN_RETRIEVE_BATCH_REPLICA_REQUEST)
 public interface ReadOnlyScanRetrieveBatchReplicaRequest extends ScanRetrieveBatchReplicaRequest, ReadOnlyReplicaRequest {
+    UUID transactionId();
 }
diff --git a/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/replicator/PartitionReplicaListener.java b/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/replicator/PartitionReplicaListener.java
index 91f807c1d5..89f034f077 100644
--- a/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/replicator/PartitionReplicaListener.java
+++ b/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/replicator/PartitionReplicaListener.java
@@ -19,6 +19,8 @@ package org.apache.ignite.internal.table.distributed.replicator;
 
 import static java.util.concurrent.CompletableFuture.allOf;
 import static java.util.concurrent.CompletableFuture.failedFuture;
+import static org.apache.ignite.internal.util.ExceptionUtils.withCause;
+import static org.apache.ignite.lang.ErrorGroups.Replicator.CURSOR_CLOSE_ERR;
 
 import java.nio.ByteBuffer;
 import java.util.ArrayList;
@@ -35,8 +37,8 @@ import java.util.concurrent.TimeoutException;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.function.Supplier;
 import java.util.stream.Collectors;
-import org.apache.ignite.hlc.HybridClock;
-import org.apache.ignite.hlc.HybridTimestamp;
+import org.apache.ignite.internal.hlc.HybridClock;
+import org.apache.ignite.internal.hlc.HybridTimestamp;
 import org.apache.ignite.internal.replicator.ReplicationGroupId;
 import org.apache.ignite.internal.replicator.exception.PrimaryReplicaMissException;
 import org.apache.ignite.internal.replicator.exception.ReplicationException;
@@ -307,7 +309,7 @@ public class PartitionReplicaListener implements ReplicaListener {
     private CompletableFuture<Object> processReadOnlyScanRetrieveBatchAction(ReadOnlyScanRetrieveBatchReplicaRequest request) {
         UUID txId = request.transactionId();
         int batchCount = request.batchSize();
-        HybridTimestamp timestamp = request.timestamp();
+        HybridTimestamp readTimestamp = request.readTimestamp();
 
         IgniteUuid cursorId = new IgniteUuid(txId, request.scanId());
 
@@ -317,7 +319,7 @@ public class PartitionReplicaListener implements ReplicaListener {
                 id -> mvDataStorage.scan(HybridTimestamp.MAX_VALUE));
 
         while (batchRows.size() < batchCount && cursor.hasNext()) {
-            BinaryRow resolvedReadResult = resolveReadResult(cursor.next(), timestamp, () -> cursor.committed(timestamp));
+            BinaryRow resolvedReadResult = resolveReadResult(cursor.next(), readTimestamp, () -> cursor.committed(readTimestamp));
 
             if (resolvedReadResult != null) {
                 batchRows.add(resolvedReadResult);
@@ -336,33 +338,60 @@ public class PartitionReplicaListener implements ReplicaListener {
     private CompletableFuture<Object> processReadOnlySingleEntryAction(ReadOnlySingleRowReplicaRequest request) {
         ByteBuffer searchKey = request.binaryRow().keySlice();
 
-        UUID indexId = indexIdOrDefault(indexScanId/*request.indexToUse()*/);
-
         if (request.requestType() != RequestType.RO_GET) {
             throw new IgniteInternalException(Replicator.REPLICA_COMMON_ERR,
                     IgniteStringFormatter.format("Unknown single request [actionType={}]", request.requestType()));
         }
 
         //TODO: IGNITE-17868 Integrate indexes into rowIds resolution along with proper lock management on search rows.
-        RowId rowId = rowIdByKey(indexId, searchKey);
-
-        ReadResult readResult = rowId == null ? null : mvDataStorage.read(rowId, request.timestamp());
+        HybridTimestamp readTimestamp = request.readTimestamp();
 
-        BinaryRow result = readResult == null ? null : resolveReadResult(readResult, request.timestamp(), () -> {
-            if (readResult.newestCommitTimestamp() == null) {
-                return null;
-            }
+        try (PartitionTimestampCursor scan = mvDataStorage.scan(readTimestamp)) {
+            while (scan.hasNext()) {
+                ReadResult readResult = scan.next();
+                HybridTimestamp newestCommitTimestamp = readResult.newestCommitTimestamp();
 
-            ReadResult committedReadResult = mvDataStorage.read(rowId, readResult.newestCommitTimestamp());
+                if (readResult.binaryRow() == null) {
+                    if (newestCommitTimestamp == null) {
+                        throw new AssertionError("Unexpected null value of the newest committed timestamp.");
+                    }
 
-            assert !committedReadResult.isWriteIntent() :
-                    "The result is not committed [rowId=" + rowId + ", timestamp="
-                            + readResult.newestCommitTimestamp() + ']';
+                    BinaryRow candidate = scan.committed(newestCommitTimestamp);
+                    if (candidate == null) {
+                        throw new AssertionError("Unexpected null value of the candidate binary row.");
+                    }
 
-            return committedReadResult.binaryRow();
-        });
+                    if (candidate.keySlice().equals(searchKey)) {
+                        return CompletableFuture.completedFuture(
+                                resolveReadResult(
+                                        readResult,
+                                        readTimestamp,
+                                        () -> scan.committed(newestCommitTimestamp)
+                                )
+                        );
+                    }
+                } else if (readResult.binaryRow().keySlice().equals(searchKey)) {
+                    return CompletableFuture.completedFuture(
+                            resolveReadResult(
+                                    readResult,
+                                    readTimestamp,
+                                    () -> newestCommitTimestamp == null ? null : scan.committed(newestCommitTimestamp)
+                            )
+                    );
+                }
+            }
+        } catch (Exception e) {
+            return failedFuture(
+                    withCause(
+                            ReplicationException::new,
+                            CURSOR_CLOSE_ERR,
+                            "Failed to close cursor.",
+                            e
+                    )
+            );
+        }
 
-        return CompletableFuture.completedFuture(result);
+        return CompletableFuture.completedFuture(null);
     }
 
     /**
@@ -375,34 +404,61 @@ public class PartitionReplicaListener implements ReplicaListener {
         Collection<ByteBuffer> keyRows = request.binaryRows().stream().map(br -> br.keySlice()).collect(
                 Collectors.toList());
 
-        UUID indexId = indexIdOrDefault(indexScanId/*request.indexToUse()*/);
-
-        if (request.requestType() != RequestType.RO_GET_ALL) {
+        if (request.requestType() !=  RequestType.RO_GET_ALL) {
             throw new IgniteInternalException(Replicator.REPLICA_COMMON_ERR,
                     IgniteStringFormatter.format("Unknown single request [actionType={}]", request.requestType()));
         }
 
         ArrayList<BinaryRow> result = new ArrayList<>(keyRows.size());
 
-        for (ByteBuffer searchKey : keyRows) {
-            //TODO: IGNITE-17868 Integrate indexes into rowIds resolution along with proper lock management on search rows.
-            RowId rowId = rowIdByKey(indexId, searchKey);
-
-            ReadResult readResult = rowId == null ? null : mvDataStorage.read(rowId, request.timestamp());
+        //TODO: IGNITE-17868 Integrate indexes into rowIds resolution along with proper lock management on search rows.
+        HybridTimestamp readTimestamp = request.readTimestamp();
 
-            result.add(readResult == null ? null : resolveReadResult(readResult, request.timestamp(), () -> {
-                if (readResult.newestCommitTimestamp() == null) {
-                    return null;
-                }
+        try (PartitionTimestampCursor scan = mvDataStorage.scan(readTimestamp)) {
+            while (scan.hasNext()) {
+                ReadResult readResult = scan.next();
+                HybridTimestamp newestCommitTimestamp = readResult.newestCommitTimestamp();
 
-                ReadResult committedReadResult = mvDataStorage.read(rowId, readResult.newestCommitTimestamp());
+                for (ByteBuffer searchKey : keyRows) {
+                    if (readResult.binaryRow() == null) {
+                        if (newestCommitTimestamp == null) {
+                            throw new AssertionError("Unexpected null value of the newest committed timestamp.");
+                        }
 
-                assert !committedReadResult.isWriteIntent() :
-                        "The result is not committed [rowId=" + rowId + ", timestamp="
-                                + readResult.newestCommitTimestamp() + ']';
+                        BinaryRow candidate = scan.committed(readResult.newestCommitTimestamp());
+                        if (candidate == null) {
+                            throw new AssertionError("Unexpected null value of the candidate binary row.");
+                        }
 
-                return committedReadResult.binaryRow();
-            }));
+                        if (candidate.keySlice().equals(searchKey)) {
+                            result.add(
+                                    resolveReadResult(
+                                            readResult,
+                                            readTimestamp,
+                                            () -> scan.committed(readResult.newestCommitTimestamp())
+                                    )
+                            );
+                        }
+                    } else if (readResult.binaryRow().keySlice().equals(searchKey)) {
+                        result.add(
+                                resolveReadResult(
+                                        readResult,
+                                        readTimestamp,
+                                        () -> newestCommitTimestamp == null ? null : scan.committed(readResult.newestCommitTimestamp())
+                                )
+                        );
+                    }
+                }
+            }
+        } catch (Exception e) {
+            return failedFuture(
+                    withCause(
+                            ReplicationException::new,
+                            CURSOR_CLOSE_ERR,
+                            "Failed to close cursor.",
+                            e
+                    )
+            );
         }
 
         return CompletableFuture.completedFuture(result);
@@ -1344,6 +1400,7 @@ public class PartitionReplicaListener implements ReplicaListener {
      * @return Resolved binary row.
      */
     private BinaryRow resolveReadResult(ReadResult readResult, HybridTimestamp timestamp, Supplier<BinaryRow> lastCommitted) {
+
         return resolveReadResult(readResult, null, timestamp, lastCommitted);
     }
 
diff --git a/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/storage/InternalTableImpl.java b/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/storage/InternalTableImpl.java
index 2f96fefa3b..c9d53a903b 100644
--- a/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/storage/InternalTableImpl.java
+++ b/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/storage/InternalTableImpl.java
@@ -19,11 +19,15 @@ package org.apache.ignite.internal.table.distributed.storage;
 
 import static java.util.concurrent.CompletableFuture.completedFuture;
 import static java.util.concurrent.CompletableFuture.failedFuture;
+import static org.apache.ignite.internal.util.ExceptionUtils.withCause;
+import static org.apache.ignite.lang.ErrorGroups.Replicator.REPLICA_UNAVAILABLE_ERR;
+import static org.apache.ignite.lang.ErrorGroups.Transactions.TX_INSUFFICIENT_READ_WRITE_OPERATION_ERR;
 
 import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
 import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.Comparator;
 import java.util.List;
 import java.util.Map;
@@ -37,7 +41,8 @@ import java.util.concurrent.atomic.AtomicLong;
 import java.util.function.BiFunction;
 import java.util.function.Function;
 import java.util.stream.Collectors;
-import org.apache.ignite.hlc.HybridClock;
+import org.apache.ignite.internal.hlc.HybridClock;
+import org.apache.ignite.internal.hlc.HybridTimestamp;
 import org.apache.ignite.internal.replicator.ReplicaService;
 import org.apache.ignite.internal.replicator.ReplicationGroupId;
 import org.apache.ignite.internal.replicator.exception.PrimaryReplicaMissException;
@@ -47,6 +52,7 @@ import org.apache.ignite.internal.schema.BinaryRowEx;
 import org.apache.ignite.internal.storage.engine.MvTableStorage;
 import org.apache.ignite.internal.table.InternalTable;
 import org.apache.ignite.internal.table.distributed.TableMessagesFactory;
+import org.apache.ignite.internal.table.distributed.replication.request.ReadOnlyScanRetrieveBatchReplicaRequest;
 import org.apache.ignite.internal.table.distributed.replication.request.ReadWriteScanRetrieveBatchReplicaRequest;
 import org.apache.ignite.internal.table.distributed.replication.request.ReadWriteScanRetrieveBatchReplicaRequestBuilder;
 import org.apache.ignite.internal.table.distributed.replicator.TablePartitionId;
@@ -194,6 +200,18 @@ public class InternalTableImpl implements InternalTable {
             InternalTransaction tx,
             IgniteTetraFunction<TablePartitionId, InternalTransaction, ReplicationGroupId, Long, ReplicaRequest> op
     ) {
+        // Check whether proposed tx is read-only. Complete future exceptionally if true.
+        // Attempting to enlist a read-only in a read-write transaction does not corrupt the transaction itself, thus read-write transaction
+        // won't be rolled back automatically - it's up to the user or outer engine.
+        if (tx != null && tx.isReadOnly()) {
+            return failedFuture(
+                    new TransactionException(
+                            TX_INSUFFICIENT_READ_WRITE_OPERATION_ERR,
+                            "Failed to enlist read-write operation into read-only transaction txId={" + tx.id() + '}'
+                    )
+            );
+        }
+
         final boolean implicit = tx == null;
 
         final InternalTransaction tx0 = implicit ? txManager.begin() : tx;
@@ -245,6 +263,19 @@ public class InternalTableImpl implements InternalTable {
             IgniteFiveFunction<TablePartitionId, Collection<BinaryRow>, InternalTransaction, ReplicationGroupId, Long, ReplicaRequest> op,
             Function<CompletableFuture<Object>[], CompletableFuture<T>> reducer
     ) {
+        // Check whether proposed tx is read-only. Complete future exceptionally if true.
+        // Attempting to enlist a read-only in a read-write transaction does not corrupt the transaction itself, thus read-write transaction
+        // won't be rolled back automatically - it's up to the user or outer engine.
+        if (tx != null && tx.isReadOnly()) {
+            return failedFuture(
+                    new TransactionException(
+                            TX_INSUFFICIENT_READ_WRITE_OPERATION_ERR,
+                            "Failed to enlist read-write operation into read-only transaction txId={" + tx.id() + '}'
+                    )
+            );
+        }
+
+
         final boolean implicit = tx == null;
 
         if (!implicit && tx.state() != null) {
@@ -431,35 +462,100 @@ public class InternalTableImpl implements InternalTable {
     /** {@inheritDoc} */
     @Override
     public CompletableFuture<BinaryRow> get(BinaryRowEx keyRow, InternalTransaction tx) {
-        return enlistInTx(
-                keyRow,
-                tx,
-                (commitPart, txo, groupId, term) -> tableMessagesFactory.readWriteSingleRowReplicaRequest()
-                        .groupId(groupId)
-                        .binaryRow(keyRow)
-                        .transactionId(txo.id())
-                        .term(term)
-                        .requestType(RequestType.RW_GET)
-                        .timestamp(clock.now())
-                        .build()
+        if (tx != null && tx.isReadOnly()) {
+            return evaluateReadOnlyRecipientNode(partId(keyRow))
+                    .thenCompose(recipientNode -> get(keyRow, tx.readTimestamp(), recipientNode));
+        } else {
+            return enlistInTx(
+                    keyRow,
+                    tx,
+                    (commitPart, txo, groupId, term) -> tableMessagesFactory.readWriteSingleRowReplicaRequest()
+                            .groupId(groupId)
+                            .binaryRow(keyRow)
+                            .transactionId(txo.id())
+                            .term(term)
+                            .requestType(RequestType.RW_GET)
+                            .timestamp(clock.now())
+                            .build()
+            );
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public CompletableFuture<BinaryRow> get(
+            BinaryRowEx keyRow,
+            @NotNull HybridTimestamp readTimestamp,
+            @NotNull ClusterNode recipientNode
+    ) {
+        int partId = partId(keyRow);
+        ReplicationGroupId partGroupId = partitionMap.get(partId).groupId();
+
+        return replicaSvc.invoke(recipientNode, tableMessagesFactory.readOnlySingleRowReplicaRequest()
+                .groupId(partGroupId)
+                .binaryRow(keyRow)
+                .requestType(RequestType.RO_GET)
+                .readTimestamp(readTimestamp)
+                .build()
         );
     }
 
     /** {@inheritDoc} */
     @Override
     public CompletableFuture<Collection<BinaryRow>> getAll(Collection<BinaryRowEx> keyRows, InternalTransaction tx) {
-        return enlistInTx(
-                keyRows,
-                tx,
-                (commitPart, keyRows0, txo, groupId, term) -> tableMessagesFactory.readWriteMultiRowReplicaRequest()
-                        .groupId(groupId)
-                        .binaryRows(keyRows0)
-                        .transactionId(txo.id())
-                        .term(term)
-                        .requestType(RequestType.RW_GET_ALL)
-                        .timestamp(clock.now())
-                        .build(),
-                this::collectMultiRowsResponses);
+        if (tx != null && tx.isReadOnly()) {
+            BinaryRowEx firstRow = keyRows.iterator().next();
+
+            if (firstRow == null) {
+                return CompletableFuture.completedFuture(Collections.emptyList());
+            } else {
+                return evaluateReadOnlyRecipientNode(partId(firstRow))
+                        .thenCompose(recipientNode -> getAll(keyRows, tx.readTimestamp(), recipientNode));
+            }
+        } else {
+            return enlistInTx(
+                    keyRows,
+                    tx,
+                    (commitPart, keyRows0, txo, groupId, term) -> tableMessagesFactory.readWriteMultiRowReplicaRequest()
+                            .groupId(groupId)
+                            .binaryRows(keyRows0)
+                            .transactionId(txo.id())
+                            .term(term)
+                            .requestType(RequestType.RW_GET_ALL)
+                            .timestamp(clock.now())
+                            .build(),
+                    this::collectMultiRowsResponses);
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public CompletableFuture<Collection<BinaryRow>> getAll(
+            Collection<BinaryRowEx> keyRows,
+            @NotNull HybridTimestamp readTimestamp,
+            @NotNull ClusterNode recipientNode
+    ) {
+        Int2ObjectOpenHashMap<List<BinaryRow>> keyRowsByPartition = mapRowsToPartitions(keyRows);
+
+        CompletableFuture<Object>[] futures = new CompletableFuture[keyRowsByPartition.size()];
+
+        int batchNum = 0;
+
+        for (Int2ObjectOpenHashMap.Entry<List<BinaryRow>> partToRows : keyRowsByPartition.int2ObjectEntrySet()) {
+            ReplicationGroupId partGroupId = partitionMap.get(partToRows.getIntKey()).groupId();
+
+            CompletableFuture<Object> fut = replicaSvc.invoke(recipientNode, tableMessagesFactory.readOnlyMultiRowReplicaRequest()
+                    .groupId(partGroupId)
+                    .binaryRows(partToRows.getValue())
+                    .requestType(RequestType.RO_GET_ALL)
+                    .readTimestamp(clock.now())
+                    .build()
+            );
+
+            futures[batchNum++] = fut;
+        }
+
+        return collectMultiRowsResponses(futures);
     }
 
     /** {@inheritDoc} */
@@ -702,17 +798,20 @@ public class InternalTableImpl implements InternalTable {
     /** {@inheritDoc} */
     @Override
     public Publisher<BinaryRow> scan(int p, @Nullable InternalTransaction tx) {
-        if (p < 0 || p >= partitions) {
-            throw new IllegalArgumentException(
-                    IgniteStringFormatter.format(
-                            "Invalid partition [partition={}, minValue={}, maxValue={}].",
-                            p,
-                            0,
-                            partitions - 1
+        // Check whether proposed tx is read-only. Complete future exceptionally if true.
+        // Attempting to enlist a read-only in a read-write transaction does not corrupt the transaction itself, thus read-write transaction
+        // won't be rolled back automatically - it's up to the user or outer engine.
+        if (tx != null && tx.isReadOnly()) {
+            throw new TransactionException(
+                    new TransactionException(
+                            TX_INSUFFICIENT_READ_WRITE_OPERATION_ERR,
+                            "Failed to enlist read-write operation into read-only transaction txId={" + tx.id() + '}'
                     )
             );
         }
 
+        validatePartitionIndex(p);
+
         final boolean implicit = tx == null;
 
         final InternalTransaction tx0 = implicit ? txManager.begin() : tx;
@@ -723,6 +822,53 @@ public class InternalTableImpl implements InternalTable {
         );
     }
 
+    /** {@inheritDoc} */
+    @Override
+    public Publisher<BinaryRow> scan(
+            int p,
+            @NotNull HybridTimestamp readTimestamp,
+            @NotNull ClusterNode recipientNode
+    ) {
+        validatePartitionIndex(p);
+
+        return new PartitionScanPublisher(
+                (scanId, batchSize) -> {
+                    ReplicationGroupId partGroupId = partitionMap.get(p).groupId();
+
+                    ReadOnlyScanRetrieveBatchReplicaRequest request = tableMessagesFactory.readOnlyScanRetrieveBatchReplicaRequest()
+                            .groupId(partGroupId)
+                            // TODO: IGNITE-17666 Close cursor tx finish.
+                            .transactionId(UUID.randomUUID())
+                            .scanId(scanId)
+                            .batchSize(batchSize)
+                            .readTimestamp(clock.now())
+                            .build();
+
+                    return replicaSvc.invoke(recipientNode, request);
+                },
+                // TODO: IGNITE-17666 Close cursor tx finish.
+                Function.identity());
+    }
+
+    /**
+     * Validates partition index.
+     *
+     * @param p Partition index.
+     * @throws IllegalArgumentException If proposed partition is out of bounds.
+     */
+    private void validatePartitionIndex(int p) {
+        if (p < 0 || p >= partitions) {
+            throw new IllegalArgumentException(
+                    IgniteStringFormatter.format(
+                            "Invalid partition [partition={}, minValue={}, maxValue={}].",
+                            p,
+                            0,
+                            partitions - 1
+                    )
+            );
+        }
+    }
+
     /**
      * Map rows to partitions.
      *
@@ -1057,4 +1203,27 @@ public class InternalTableImpl implements InternalTable {
             srv.shutdown();
         }
     }
+
+    // TODO: IGNITE-17963 Use smarter logic for recipient node evaluation.
+    /**
+     * Evaluated cluster node for read-only request processing.
+     *
+     * @param partId Partition id.
+     * @return Cluster node to evalute read-only request.
+     */
+    protected CompletableFuture<ClusterNode> evaluateReadOnlyRecipientNode(int partId) {
+        RaftGroupService svc = partitionMap.get(partId);
+
+        return svc.refreshAndGetLeaderWithTerm().handle((res, e) -> {
+            if (e != null) {
+                throw withCause(TransactionException::new, REPLICA_UNAVAILABLE_ERR, e);
+            } else {
+                if (res == null || res.getKey() == null) {
+                    throw withCause(TransactionException::new, REPLICA_UNAVAILABLE_ERR, e);
+                } else {
+                    return clusterNodeResolver.apply(res.get1().address());
+                }
+            }
+        });
+    }
 }
diff --git a/modules/table/src/test/java/org/apache/ignite/internal/table/TxAbstractTest.java b/modules/table/src/test/java/org/apache/ignite/internal/table/TxAbstractTest.java
index 08c67303b1..0d9cc2d77b 100644
--- a/modules/table/src/test/java/org/apache/ignite/internal/table/TxAbstractTest.java
+++ b/modules/table/src/test/java/org/apache/ignite/internal/table/TxAbstractTest.java
@@ -1788,4 +1788,124 @@ public abstract class TxAbstractTest extends IgniteAbstractTest {
                     completedFuture(Tuple.create().set("balance1", val1).set("balance2", val2))));
         });
     }
+
+
+    @Test
+    public void testReadOnlyGet() {
+        accounts.recordView().upsert(null, makeValue(1, 100.));
+
+        Transaction readOnlyTx = igniteTransactions.readOnly().begin();
+        assertEquals(100., accounts.recordView().get(readOnlyTx, makeKey(1)).doubleValue("balance"));
+    }
+
+    @Disabled("https://issues.apache.org/jira/browse/IGNITE-17967")
+    @Test
+    public void testReadOnlyGetWriteIntentResolutionUpdate() {
+        accounts.recordView().upsert(null, makeValue(1, 100.));
+
+        // Pending tx
+        Transaction tx = igniteTransactions.begin();
+        accounts.recordView().upsert(tx, makeValue(1, 300.));
+
+        // Update
+        Transaction readOnlyTx = igniteTransactions.readOnly().begin();
+        assertEquals(100., accounts.recordView().get(readOnlyTx, makeKey(1)).doubleValue("balance"));
+
+        // Commit pending tx.
+        tx.commit();
+
+        // Same read-only transaction.
+        assertEquals(100., accounts.recordView().get(readOnlyTx, makeKey(1)).doubleValue("balance"));
+
+        // New read-only transaction.
+        Transaction readOnlyTx2 = igniteTransactions.readOnly().begin();
+        assertEquals(300., accounts.recordView().get(readOnlyTx2, makeKey(1)).doubleValue("balance"));
+    }
+
+    @Disabled("https://issues.apache.org/jira/browse/IGNITE-17967, https://issues.apache.org/jira/browse/IGNITE-17968")
+    @Test
+    public void testReadOnlyGetWriteIntentResolutionRemove() {
+        accounts.recordView().upsert(null, makeValue(1, 100.));
+
+        // Pending tx
+        Transaction tx = igniteTransactions.begin();
+        accounts.recordView().delete(tx, makeKey(1));
+
+        // Remove.
+        Transaction readOnlyTx = igniteTransactions.readOnly().begin();
+        assertEquals(100., accounts.recordView().get(readOnlyTx, makeKey(1)).doubleValue("balance"));
+
+        // Commit pending tx.
+        tx.commit();
+
+        // Same read-only transaction.
+        assertEquals(100., accounts.recordView().get(readOnlyTx, makeKey(1)).doubleValue("balance"));
+
+        // New read-only transaction.
+        Transaction readOnlyTx2 = igniteTransactions.readOnly().begin();
+        assertNull(accounts.recordView().get(readOnlyTx2, makeKey(1)).doubleValue("balance"));
+    }
+
+    @Test
+    public void testReadOnlyGetAll() {
+        accounts.recordView().upsert(null, makeValue(1, 100.));
+        accounts.recordView().upsert(null, makeValue(2, 200.));
+        accounts.recordView().upsert(null, makeValue(3, 300.));
+
+        Transaction readOnlyTx = igniteTransactions.readOnly().begin();
+        Collection<Tuple> retrievedKeys = accounts.recordView().getAll(readOnlyTx, List.of(makeKey(1), makeKey(2)));
+        validateBalance(retrievedKeys, 100., 200.);
+    }
+
+    @Disabled("https://issues.apache.org/jira/browse/IGNITE-17967")
+    // TODO: IGNITE-17968 Remove after fix.
+    @Test
+    public void testReadOnlyPendingWriteIntentSkipped() {
+        accounts.recordView().upsert(null, makeValue(1, 100.));
+        accounts.recordView().upsert(null, makeValue(2, 200.));
+
+        // Pending tx
+        Transaction tx = igniteTransactions.begin();
+        accounts.recordView().upsert(tx, makeValue(2, 300.));
+
+        Transaction readOnlyTx = igniteTransactions.readOnly().begin();
+        Collection<Tuple> retrievedKeys = accounts.recordView().getAll(readOnlyTx, List.of(makeKey(1), makeKey(2)));
+        validateBalance(retrievedKeys, 100., 200.);
+
+        // Commit pending tx.
+        tx.commit();
+
+        Collection<Tuple> retrievedKeys2 = accounts.recordView().getAll(readOnlyTx, List.of(makeKey(1), makeKey(2)));
+        validateBalance(retrievedKeys2, 100., 300.);
+
+        Transaction readOnlyTx2 = igniteTransactions.readOnly().begin();
+        Collection<Tuple> retrievedKeys3 = accounts.recordView().getAll(readOnlyTx2, List.of(makeKey(1), makeKey(2)));
+        validateBalance(retrievedKeys3, 100., 300.);
+    }
+
+    @Disabled("https://issues.apache.org/jira/browse/IGNITE-17967, https://issues.apache.org/jira/browse/IGNITE-17968")
+    @Test
+    public void testReadOnlyPendingWriteIntentSkippedCombined() {
+        accounts.recordView().upsert(null, makeValue(1, 100.));
+        accounts.recordView().upsert(null, makeValue(2, 200.));
+
+        // Pending tx
+        Transaction tx = igniteTransactions.begin();
+        accounts.recordView().delete(tx, makeKey(1));
+        accounts.recordView().upsert(tx, makeValue(2, 300.));
+
+        Transaction readOnlyTx = igniteTransactions.readOnly().begin();
+        Collection<Tuple> retrievedKeys = accounts.recordView().getAll(readOnlyTx, List.of(makeKey(1), makeKey(2)));
+        validateBalance(retrievedKeys, 100., 200.);
+
+        // Commit pending tx.
+        tx.commit();
+
+        Collection<Tuple> retrievedKeys2 = accounts.recordView().getAll(readOnlyTx, List.of(makeKey(1), makeKey(2)));
+        validateBalance(retrievedKeys2, 100., 300.);
+
+        Transaction readOnlyTx2 = igniteTransactions.readOnly().begin();
+        Collection<Tuple> retrievedKeys3 = accounts.recordView().getAll(readOnlyTx2, List.of(makeKey(1), makeKey(2)));
+        validateBalance(retrievedKeys3, 300.);
+    }
 }
diff --git a/modules/table/src/test/java/org/apache/ignite/internal/table/TxLocalTest.java b/modules/table/src/test/java/org/apache/ignite/internal/table/TxLocalTest.java
index c5fa5638f9..cb0bdcab87 100644
--- a/modules/table/src/test/java/org/apache/ignite/internal/table/TxLocalTest.java
+++ b/modules/table/src/test/java/org/apache/ignite/internal/table/TxLocalTest.java
@@ -26,7 +26,7 @@ import static org.mockito.Mockito.when;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.concurrent.CompletableFuture;
-import org.apache.ignite.hlc.HybridClock;
+import org.apache.ignite.internal.hlc.HybridClock;
 import org.apache.ignite.internal.replicator.ReplicaService;
 import org.apache.ignite.internal.replicator.ReplicationGroupId;
 import org.apache.ignite.internal.replicator.listener.ReplicaListener;
diff --git a/modules/table/src/test/java/org/apache/ignite/internal/table/distributed/command/PartitionRaftCommandsSerializationTest.java b/modules/table/src/test/java/org/apache/ignite/internal/table/distributed/command/PartitionRaftCommandsSerializationTest.java
index d828c4c80e..f5435165e7 100644
--- a/modules/table/src/test/java/org/apache/ignite/internal/table/distributed/command/PartitionRaftCommandsSerializationTest.java
+++ b/modules/table/src/test/java/org/apache/ignite/internal/table/distributed/command/PartitionRaftCommandsSerializationTest.java
@@ -33,7 +33,7 @@ import java.util.Locale;
 import java.util.Map;
 import java.util.Objects;
 import java.util.UUID;
-import org.apache.ignite.hlc.HybridClock;
+import org.apache.ignite.internal.hlc.HybridClock;
 import org.apache.ignite.internal.replicator.ReplicationGroupId;
 import org.apache.ignite.internal.schema.BinaryRow;
 import org.apache.ignite.internal.schema.Column;
diff --git a/modules/table/src/test/java/org/apache/ignite/internal/table/distributed/raft/PartitionCommandListenerTest.java b/modules/table/src/test/java/org/apache/ignite/internal/table/distributed/raft/PartitionCommandListenerTest.java
index ee1df2e071..23555da62c 100644
--- a/modules/table/src/test/java/org/apache/ignite/internal/table/distributed/raft/PartitionCommandListenerTest.java
+++ b/modules/table/src/test/java/org/apache/ignite/internal/table/distributed/raft/PartitionCommandListenerTest.java
@@ -39,8 +39,8 @@ import java.util.concurrent.atomic.AtomicLong;
 import java.util.function.BiConsumer;
 import java.util.function.Consumer;
 import java.util.function.Function;
-import org.apache.ignite.hlc.HybridClock;
-import org.apache.ignite.hlc.HybridTimestamp;
+import org.apache.ignite.internal.hlc.HybridClock;
+import org.apache.ignite.internal.hlc.HybridTimestamp;
 import org.apache.ignite.internal.replicator.ReplicaService;
 import org.apache.ignite.internal.schema.BinaryRow;
 import org.apache.ignite.internal.schema.Column;
diff --git a/modules/table/src/test/java/org/apache/ignite/internal/table/distributed/replication/PartitionReplicaListenerTest.java b/modules/table/src/test/java/org/apache/ignite/internal/table/distributed/replication/PartitionReplicaListenerTest.java
index dbf62d6d59..eb1c640790 100644
--- a/modules/table/src/test/java/org/apache/ignite/internal/table/distributed/replication/PartitionReplicaListenerTest.java
+++ b/modules/table/src/test/java/org/apache/ignite/internal/table/distributed/replication/PartitionReplicaListenerTest.java
@@ -33,8 +33,8 @@ import java.util.Objects;
 import java.util.UUID;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ConcurrentHashMap;
-import org.apache.ignite.hlc.HybridClock;
-import org.apache.ignite.hlc.HybridTimestamp;
+import org.apache.ignite.internal.hlc.HybridClock;
+import org.apache.ignite.internal.hlc.HybridTimestamp;
 import org.apache.ignite.internal.replicator.ReplicationGroupId;
 import org.apache.ignite.internal.schema.BinaryRow;
 import org.apache.ignite.internal.schema.Column;
@@ -266,8 +266,7 @@ public class PartitionReplicaListenerTest extends IgniteAbstractTest {
 
         CompletableFuture fut = partitionReplicaListener.invoke(TABLE_MESSAGES_FACTORY.readOnlySingleRowReplicaRequest()
                 .groupId(grpId)
-                .timestamp(clock.now())
-                .transactionId(Timestamp.nextVersion().toUuid())
+                .readTimestamp(clock.now())
                 .binaryRow(testBinaryKey)
                 .requestType(RequestType.RO_GET)
                 .build());
@@ -290,8 +289,7 @@ public class PartitionReplicaListenerTest extends IgniteAbstractTest {
 
         CompletableFuture fut = partitionReplicaListener.invoke(TABLE_MESSAGES_FACTORY.readOnlySingleRowReplicaRequest()
                 .groupId(grpId)
-                .timestamp(clock.now())
-                .transactionId(Timestamp.nextVersion().toUuid())
+                .readTimestamp(clock.now())
                 .binaryRow(testBinaryKey)
                 .requestType(RequestType.RO_GET)
                 .build());
@@ -314,8 +312,7 @@ public class PartitionReplicaListenerTest extends IgniteAbstractTest {
 
         CompletableFuture fut = partitionReplicaListener.invoke(TABLE_MESSAGES_FACTORY.readOnlySingleRowReplicaRequest()
                 .groupId(grpId)
-                .timestamp(clock.now())
-                .transactionId(Timestamp.nextVersion().toUuid())
+                .readTimestamp(clock.now())
                 .binaryRow(testBinaryKey)
                 .requestType(RequestType.RO_GET)
                 .build());
@@ -337,8 +334,7 @@ public class PartitionReplicaListenerTest extends IgniteAbstractTest {
 
         CompletableFuture fut = partitionReplicaListener.invoke(TABLE_MESSAGES_FACTORY.readOnlySingleRowReplicaRequest()
                 .groupId(grpId)
-                .timestamp(clock.now())
-                .transactionId(Timestamp.nextVersion().toUuid())
+                .readTimestamp(clock.now())
                 .binaryRow(testBinaryKey)
                 .requestType(RequestType.RO_GET)
                 .build());
@@ -361,8 +357,7 @@ public class PartitionReplicaListenerTest extends IgniteAbstractTest {
 
         CompletableFuture fut = partitionReplicaListener.invoke(TABLE_MESSAGES_FACTORY.readOnlySingleRowReplicaRequest()
                 .groupId(grpId)
-                .timestamp(clock.now())
-                .transactionId(Timestamp.nextVersion().toUuid())
+                .readTimestamp(clock.now())
                 .binaryRow(testBinaryKey)
                 .requestType(RequestType.RO_GET)
                 .build());
diff --git a/modules/table/src/test/java/org/apache/ignite/internal/table/impl/DummyInternalTableImpl.java b/modules/table/src/test/java/org/apache/ignite/internal/table/impl/DummyInternalTableImpl.java
index f30ac90119..a0c1b15dbf 100644
--- a/modules/table/src/test/java/org/apache/ignite/internal/table/impl/DummyInternalTableImpl.java
+++ b/modules/table/src/test/java/org/apache/ignite/internal/table/impl/DummyInternalTableImpl.java
@@ -30,7 +30,7 @@ import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.atomic.AtomicLong;
 import javax.naming.OperationNotSupportedException;
-import org.apache.ignite.hlc.HybridClock;
+import org.apache.ignite.internal.hlc.HybridClock;
 import org.apache.ignite.internal.replicator.ReplicaService;
 import org.apache.ignite.internal.replicator.ReplicationGroupId;
 import org.apache.ignite.internal.replicator.listener.ReplicaListener;
@@ -135,7 +135,7 @@ public class DummyInternalTableImpl extends InternalTableImpl {
                 mock(MvTableStorage.class),
                 mock(TxStateTableStorage.class),
                 replicaSvc,
-                mock(HybridClock.class)
+                new HybridClock()
         );
         RaftGroupService svc = partitionMap.get(0);
 
@@ -268,4 +268,10 @@ public class DummyInternalTableImpl extends InternalTableImpl {
     public int partition(BinaryRowEx keyRow) {
         return 0;
     }
+
+    /** {@inheritDoc} */
+    @Override
+    public CompletableFuture<ClusterNode> evaluateReadOnlyRecipientNode(int partId) {
+        return CompletableFuture.completedFuture(mock(ClusterNode.class));
+    }
 }
diff --git a/modules/transactions/src/main/java/org/apache/ignite/internal/tx/InternalTransaction.java b/modules/transactions/src/main/java/org/apache/ignite/internal/tx/InternalTransaction.java
index 0baefbbc33..c66ddbc260 100644
--- a/modules/transactions/src/main/java/org/apache/ignite/internal/tx/InternalTransaction.java
+++ b/modules/transactions/src/main/java/org/apache/ignite/internal/tx/InternalTransaction.java
@@ -82,5 +82,6 @@ public interface InternalTransaction extends Transaction {
      *
      * @param resultFuture Operation result future.
      */
+    @Deprecated
     void enlistResultFuture(CompletableFuture<?> resultFuture);
 }
diff --git a/modules/transactions/src/main/java/org/apache/ignite/internal/tx/TxManager.java b/modules/transactions/src/main/java/org/apache/ignite/internal/tx/TxManager.java
index cbacf446d2..96f5cf1ffd 100644
--- a/modules/transactions/src/main/java/org/apache/ignite/internal/tx/TxManager.java
+++ b/modules/transactions/src/main/java/org/apache/ignite/internal/tx/TxManager.java
@@ -21,7 +21,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.UUID;
 import java.util.concurrent.CompletableFuture;
-import org.apache.ignite.hlc.HybridTimestamp;
+import org.apache.ignite.internal.hlc.HybridTimestamp;
 import org.apache.ignite.internal.manager.IgniteComponent;
 import org.apache.ignite.internal.replicator.ReplicationGroupId;
 import org.apache.ignite.lang.IgniteBiTuple;
@@ -34,12 +34,21 @@ import org.jetbrains.annotations.TestOnly;
  */
 public interface TxManager extends IgniteComponent {
     /**
-     * Starts a transaction coordinated by a local node.
+     * Starts a read-write transaction coordinated by a local node.
      *
      * @return The transaction.
      */
     InternalTransaction begin();
 
+    /**
+     * Starts either read-write or read-only transaction, depending on {@code readOnly} parameter value.
+     *
+     * @param readOnly {@code true} in order to start a read-only transaction, {@code false} in order to start read-write one.
+     *      Calling begin with readOnly {@code false} is an equivalent of TxManager#begin().
+     * @return The started transaction.
+     */
+    InternalTransaction begin(boolean readOnly);
+
     /**
      * Returns a transaction state.
      *
diff --git a/modules/transactions/src/main/java/org/apache/ignite/internal/tx/TxMeta.java b/modules/transactions/src/main/java/org/apache/ignite/internal/tx/TxMeta.java
index 450c868885..4953e51217 100644
--- a/modules/transactions/src/main/java/org/apache/ignite/internal/tx/TxMeta.java
+++ b/modules/transactions/src/main/java/org/apache/ignite/internal/tx/TxMeta.java
@@ -21,7 +21,7 @@ import static java.util.Collections.unmodifiableList;
 
 import java.io.Serializable;
 import java.util.List;
-import org.apache.ignite.hlc.HybridTimestamp;
+import org.apache.ignite.internal.hlc.HybridTimestamp;
 import org.apache.ignite.internal.replicator.ReplicationGroupId;
 import org.apache.ignite.internal.tostring.S;
 
diff --git a/modules/transactions/src/main/java/org/apache/ignite/internal/tx/impl/IgniteAbstractTransactionImpl.java b/modules/transactions/src/main/java/org/apache/ignite/internal/tx/impl/IgniteAbstractTransactionImpl.java
new file mode 100644
index 0000000000..0cf02f7fa6
--- /dev/null
+++ b/modules/transactions/src/main/java/org/apache/ignite/internal/tx/impl/IgniteAbstractTransactionImpl.java
@@ -0,0 +1,107 @@
+/*
+ * 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.tx.impl;
+
+import static org.apache.ignite.internal.util.ExceptionUtils.withCause;
+import static org.apache.ignite.lang.ErrorGroups.Transactions.TX_COMMIT_ERR;
+import static org.apache.ignite.lang.ErrorGroups.Transactions.TX_ROLLBACK_ERR;
+
+import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
+import org.apache.ignite.internal.tx.InternalTransaction;
+import org.apache.ignite.internal.tx.TxManager;
+import org.apache.ignite.internal.tx.TxState;
+import org.apache.ignite.tx.TransactionException;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * An abstract implementation of an ignite internal transaction.
+ */
+public abstract class IgniteAbstractTransactionImpl implements InternalTransaction {
+    /** The id. */
+    private final UUID id;
+
+    /** The transaction manager. */
+    protected final TxManager txManager;
+
+    /**
+     * The constructor.
+     *
+     * @param txManager The tx manager.
+     * @param id The id.
+     */
+    public IgniteAbstractTransactionImpl(TxManager txManager, @NotNull UUID id) {
+        this.txManager = txManager;
+        this.id = id;
+    }
+
+    /** {@inheritDoc} */
+    @NotNull
+    @Override
+    public UUID id() {
+        return id;
+    }
+
+    /** {@inheritDoc} */
+    @Nullable
+    @Override
+    public TxState state() {
+        return txManager.state(id);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void commit() throws TransactionException {
+        try {
+            commitAsync().get();
+        } catch (Exception e) {
+            throw withCause(TransactionException::new, TX_COMMIT_ERR, e);
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public CompletableFuture<Void> commitAsync() {
+        return finish(true);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void rollback() throws TransactionException {
+        try {
+            rollbackAsync().get();
+        } catch (Exception e) {
+            throw withCause(TransactionException::new, TX_ROLLBACK_ERR, e);
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public CompletableFuture<Void> rollbackAsync() {
+        return finish(false);
+    }
+
+    /**
+     * Finishes a transaction.
+     *
+     * @param commit {@code true} to commit, false to rollback.
+     * @return The future.
+     */
+    protected abstract CompletableFuture<Void> finish(boolean commit);
+}
diff --git a/modules/transactions/src/main/java/org/apache/ignite/internal/tx/impl/IgniteTransactionsImpl.java b/modules/transactions/src/main/java/org/apache/ignite/internal/tx/impl/IgniteTransactionsImpl.java
index 81384a09df..1e988363d0 100644
--- a/modules/transactions/src/main/java/org/apache/ignite/internal/tx/impl/IgniteTransactionsImpl.java
+++ b/modules/transactions/src/main/java/org/apache/ignite/internal/tx/impl/IgniteTransactionsImpl.java
@@ -28,6 +28,8 @@ import org.apache.ignite.tx.Transaction;
 public class IgniteTransactionsImpl implements IgniteTransactions {
     private final TxManager txManager;
 
+    private boolean readOnly = false;
+
     /**
      * The constructor.
      *
@@ -37,6 +39,17 @@ public class IgniteTransactionsImpl implements IgniteTransactions {
         this.txManager = txManager;
     }
 
+    /**
+     * The constructor.
+     *
+     * @param txManager The manager.
+     * @param readOnly Read-only
+     */
+    public IgniteTransactionsImpl(TxManager txManager, boolean readOnly) {
+        this(txManager);
+        this.readOnly = readOnly;
+    }
+
     /** {@inheritDoc} */
     @Override
     public IgniteTransactions withTimeout(long timeout) {
@@ -44,15 +57,21 @@ public class IgniteTransactionsImpl implements IgniteTransactions {
         throw new UnsupportedOperationException();
     }
 
+    /** {@inheritDoc} */
+    @Override
+    public IgniteTransactions readOnly() {
+        return new IgniteTransactionsImpl(txManager, true);
+    }
+
     /** {@inheritDoc} */
     @Override
     public Transaction begin() {
-        return txManager.begin();
+        return txManager.begin(readOnly);
     }
 
     /** {@inheritDoc} */
     @Override
     public CompletableFuture<Transaction> beginAsync() {
-        return CompletableFuture.completedFuture(txManager.begin());
+        return CompletableFuture.completedFuture(txManager.begin(readOnly));
     }
 }
diff --git a/modules/transactions/src/main/java/org/apache/ignite/internal/tx/impl/ReadOnlyTransactionImpl.java b/modules/transactions/src/main/java/org/apache/ignite/internal/tx/impl/ReadOnlyTransactionImpl.java
new file mode 100644
index 0000000000..7d8308821e
--- /dev/null
+++ b/modules/transactions/src/main/java/org/apache/ignite/internal/tx/impl/ReadOnlyTransactionImpl.java
@@ -0,0 +1,101 @@
+/*
+ * 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.tx.impl;
+
+import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
+import org.apache.ignite.internal.hlc.HybridTimestamp;
+import org.apache.ignite.internal.replicator.ReplicationGroupId;
+import org.apache.ignite.internal.tx.TxManager;
+import org.apache.ignite.lang.IgniteBiTuple;
+import org.apache.ignite.network.ClusterNode;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * The read-only implementation of an internal transaction.
+ */
+public class ReadOnlyTransactionImpl extends IgniteAbstractTransactionImpl {
+    /** The read timestamp. */
+    private final HybridTimestamp readTimestamp;
+
+    /**
+     * The constructor.
+     *
+     * @param txManager The tx manager.
+     * @param id The id.
+     * @param readTimestamp The read timestamp.
+     */
+    public ReadOnlyTransactionImpl(
+            TxManager txManager,
+            @NotNull UUID id,
+            HybridTimestamp readTimestamp
+    ) {
+        super(txManager, id);
+        this.readTimestamp = readTimestamp;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean isReadOnly() {
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public HybridTimestamp readTimestamp() {
+        return readTimestamp;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public IgniteBiTuple<ClusterNode, Long> enlist(ReplicationGroupId replicationGroupId, IgniteBiTuple<ClusterNode, Long> nodeAndTerm) {
+        // TODO: IGNITE-17666 Close cursor tx finish.
+        return null;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public IgniteBiTuple<ClusterNode, Long> enlistedNodeAndTerm(ReplicationGroupId replicationGroupId) {
+        return null;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean assignCommitPartition(ReplicationGroupId replicationGroupId) {
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public ReplicationGroupId commitPartition() {
+        return null;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void enlistResultFuture(CompletableFuture<?> resultFuture) {
+        // No-op.
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected CompletableFuture<Void> finish(boolean commit) {
+        // TODO: IGNITE-17666 Close cursor tx finish.
+        return CompletableFuture.completedFuture(null);
+    }
+}
diff --git a/modules/transactions/src/main/java/org/apache/ignite/internal/tx/impl/TransactionImpl.java b/modules/transactions/src/main/java/org/apache/ignite/internal/tx/impl/ReadWriteTransactionImpl.java
similarity index 73%
rename from modules/transactions/src/main/java/org/apache/ignite/internal/tx/impl/TransactionImpl.java
rename to modules/transactions/src/main/java/org/apache/ignite/internal/tx/impl/ReadWriteTransactionImpl.java
index e740c8428d..c7652adb88 100644
--- a/modules/transactions/src/main/java/org/apache/ignite/internal/tx/impl/TransactionImpl.java
+++ b/modules/transactions/src/main/java/org/apache/ignite/internal/tx/impl/ReadWriteTransactionImpl.java
@@ -17,10 +17,6 @@
 
 package org.apache.ignite.internal.tx.impl;
 
-import static org.apache.ignite.internal.util.ExceptionUtils.withCause;
-import static org.apache.ignite.lang.ErrorGroups.Transactions.TX_COMMIT_ERR;
-import static org.apache.ignite.lang.ErrorGroups.Transactions.TX_ROLLBACK_ERR;
-
 import java.util.ArrayList;
 import java.util.LinkedHashMap;
 import java.util.List;
@@ -30,32 +26,22 @@ import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.atomic.AtomicReference;
+import org.apache.ignite.internal.hlc.HybridTimestamp;
 import org.apache.ignite.internal.logger.IgniteLogger;
 import org.apache.ignite.internal.logger.Loggers;
 import org.apache.ignite.internal.replicator.ReplicationGroupId;
 import org.apache.ignite.internal.tx.InternalTransaction;
 import org.apache.ignite.internal.tx.TxManager;
-import org.apache.ignite.internal.tx.TxState;
 import org.apache.ignite.lang.IgniteBiTuple;
 import org.apache.ignite.network.ClusterNode;
-import org.apache.ignite.tx.TransactionException;
 import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
 
 /**
- * The implementation of an internal transaction.
- *
- * <p>Delegates state management to tx manager.
+ * The read-write implementation of an internal transaction.
  */
-public class TransactionImpl implements InternalTransaction {
+public class ReadWriteTransactionImpl extends IgniteAbstractTransactionImpl {
     private static final IgniteLogger LOG = Loggers.forClass(InternalTransaction.class);
 
-    /** The id. */
-    private final UUID id;
-
-    /** The transaction manager. */
-    private final TxManager txManager;
-
     /** Enlisted replication groups: replication group id -> (primary replica node, raft term). */
     private final Map<ReplicationGroupId, IgniteBiTuple<ClusterNode, Long>> enlisted = new ConcurrentHashMap<>();
 
@@ -71,9 +57,8 @@ public class TransactionImpl implements InternalTransaction {
      * @param txManager The tx manager.
      * @param id The id.
      */
-    public TransactionImpl(TxManager txManager, @NotNull UUID id) {
-        this.txManager = txManager;
-        this.id = id;
+    public ReadWriteTransactionImpl(TxManager txManager, @NotNull UUID id) {
+        super(txManager, id);
     }
 
     /** {@inheritDoc} */
@@ -88,26 +73,12 @@ public class TransactionImpl implements InternalTransaction {
         return commitPartitionRef.get();
     }
 
-    /** {@inheritDoc} */
-    @NotNull
-    @Override
-    public UUID id() {
-        return id;
-    }
-
     /** {@inheritDoc} */
     @Override
     public IgniteBiTuple<ClusterNode, Long> enlistedNodeAndTerm(ReplicationGroupId partGroupId) {
         return enlisted.get(partGroupId);
     }
 
-    /** {@inheritDoc} */
-    @Nullable
-    @Override
-    public TxState state() {
-        return txManager.state(id);
-    }
-
     /** {@inheritDoc} */
     @Override
     public IgniteBiTuple<ClusterNode, Long> enlist(ReplicationGroupId replicationGroupId, IgniteBiTuple<ClusterNode, Long> nodeAndTerm) {
@@ -118,43 +89,7 @@ public class TransactionImpl implements InternalTransaction {
 
     /** {@inheritDoc} */
     @Override
-    public void commit() throws TransactionException {
-        try {
-            commitAsync().get();
-        } catch (Exception e) {
-            throw withCause(TransactionException::new, TX_COMMIT_ERR, e);
-        }
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public CompletableFuture<Void> commitAsync() {
-        return finish(true);
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public void rollback() throws TransactionException {
-        try {
-            rollbackAsync().get();
-        } catch (Exception e) {
-            throw withCause(TransactionException::new, TX_ROLLBACK_ERR, e);
-        }
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public CompletableFuture<Void> rollbackAsync() {
-        return finish(false);
-    }
-
-    /**
-     * Finishes a transaction.
-     *
-     * @param commit {@code true} to commit, false to rollback.
-     * @return The future.
-     */
-    private CompletableFuture<Void> finish(boolean commit) {
+    protected CompletableFuture<Void> finish(boolean commit) {
         // TODO: https://issues.apache.org/jira/browse/IGNITE-17688 Add proper exception handling.
         return CompletableFuture
                 .allOf(enlistedResults.toArray(new CompletableFuture[0]))
@@ -181,8 +116,8 @@ public class TransactionImpl implements InternalTransaction {
                                 ClusterNode recipientNode = enlisted.get(commitPart).get1();
                                 Long term = enlisted.get(commitPart).get2();
 
-                                LOG.debug("Finish [recipientNode={}, term={} commit={}, txId={}, groups={} commitPart={}",
-                                        recipientNode, term, commit, id, groups, commitPart);
+                                LOG.debug("Finish [recipientNode={}, term={} commit={}, txId={}, groups={}",
+                                        recipientNode, term, commit, id(), groups);
 
                                 return txManager.finish(
                                         commitPart,
@@ -190,7 +125,7 @@ public class TransactionImpl implements InternalTransaction {
                                         term,
                                         commit,
                                         groups,
-                                        id
+                                        id()
                                 );
                             } else {
                                 return CompletableFuture.completedFuture(null);
@@ -204,4 +139,16 @@ public class TransactionImpl implements InternalTransaction {
     public void enlistResultFuture(CompletableFuture<?> resultFuture) {
         enlistedResults.add(resultFuture);
     }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean isReadOnly() {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public HybridTimestamp readTimestamp() {
+        return null;
+    }
 }
diff --git a/modules/transactions/src/main/java/org/apache/ignite/internal/tx/impl/TxManagerImpl.java b/modules/transactions/src/main/java/org/apache/ignite/internal/tx/impl/TxManagerImpl.java
index 8e187438de..9179fc35c8 100644
--- a/modules/transactions/src/main/java/org/apache/ignite/internal/tx/impl/TxManagerImpl.java
+++ b/modules/transactions/src/main/java/org/apache/ignite/internal/tx/impl/TxManagerImpl.java
@@ -24,8 +24,8 @@ import java.util.Map;
 import java.util.UUID;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ConcurrentHashMap;
-import org.apache.ignite.hlc.HybridClock;
-import org.apache.ignite.hlc.HybridTimestamp;
+import org.apache.ignite.internal.hlc.HybridClock;
+import org.apache.ignite.internal.hlc.HybridTimestamp;
 import org.apache.ignite.internal.replicator.ReplicaService;
 import org.apache.ignite.internal.replicator.ReplicationGroupId;
 import org.apache.ignite.internal.tx.InternalTransaction;
@@ -77,9 +77,15 @@ public class TxManagerImpl implements TxManager {
     /** {@inheritDoc} */
     @Override
     public InternalTransaction begin() {
+        return begin(false);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public InternalTransaction begin(boolean readOnly) {
         UUID txId = Timestamp.nextVersion().toUuid();
 
-        return new TransactionImpl(this, txId);
+        return readOnly ? new ReadOnlyTransactionImpl(this, txId, clock.now()) : new ReadWriteTransactionImpl(this, txId);
     }
 
     /** {@inheritDoc} */
diff --git a/modules/transactions/src/main/java/org/apache/ignite/internal/tx/message/TxCleanupReplicaRequest.java b/modules/transactions/src/main/java/org/apache/ignite/internal/tx/message/TxCleanupReplicaRequest.java
index 8e63947127..1b8a23162a 100644
--- a/modules/transactions/src/main/java/org/apache/ignite/internal/tx/message/TxCleanupReplicaRequest.java
+++ b/modules/transactions/src/main/java/org/apache/ignite/internal/tx/message/TxCleanupReplicaRequest.java
@@ -18,7 +18,7 @@
 package org.apache.ignite.internal.tx.message;
 
 import java.util.UUID;
-import org.apache.ignite.hlc.HybridTimestamp;
+import org.apache.ignite.internal.hlc.HybridTimestamp;
 import org.apache.ignite.internal.replicator.message.ReplicaRequest;
 import org.apache.ignite.internal.replicator.message.TimestampAware;
 import org.apache.ignite.network.annotations.Marshallable;
diff --git a/modules/transactions/src/main/java/org/apache/ignite/internal/tx/message/TxFinishReplicaRequest.java b/modules/transactions/src/main/java/org/apache/ignite/internal/tx/message/TxFinishReplicaRequest.java
index c1bcee3451..4a6f91f669 100644
--- a/modules/transactions/src/main/java/org/apache/ignite/internal/tx/message/TxFinishReplicaRequest.java
+++ b/modules/transactions/src/main/java/org/apache/ignite/internal/tx/message/TxFinishReplicaRequest.java
@@ -20,7 +20,7 @@ package org.apache.ignite.internal.tx.message;
 import java.util.List;
 import java.util.Map;
 import java.util.UUID;
-import org.apache.ignite.hlc.HybridTimestamp;
+import org.apache.ignite.internal.hlc.HybridTimestamp;
 import org.apache.ignite.internal.replicator.ReplicationGroupId;
 import org.apache.ignite.internal.replicator.message.ReplicaRequest;
 import org.apache.ignite.internal.replicator.message.TimestampAware;
diff --git a/modules/transactions/src/main/java/org/apache/ignite/internal/tx/message/TxStateReplicaRequest.java b/modules/transactions/src/main/java/org/apache/ignite/internal/tx/message/TxStateReplicaRequest.java
index 43a8466cb3..983b9133e9 100644
--- a/modules/transactions/src/main/java/org/apache/ignite/internal/tx/message/TxStateReplicaRequest.java
+++ b/modules/transactions/src/main/java/org/apache/ignite/internal/tx/message/TxStateReplicaRequest.java
@@ -18,7 +18,7 @@
 package org.apache.ignite.internal.tx.message;
 
 import java.util.UUID;
-import org.apache.ignite.hlc.HybridTimestamp;
+import org.apache.ignite.internal.hlc.HybridTimestamp;
 import org.apache.ignite.internal.replicator.message.ReplicaRequest;
 import org.apache.ignite.network.annotations.Marshallable;
 import org.apache.ignite.network.annotations.Transferable;
diff --git a/modules/transactions/src/test/java/org/apache/ignite/internal/tx/TxManagerTest.java b/modules/transactions/src/test/java/org/apache/ignite/internal/tx/TxManagerTest.java
index 907335cbd5..5a6ffa759f 100644
--- a/modules/transactions/src/test/java/org/apache/ignite/internal/tx/TxManagerTest.java
+++ b/modules/transactions/src/test/java/org/apache/ignite/internal/tx/TxManagerTest.java
@@ -25,7 +25,7 @@ import static org.mockito.Answers.RETURNS_DEEP_STUBS;
 
 import java.util.Objects;
 import java.util.UUID;
-import org.apache.ignite.hlc.HybridClock;
+import org.apache.ignite.internal.hlc.HybridClock;
 import org.apache.ignite.internal.replicator.ReplicaService;
 import org.apache.ignite.internal.replicator.ReplicationGroupId;
 import org.apache.ignite.internal.testframework.IgniteAbstractTest;
diff --git a/modules/transactions/src/test/java/org/apache/ignite/internal/tx/storage/state/TxStateStorageAbstractTest.java b/modules/transactions/src/test/java/org/apache/ignite/internal/tx/storage/state/TxStateStorageAbstractTest.java
index 2361df443d..2c0b393934 100644
--- a/modules/transactions/src/test/java/org/apache/ignite/internal/tx/storage/state/TxStateStorageAbstractTest.java
+++ b/modules/transactions/src/test/java/org/apache/ignite/internal/tx/storage/state/TxStateStorageAbstractTest.java
@@ -32,7 +32,7 @@ import java.util.Map;
 import java.util.Objects;
 import java.util.UUID;
 import java.util.stream.IntStream;
-import org.apache.ignite.hlc.HybridTimestamp;
+import org.apache.ignite.internal.hlc.HybridTimestamp;
 import org.apache.ignite.internal.replicator.ReplicationGroupId;
 import org.apache.ignite.internal.testframework.WorkDirectory;
 import org.apache.ignite.internal.testframework.WorkDirectoryExtension;