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

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


The following commit(s) were added to refs/heads/main by this push:
     new c6761344e6 IGNITE-17535 Implemented a hash index B+Tree (#1021)
c6761344e6 is described below

commit c6761344e635b7c7798a2fd3bcb9316d932936be
Author: Kirill Tkalenko <tkalkir...@yandex.ru>
AuthorDate: Wed Aug 31 13:18:48 2022 +0300

    IGNITE-17535 Implemented a hash index B+Tree (#1021)
---
 .../pagememory/datastructure/DataStructure.java    |   4 +-
 .../pagememory/freelist/AbstractFreeList.java      |   2 +-
 .../internal/pagememory/freelist/PagesList.java    |  10 +-
 .../internal/pagememory/io/AbstractDataPageIo.java | 114 ++++---------
 .../pagememory/persistence/PageStoreWriter.java    |   1 -
 .../replacement/DelayedDirtyPageWrite.java         |   1 -
 .../persistence/store/AbstractFilePageStoreIo.java |   2 +-
 .../internal/pagememory/util/PageIdUtils.java      |  70 ++++----
 .../pagememory/util}/PartitionlessLinks.java       |  17 +-
 .../pagememory/freelist/TestDataPageIo.java        |   6 +-
 .../internal/pagememory/util/PageIdUtilsTest.java  |   4 +-
 .../pagememory/index/IndexPageIoModule.java        |  12 +-
 .../storage/pagememory/index/IndexPageTypes.java   |  44 +++++
 .../pagememory/index/freelist/IndexColumns.java    | 120 +++++++++++++
 .../index/freelist/IndexColumnsFreeList.java       |  77 +++++++++
 .../ReadIndexColumnsValue.java}                    |  32 ++--
 .../index/freelist/io/IndexColumnsDataIo.java      |  83 +++++++++
 .../pagememory/index/hash/HashIndexRow.java        |  61 +++++++
 .../pagememory/index/hash/HashIndexRowKey.java     |  55 ++++++
 .../IndexMetaTree.java => hash/HashIndexTree.java} |  66 ++++----
 .../hash/InsertHashIndexRowInvokeClosure.java      |  83 +++++++++
 .../hash/RemoveHashIndexRowInvokeClosure.java      |  99 +++++++++++
 .../index/hash/io/HashIndexTreeInnerIo.java        |  61 +++++++
 .../pagememory/index/hash/io/HashIndexTreeIo.java  | 188 +++++++++++++++++++++
 .../index/hash/io/HashIndexTreeLeafIo.java         |  61 +++++++
 .../io/HashIndexTreeMetaIo.java}                   |  18 +-
 .../pagememory/index/meta/IndexMetaTree.java       |  16 +-
 .../pagememory/index/meta/io/IndexMetaInnerIo.java |   6 +-
 .../pagememory/index/meta/io/IndexMetaIo.java      |   3 +-
 .../pagememory/index/meta/io/IndexMetaLeafIo.java  |   6 +-
 .../index/meta/io/IndexMetaTreeMetaIo.java         |   6 +-
 .../mv/AbstractPageMemoryMvPartitionStorage.java   |   8 +-
 .../storage/pagememory/mv/ReadRowVersion.java      |   2 +-
 .../internal/storage/pagememory/mv/RowVersion.java |   5 +-
 .../storage/pagememory/mv/RowVersionFreeList.java  |   3 -
 .../pagememory/mv/ScanVersionChainByTimestamp.java |   5 +-
 .../storage/pagememory/mv/VersionChain.java        |   4 +-
 .../storage/pagememory/mv/io/RowVersionDataIo.java |  35 ++--
 .../storage/pagememory/mv/io/VersionChainIo.java   |   6 +-
 39 files changed, 1125 insertions(+), 271 deletions(-)

diff --git 
a/modules/page-memory/src/main/java/org/apache/ignite/internal/pagememory/datastructure/DataStructure.java
 
b/modules/page-memory/src/main/java/org/apache/ignite/internal/pagememory/datastructure/DataStructure.java
index 355ec47a09..16d0559ff7 100644
--- 
a/modules/page-memory/src/main/java/org/apache/ignite/internal/pagememory/datastructure/DataStructure.java
+++ 
b/modules/page-memory/src/main/java/org/apache/ignite/internal/pagememory/datastructure/DataStructure.java
@@ -19,7 +19,7 @@ package org.apache.ignite.internal.pagememory.datastructure;
 
 import static org.apache.ignite.internal.pagememory.PageIdAllocator.FLAG_DATA;
 import static 
org.apache.ignite.internal.pagememory.PageIdAllocator.MAX_PARTITION_ID;
-import static 
org.apache.ignite.internal.pagememory.util.PageIdUtils.MAX_ITEMID_NUM;
+import static 
org.apache.ignite.internal.pagememory.util.PageIdUtils.MAX_ITEM_ID_NUM;
 import static org.apache.ignite.internal.pagememory.util.PageIdUtils.flag;
 import static org.apache.ignite.internal.pagememory.util.PageIdUtils.itemId;
 import static 
org.apache.ignite.internal.pagememory.util.PageIdUtils.partitionId;
@@ -453,7 +453,7 @@ public abstract class DataStructure {
             recycled = PageIdUtils.rotatePageId(pageId);
         }
 
-        assert itemId(recycled) > 0 && itemId(recycled) <= MAX_ITEMID_NUM : 
IgniteUtils.hexLong(recycled);
+        assert itemId(recycled) > 0 && itemId(recycled) <= MAX_ITEM_ID_NUM : 
IgniteUtils.hexLong(recycled);
 
         PageIo.setPageId(pageAddr, recycled);
 
diff --git 
a/modules/page-memory/src/main/java/org/apache/ignite/internal/pagememory/freelist/AbstractFreeList.java
 
b/modules/page-memory/src/main/java/org/apache/ignite/internal/pagememory/freelist/AbstractFreeList.java
index 287caa5ff7..9c104cd9a0 100644
--- 
a/modules/page-memory/src/main/java/org/apache/ignite/internal/pagememory/freelist/AbstractFreeList.java
+++ 
b/modules/page-memory/src/main/java/org/apache/ignite/internal/pagememory/freelist/AbstractFreeList.java
@@ -81,7 +81,7 @@ public abstract class AbstractFreeList<T extends Storable> 
extends PagesList imp
     private final @Nullable AtomicLong pageListCacheLimit;
 
     /** Page eviction tracker. */
-    private final PageEvictionTracker evictionTracker;
+    protected final PageEvictionTracker evictionTracker;
 
     private final PageHandler<T, Boolean> updateRow = new UpdateRowHandler();
 
diff --git 
a/modules/page-memory/src/main/java/org/apache/ignite/internal/pagememory/freelist/PagesList.java
 
b/modules/page-memory/src/main/java/org/apache/ignite/internal/pagememory/freelist/PagesList.java
index 11b54485d3..724396b9f9 100644
--- 
a/modules/page-memory/src/main/java/org/apache/ignite/internal/pagememory/freelist/PagesList.java
+++ 
b/modules/page-memory/src/main/java/org/apache/ignite/internal/pagememory/freelist/PagesList.java
@@ -25,8 +25,8 @@ import static 
org.apache.ignite.internal.pagememory.PageIdAllocator.FLAG_DATA;
 import static 
org.apache.ignite.internal.pagememory.freelist.io.PagesListNodeIo.T_PAGE_LIST_NODE;
 import static org.apache.ignite.internal.pagememory.io.PageIo.getPageId;
 import static org.apache.ignite.internal.pagememory.io.PageIo.getType;
-import static 
org.apache.ignite.internal.pagememory.util.PageIdUtils.MAX_ITEMID_NUM;
-import static 
org.apache.ignite.internal.pagememory.util.PageIdUtils.changeType;
+import static 
org.apache.ignite.internal.pagememory.util.PageIdUtils.MAX_ITEM_ID_NUM;
+import static 
org.apache.ignite.internal.pagememory.util.PageIdUtils.changeFlag;
 import static org.apache.ignite.internal.pagememory.util.PageIdUtils.itemId;
 import static org.apache.ignite.internal.pagememory.util.PageIdUtils.pageId;
 import static 
org.apache.ignite.internal.pagememory.util.PageIdUtils.partitionId;
@@ -988,7 +988,7 @@ public abstract class PagesList extends DataStructure {
             assert dataIo.isEmpty(dataAddr); // We can put only empty data 
pages to reuse bucket.
 
             // Change page type to index and add it as next node page to this 
list.
-            long newDataId = changeType(dataId, FLAG_AUX);
+            long newDataId = changeFlag(dataId, FLAG_AUX);
 
             setupNextPage(io, pageId, pageAddr, newDataId, dataAddr);
 
@@ -1065,7 +1065,7 @@ public abstract class PagesList extends DataStructure {
 
         try {
             while ((nextId = bag.pollFreePage()) != 0L) {
-                assert itemId(nextId) > 0 && itemId(nextId) <= MAX_ITEMID_NUM 
: hexLong(nextId);
+                assert itemId(nextId) > 0 && itemId(nextId) <= MAX_ITEM_ID_NUM 
: hexLong(nextId);
 
                 int idx = io.addPage(prevAddr, nextId, pageSize());
 
@@ -1266,7 +1266,7 @@ public abstract class PagesList extends DataStructure {
 
                         dirty = true;
 
-                        if (isReuseBucket(bucket) && !(itemId(pageId) > 0 && 
itemId(pageId) <= MAX_ITEMID_NUM)) {
+                        if (isReuseBucket(bucket) && !(itemId(pageId) > 0 && 
itemId(pageId) <= MAX_ITEM_ID_NUM)) {
                             throw corruptedFreeListException("Incorrectly 
recycled pageId in reuse bucket: " + hexLong(pageId), pageId);
                         }
 
diff --git 
a/modules/page-memory/src/main/java/org/apache/ignite/internal/pagememory/io/AbstractDataPageIo.java
 
b/modules/page-memory/src/main/java/org/apache/ignite/internal/pagememory/io/AbstractDataPageIo.java
index c63fac03e0..ac7b967b7f 100644
--- 
a/modules/page-memory/src/main/java/org/apache/ignite/internal/pagememory/io/AbstractDataPageIo.java
+++ 
b/modules/page-memory/src/main/java/org/apache/ignite/internal/pagememory/io/AbstractDataPageIo.java
@@ -1235,99 +1235,32 @@ public abstract class AbstractDataPageIo<T extends 
Storable> extends PageIo {
     ) throws IgniteInternalCheckedException {
         assertPageType(pageAddr);
 
-        return addRowFragment(pageMem, pageId, pageAddr, written, rowSize, 
row.link(), row, null, pageSize);
-    }
-
-    /**
-     * Adds this payload as a fragment to this data page.
-     *
-     * @param pageId Page ID to use to construct a link.
-     * @param pageAddr Page address.
-     * @param payload Payload bytes.
-     * @param lastLink Link to the previous written fragment (link to the 
tail).
-     * @param pageSize Page size.
-     * @throws IgniteInternalCheckedException If failed.
-     */
-    public void addRowFragment(
-            long pageId,
-            long pageAddr,
-            byte[] payload,
-            long lastLink,
-            int pageSize
-    ) throws IgniteInternalCheckedException {
-        assertPageType(pageAddr);
+        assert row != null;
 
-        addRowFragment(null, pageId, pageAddr, 0, 0, lastLink, null, payload, 
pageSize);
-    }
-
-    /**
-     * Adds maximum possible fragment of the given row to this data page and 
sets respective link to the row.
-     *
-     * @param pageMem Page memory.
-     * @param pageId Page ID to use to construct a link.
-     * @param pageAddr Page address.
-     * @param written Number of bytes of row size that was already written.
-     * @param rowSize Row size.
-     * @param lastLink Link to the previous written fragment (link to the 
tail).
-     * @param row Row.
-     * @param payload Payload bytes.
-     * @param pageSize Page size.
-     * @return Written payload size.
-     * @throws IgniteInternalCheckedException If failed.
-     */
-    private int addRowFragment(
-            PageMemory pageMem,
-            long pageId,
-            long pageAddr,
-            int written,
-            int rowSize,
-            long lastLink,
-            T row,
-            byte[] payload,
-            int pageSize
-    ) throws IgniteInternalCheckedException {
-        assert payload == null ^ row == null;
+        long lastLink = row.link();
 
         int directCnt = getDirectCount(pageAddr);
         int indirectCnt = getIndirectCount(pageAddr);
 
-        int payloadSize = payload != null ? payload.length :
-                Math.min(rowSize - written, getFreeSpace(pageAddr));
+        int payloadSize = Math.min(rowSize - written, getFreeSpace(pageAddr));
 
-        if (row != null) {
-            int remain = rowSize - written - payloadSize;
-            int hdrSize = row.headerSize();
-
-            // We need page header (i.e. MVCC info) is located entirely on the 
very first page in chain.
-            // So we force moving it to the next page if it could not fit 
entirely on this page.
-            if (remain > 0 && remain < hdrSize) {
-                payloadSize -= hdrSize - remain;
-            }
-        }
+        assert payloadSize >= row.headerSize() || written >= row.headerSize();
 
         int fullEntrySize = getPageEntrySize(payloadSize, SHOW_PAYLOAD_LEN | 
SHOW_LINK | SHOW_ITEM);
         int dataOff = getDataOffsetForWrite(pageAddr, fullEntrySize, 
directCnt, indirectCnt, pageSize);
 
-        if (payload == null) {
-            ByteBuffer buf = pageMem.pageBuffer(pageAddr);
-
-            buf.position(dataOff);
+        ByteBuffer buf = pageMem.pageBuffer(pageAddr);
 
-            short p = (short) (payloadSize | FRAGMENTED_FLAG);
+        buf.position(dataOff);
 
-            buf.putShort(p);
-            buf.putLong(lastLink);
+        short fragmentSize = (short) (payloadSize | FRAGMENTED_FLAG);
 
-            int rowOff = rowSize - written - payloadSize;
+        buf.putShort(fragmentSize);
+        buf.putLong(lastLink);
 
-            writeFragmentData(row, buf, rowOff, payloadSize);
-        } else {
-            PageUtils.putShort(pageAddr, dataOff, (short) (payloadSize | 
FRAGMENTED_FLAG));
+        int rowOff = rowSize - written - payloadSize;
 
-            PageUtils.putLong(pageAddr, dataOff + 2, lastLink);
-
-            PageUtils.putBytes(pageAddr, dataOff + 10, payload);
-        }
+        writeFragmentData(row, buf, rowOff, payloadSize);
 
         int itemId = addItem(pageAddr, fullEntrySize, directCnt, indirectCnt, 
dataOff, pageSize);
 
@@ -1353,18 +1286,39 @@ public abstract class AbstractDataPageIo<T extends 
Storable> extends PageIo {
      * Writes row data fragment.
      *
      * @param row Row.
-     * @param buf Byte buffer.
+     * @param pageBuf Byte buffer.
      * @param rowOff Offset in row data bytes.
      * @param payloadSize Data length that should be written in a fragment.
      * @throws IgniteInternalCheckedException If failed.
      */
     protected abstract void writeFragmentData(
             final T row,
-            final ByteBuffer buf,
+            final ByteBuffer pageBuf,
             final int rowOff,
             final int payloadSize
     ) throws IgniteInternalCheckedException;
 
+    /**
+     * Writes a content of a byte buffer into a page.
+     *
+     * @param pageBuffer Direct page buffer.
+     * @param valueBuffer Byte buffer with value bytes.
+     * @param offset Offset within the value buffer.
+     * @param payloadSize Number of bytes to write.
+     */
+    protected void putValueBufferIntoPage(ByteBuffer pageBuffer, ByteBuffer 
valueBuffer, int offset, int payloadSize) {
+        int oldPosition = valueBuffer.position();
+        int oldLimit = valueBuffer.limit();
+
+        valueBuffer.position(offset);
+        valueBuffer.limit(offset + payloadSize);
+
+        pageBuffer.put(valueBuffer);
+
+        valueBuffer.position(oldPosition);
+        valueBuffer.limit(oldLimit);
+    }
+
     /**
      * Inserts an item.
      *
diff --git 
a/modules/page-memory/src/main/java/org/apache/ignite/internal/pagememory/persistence/PageStoreWriter.java
 
b/modules/page-memory/src/main/java/org/apache/ignite/internal/pagememory/persistence/PageStoreWriter.java
index 8aeb212f8c..519ce986f7 100644
--- 
a/modules/page-memory/src/main/java/org/apache/ignite/internal/pagememory/persistence/PageStoreWriter.java
+++ 
b/modules/page-memory/src/main/java/org/apache/ignite/internal/pagememory/persistence/PageStoreWriter.java
@@ -26,7 +26,6 @@ import org.apache.ignite.lang.IgniteInternalCheckedException;
 /**
  * Interface for write page to {@link PageStore}.
  */
-// TODO: IGNITE-15818 Maybe refactor.
 public interface PageStoreWriter {
     /**
      * Callback for write page. {@link PersistentPageMemory} will copy page 
content to buffer before call.
diff --git 
a/modules/page-memory/src/main/java/org/apache/ignite/internal/pagememory/persistence/replacement/DelayedDirtyPageWrite.java
 
b/modules/page-memory/src/main/java/org/apache/ignite/internal/pagememory/persistence/replacement/DelayedDirtyPageWrite.java
index 8e4bd74d24..97b3469633 100644
--- 
a/modules/page-memory/src/main/java/org/apache/ignite/internal/pagememory/persistence/replacement/DelayedDirtyPageWrite.java
+++ 
b/modules/page-memory/src/main/java/org/apache/ignite/internal/pagememory/persistence/replacement/DelayedDirtyPageWrite.java
@@ -32,7 +32,6 @@ import org.jetbrains.annotations.Nullable;
  * segment lock. Page data is copied into temp buffer during {@link 
#write(PersistentPageMemory, FullPageId, ByteBuffer)} and then sent to
  * real implementation by {@link #finishReplacement}.
  */
-// TODO: IGNITE-15818 Maybe refactor.
 public class DelayedDirtyPageWrite implements WriteDirtyPage {
     /** Real flush dirty page implementation. */
     private final WriteDirtyPage flushDirtyPage;
diff --git 
a/modules/page-memory/src/main/java/org/apache/ignite/internal/pagememory/persistence/store/AbstractFilePageStoreIo.java
 
b/modules/page-memory/src/main/java/org/apache/ignite/internal/pagememory/persistence/store/AbstractFilePageStoreIo.java
index 7043f90abf..dbe36c50dd 100644
--- 
a/modules/page-memory/src/main/java/org/apache/ignite/internal/pagememory/persistence/store/AbstractFilePageStoreIo.java
+++ 
b/modules/page-memory/src/main/java/org/apache/ignite/internal/pagememory/persistence/store/AbstractFilePageStoreIo.java
@@ -54,7 +54,7 @@ public abstract class AbstractFilePageStoreIo implements 
Closeable {
     private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
 
     /** Skip CRC calculation flag. */
-    // TODO: IGNITE-17011 Move to config
+    // TODO: IGNITE-16350 Move to config
     private final boolean skipCrc = getBoolean("IGNITE_PDS_SKIP_CRC");
 
     private volatile Path filePath;
diff --git 
a/modules/page-memory/src/main/java/org/apache/ignite/internal/pagememory/util/PageIdUtils.java
 
b/modules/page-memory/src/main/java/org/apache/ignite/internal/pagememory/util/PageIdUtils.java
index 9712ad3cc1..67eaac3766 100644
--- 
a/modules/page-memory/src/main/java/org/apache/ignite/internal/pagememory/util/PageIdUtils.java
+++ 
b/modules/page-memory/src/main/java/org/apache/ignite/internal/pagememory/util/PageIdUtils.java
@@ -17,6 +17,8 @@
 
 package org.apache.ignite.internal.pagememory.util;
 
+import static org.apache.ignite.internal.pagememory.PageIdAllocator.FLAG_DATA;
+
 import org.apache.ignite.internal.pagememory.FullPageId;
 import org.apache.ignite.internal.pagememory.PageIdAllocator;
 import org.apache.ignite.internal.util.IgniteUtils;
@@ -27,6 +29,9 @@ import org.apache.ignite.internal.util.IgniteUtils;
  * @see FullPageId
  */
 public final class PageIdUtils {
+    /** Represents an absent or missing link. */
+    public static final long NULL_LINK = 0;
+
     /** Size of the page index portion. */
     public static final int PAGE_IDX_SIZE = Integer.SIZE;
 
@@ -40,7 +45,7 @@ public final class PageIdUtils {
     public static final int OFFSET_SIZE = Byte.SIZE;
 
     /** Size of a tag portion. */
-    public static final int TAG_SIZE = 2 * Byte.SIZE;
+    public static final int TAG_SIZE = Short.SIZE;
 
     /** Page index mask. */
     public static final long PAGE_IDX_MASK = ~(-1L << PAGE_IDX_SIZE);
@@ -54,74 +59,64 @@ public final class PageIdUtils {
     /** Page Index is a monotonically growing number within each partition. */
     public static final long PART_ID_MASK = ~(-1L << PART_ID_SIZE);
 
-    /** Flags mask. Flags consists of a number of reserved bits, and page type 
(data/index page). */
+    /** Flag mask {@link PageIdAllocator#FLAG_DATA} of {@link 
PageIdAllocator#FLAG_AUX}. */
     public static final long FLAG_MASK = ~(-1L << FLAG_SIZE);
 
     /** Effective page ID mask. */
     private static final long EFFECTIVE_PAGE_ID_MASK = ~(-1L << (PAGE_IDX_SIZE 
+ PART_ID_SIZE));
 
-    /**
-     * Offset of a Rotation ID inside a Page ID.
-     */
+    /** Offset of a rotation ID inside a Page ID. */
     private static final long ROTATION_ID_OFFSET = PAGE_IDX_SIZE + 
PART_ID_SIZE + FLAG_SIZE;
 
     /** Page ID mask that excludes link. */
     private static final long PAGE_ID_MASK = ~(-1L << ROTATION_ID_OFFSET);
 
-    /** Max itemid number. */
-    public static final int MAX_ITEMID_NUM = 0xFE;
+    /** Max item ID number. */
+    public static final int MAX_ITEM_ID_NUM = 0xFE;
 
     /** Maximum page number. */
     public static final long MAX_PAGE_NUM = (1L << PAGE_IDX_SIZE) - 1;
 
-    /** Maximum page number. */
+    /** Maximum partition ID. */
     public static final int MAX_PART_ID = (1 << PART_ID_SIZE) - 1;
 
-    /** Private constructor. */
-    private PageIdUtils() {
-        // No-op.
-    }
-
     /**
      * Constructs a page link by the given page ID and 8-byte words within the 
page.
      *
      * @param pageId Page ID.
      * @param itemId Item ID.
-     * @return Page link.
      */
     public static long link(long pageId, int itemId) {
-        assert itemId >= 0 && itemId <= MAX_ITEMID_NUM : itemId;
+        assert itemId >= 0 && itemId <= MAX_ITEM_ID_NUM : itemId;
         assert (pageId >> ROTATION_ID_OFFSET) == 0 : 
IgniteUtils.hexLong(pageId);
 
         return pageId | (((long) itemId) << ROTATION_ID_OFFSET);
     }
 
     /**
-     * Extracts a page index from the given page ID.
+     * Extracts a page index from the page ID.
      *
      * @param pageId Page ID.
-     * @return Page index.
      */
     public static int pageIndex(long pageId) {
         return (int) (pageId & PAGE_IDX_MASK); // 4 bytes
     }
 
     /**
-     * Extracts a page ID from the given page link.
+     * Extracts a page ID from the page link.
      *
      * @param link Page link.
-     * @return Page ID.
      */
     public static long pageId(long link) {
-        return flag(link) == PageIdAllocator.FLAG_DATA ? link & PAGE_ID_MASK : 
link;
+        return flag(link) == FLAG_DATA ? link & PAGE_ID_MASK : link;
     }
 
     /**
      * Creates page ID from its components.
      *
      * @param partitionId Partition ID.
-     * @param flag        Flags (a number of reserved bits, and page type 
(data/index page))
-     * @param pageIdx     Page index, monotonically growing number within each 
partition
+     * @param flag Flag: {@link PageIdAllocator#FLAG_DATA} of {@link 
PageIdAllocator#FLAG_AUX}.
+     * @param pageIdx Page index, monotonically growing number within each 
partition.
      * @return Page ID constructed from the given pageIdx and partition ID, 
see {@link FullPageId}
      */
     public static long pageId(int partitionId, byte flag, int pageIdx) {
@@ -134,10 +129,9 @@ public final class PageIdUtils {
     }
 
     /**
-     * Converts link into an effective page ID: pageId with only pageIdx and 
partitionId.
+     * Converts page link into an effective page ID: page ID with only page 
index and partition ID.
      *
      * @param link Page link.
-     * @return Effective page id.
      */
     public static long effectivePageId(long link) {
         return link & EFFECTIVE_PAGE_ID_MASK;
@@ -147,37 +141,33 @@ public final class PageIdUtils {
      * Checks whether page ID matches effective page ID.
      *
      * @param pageId Page id.
-     * @return {@code True} if page id is equal to effective page id.
      */
     public static boolean isEffectivePageId(long pageId) {
         return (pageId & ~EFFECTIVE_PAGE_ID_MASK) == 0;
     }
 
     /**
-     * Index of the item inside of data page.
+     * Extracts item id (Offset in 8-byte words) from the page link.
      *
      * @param link Page link.
-     * @return Offset in 8-byte words.
      */
     public static int itemId(long link) {
         return (int) ((link >> ROTATION_ID_OFFSET) & OFFSET_MASK);
     }
 
     /**
-     * Tag of pageId.
+     * Extracts tag (item id + flag) from the page link.
      *
      * @param link Page link.
-     * @return tag - item id + flags
      */
     public static int tag(long link) {
         return (int) ((link >> (PAGE_IDX_SIZE + PART_ID_SIZE)) & TAG_MASK);
     }
 
     /**
-     * Extracts flags byte from the page ID.
+     * Extracts flag ({@link PageIdAllocator#FLAG_DATA} of {@link 
PageIdAllocator#FLAG_AUX}) from the page ID.
      *
      * @param pageId Page ID.
-     * @return Flag.
      */
     public static byte flag(long pageId) {
         return (byte) ((pageId >>> (PART_ID_SIZE + PAGE_IDX_SIZE)) & 
FLAG_MASK);
@@ -187,14 +177,15 @@ public final class PageIdUtils {
      * Extracts partition ID from the page ID.
      *
      * @param pageId Page ID.
-     * @return Partition ID.
      */
     public static int partitionId(long pageId) {
         return (int) ((pageId >>> PAGE_IDX_SIZE) & PART_ID_MASK);
     }
 
     /**
-     * Returns the Rotation ID of a page identified by the given ID.
+     * Extracts rotation ID from the page ID.
+     *
+     * @param pageId Page ID.
      */
     public static long rotationId(long pageId) {
         return pageId >>> ROTATION_ID_OFFSET;
@@ -209,7 +200,7 @@ public final class PageIdUtils {
     public static long rotatePageId(long pageId) {
         long updatedRotationId = rotationId(pageId) + 1;
 
-        if (updatedRotationId > MAX_ITEMID_NUM) {
+        if (updatedRotationId > MAX_ITEM_ID_NUM) {
             updatedRotationId = 1; // We always want non-zero updatedRotationId
         }
 
@@ -227,14 +218,14 @@ public final class PageIdUtils {
     }
 
     /**
-     * Change page type.
+     * Change page flag.
      *
      * @param pageId Old page ID.
-     * @param type   New page type.
+     * @param flag New page flag: {@link PageIdAllocator#FLAG_AUX} or {@link 
PageIdAllocator#FLAG_DATA}.
      * @return Changed page ID.
      */
-    public static long changeType(long pageId, byte type) {
-        return pageId(partitionId(pageId), type, pageIndex(pageId));
+    public static long changeFlag(long pageId, byte flag) {
+        return pageId(partitionId(pageId), flag, pageIndex(pageId));
     }
 
     /**
@@ -254,8 +245,9 @@ public final class PageIdUtils {
     /**
      * Replaces partition ID in the page ID.
      *
-     * @param pageId      Page ID.
+     * @param pageId Page ID.
      * @param partitionId Partition ID.
+     * @return Changed page ID.
      */
     public static long changePartitionId(long pageId, int partitionId) {
         byte flag = flag(pageId);
diff --git 
a/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/mv/PartitionlessLinks.java
 
b/modules/page-memory/src/main/java/org/apache/ignite/internal/pagememory/util/PartitionlessLinks.java
similarity index 86%
rename from 
modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/mv/PartitionlessLinks.java
rename to 
modules/page-memory/src/main/java/org/apache/ignite/internal/pagememory/util/PartitionlessLinks.java
index 26bb54a421..996d8db58e 100644
--- 
a/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/mv/PartitionlessLinks.java
+++ 
b/modules/page-memory/src/main/java/org/apache/ignite/internal/pagememory/util/PartitionlessLinks.java
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.internal.storage.pagememory.mv;
+package org.apache.ignite.internal.pagememory.util;
 
 import static org.apache.ignite.internal.pagememory.util.PageIdUtils.link;
 import static org.apache.ignite.internal.pagememory.util.PageIdUtils.pageId;
@@ -27,23 +27,22 @@ import static 
org.apache.ignite.internal.pagememory.util.PageUtils.putInt;
 import static org.apache.ignite.internal.pagememory.util.PageUtils.putShort;
 
 import java.nio.ByteBuffer;
-import org.apache.ignite.internal.pagememory.util.PageIdUtils;
 
 /**
- * Handling of <em>partitionless links</em>, that is, page memory links from 
which partition ID is removed. They are used to spare storage
- * space in cases when we know the partition ID from the context.
+ * Handling of <em>partitionless links</em>, that is, page memory links from 
which partition ID is removed.
+ *
+ * <p>They are used to save storage space in cases when we know the partition 
ID from the context.
  *
  * @see PageIdUtils#link(long, int)
  */
 public class PartitionlessLinks {
-    /**
-     * Number of bytes a partitionless link takes in storage.
-     */
+    /** Number of bytes a partitionless link takes in storage. */
     public static final int PARTITIONLESS_LINK_SIZE_BYTES = 6;
 
     /**
      * Reads a partitionless link from the memory.
      *
+     * @param partitionId Partition ID.
      * @param pageAddr Page address.
      * @param offset Data offset.
      * @return Partitionless link.
@@ -52,11 +51,11 @@ public class PartitionlessLinks {
         int tag = getShort(pageAddr, offset) & 0xFFFF;
         int pageIdx = getInt(pageAddr, offset + Short.BYTES);
 
-        // Links to metapages are impossible. For the sake of simplicity, 
NULL_LINK is returned in this case.
+        // NULL_LINK is stored as zeroes. This is fine, because no real link 
can be like this. Page with index 0 is never a data page.
         if (pageIdx == 0) {
             assert tag == 0 : tag;
 
-            return RowVersion.NULL_LINK;
+            return PageIdUtils.NULL_LINK;
         }
 
         byte flags = (byte) tag;
diff --git 
a/modules/page-memory/src/test/java/org/apache/ignite/internal/pagememory/freelist/TestDataPageIo.java
 
b/modules/page-memory/src/test/java/org/apache/ignite/internal/pagememory/freelist/TestDataPageIo.java
index 336fe16032..0a9567b1b5 100644
--- 
a/modules/page-memory/src/test/java/org/apache/ignite/internal/pagememory/freelist/TestDataPageIo.java
+++ 
b/modules/page-memory/src/test/java/org/apache/ignite/internal/pagememory/freelist/TestDataPageIo.java
@@ -57,11 +57,11 @@ class TestDataPageIo extends 
AbstractDataPageIo<TestDataRow> {
 
     /** {@inheritDoc} */
     @Override
-    protected void writeFragmentData(TestDataRow row, ByteBuffer buf, int 
rowOff, int payloadSize) {
-        assertPageType(buf);
+    protected void writeFragmentData(TestDataRow row, ByteBuffer pageBuf, int 
rowOff, int payloadSize) {
+        assertPageType(pageBuf);
 
         if (payloadSize > 0) {
-            buf.put(row.bytes, rowOff, payloadSize);
+            pageBuf.put(row.bytes, rowOff, payloadSize);
         }
     }
 
diff --git 
a/modules/page-memory/src/test/java/org/apache/ignite/internal/pagememory/util/PageIdUtilsTest.java
 
b/modules/page-memory/src/test/java/org/apache/ignite/internal/pagememory/util/PageIdUtilsTest.java
index 7760e22d0e..10ad125185 100644
--- 
a/modules/page-memory/src/test/java/org/apache/ignite/internal/pagememory/util/PageIdUtilsTest.java
+++ 
b/modules/page-memory/src/test/java/org/apache/ignite/internal/pagememory/util/PageIdUtilsTest.java
@@ -121,11 +121,11 @@ public class PageIdUtilsTest {
     }
 
     @Test
-    public void testRandomIds() throws Exception {
+    public void testRandomIds() {
         Random rnd = new Random();
 
         for (int i = 0; i < 50_000; i++) {
-            int off = rnd.nextInt(PageIdUtils.MAX_ITEMID_NUM + 1);
+            int off = rnd.nextInt(PageIdUtils.MAX_ITEM_ID_NUM + 1);
             int partId = rnd.nextInt(PageIdUtils.MAX_PART_ID + 1);
             int pageNum = rnd.nextInt();
 
diff --git 
a/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/index/IndexPageIoModule.java
 
b/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/index/IndexPageIoModule.java
index d463333673..a00368d279 100644
--- 
a/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/index/IndexPageIoModule.java
+++ 
b/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/index/IndexPageIoModule.java
@@ -22,6 +22,10 @@ import java.util.List;
 import org.apache.ignite.internal.pagememory.PageMemory;
 import org.apache.ignite.internal.pagememory.io.IoVersions;
 import org.apache.ignite.internal.pagememory.io.PageIoModule;
+import 
org.apache.ignite.internal.storage.pagememory.index.freelist.io.IndexColumnsDataIo;
+import 
org.apache.ignite.internal.storage.pagememory.index.hash.io.HashIndexTreeInnerIo;
+import 
org.apache.ignite.internal.storage.pagememory.index.hash.io.HashIndexTreeLeafIo;
+import 
org.apache.ignite.internal.storage.pagememory.index.hash.io.HashIndexTreeMetaIo;
 import 
org.apache.ignite.internal.storage.pagememory.index.meta.io.IndexMetaInnerIo;
 import 
org.apache.ignite.internal.storage.pagememory.index.meta.io.IndexMetaLeafIo;
 import 
org.apache.ignite.internal.storage.pagememory.index.meta.io.IndexMetaTreeMetaIo;
@@ -34,9 +38,15 @@ public class IndexPageIoModule implements PageIoModule {
     @Override
     public Collection<IoVersions<?>> ioVersions() {
         return List.of(
+                IndexColumnsDataIo.VERSIONS,
+                // Meta tree IO.
                 IndexMetaTreeMetaIo.VERSIONS,
                 IndexMetaInnerIo.VERSIONS,
-                IndexMetaLeafIo.VERSIONS
+                IndexMetaLeafIo.VERSIONS,
+                // Hash index IO.
+                HashIndexTreeMetaIo.VERSIONS,
+                HashIndexTreeInnerIo.VERSIONS,
+                HashIndexTreeLeafIo.VERSIONS
         );
     }
 }
diff --git 
a/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/index/IndexPageTypes.java
 
b/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/index/IndexPageTypes.java
new file mode 100644
index 0000000000..1621b2af26
--- /dev/null
+++ 
b/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/index/IndexPageTypes.java
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.storage.pagememory.index;
+
+/**
+ * Collection of all page types that relate to indexes.
+ */
+public interface IndexPageTypes {
+    /** Page IO type. */
+    short T_VALUE_VERSION_DATA_IO = 100;
+
+    /** Index meta tree meta IO type. */
+    short T_INDEX_META_TREE_META_IO = 101;
+
+    /** Index meta tree inner IO type. */
+    short T_INDEX_META_INNER_IO = 102;
+
+    /** Index meta tree leaf IO type. */
+    short T_INDEX_META_LEAF_IO = 103;
+
+    /** Hash index tree meta IO type. */
+    short T_HASH_INDEX_META_IO = 10_000;
+
+    /** Hash index tree inner IO type. */
+    short T_HASH_INDEX_INNER_IO = 10_001;
+
+    /** Hash index tree meta IO type. */
+    short T_HASH_INDEX_LEAF_IO = 10_002;
+}
diff --git 
a/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/index/freelist/IndexColumns.java
 
b/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/index/freelist/IndexColumns.java
new file mode 100644
index 0000000000..803bb9940c
--- /dev/null
+++ 
b/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/index/freelist/IndexColumns.java
@@ -0,0 +1,120 @@
+/*
+ * 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.storage.pagememory.index.freelist;
+
+import static org.apache.ignite.internal.pagememory.util.PageIdUtils.NULL_LINK;
+
+import java.nio.ByteBuffer;
+import org.apache.ignite.internal.pagememory.Storable;
+import org.apache.ignite.internal.pagememory.io.AbstractDataPageIo;
+import org.apache.ignite.internal.pagememory.io.IoVersions;
+import 
org.apache.ignite.internal.storage.pagememory.index.freelist.io.IndexColumnsDataIo;
+import org.apache.ignite.lang.IgniteInternalCheckedException;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Index columns to store in free list.
+ */
+public class IndexColumns implements Storable {
+    /** Size offset. */
+    public static final int SIZE_OFFSET = 0;
+
+    /** Value offset. Value goes right after the size. */
+    public static final int VALUE_OFFSET = SIZE_OFFSET + Integer.BYTES;
+
+    /** Partition ID. */
+    private final int partitionId;
+
+    /** Link value. */
+    private long link = NULL_LINK;
+
+    /** Byte buffer with binary tuple data. */
+    private final @Nullable ByteBuffer valueBuffer;
+
+    /**
+     * Constructor.
+     *
+     * @param partitionId Partition ID.
+     * @param valueBuffer Value buffer.
+     */
+
+    public IndexColumns(int partitionId, @Nullable ByteBuffer valueBuffer) {
+        this.partitionId = partitionId;
+        this.valueBuffer = valueBuffer;
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param partitionId Partition ID.
+     * @param link Link.
+     * @param valueBuffer Value buffer.
+     */
+    public IndexColumns(int partitionId, long link, @Nullable ByteBuffer 
valueBuffer) {
+        this.partitionId = partitionId;
+        this.link = link;
+        this.valueBuffer = valueBuffer;
+    }
+
+    /**
+     * Returns the size of binary tuple.
+     */
+    public int valueSize() {
+        assert valueBuffer != null;
+
+        return valueBuffer.limit();
+    }
+
+    /**
+     * Returns a byte buffer that contains binary tuple data.
+     */
+    public ByteBuffer valueBuffer() {
+        return valueBuffer;
+    }
+
+    @Override
+    public void link(long link) {
+        this.link = link;
+    }
+
+    @Override
+    public long link() {
+        return link;
+    }
+
+    @Override
+    public int partition() {
+        return partitionId;
+    }
+
+    @Override
+    public int size() throws IgniteInternalCheckedException {
+        return VALUE_OFFSET + valueSize();
+    }
+
+    @Override
+    public int headerSize() {
+        // Size of the tuple and its header. For further use in future 
optimizations.
+        return VALUE_OFFSET + Byte.BYTES;
+    }
+
+    @Override
+    public IoVersions<? extends AbstractDataPageIo<?>> ioVersions() {
+        return IndexColumnsDataIo.VERSIONS;
+    }
+}
diff --git 
a/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/index/freelist/IndexColumnsFreeList.java
 
b/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/index/freelist/IndexColumnsFreeList.java
new file mode 100644
index 0000000000..b66373bcfc
--- /dev/null
+++ 
b/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/index/freelist/IndexColumnsFreeList.java
@@ -0,0 +1,77 @@
+/*
+ * 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.storage.pagememory.index.freelist;
+
+import java.util.concurrent.atomic.AtomicLong;
+import org.apache.ignite.internal.logger.IgniteLogger;
+import org.apache.ignite.internal.pagememory.PageMemory;
+import org.apache.ignite.internal.pagememory.evict.PageEvictionTracker;
+import org.apache.ignite.internal.pagememory.freelist.AbstractFreeList;
+import org.apache.ignite.internal.pagememory.metric.IoStatisticsHolder;
+import org.apache.ignite.internal.pagememory.reuse.ReuseList;
+import org.apache.ignite.internal.pagememory.util.PageLockListener;
+import org.apache.ignite.lang.IgniteInternalCheckedException;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Free list implementation to store {@link IndexColumns} values.
+ */
+public class IndexColumnsFreeList extends AbstractFreeList<IndexColumns>  {
+    /**
+     * Constructor.
+     *
+     * @param grpId Group ID.
+     * @param partId Partition ID.
+     * @param pageMem Page memory.
+     * @param reuseList Reuse list or {@code null} if this free list will be a 
reuse list for itself.
+     * @param lockLsnr Page lock listener.
+     * @param log Logger.
+     * @param metaPageId Metadata page ID.
+     * @param initNew {@code True} if new metadata should be initialized.
+     * @param pageListCacheLimit Page list cache limit.
+     * @param evictionTracker Page eviction tracker.
+     * @throws IgniteInternalCheckedException If failed.
+     */
+    public IndexColumnsFreeList(
+            int grpId,
+            int partId,
+            PageMemory pageMem,
+            @Nullable ReuseList reuseList,
+            PageLockListener lockLsnr,
+            IgniteLogger log,
+            long metaPageId,
+            boolean initNew,
+            @Nullable AtomicLong pageListCacheLimit,
+            PageEvictionTracker evictionTracker,
+            IoStatisticsHolder statHolder
+    ) throws IgniteInternalCheckedException {
+        super(
+                grpId,
+                partId,
+                "IndexColumnsFreeList_" + grpId,
+                pageMem,
+                reuseList,
+                lockLsnr,
+                log,
+                metaPageId,
+                initNew,
+                pageListCacheLimit,
+                evictionTracker
+        );
+    }
+}
diff --git 
a/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/index/meta/io/IndexMetaTreeMetaIo.java
 
b/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/index/freelist/ReadIndexColumnsValue.java
similarity index 51%
copy from 
modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/index/meta/io/IndexMetaTreeMetaIo.java
copy to 
modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/index/freelist/ReadIndexColumnsValue.java
index 9e8d229c15..9c30911e0a 100644
--- 
a/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/index/meta/io/IndexMetaTreeMetaIo.java
+++ 
b/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/index/freelist/ReadIndexColumnsValue.java
@@ -15,28 +15,24 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.internal.storage.pagememory.index.meta.io;
+package org.apache.ignite.internal.storage.pagememory.index.freelist;
 
-import org.apache.ignite.internal.pagememory.io.IoVersions;
-import org.apache.ignite.internal.pagememory.tree.io.BplusMetaIo;
-import org.apache.ignite.internal.storage.pagememory.index.meta.IndexMetaTree;
+import org.apache.ignite.internal.pagememory.datapage.ReadPageMemoryRowValue;
+import org.apache.ignite.internal.storage.pagememory.mv.RowVersion;
 
 /**
- * IO routines for {@link IndexMetaTree} meta pages.
+ * Reads {@link RowVersion#value()} from page-memory.
  */
-public class IndexMetaTreeMetaIo extends BplusMetaIo {
-    /** Page IO type. */
-    public static final short T_INDEX_META_TREE_META_IO = 13;
-
-    /** I/O versions. */
-    public static final IoVersions<IndexMetaTreeMetaIo> VERSIONS = new 
IoVersions<>(new IndexMetaTreeMetaIo(1));
+public class ReadIndexColumnsValue extends ReadPageMemoryRowValue {
+    /** {@inheritDoc} */
+    @Override
+    protected int valueSizeOffsetInFirstSlot() {
+        return IndexColumns.SIZE_OFFSET;
+    }
 
-    /**
-     * Constructor.
-     *
-     * @param ver Page format version.
-     */
-    protected IndexMetaTreeMetaIo(int ver) {
-        super(T_INDEX_META_TREE_META_IO, ver);
+    /** {@inheritDoc} */
+    @Override
+    protected int valueOffsetInFirstSlot() {
+        return IndexColumns.VALUE_OFFSET;
     }
 }
diff --git 
a/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/index/freelist/io/IndexColumnsDataIo.java
 
b/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/index/freelist/io/IndexColumnsDataIo.java
new file mode 100644
index 0000000000..002c86b949
--- /dev/null
+++ 
b/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/index/freelist/io/IndexColumnsDataIo.java
@@ -0,0 +1,83 @@
+/*
+ * 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.storage.pagememory.index.freelist.io;
+
+import static 
org.apache.ignite.internal.pagememory.util.PageUtils.putByteBuffer;
+import static org.apache.ignite.internal.pagememory.util.PageUtils.putInt;
+
+import java.nio.ByteBuffer;
+import org.apache.ignite.internal.pagememory.io.AbstractDataPageIo;
+import org.apache.ignite.internal.pagememory.io.IoVersions;
+import org.apache.ignite.internal.storage.pagememory.index.IndexPageTypes;
+import 
org.apache.ignite.internal.storage.pagememory.index.freelist.IndexColumns;
+import org.apache.ignite.lang.IgniteStringBuilder;
+
+/**
+ * Data pages IO for {@link IndexColumns}.
+ */
+public class IndexColumnsDataIo extends AbstractDataPageIo<IndexColumns> {
+    /** I/O versions. */
+    public static final IoVersions<IndexColumnsDataIo> VERSIONS = new 
IoVersions<>(new IndexColumnsDataIo(1));
+
+    /**
+     * Constructor.
+     *
+     * @param ver Page format version.
+     */
+    protected IndexColumnsDataIo(int ver) {
+        super(IndexPageTypes.T_VALUE_VERSION_DATA_IO, ver);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected void writeRowData(long pageAddr, int dataOff, int payloadSize, 
IndexColumns row, boolean newRow) {
+        assertPageType(pageAddr);
+
+        putInt(pageAddr, dataOff + IndexColumns.SIZE_OFFSET, row.valueSize());
+
+        putByteBuffer(pageAddr, dataOff + IndexColumns.VALUE_OFFSET, 
row.valueBuffer());
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected void writeFragmentData(IndexColumns row, ByteBuffer pageBuf, int 
rowOff, int payloadSize) {
+        assertPageType(pageBuf);
+
+        if (rowOff == 0) {
+            // First fragment.
+            assert row.headerSize() <= payloadSize;
+
+            pageBuf.putInt(row.valueSize());
+
+            putValueBufferIntoPage(pageBuf, row.valueBuffer(), 0, payloadSize 
- IndexColumns.VALUE_OFFSET);
+        } else {
+            // Not a first fragment.
+            assert rowOff >= row.headerSize();
+
+            putValueBufferIntoPage(pageBuf, row.valueBuffer(), rowOff - 
IndexColumns.VALUE_OFFSET, payloadSize);
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected void printPage(long addr, int pageSize, IgniteStringBuilder sb) {
+        sb.app("IndexColumnsDataIo [\n");
+        printPageLayout(addr, pageSize, sb);
+        sb.app("\n]");
+    }
+}
diff --git 
a/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/index/hash/HashIndexRow.java
 
b/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/index/hash/HashIndexRow.java
new file mode 100644
index 0000000000..320a4033e1
--- /dev/null
+++ 
b/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/index/hash/HashIndexRow.java
@@ -0,0 +1,61 @@
+/*
+ * 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.storage.pagememory.index.hash;
+
+import org.apache.ignite.internal.storage.RowId;
+import org.apache.ignite.internal.storage.index.IndexRow;
+import 
org.apache.ignite.internal.storage.pagememory.index.freelist.IndexColumns;
+import org.apache.ignite.internal.util.HashUtils;
+
+/**
+ * {@link IndexRow} implementation used in the {@link HashIndexTree}.
+ */
+public class HashIndexRow extends HashIndexRowKey {
+    /** Row id. */
+    private final RowId rowId;
+
+    /**
+     * Constructor.
+     *
+     * @param indexColumns Index columns.
+     * @param rowId Row id.
+     */
+    public HashIndexRow(IndexColumns indexColumns, RowId rowId) {
+        this(HashUtils.hash32(indexColumns.valueBuffer()), indexColumns, 
rowId);
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param indexColumnsHash Hash of the index columns.
+     * @param indexColumns Index columns.
+     * @param rowId Row id.
+     */
+    public HashIndexRow(int indexColumnsHash, IndexColumns indexColumns, RowId 
rowId) {
+        super(indexColumnsHash, indexColumns);
+
+        this.rowId = rowId;
+    }
+
+    /**
+     * Returns a row id of the row.
+     */
+    public RowId rowId() {
+        return rowId;
+    }
+}
diff --git 
a/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/index/hash/HashIndexRowKey.java
 
b/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/index/hash/HashIndexRowKey.java
new file mode 100644
index 0000000000..62e520fd56
--- /dev/null
+++ 
b/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/index/hash/HashIndexRowKey.java
@@ -0,0 +1,55 @@
+/*
+ * 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.storage.pagememory.index.hash;
+
+import 
org.apache.ignite.internal.storage.pagememory.index.freelist.IndexColumns;
+
+/**
+ * Key to search for a {@link HashIndexRow} in the {@link HashIndexTree}.
+ */
+public class HashIndexRowKey {
+    private final int indexColumnsHash;
+
+    private final IndexColumns indexColumns;
+
+    /**
+     * Constructor.
+     *
+     * @param indexColumnsHash Hash of the index columns.
+     * @param indexColumns Index columns.
+     */
+    public HashIndexRowKey(int indexColumnsHash, IndexColumns indexColumns) {
+        this.indexColumnsHash = indexColumnsHash;
+
+        this.indexColumns = indexColumns;
+    }
+
+    /**
+     * Returns the hash of the index columns.
+     */
+    public int indexColumnsHash() {
+        return indexColumnsHash;
+    }
+
+    /**
+     * Returns an indexed columns value.
+     */
+    public IndexColumns indexColumns() {
+        return indexColumns;
+    }
+}
diff --git 
a/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/index/meta/IndexMetaTree.java
 
b/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/index/hash/HashIndexTree.java
similarity index 51%
copy from 
modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/index/meta/IndexMetaTree.java
copy to 
modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/index/hash/HashIndexTree.java
index e3d55bb0d6..d9ebdc48d2 100644
--- 
a/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/index/meta/IndexMetaTree.java
+++ 
b/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/index/hash/HashIndexTree.java
@@ -15,25 +15,29 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.internal.storage.pagememory.index.meta;
+package org.apache.ignite.internal.storage.pagememory.index.hash;
 
 import java.util.concurrent.atomic.AtomicLong;
 import org.apache.ignite.internal.pagememory.PageMemory;
+import org.apache.ignite.internal.pagememory.datapage.DataPageReader;
 import org.apache.ignite.internal.pagememory.reuse.ReuseList;
 import org.apache.ignite.internal.pagememory.tree.BplusTree;
 import org.apache.ignite.internal.pagememory.tree.io.BplusIo;
 import org.apache.ignite.internal.pagememory.util.PageLockListener;
-import 
org.apache.ignite.internal.storage.pagememory.index.meta.io.IndexMetaInnerIo;
-import org.apache.ignite.internal.storage.pagememory.index.meta.io.IndexMetaIo;
-import 
org.apache.ignite.internal.storage.pagememory.index.meta.io.IndexMetaLeafIo;
-import 
org.apache.ignite.internal.storage.pagememory.index.meta.io.IndexMetaTreeMetaIo;
+import 
org.apache.ignite.internal.storage.pagememory.index.hash.io.HashIndexTreeInnerIo;
+import 
org.apache.ignite.internal.storage.pagememory.index.hash.io.HashIndexTreeIo;
+import 
org.apache.ignite.internal.storage.pagememory.index.hash.io.HashIndexTreeLeafIo;
+import 
org.apache.ignite.internal.storage.pagememory.index.hash.io.HashIndexTreeMetaIo;
 import org.apache.ignite.lang.IgniteInternalCheckedException;
 import org.jetbrains.annotations.Nullable;
 
 /**
- * Tree for storing index meta-information, such as the root of an index tree.
+ * {@link BplusTree} implementation for storing {@link HashIndexRow}.
  */
-public class IndexMetaTree extends BplusTree<IndexMeta, IndexMeta> {
+public class HashIndexTree extends BplusTree<HashIndexRowKey, HashIndexRow> {
+    /** Data page reader instance to read payload from data pages. */
+    private final DataPageReader dataPageReader;
+
     /**
      * Constructor.
      *
@@ -48,9 +52,9 @@ public class IndexMetaTree extends BplusTree<IndexMeta, 
IndexMeta> {
      * @param initNew {@code True} if new tree should be created.
      * @throws IgniteInternalCheckedException If failed.
      */
-    public IndexMetaTree(
+    public HashIndexTree(
             int grpId,
-            String grpName,
+            @Nullable String grpName,
             int partId,
             PageMemory pageMem,
             PageLockListener lockLsnr,
@@ -59,36 +63,40 @@ public class IndexMetaTree extends BplusTree<IndexMeta, 
IndexMeta> {
             @Nullable ReuseList reuseList,
             boolean initNew
     ) throws IgniteInternalCheckedException {
-        super(
-                "IndexMetaTree_" + grpId,
-                grpId,
-                grpName,
-                partId,
-                pageMem,
-                lockLsnr,
-                globalRmvId,
-                metaPageId,
-                reuseList
-        );
+        super("HashIndexTree_" + grpId, grpId, grpName, partId, pageMem, 
lockLsnr, globalRmvId, metaPageId, reuseList);
+
+        setIos(HashIndexTreeInnerIo.VERSIONS, HashIndexTreeLeafIo.VERSIONS, 
HashIndexTreeMetaIo.VERSIONS);
 
-        setIos(IndexMetaInnerIo.VERSIONS, IndexMetaLeafIo.VERSIONS, 
IndexMetaTreeMetaIo.VERSIONS);
+        dataPageReader = new DataPageReader(pageMem, grpId, 
statisticsHolder());
 
         initTree(initNew);
     }
 
-    /** {@inheritDoc} */
+    /**
+     * Returns a partition id.
+     */
+    public int partitionId() {
+        return partId;
+    }
+
+    /**
+     * Returns a data page reader instance to read payload from data pages.
+     */
+    public DataPageReader dataPageReader() {
+        return dataPageReader;
+    }
+
     @Override
-    protected int compare(BplusIo<IndexMeta> io, long pageAddr, int idx, 
IndexMeta row) {
-        IndexMetaIo indexMetaIo = (IndexMetaIo) io;
+    protected int compare(BplusIo<HashIndexRowKey> io, long pageAddr, int idx, 
HashIndexRowKey row) throws IgniteInternalCheckedException {
+        HashIndexTreeIo hashIndexTreeIo = (HashIndexTreeIo) io;
 
-        return indexMetaIo.compare(pageAddr, idx, row);
+        return hashIndexTreeIo.compare(dataPageReader, partId, pageAddr, idx, 
row);
     }
 
-    /** {@inheritDoc} */
     @Override
-    public IndexMeta getRow(BplusIo<IndexMeta> io, long pageAddr, int idx, 
Object x) {
-        IndexMetaIo indexMetaIo = (IndexMetaIo) io;
+    public HashIndexRow getRow(BplusIo<HashIndexRowKey> io, long pageAddr, int 
idx, Object x) throws IgniteInternalCheckedException {
+        HashIndexTreeIo hashIndexTreeIo = (HashIndexTreeIo) io;
 
-        return indexMetaIo.getRow(pageAddr, idx);
+        return hashIndexTreeIo.getRow(dataPageReader, partId, pageAddr, idx);
     }
 }
diff --git 
a/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/index/hash/InsertHashIndexRowInvokeClosure.java
 
b/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/index/hash/InsertHashIndexRowInvokeClosure.java
new file mode 100644
index 0000000000..c6099ead0a
--- /dev/null
+++ 
b/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/index/hash/InsertHashIndexRowInvokeClosure.java
@@ -0,0 +1,83 @@
+/*
+ * 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.storage.pagememory.index.hash;
+
+
+import static org.apache.ignite.internal.pagememory.util.PageIdUtils.NULL_LINK;
+
+import org.apache.ignite.internal.pagememory.metric.IoStatisticsHolder;
+import org.apache.ignite.internal.pagememory.tree.IgniteTree.InvokeClosure;
+import org.apache.ignite.internal.pagememory.tree.IgniteTree.OperationType;
+import 
org.apache.ignite.internal.storage.pagememory.index.freelist.IndexColumns;
+import 
org.apache.ignite.internal.storage.pagememory.index.freelist.IndexColumnsFreeList;
+import org.apache.ignite.lang.IgniteInternalCheckedException;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Insert closure that inserts corresponding {@link IndexColumns} into a 
{@link IndexColumnsFreeList} before writing to the
+ * {@link HashIndexTree}.
+ */
+public class InsertHashIndexRowInvokeClosure implements 
InvokeClosure<HashIndexRow> {
+    /** Hash index row instance for insertion. */
+    private final HashIndexRow hashIndexRow;
+
+    /** Free list to insert data into in case of necessity. */
+    private final IndexColumnsFreeList freeList;
+
+    /** Statistics holder to track IO operations. */
+    private final IoStatisticsHolder statHolder;
+
+    /** Operation type, either {@link OperationType#PUT} or {@link 
OperationType#NOOP} depending on the tree state. */
+    private OperationType operationType = OperationType.PUT;
+
+    /**
+     * Constructor.
+     *
+     * @param hashIndexRow Hash index row instance for insertion.
+     * @param freeList Free list to insert data into in case of necessity.
+     * @param statHolder Statistics holder to track IO operations.
+     */
+    public InsertHashIndexRowInvokeClosure(HashIndexRow hashIndexRow, 
IndexColumnsFreeList freeList, IoStatisticsHolder statHolder) {
+        assert hashIndexRow.indexColumns().link() == NULL_LINK;
+
+        this.hashIndexRow = hashIndexRow;
+        this.freeList = freeList;
+        this.statHolder = statHolder;
+    }
+
+    @Override
+    public void call(@Nullable HashIndexRow oldRow) throws 
IgniteInternalCheckedException {
+        if (oldRow != null) {
+            operationType = OperationType.NOOP;
+
+            return;
+        }
+
+        freeList.insertDataRow(hashIndexRow.indexColumns(), statHolder);
+    }
+
+    @Override
+    public @Nullable HashIndexRow newRow() {
+        return hashIndexRow;
+    }
+
+    @Override
+    public OperationType operationType() {
+        return operationType;
+    }
+}
diff --git 
a/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/index/hash/RemoveHashIndexRowInvokeClosure.java
 
b/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/index/hash/RemoveHashIndexRowInvokeClosure.java
new file mode 100644
index 0000000000..bdd342bc75
--- /dev/null
+++ 
b/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/index/hash/RemoveHashIndexRowInvokeClosure.java
@@ -0,0 +1,99 @@
+/*
+ * 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.storage.pagememory.index.hash;
+
+
+import static org.apache.ignite.internal.pagememory.util.PageIdUtils.NULL_LINK;
+
+import org.apache.ignite.internal.pagememory.metric.IoStatisticsHolder;
+import org.apache.ignite.internal.pagememory.tree.BplusTree;
+import org.apache.ignite.internal.pagememory.tree.IgniteTree.InvokeClosure;
+import org.apache.ignite.internal.pagememory.tree.IgniteTree.OperationType;
+import 
org.apache.ignite.internal.storage.pagememory.index.freelist.IndexColumns;
+import 
org.apache.ignite.internal.storage.pagememory.index.freelist.IndexColumnsFreeList;
+import org.apache.ignite.lang.IgniteInternalCheckedException;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Insert closure that removes corresponding {@link IndexColumns} from a 
{@link IndexColumnsFreeList} after removing it from the
+ * {@link HashIndexTree}.
+ */
+public class RemoveHashIndexRowInvokeClosure implements 
InvokeClosure<HashIndexRow> {
+    /** Hash index row instance for removal. */
+    private final HashIndexRow hashIndexRow;
+
+    /** Free list to insert data into in case of necessity. */
+    private final IndexColumnsFreeList freeList;
+
+    /** Statistics holder to track IO operations. */
+    private final IoStatisticsHolder statHolder;
+
+    /** Operation type, either {@link OperationType#REMOVE} or {@link 
OperationType#NOOP} if row is missing. */
+    private OperationType operationType = OperationType.REMOVE;
+
+    /**
+     * Constructor.
+     *
+     * @param hashIndexRow Hash index row instance for removal.
+     * @param freeList Free list to insert data into in case of necessity.
+     * @param statHolder Statistics holder to track IO operations.
+     */
+    public RemoveHashIndexRowInvokeClosure(HashIndexRow hashIndexRow, 
IndexColumnsFreeList freeList, IoStatisticsHolder statHolder) {
+        assert hashIndexRow.indexColumns().link() == 0L;
+
+        this.hashIndexRow = hashIndexRow;
+        this.freeList = freeList;
+        this.statHolder = statHolder;
+    }
+
+    @Override
+    public void call(@Nullable HashIndexRow oldRow) {
+        if (oldRow == null) {
+            operationType = OperationType.NOOP;
+        } else {
+            hashIndexRow.indexColumns().link(oldRow.indexColumns().link());
+        }
+    }
+
+    @Override
+    public @Nullable HashIndexRow newRow() {
+        return null;
+    }
+
+    @Override
+    public OperationType operationType() {
+        return operationType;
+    }
+
+    /**
+     * Method to call after {@link BplusTree#invoke(Object, Object, 
InvokeClosure)} has completed.
+     *
+     * @throws IgniteInternalCheckedException If failed to remove data from 
the free list.
+     */
+    public void afterCompletion() throws IgniteInternalCheckedException {
+        IndexColumns indexColumns = hashIndexRow.indexColumns();
+
+        if (indexColumns.link() != NULL_LINK) {
+            assert operationType == OperationType.REMOVE;
+
+            freeList.removeDataRowByLink(indexColumns.link(), statHolder);
+
+            indexColumns.link(NULL_LINK);
+        }
+    }
+}
diff --git 
a/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/index/hash/io/HashIndexTreeInnerIo.java
 
b/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/index/hash/io/HashIndexTreeInnerIo.java
new file mode 100644
index 0000000000..ffd3fe93b1
--- /dev/null
+++ 
b/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/index/hash/io/HashIndexTreeInnerIo.java
@@ -0,0 +1,61 @@
+/*
+ * 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.storage.pagememory.index.hash.io;
+
+import org.apache.ignite.internal.pagememory.io.IoVersions;
+import org.apache.ignite.internal.pagememory.tree.BplusTree;
+import org.apache.ignite.internal.pagememory.tree.io.BplusInnerIo;
+import org.apache.ignite.internal.pagememory.tree.io.BplusIo;
+import org.apache.ignite.internal.storage.pagememory.index.IndexPageTypes;
+import 
org.apache.ignite.internal.storage.pagememory.index.hash.HashIndexRowKey;
+import org.apache.ignite.internal.storage.pagememory.index.hash.HashIndexTree;
+import org.apache.ignite.lang.IgniteInternalCheckedException;
+
+/**
+ * {@link BplusInnerIo} implementation for {@link HashIndexTree}.
+ */
+public class HashIndexTreeInnerIo extends BplusInnerIo<HashIndexRowKey> 
implements HashIndexTreeIo {
+    /** I/O versions. */
+    public static final IoVersions<HashIndexTreeInnerIo> VERSIONS = new 
IoVersions<>(new HashIndexTreeInnerIo(1));
+
+    /**
+     * Constructor.
+     *
+     * @param ver Page format version.
+     */
+    protected HashIndexTreeInnerIo(int ver) {
+        super(IndexPageTypes.T_HASH_INDEX_INNER_IO, ver, true, SIZE_IN_BYTES);
+    }
+
+    @Override
+    public void store(long dstPageAddr, int dstIdx, BplusIo<HashIndexRowKey> 
srcIo, long srcPageAddr, int srcIdx) {
+        HashIndexTreeIo.super.store(dstPageAddr, dstIdx, srcIo, srcPageAddr, 
srcIdx);
+    }
+
+    @Override
+    public void storeByOffset(long pageAddr, int off, HashIndexRowKey row) {
+        HashIndexTreeIo.super.storeByOffset(pageAddr, off, row);
+    }
+
+    @Override
+    public HashIndexRowKey getLookupRow(BplusTree<HashIndexRowKey, ?> tree, 
long pageAddr, int idx) throws IgniteInternalCheckedException {
+        HashIndexTree hashIndexTree = (HashIndexTree) tree;
+
+        return getRow(hashIndexTree.dataPageReader(), 
hashIndexTree.partitionId(), pageAddr, idx);
+    }
+}
diff --git 
a/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/index/hash/io/HashIndexTreeIo.java
 
b/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/index/hash/io/HashIndexTreeIo.java
new file mode 100644
index 0000000000..395bbb3f05
--- /dev/null
+++ 
b/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/index/hash/io/HashIndexTreeIo.java
@@ -0,0 +1,188 @@
+/*
+ * 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.storage.pagememory.index.hash.io;
+
+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 static 
org.apache.ignite.internal.pagememory.util.PartitionlessLinks.PARTITIONLESS_LINK_SIZE_BYTES;
+import static 
org.apache.ignite.internal.pagememory.util.PartitionlessLinks.readPartitionlessLink;
+import static 
org.apache.ignite.internal.pagememory.util.PartitionlessLinks.writePartitionlessLink;
+
+import java.nio.ByteBuffer;
+import java.util.UUID;
+import org.apache.ignite.internal.pagememory.datapage.DataPageReader;
+import org.apache.ignite.internal.pagememory.tree.io.BplusIo;
+import org.apache.ignite.internal.pagememory.util.PageUtils;
+import org.apache.ignite.internal.storage.RowId;
+import 
org.apache.ignite.internal.storage.pagememory.index.freelist.IndexColumns;
+import 
org.apache.ignite.internal.storage.pagememory.index.freelist.ReadIndexColumnsValue;
+import org.apache.ignite.internal.storage.pagememory.index.hash.HashIndexRow;
+import 
org.apache.ignite.internal.storage.pagememory.index.hash.HashIndexRowKey;
+import org.apache.ignite.internal.storage.pagememory.index.meta.IndexMeta;
+import org.apache.ignite.lang.IgniteInternalCheckedException;
+
+/**
+ * Interface for {@link IndexMeta} B+Tree-related IO.
+ *
+ * <p>Defines a following data layout:
+ * <ul>
+ *     <li>Index ID - {@link UUID} (16 bytes);</li>
+ *     <li>Index root page ID - long (8 bytes).</li>
+ * </ul>
+ */
+public interface HashIndexTreeIo {
+    /** Offset of the index columns hash (4 bytes). */
+    int INDEX_COLUMNS_HASH_OFFSET = 0;
+
+    /** Offset of the index column link (6 bytes). */
+    int INDEX_COLUMNS_LINK_OFFSET = INDEX_COLUMNS_HASH_OFFSET + Integer.BYTES;
+
+    /** Offset of rowId's most significant bits, 8 bytes. */
+    int ROW_ID_MSB_OFFSET = INDEX_COLUMNS_LINK_OFFSET + 
PARTITIONLESS_LINK_SIZE_BYTES;
+
+    /** Offset of rowId's least significant bits, 8 bytes. */
+    int ROW_ID_LSB_OFFSET = ROW_ID_MSB_OFFSET + Long.BYTES;
+
+    /** Payload size in bytes. */
+    int SIZE_IN_BYTES = ROW_ID_LSB_OFFSET + Long.BYTES;
+
+    /**
+     * Returns an offset of the element inside the page.
+     *
+     * @see BplusIo#offset(int)
+     */
+    int offset(int idx);
+
+    /**
+     * Stores a hash index row, copied from another page.
+     *
+     * @see BplusIo#store(long, int, BplusIo, long, int)
+     */
+    default void store(long dstPageAddr, int dstIdx, BplusIo<HashIndexRowKey> 
srcIo, long srcPageAddr, int srcIdx) {
+        int dstOffset = offset(dstIdx);
+        int srcOffset = offset(srcIdx);
+
+        PageUtils.copyMemory(srcPageAddr, srcOffset, dstPageAddr, dstOffset, 
SIZE_IN_BYTES);
+    }
+
+    /**
+     * Stores a hash index row in the page.
+     *
+     * @see BplusIo#storeByOffset(long, int, Object)
+     */
+    default void storeByOffset(long pageAddr, int off, HashIndexRowKey rowKey) 
{
+        assert rowKey instanceof HashIndexRow;
+
+        HashIndexRow hashIndexRow = (HashIndexRow) rowKey;
+
+        putInt(pageAddr, off + INDEX_COLUMNS_HASH_OFFSET, 
hashIndexRow.indexColumnsHash());
+
+        writePartitionlessLink(pageAddr + off + INDEX_COLUMNS_LINK_OFFSET, 
hashIndexRow.indexColumns().link());
+
+        RowId rowId = hashIndexRow.rowId();
+
+        putLong(pageAddr, off + ROW_ID_MSB_OFFSET, 
rowId.mostSignificantBits());
+        putLong(pageAddr, off + ROW_ID_LSB_OFFSET, 
rowId.leastSignificantBits());
+    }
+
+    /**
+     * Compare the {@link HashIndexRowKey} from the page with passed {@link 
HashIndexRowKey}.
+     *
+     * @param pageAddr Page address.
+     * @param idx Element's index.
+     * @param rowKey Lookup index row key.
+     * @return Comparison result.
+     */
+    default int compare(DataPageReader dataPageReader, int partitionId, long 
pageAddr, int idx, HashIndexRowKey rowKey)
+            throws IgniteInternalCheckedException {
+        assert rowKey instanceof HashIndexRow;
+
+        HashIndexRow hashIndexRow = (HashIndexRow) rowKey;
+
+        int off = offset(idx);
+
+        int cmp = Integer.compare(getInt(pageAddr, off + 
INDEX_COLUMNS_HASH_OFFSET), hashIndexRow.indexColumnsHash());
+
+        if (cmp != 0) {
+            return cmp;
+        }
+
+        long link = readPartitionlessLink(partitionId, pageAddr, off + 
INDEX_COLUMNS_LINK_OFFSET);
+
+        //TODO Add in-place compare in IGNITE-17536
+        ReadIndexColumnsValue indexColumnsTraversal = new 
ReadIndexColumnsValue();
+
+        dataPageReader.traverse(link, indexColumnsTraversal, null);
+
+        ByteBuffer indexColumnsBuffer = 
ByteBuffer.wrap(indexColumnsTraversal.result());
+
+        cmp = 
indexColumnsBuffer.compareTo(hashIndexRow.indexColumns().valueBuffer());
+
+        if (cmp != 0) {
+            return cmp;
+        }
+
+        long rowIdMsb = getLong(pageAddr, off + ROW_ID_MSB_OFFSET);
+
+        cmp = Long.compare(rowIdMsb, 
hashIndexRow.rowId().mostSignificantBits());
+
+        if (cmp != 0) {
+            return cmp;
+        }
+
+        long rowIdLsb = getLong(pageAddr, off + ROW_ID_LSB_OFFSET);
+
+        return Long.compare(rowIdLsb, 
hashIndexRow.rowId().leastSignificantBits());
+    }
+
+    /**
+     * Reads a hash index row value.
+     *
+     * @param dataPageReader Data page reader instance to read payload from 
data pages.
+     * @param partitionId Partition id.
+     * @param pageAddr Page address.
+     * @param idx Element's index.
+     * @return Hash index row.
+     * @throws IgniteInternalCheckedException If failed to read payload from 
data pages.
+     */
+    default HashIndexRow getRow(DataPageReader dataPageReader, int 
partitionId, long pageAddr, int idx)
+            throws IgniteInternalCheckedException {
+        int off = offset(idx);
+
+        int hash = getInt(pageAddr, off + INDEX_COLUMNS_HASH_OFFSET);
+
+        long link = readPartitionlessLink(partitionId, pageAddr, off + 
INDEX_COLUMNS_LINK_OFFSET);
+
+        ReadIndexColumnsValue indexColumnsTraversal = new 
ReadIndexColumnsValue();
+
+        dataPageReader.traverse(link, indexColumnsTraversal, null);
+
+        ByteBuffer indexColumnsBuffer = 
ByteBuffer.wrap(indexColumnsTraversal.result());
+
+        IndexColumns indexColumns = new IndexColumns(partitionId, link, 
indexColumnsBuffer);
+
+        long rowIdMsb = getLong(pageAddr, off + ROW_ID_MSB_OFFSET);
+        long rowIdLsb = getLong(pageAddr, off + ROW_ID_LSB_OFFSET);
+
+        RowId rowId = new RowId(partitionId, rowIdMsb, rowIdLsb);
+
+        return new HashIndexRow(hash, indexColumns, rowId);
+    }
+}
diff --git 
a/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/index/hash/io/HashIndexTreeLeafIo.java
 
b/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/index/hash/io/HashIndexTreeLeafIo.java
new file mode 100644
index 0000000000..02affe53ec
--- /dev/null
+++ 
b/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/index/hash/io/HashIndexTreeLeafIo.java
@@ -0,0 +1,61 @@
+/*
+ * 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.storage.pagememory.index.hash.io;
+
+import org.apache.ignite.internal.pagememory.io.IoVersions;
+import org.apache.ignite.internal.pagememory.tree.BplusTree;
+import org.apache.ignite.internal.pagememory.tree.io.BplusIo;
+import org.apache.ignite.internal.pagememory.tree.io.BplusLeafIo;
+import org.apache.ignite.internal.storage.pagememory.index.IndexPageTypes;
+import 
org.apache.ignite.internal.storage.pagememory.index.hash.HashIndexRowKey;
+import org.apache.ignite.internal.storage.pagememory.index.hash.HashIndexTree;
+import org.apache.ignite.lang.IgniteInternalCheckedException;
+
+/**
+ * {@link BplusLeafIo} implementation for {@link HashIndexTree}.
+ */
+public class HashIndexTreeLeafIo extends BplusLeafIo<HashIndexRowKey> 
implements HashIndexTreeIo {
+    /** I/O versions. */
+    public static final IoVersions<HashIndexTreeLeafIo> VERSIONS = new 
IoVersions<>(new HashIndexTreeLeafIo(1));
+
+    /**
+     * Constructor.
+     *
+     * @param ver Page format version.
+     */
+    protected HashIndexTreeLeafIo(int ver) {
+        super(IndexPageTypes.T_HASH_INDEX_LEAF_IO, ver, SIZE_IN_BYTES);
+    }
+
+    @Override
+    public void store(long dstPageAddr, int dstIdx, BplusIo<HashIndexRowKey> 
srcIo, long srcPageAddr, int srcIdx) {
+        HashIndexTreeIo.super.store(dstPageAddr, dstIdx, srcIo, srcPageAddr, 
srcIdx);
+    }
+
+    @Override
+    public void storeByOffset(long pageAddr, int off, HashIndexRowKey row) {
+        HashIndexTreeIo.super.storeByOffset(pageAddr, off, row);
+    }
+
+    @Override
+    public HashIndexRowKey getLookupRow(BplusTree<HashIndexRowKey, ?> tree, 
long pageAddr, int idx) throws IgniteInternalCheckedException {
+        HashIndexTree hashIndexTree = (HashIndexTree) tree;
+
+        return getRow(hashIndexTree.dataPageReader(), 
hashIndexTree.partitionId(), pageAddr, idx);
+    }
+}
diff --git 
a/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/index/meta/io/IndexMetaTreeMetaIo.java
 
b/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/index/hash/io/HashIndexTreeMetaIo.java
similarity index 66%
copy from 
modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/index/meta/io/IndexMetaTreeMetaIo.java
copy to 
modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/index/hash/io/HashIndexTreeMetaIo.java
index 9e8d229c15..46a7ad0760 100644
--- 
a/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/index/meta/io/IndexMetaTreeMetaIo.java
+++ 
b/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/index/hash/io/HashIndexTreeMetaIo.java
@@ -15,28 +15,26 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.internal.storage.pagememory.index.meta.io;
+package org.apache.ignite.internal.storage.pagememory.index.hash.io;
 
 import org.apache.ignite.internal.pagememory.io.IoVersions;
 import org.apache.ignite.internal.pagememory.tree.io.BplusMetaIo;
-import org.apache.ignite.internal.storage.pagememory.index.meta.IndexMetaTree;
+import org.apache.ignite.internal.storage.pagememory.index.IndexPageTypes;
+import org.apache.ignite.internal.storage.pagememory.index.hash.HashIndexTree;
 
 /**
- * IO routines for {@link IndexMetaTree} meta pages.
+ * IO routines for {@link HashIndexTree} meta pages.
  */
-public class IndexMetaTreeMetaIo extends BplusMetaIo {
-    /** Page IO type. */
-    public static final short T_INDEX_META_TREE_META_IO = 13;
-
+public class HashIndexTreeMetaIo extends BplusMetaIo {
     /** I/O versions. */
-    public static final IoVersions<IndexMetaTreeMetaIo> VERSIONS = new 
IoVersions<>(new IndexMetaTreeMetaIo(1));
+    public static final IoVersions<HashIndexTreeMetaIo> VERSIONS = new 
IoVersions<>(new HashIndexTreeMetaIo(1));
 
     /**
      * Constructor.
      *
      * @param ver Page format version.
      */
-    protected IndexMetaTreeMetaIo(int ver) {
-        super(T_INDEX_META_TREE_META_IO, ver);
+    protected HashIndexTreeMetaIo(int ver) {
+        super(IndexPageTypes.T_HASH_INDEX_META_IO, ver);
     }
 }
diff --git 
a/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/index/meta/IndexMetaTree.java
 
b/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/index/meta/IndexMetaTree.java
index e3d55bb0d6..99daf6b7ef 100644
--- 
a/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/index/meta/IndexMetaTree.java
+++ 
b/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/index/meta/IndexMetaTree.java
@@ -31,7 +31,7 @@ import org.apache.ignite.lang.IgniteInternalCheckedException;
 import org.jetbrains.annotations.Nullable;
 
 /**
- * Tree for storing index meta-information, such as the root of an index tree.
+ * {@link BplusTree} implementation for storing {@link IndexMeta}.
  */
 public class IndexMetaTree extends BplusTree<IndexMeta, IndexMeta> {
     /**
@@ -59,24 +59,13 @@ public class IndexMetaTree extends BplusTree<IndexMeta, 
IndexMeta> {
             @Nullable ReuseList reuseList,
             boolean initNew
     ) throws IgniteInternalCheckedException {
-        super(
-                "IndexMetaTree_" + grpId,
-                grpId,
-                grpName,
-                partId,
-                pageMem,
-                lockLsnr,
-                globalRmvId,
-                metaPageId,
-                reuseList
-        );
+        super("IndexMetaTree_" + grpId, grpId, grpName, partId, pageMem, 
lockLsnr, globalRmvId, metaPageId, reuseList);
 
         setIos(IndexMetaInnerIo.VERSIONS, IndexMetaLeafIo.VERSIONS, 
IndexMetaTreeMetaIo.VERSIONS);
 
         initTree(initNew);
     }
 
-    /** {@inheritDoc} */
     @Override
     protected int compare(BplusIo<IndexMeta> io, long pageAddr, int idx, 
IndexMeta row) {
         IndexMetaIo indexMetaIo = (IndexMetaIo) io;
@@ -84,7 +73,6 @@ public class IndexMetaTree extends BplusTree<IndexMeta, 
IndexMeta> {
         return indexMetaIo.compare(pageAddr, idx, row);
     }
 
-    /** {@inheritDoc} */
     @Override
     public IndexMeta getRow(BplusIo<IndexMeta> io, long pageAddr, int idx, 
Object x) {
         IndexMetaIo indexMetaIo = (IndexMetaIo) io;
diff --git 
a/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/index/meta/io/IndexMetaInnerIo.java
 
b/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/index/meta/io/IndexMetaInnerIo.java
index 0dca86a381..18507991c9 100644
--- 
a/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/index/meta/io/IndexMetaInnerIo.java
+++ 
b/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/index/meta/io/IndexMetaInnerIo.java
@@ -21,6 +21,7 @@ import org.apache.ignite.internal.pagememory.io.IoVersions;
 import org.apache.ignite.internal.pagememory.tree.BplusTree;
 import org.apache.ignite.internal.pagememory.tree.io.BplusInnerIo;
 import org.apache.ignite.internal.pagememory.tree.io.BplusIo;
+import org.apache.ignite.internal.storage.pagememory.index.IndexPageTypes;
 import org.apache.ignite.internal.storage.pagememory.index.meta.IndexMeta;
 import org.apache.ignite.internal.storage.pagememory.index.meta.IndexMetaTree;
 
@@ -28,9 +29,6 @@ import 
org.apache.ignite.internal.storage.pagememory.index.meta.IndexMetaTree;
  * IO routines for {@link IndexMetaTree} inner pages.
  */
 public class IndexMetaInnerIo extends BplusInnerIo<IndexMeta> implements 
IndexMetaIo {
-    /** Page IO type. */
-    public static final short T_INDEX_META_INNER_IO = 15;
-
     /** I/O versions. */
     public static final IoVersions<IndexMetaInnerIo> VERSIONS = new 
IoVersions<>(new IndexMetaInnerIo(1));
 
@@ -40,7 +38,7 @@ public class IndexMetaInnerIo extends BplusInnerIo<IndexMeta> 
implements IndexMe
      * @param ver Page format version.
      */
     private IndexMetaInnerIo(int ver) {
-        super(T_INDEX_META_INNER_IO, ver, true, SIZE_IN_BYTES);
+        super(IndexPageTypes.T_INDEX_META_INNER_IO, ver, true, SIZE_IN_BYTES);
     }
 
     /** {@inheritDoc} */
diff --git 
a/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/index/meta/io/IndexMetaIo.java
 
b/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/index/meta/io/IndexMetaIo.java
index 92b5996912..61e8e95dd4 100644
--- 
a/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/index/meta/io/IndexMetaIo.java
+++ 
b/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/index/meta/io/IndexMetaIo.java
@@ -24,7 +24,6 @@ import java.util.UUID;
 import org.apache.ignite.internal.pagememory.tree.io.BplusIo;
 import org.apache.ignite.internal.pagememory.util.PageUtils;
 import org.apache.ignite.internal.storage.pagememory.index.meta.IndexMeta;
-import org.apache.ignite.internal.storage.pagememory.index.meta.IndexMetaTree;
 
 /**
  * Interface for {@link IndexMeta} B+Tree-related IO.
@@ -56,7 +55,7 @@ public interface IndexMetaIo {
     int offset(int idx);
 
     /**
-     * Compare the index meta from the page with passed index meta, thus 
defining the order of element in the {@link IndexMetaTree}.
+     * Compare the {@link IndexMeta} from the page with passed {@link 
IndexMeta}.
      *
      * @param pageAddr Page address.
      * @param idx Element's index.
diff --git 
a/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/index/meta/io/IndexMetaLeafIo.java
 
b/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/index/meta/io/IndexMetaLeafIo.java
index c5d938d786..5314020808 100644
--- 
a/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/index/meta/io/IndexMetaLeafIo.java
+++ 
b/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/index/meta/io/IndexMetaLeafIo.java
@@ -21,6 +21,7 @@ import org.apache.ignite.internal.pagememory.io.IoVersions;
 import org.apache.ignite.internal.pagememory.tree.BplusTree;
 import org.apache.ignite.internal.pagememory.tree.io.BplusIo;
 import org.apache.ignite.internal.pagememory.tree.io.BplusLeafIo;
+import org.apache.ignite.internal.storage.pagememory.index.IndexPageTypes;
 import org.apache.ignite.internal.storage.pagememory.index.meta.IndexMeta;
 import org.apache.ignite.internal.storage.pagememory.index.meta.IndexMetaTree;
 
@@ -28,9 +29,6 @@ import 
org.apache.ignite.internal.storage.pagememory.index.meta.IndexMetaTree;
  * IO routines for {@link IndexMetaTree} leaf pages.
  */
 public class IndexMetaLeafIo extends BplusLeafIo<IndexMeta> implements 
IndexMetaIo {
-    /** Page IO type. */
-    public static final short T_INDEX_META_LEAF_IO = 14;
-
     /** I/O versions. */
     public static final IoVersions<IndexMetaLeafIo> VERSIONS = new 
IoVersions<>(new IndexMetaLeafIo(1));
 
@@ -40,7 +38,7 @@ public class IndexMetaLeafIo extends BplusLeafIo<IndexMeta> 
implements IndexMeta
      * @param ver Page format version.
      */
     private IndexMetaLeafIo(int ver) {
-        super(T_INDEX_META_LEAF_IO, ver, SIZE_IN_BYTES);
+        super(IndexPageTypes.T_INDEX_META_LEAF_IO, ver, SIZE_IN_BYTES);
     }
 
     /** {@inheritDoc} */
diff --git 
a/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/index/meta/io/IndexMetaTreeMetaIo.java
 
b/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/index/meta/io/IndexMetaTreeMetaIo.java
index 9e8d229c15..9b67160131 100644
--- 
a/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/index/meta/io/IndexMetaTreeMetaIo.java
+++ 
b/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/index/meta/io/IndexMetaTreeMetaIo.java
@@ -19,15 +19,13 @@ package 
org.apache.ignite.internal.storage.pagememory.index.meta.io;
 
 import org.apache.ignite.internal.pagememory.io.IoVersions;
 import org.apache.ignite.internal.pagememory.tree.io.BplusMetaIo;
+import org.apache.ignite.internal.storage.pagememory.index.IndexPageTypes;
 import org.apache.ignite.internal.storage.pagememory.index.meta.IndexMetaTree;
 
 /**
  * IO routines for {@link IndexMetaTree} meta pages.
  */
 public class IndexMetaTreeMetaIo extends BplusMetaIo {
-    /** Page IO type. */
-    public static final short T_INDEX_META_TREE_META_IO = 13;
-
     /** I/O versions. */
     public static final IoVersions<IndexMetaTreeMetaIo> VERSIONS = new 
IoVersions<>(new IndexMetaTreeMetaIo(1));
 
@@ -37,6 +35,6 @@ public class IndexMetaTreeMetaIo extends BplusMetaIo {
      * @param ver Page format version.
      */
     protected IndexMetaTreeMetaIo(int ver) {
-        super(T_INDEX_META_TREE_META_IO, ver);
+        super(IndexPageTypes.T_INDEX_META_TREE_META_IO, ver);
     }
 }
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 0261dde770..5c89a17678 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
@@ -17,6 +17,8 @@
 
 package org.apache.ignite.internal.storage.pagememory.mv;
 
+import static org.apache.ignite.internal.pagememory.util.PageIdUtils.NULL_LINK;
+
 import java.nio.ByteBuffer;
 import java.util.NoSuchElementException;
 import java.util.UUID;
@@ -226,9 +228,9 @@ public abstract class AbstractPageMemoryMvPartitionStorage 
implements MvPartitio
         VersionChain currentChain = findVersionChain(rowId);
 
         if (currentChain == null) {
-            RowVersion newVersion = insertRowVersion(row, 
RowVersion.NULL_LINK);
+            RowVersion newVersion = insertRowVersion(row, NULL_LINK);
 
-            VersionChain versionChain = new VersionChain(rowId, txId, 
newVersion.link(), RowVersion.NULL_LINK);
+            VersionChain versionChain = new VersionChain(rowId, txId, 
newVersion.link(), NULL_LINK);
 
             updateVersionChain(versionChain);
 
@@ -277,7 +279,7 @@ public abstract class AbstractPageMemoryMvPartitionStorage 
implements MvPartitio
             // Next can be safely replaced with any value (like 0), because 
this field is only used when there
             // is some uncommitted value, but when we add an uncommitted 
value, we 'fix' such placeholder value
             // (like 0) by replacing it with a valid value.
-            VersionChain versionChainReplacement = new VersionChain(rowId, 
null, latestVersion.nextLink(), RowVersion.NULL_LINK);
+            VersionChain versionChainReplacement = new VersionChain(rowId, 
null, latestVersion.nextLink(), NULL_LINK);
 
             updateVersionChain(versionChainReplacement);
         } else {
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 3441318ef6..20f5988449 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
@@ -17,7 +17,7 @@
 
 package org.apache.ignite.internal.storage.pagememory.mv;
 
-import static 
org.apache.ignite.internal.storage.pagememory.mv.PartitionlessLinks.readPartitionlessLink;
+import static 
org.apache.ignite.internal.pagememory.util.PartitionlessLinks.readPartitionlessLink;
 
 import java.nio.ByteBuffer;
 import java.util.function.Predicate;
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 2b13ab7ccd..60425a0402 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
@@ -18,6 +18,7 @@
 package org.apache.ignite.internal.storage.pagememory.mv;
 
 import static org.apache.ignite.hlc.HybridTimestamp.HYBRID_TIMESTAMP_SIZE;
+import static org.apache.ignite.internal.pagememory.util.PageIdUtils.NULL_LINK;
 
 import java.nio.ByteBuffer;
 import java.util.Objects;
@@ -25,6 +26,7 @@ import org.apache.ignite.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;
+import org.apache.ignite.internal.pagememory.util.PartitionlessLinks;
 import org.apache.ignite.internal.storage.pagememory.mv.io.RowVersionDataIo;
 import org.apache.ignite.internal.tostring.IgniteToStringExclude;
 import org.apache.ignite.internal.tostring.S;
@@ -34,9 +36,6 @@ import org.jetbrains.annotations.Nullable;
  * Represents row version inside row version chain.
  */
 public final class RowVersion implements Storable {
-    /** Represents an absent partitionless link. */
-    public static final long NULL_LINK = 0;
-
     private static final int NEXT_LINK_STORE_SIZE_BYTES = 
PartitionlessLinks.PARTITIONLESS_LINK_SIZE_BYTES;
     private static final int VALUE_SIZE_STORE_SIZE_BYTES = Integer.BYTES;
 
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 a3a20abb06..c853b4da95 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
@@ -39,8 +39,6 @@ import org.jetbrains.annotations.Nullable;
 public class RowVersionFreeList extends AbstractFreeList<RowVersion> {
     private static final IgniteLogger LOG = 
Loggers.forClass(RowVersionFreeList.class);
 
-    private final PageEvictionTracker evictionTracker;
-
     private final IoStatisticsHolder statHolder;
 
     private final UpdateTimestampHandler updateTimestampHandler = new 
UpdateTimestampHandler();
@@ -87,7 +85,6 @@ public class RowVersionFreeList extends 
AbstractFreeList<RowVersion> {
                 evictionTracker
         );
 
-        this.evictionTracker = evictionTracker;
         this.statHolder = statHolder;
     }
 
diff --git 
a/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/mv/ScanVersionChainByTimestamp.java
 
b/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/mv/ScanVersionChainByTimestamp.java
index 569c2086aa..7d5fd1616e 100644
--- 
a/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/mv/ScanVersionChainByTimestamp.java
+++ 
b/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/mv/ScanVersionChainByTimestamp.java
@@ -17,7 +17,8 @@
 
 package org.apache.ignite.internal.storage.pagememory.mv;
 
-import static 
org.apache.ignite.internal.storage.pagememory.mv.PartitionlessLinks.readPartitionlessLink;
+import static org.apache.ignite.internal.pagememory.util.PageIdUtils.NULL_LINK;
+import static 
org.apache.ignite.internal.pagememory.util.PartitionlessLinks.readPartitionlessLink;
 
 import org.apache.ignite.hlc.HybridTimestamp;
 import org.apache.ignite.internal.pagememory.datapage.PageMemoryTraversal;
@@ -80,7 +81,7 @@ class ScanVersionChainByTimestamp implements 
PageMemoryTraversal<HybridTimestamp
     private long advanceToNextVersion(long pageAddr, DataPagePayload payload) {
         long nextLink = readPartitionlessLink(partitionId, pageAddr, 
payload.offset() + RowVersion.NEXT_LINK_OFFSET);
 
-        if (nextLink == RowVersion.NULL_LINK) {
+        if (nextLink == NULL_LINK) {
             return STOP_TRAVERSAL;
         }
 
diff --git 
a/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/mv/VersionChain.java
 
b/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/mv/VersionChain.java
index 022c1cf94a..c650bed4dc 100644
--- 
a/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/mv/VersionChain.java
+++ 
b/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/mv/VersionChain.java
@@ -17,6 +17,8 @@
 
 package org.apache.ignite.internal.storage.pagememory.mv;
 
+import static org.apache.ignite.internal.pagememory.util.PageIdUtils.NULL_LINK;
+
 import java.util.UUID;
 import org.apache.ignite.internal.storage.RowId;
 import org.apache.ignite.internal.tostring.S;
@@ -91,7 +93,7 @@ public class VersionChain extends VersionChainKey {
      * Returns {@code true} if this version chain has at least one committed 
version.
      */
     public boolean hasCommittedVersions() {
-        return newestCommittedLink() != RowVersion.NULL_LINK;
+        return newestCommittedLink() != NULL_LINK;
     }
 
     /** {@inheritDoc} */
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 c7d8846112..23c6b17988 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
@@ -20,14 +20,14 @@ package org.apache.ignite.internal.storage.pagememory.mv.io;
 import static 
org.apache.ignite.internal.pagememory.util.PageUtils.putByteBuffer;
 import static org.apache.ignite.internal.pagememory.util.PageUtils.putInt;
 import static org.apache.ignite.internal.pagememory.util.PageUtils.putShort;
-import static 
org.apache.ignite.internal.storage.pagememory.mv.PartitionlessLinks.writePartitionlessLink;
+import static 
org.apache.ignite.internal.pagememory.util.PartitionlessLinks.writePartitionlessLink;
 
 import java.nio.ByteBuffer;
 import org.apache.ignite.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;
 import org.apache.ignite.internal.storage.pagememory.mv.HybridTimestamps;
-import org.apache.ignite.internal.storage.pagememory.mv.PartitionlessLinks;
 import org.apache.ignite.internal.storage.pagememory.mv.RowVersion;
 import org.apache.ignite.lang.IgniteStringBuilder;
 import org.jetbrains.annotations.Nullable;
@@ -73,44 +73,29 @@ public class RowVersionDataIo extends 
AbstractDataPageIo<RowVersion> {
 
     /** {@inheritDoc} */
     @Override
-    protected void writeFragmentData(RowVersion row, ByteBuffer buf, int 
rowOff, int payloadSize) {
-        assertPageType(buf);
+    protected void writeFragmentData(RowVersion row, ByteBuffer pageBuf, int 
rowOff, int payloadSize) {
+        assertPageType(pageBuf);
 
         if (rowOff == 0) {
             // first fragment
             assert row.headerSize() <= payloadSize : "Header must entirely fit 
in the first fragment, but header size is "
                     + row.headerSize() + " and payload size is " + payloadSize;
 
-            HybridTimestamps.writeTimestampToBuffer(buf, row.timestamp());
+            HybridTimestamps.writeTimestampToBuffer(pageBuf, row.timestamp());
 
-            PartitionlessLinks.writeToBuffer(buf, row.nextLink());
+            PartitionlessLinks.writeToBuffer(pageBuf, row.nextLink());
 
-            buf.putInt(row.valueSize());
+            pageBuf.putInt(row.valueSize());
 
-            int valueBytesToWrite = payloadSize - row.headerSize();
-            putValueFragmentToBuffer(row, buf, 0, valueBytesToWrite);
+            putValueBufferIntoPage(pageBuf, row.value(), 0, payloadSize - 
row.headerSize());
         } else {
             // non-first fragment
-            assert rowOff > row.headerSize();
+            assert rowOff >= row.headerSize();
 
-            putValueFragmentToBuffer(row, buf, rowOff - row.headerSize(), 
payloadSize);
+            putValueBufferIntoPage(pageBuf, row.value(), rowOff - 
row.headerSize(), payloadSize);
         }
     }
 
-    private void putValueFragmentToBuffer(RowVersion row, ByteBuffer buf, int 
readBufferPosition, int valueBytesToWrite) {
-        ByteBuffer valueBuffer = row.value();
-
-        int oldLimit = valueBuffer.limit();
-        int oldPosition = valueBuffer.position();
-
-        valueBuffer.position(readBufferPosition);
-        valueBuffer.limit(valueBuffer.position() + valueBytesToWrite);
-        buf.put(valueBuffer);
-
-        valueBuffer.position(oldPosition);
-        valueBuffer.limit(oldLimit);
-    }
-
     /**
      * Updates timestamp leaving the rest untouched.
      *
diff --git 
a/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/mv/io/VersionChainIo.java
 
b/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/mv/io/VersionChainIo.java
index 63461d1257..7e5fcbe202 100644
--- 
a/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/mv/io/VersionChainIo.java
+++ 
b/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/mv/io/VersionChainIo.java
@@ -19,9 +19,9 @@ package org.apache.ignite.internal.storage.pagememory.mv.io;
 
 import static org.apache.ignite.internal.pagememory.util.PageUtils.getLong;
 import static org.apache.ignite.internal.pagememory.util.PageUtils.putLong;
-import static 
org.apache.ignite.internal.storage.pagememory.mv.PartitionlessLinks.PARTITIONLESS_LINK_SIZE_BYTES;
-import static 
org.apache.ignite.internal.storage.pagememory.mv.PartitionlessLinks.readPartitionlessLink;
-import static 
org.apache.ignite.internal.storage.pagememory.mv.PartitionlessLinks.writePartitionlessLink;
+import static 
org.apache.ignite.internal.pagememory.util.PartitionlessLinks.PARTITIONLESS_LINK_SIZE_BYTES;
+import static 
org.apache.ignite.internal.pagememory.util.PartitionlessLinks.readPartitionlessLink;
+import static 
org.apache.ignite.internal.pagememory.util.PartitionlessLinks.writePartitionlessLink;
 import static 
org.apache.ignite.internal.storage.pagememory.mv.VersionChain.NULL_UUID_COMPONENT;
 
 import java.util.UUID;

Reply via email to