You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by ir...@apache.org on 2018/05/23 23:06:44 UTC

ignite git commit: IGNITE-4958 Make data pages recyclable into index/meta/etc pages and vice versa - Fixes #3780.

Repository: ignite
Updated Branches:
  refs/heads/master 24cee46b1 -> 0bdfa20cd


IGNITE-4958 Make data pages recyclable into index/meta/etc pages and vice versa - Fixes #3780.

Signed-off-by: Ivan Rakov <ir...@apache.org>


Project: http://git-wip-us.apache.org/repos/asf/ignite/repo
Commit: http://git-wip-us.apache.org/repos/asf/ignite/commit/0bdfa20c
Tree: http://git-wip-us.apache.org/repos/asf/ignite/tree/0bdfa20c
Diff: http://git-wip-us.apache.org/repos/asf/ignite/diff/0bdfa20c

Branch: refs/heads/master
Commit: 0bdfa20cd393ea896a59f0fbd9270f8b8128dae6
Parents: 24cee46
Author: Dmitriy Sorokin <d....@gmail.com>
Authored: Thu May 24 01:56:01 2018 +0300
Committer: Ivan Rakov <ir...@apache.org>
Committed: Thu May 24 01:56:01 2018 +0300

----------------------------------------------------------------------
 .../ignite/internal/pagemem/PageIdUtils.java    |   5 +-
 .../internal/pagemem/wal/record/WALRecord.java  |   5 +-
 .../wal/record/delta/RotatedIdPartRecord.java   |  66 ++++++++
 .../cache/persistence/DataStructure.java        |  34 ++++-
 .../persistence/freelist/AbstractFreeList.java  | 104 ++++++++-----
 .../cache/persistence/freelist/PagesList.java   |  60 ++++++--
 .../cache/persistence/tree/BPlusTree.java       |  29 +---
 .../cache/persistence/tree/io/PageIO.java       |  35 ++++-
 .../tree/reuse/LongListReuseBag.java            |  46 ++++++
 .../persistence/wal/record/RecordTypes.java     |   1 +
 .../wal/serializer/RecordDataV1Serializer.java  |  24 +++
 .../pagemem/impl/PageIdUtilsSelfTest.java       |   3 +-
 ...ageEvictionPagesRecyclingAndReusingTest.java | 153 +++++++++++++++++++
 .../pagemem/FillFactorMetricTest.java           |   5 +-
 .../IgniteCacheEvictionSelfTestSuite.java       |   3 +
 15 files changed, 479 insertions(+), 94 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ignite/blob/0bdfa20c/modules/core/src/main/java/org/apache/ignite/internal/pagemem/PageIdUtils.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/pagemem/PageIdUtils.java b/modules/core/src/main/java/org/apache/ignite/internal/pagemem/PageIdUtils.java
index 2754d79..1335269 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/pagemem/PageIdUtils.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/pagemem/PageIdUtils.java
@@ -184,7 +184,10 @@ public final class PageIdUtils {
      * @return New page ID.
      */
     public static long rotatePageId(long pageId) {
-        long updatedRotationId = (pageId >> PAGE_IDX_SIZE + PART_ID_SIZE + FLAG_SIZE) + 1;
+        long updatedRotationId = (pageId >>> PAGE_IDX_SIZE + PART_ID_SIZE + FLAG_SIZE) + 1;
+
+        if (updatedRotationId > MAX_ITEMID_NUM)
+            updatedRotationId = 1; // We always want non-zero updatedRotationId
 
         return (pageId & PAGE_ID_MASK) |
             (updatedRotationId << (PAGE_IDX_SIZE + PART_ID_SIZE + FLAG_SIZE));

http://git-wip-us.apache.org/repos/asf/ignite/blob/0bdfa20c/modules/core/src/main/java/org/apache/ignite/internal/pagemem/wal/record/WALRecord.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/pagemem/wal/record/WALRecord.java b/modules/core/src/main/java/org/apache/ignite/internal/pagemem/wal/record/WALRecord.java
index 4fae179..52bd034 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/pagemem/wal/record/WALRecord.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/pagemem/wal/record/WALRecord.java
@@ -175,7 +175,10 @@ public abstract class WALRecord {
         EXCHANGE,
 
         /** Baseline topology record. */
-        BASELINE_TOP_RECORD;
+        BASELINE_TOP_RECORD,
+
+        /** Rotated id part record. */
+        ROTATED_ID_PART_RECORD;
 
         /** */
         private static final RecordType[] VALS = RecordType.values();

http://git-wip-us.apache.org/repos/asf/ignite/blob/0bdfa20c/modules/core/src/main/java/org/apache/ignite/internal/pagemem/wal/record/delta/RotatedIdPartRecord.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/pagemem/wal/record/delta/RotatedIdPartRecord.java b/modules/core/src/main/java/org/apache/ignite/internal/pagemem/wal/record/delta/RotatedIdPartRecord.java
new file mode 100644
index 0000000..24a45c6
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/pagemem/wal/record/delta/RotatedIdPartRecord.java
@@ -0,0 +1,66 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.pagemem.wal.record.delta;
+
+import org.apache.ignite.IgniteCheckedException;
+import org.apache.ignite.internal.pagemem.PageMemory;
+import org.apache.ignite.internal.processors.cache.persistence.tree.io.PageIO;
+import org.apache.ignite.internal.util.typedef.internal.S;
+
+/**
+ * Rotated (when page has been recycled) id part delta record.
+ */
+public class RotatedIdPartRecord extends PageDeltaRecord {
+    /** Rotated id part. */
+    private byte rotatedIdPart;
+
+    /**
+     * @param grpId Group id.
+     * @param pageId Page id.
+     * @param rotatedIdPart Rotated id part.
+     */
+    public RotatedIdPartRecord(int grpId, long pageId, int rotatedIdPart) {
+        super(grpId, pageId);
+
+        assert rotatedIdPart >= 0 && rotatedIdPart <= 0xFF;
+
+        this.rotatedIdPart = (byte) rotatedIdPart;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void applyDelta(PageMemory pageMem, long pageAddr) throws IgniteCheckedException {
+        PageIO.setRotatedIdPart(pageAddr, rotatedIdPart);
+    }
+
+    /** {@inheritDoc} */
+    @Override public RecordType type() {
+        return RecordType.ROTATED_ID_PART_RECORD;
+    }
+
+    /**
+     * @return Rotated id part.
+     */
+    public byte rotatedIdPart() {
+        return rotatedIdPart;
+    }
+
+    /** {@inheritDoc} */
+    @Override public String toString() {
+        return S.toString(RotatedIdPartRecord.class, this, "super", super.toString());
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/0bdfa20c/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/DataStructure.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/DataStructure.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/DataStructure.java
index 663a139..0177407 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/DataStructure.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/DataStructure.java
@@ -25,6 +25,7 @@ import org.apache.ignite.internal.pagemem.PageIdUtils;
 import org.apache.ignite.internal.pagemem.PageMemory;
 import org.apache.ignite.internal.pagemem.wal.IgniteWriteAheadLogManager;
 import org.apache.ignite.internal.pagemem.wal.record.delta.RecycleRecord;
+import org.apache.ignite.internal.pagemem.wal.record.delta.RotatedIdPartRecord;
 import org.apache.ignite.internal.processors.cache.persistence.tree.io.PageIO;
 import org.apache.ignite.internal.processors.cache.persistence.tree.reuse.ReuseBag;
 import org.apache.ignite.internal.processors.cache.persistence.tree.reuse.ReuseList;
@@ -36,6 +37,7 @@ import static org.apache.ignite.internal.pagemem.PageIdAllocator.FLAG_DATA;
 import static org.apache.ignite.internal.pagemem.PageIdAllocator.FLAG_IDX;
 import static org.apache.ignite.internal.pagemem.PageIdAllocator.INDEX_PARTITION;
 import static org.apache.ignite.internal.pagemem.PageIdAllocator.MAX_PARTITION_ID;
+import static org.apache.ignite.internal.pagemem.PageIdUtils.MAX_ITEMID_NUM;
 
 /**
  * Base class for all the data structures based on {@link PageMemory}.
@@ -346,7 +348,7 @@ public abstract class DataStructure implements PageLockListener {
      * @param page Page pointer.
      * @param pageAddr Page address.
      * @param walPlc Full page WAL record policy.
-     * @return Rotated page ID.
+     * @return Recycled page ID.
      * @throws IgniteCheckedException If failed.
      */
     protected final long recyclePage(
@@ -354,14 +356,34 @@ public abstract class DataStructure implements PageLockListener {
         long page,
         long pageAddr,
         Boolean walPlc) throws IgniteCheckedException {
-        long rotated = PageIdUtils.rotatePageId(pageId);
+        long recycled = 0;
 
-        PageIO.setPageId(pageAddr, rotated);
+        boolean needWalDeltaRecord = needWalDeltaRecord(pageId, page, walPlc);
 
-        if (needWalDeltaRecord(pageId, page, walPlc))
-            wal.log(new RecycleRecord(grpId, pageId, rotated));
+        if (PageIdUtils.tag(pageId) == FLAG_DATA) {
+            int rotatedIdPart = PageIO.getRotatedIdPart(pageAddr);
 
-        return rotated;
+            if (rotatedIdPart != 0) {
+                recycled = PageIdUtils.link(pageId, rotatedIdPart > MAX_ITEMID_NUM ? 1 : rotatedIdPart);
+
+                PageIO.setRotatedIdPart(pageAddr, 0);
+
+                if (needWalDeltaRecord)
+                    wal.log(new RotatedIdPartRecord(grpId, pageId, 0));
+            }
+        }
+
+        if (recycled == 0)
+            recycled = PageIdUtils.rotatePageId(pageId);
+
+        assert PageIdUtils.itemId(recycled) > 0 && PageIdUtils.itemId(recycled) <= MAX_ITEMID_NUM : U.hexLong(recycled);
+
+        PageIO.setPageId(pageAddr, recycled);
+
+        if (needWalDeltaRecord)
+            wal.log(new RecycleRecord(grpId, pageId, recycled));
+
+        return recycled;
     }
 
     /**

http://git-wip-us.apache.org/repos/asf/ignite/blob/0bdfa20c/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/freelist/AbstractFreeList.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/freelist/AbstractFreeList.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/freelist/AbstractFreeList.java
index 5f5948d..c1a48bb 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/freelist/AbstractFreeList.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/freelist/AbstractFreeList.java
@@ -28,18 +28,18 @@ import org.apache.ignite.internal.pagemem.wal.record.delta.DataPageInsertFragmen
 import org.apache.ignite.internal.pagemem.wal.record.delta.DataPageInsertRecord;
 import org.apache.ignite.internal.pagemem.wal.record.delta.DataPageRemoveRecord;
 import org.apache.ignite.internal.pagemem.wal.record.delta.DataPageUpdateRecord;
-import org.apache.ignite.internal.processors.cache.persistence.DataRegionMetricsImpl;
 import org.apache.ignite.internal.processors.cache.persistence.DataRegion;
+import org.apache.ignite.internal.processors.cache.persistence.DataRegionMetricsImpl;
 import org.apache.ignite.internal.processors.cache.persistence.Storable;
 import org.apache.ignite.internal.processors.cache.persistence.evict.PageEvictionTracker;
 import org.apache.ignite.internal.processors.cache.persistence.tree.io.AbstractDataPageIO;
 import org.apache.ignite.internal.processors.cache.persistence.tree.io.DataPagePayload;
 import org.apache.ignite.internal.processors.cache.persistence.tree.io.IOVersions;
 import org.apache.ignite.internal.processors.cache.persistence.tree.io.PageIO;
+import org.apache.ignite.internal.processors.cache.persistence.tree.reuse.LongListReuseBag;
 import org.apache.ignite.internal.processors.cache.persistence.tree.reuse.ReuseBag;
 import org.apache.ignite.internal.processors.cache.persistence.tree.reuse.ReuseList;
 import org.apache.ignite.internal.processors.cache.persistence.tree.util.PageHandler;
-import org.apache.ignite.internal.util.typedef.T2;
 import org.apache.ignite.internal.util.typedef.internal.U;
 
 /**
@@ -76,9 +76,6 @@ public abstract class AbstractFreeList<T extends Storable> extends PagesList imp
     private final int MIN_SIZE_FOR_DATA_PAGE;
 
     /** */
-    private final int emptyDataPagesBucket;
-
-    /** */
     private final PageHandler<T, Boolean> updateRow = new UpdateRowHandler();
 
     /** */
@@ -256,12 +253,12 @@ public abstract class AbstractFreeList<T extends Storable> extends PagesList imp
     }
 
     /** */
-    private final PageHandler<Void, Long> rmvRow;
+    private final PageHandler<ReuseBag, Long> rmvRow;
 
     /**
      *
      */
-    private final class RemoveRowHandler extends PageHandler<Void, Long> {
+    private final class RemoveRowHandler extends PageHandler<ReuseBag, Long> {
         /** Indicates whether partition ID should be masked from page ID. */
         private final boolean maskPartId;
 
@@ -277,7 +274,7 @@ public abstract class AbstractFreeList<T extends Storable> extends PagesList imp
             long pageAddr,
             PageIO iox,
             Boolean walPlc,
-            Void ignored,
+            ReuseBag reuseBag,
             int itemId)
             throws IgniteCheckedException {
             AbstractDataPageIO<T> io = (AbstractDataPageIO<T>)iox;
@@ -296,21 +293,27 @@ public abstract class AbstractFreeList<T extends Storable> extends PagesList imp
             if (newFreeSpace > MIN_PAGE_FREE_SPACE) {
                 int newBucket = bucket(newFreeSpace, false);
 
-                if (oldFreeSpace > MIN_PAGE_FREE_SPACE) {
+                boolean putIsNeeded = oldFreeSpace <= MIN_PAGE_FREE_SPACE;
+
+                if (!putIsNeeded) {
                     int oldBucket = bucket(oldFreeSpace, false);
 
                     if (oldBucket != newBucket) {
                         // It is possible that page was concurrently taken for put, in this case put will handle bucket change.
                         pageId = maskPartId ? PageIdUtils.maskPartitionId(pageId) : pageId;
-                        if (removeDataPage(pageId, page, pageAddr, io, oldBucket))
-                            put(null, pageId, page, pageAddr, newBucket);
+
+                        putIsNeeded = removeDataPage(pageId, page, pageAddr, io, oldBucket);
                     }
                 }
-                else
-                    put(null, pageId, page, pageAddr, newBucket);
 
-                if (io.isEmpty(pageAddr))
+                if (io.isEmpty(pageAddr)) {
                     evictionTracker.forgetPage(pageId);
+
+                    if (putIsNeeded)
+                        reuseBag.addFreePage(recyclePage(pageId, page, pageAddr, null));
+                }
+                else if (putIsNeeded)
+                    put(null, pageId, page, pageAddr, newBucket);
             }
 
             // For common case boxed 0L will be cached inside of Long, so no garbage will be produced.
@@ -365,8 +368,6 @@ public abstract class AbstractFreeList<T extends Storable> extends PagesList imp
 
         this.memMetrics = memMetrics;
 
-        emptyDataPagesBucket = bucket(MIN_SIZE_FOR_DATA_PAGE, false);
-
         init(metaPageId, initNew);
     }
 
@@ -473,44 +474,63 @@ public abstract class AbstractFreeList<T extends Storable> extends PagesList imp
             if (written != 0)
                 memMetrics.incrementLargeEntriesPages();
 
-            int freeSpace = Math.min(MIN_SIZE_FOR_DATA_PAGE, rowSize - written);
+            int remaining = rowSize - written;
 
             long pageId = 0L;
 
-            if (freeSpace == MIN_SIZE_FOR_DATA_PAGE)
-                pageId = takeEmptyPage(emptyDataPagesBucket, ioVersions());
-
-            boolean reuseBucket = false;
-
-            // TODO: properly handle reuse bucket.
-            if (pageId == 0L) {
-                for (int b = bucket(freeSpace, false) + 1; b < BUCKETS - 1; b++) {
-                    pageId = takeEmptyPage(b, ioVersions());
-
-                    if (pageId != 0L) {
-                        reuseBucket = isReuseBucket(b);
+            for (int b = remaining < MIN_SIZE_FOR_DATA_PAGE ? bucket(remaining, false) + 1 : REUSE_BUCKET; b < BUCKETS; b++) {
+                pageId = takeEmptyPage(b, ioVersions());
 
-                        break;
-                    }
-                }
+                if (pageId != 0L)
+                    break;
             }
 
-            boolean allocated = pageId == 0L;
+            AbstractDataPageIO<T> initIo = null;
 
-            if (allocated)
+            if (pageId == 0L) {
                 pageId = allocateDataPage(row.partition());
+
+                initIo = ioVersions().latest();
+            }
+            else if (PageIdUtils.tag(pageId) != PageIdAllocator.FLAG_DATA)
+                pageId = initReusedPage(pageId, row.partition());
             else
                 pageId = PageIdUtils.changePartitionId(pageId, (row.partition()));
 
-            AbstractDataPageIO<T> init = reuseBucket || allocated ? ioVersions().latest() : null;
-
-            written = write(pageId, writeRow, init, row, written, FAIL_I);
+            written = write(pageId, writeRow, initIo, row, written, FAIL_I);
 
             assert written != FAIL_I; // We can't fail here.
         }
         while (written != COMPLETE);
     }
 
+    /**
+     * @param reusedPageId Reused page id.
+     * @param partId Partition id.
+     * @return Prepared page id.
+     *
+     * @see PagesList#initReusedPage(long, long, long, int, byte, PageIO)
+     */
+    private long initReusedPage(long reusedPageId, int partId) throws IgniteCheckedException {
+        long reusedPage = acquirePage(reusedPageId);
+        try {
+            long reusedPageAddr = writeLock(reusedPageId, reusedPage);
+
+            assert reusedPageAddr != 0;
+
+            try {
+                return initReusedPage(reusedPageId, reusedPage, reusedPageAddr,
+                    partId, PageIdAllocator.FLAG_DATA, ioVersions().latest());
+            }
+            finally {
+                writeUnlock(reusedPageId, reusedPage, reusedPageAddr, true);
+            }
+        }
+        finally {
+            releasePage(reusedPageId, reusedPage);
+        }
+    }
+
     /** {@inheritDoc} */
     @Override public boolean updateDataRow(long link, T row) throws IgniteCheckedException {
         assert link != 0;
@@ -532,7 +552,9 @@ public abstract class AbstractFreeList<T extends Storable> extends PagesList imp
         long pageId = PageIdUtils.pageId(link);
         int itemId = PageIdUtils.itemId(link);
 
-        long nextLink = write(pageId, rmvRow, itemId, FAIL_L);
+        ReuseBag bag = new LongListReuseBag();
+
+        long nextLink = write(pageId, rmvRow, bag, itemId, FAIL_L);
 
         assert nextLink != FAIL_L; // Can't fail here.
 
@@ -542,10 +564,12 @@ public abstract class AbstractFreeList<T extends Storable> extends PagesList imp
             itemId = PageIdUtils.itemId(nextLink);
             pageId = PageIdUtils.pageId(nextLink);
 
-            nextLink = write(pageId, rmvRow, itemId, FAIL_L);
+            nextLink = write(pageId, rmvRow, bag, itemId, FAIL_L);
 
             assert nextLink != FAIL_L; // Can't fail here.
         }
+
+        reuseList.addForRecycle(bag);
     }
 
     /** {@inheritDoc} */
@@ -567,7 +591,7 @@ public abstract class AbstractFreeList<T extends Storable> extends PagesList imp
      * @return Number of empty data pages in free list.
      */
     public int emptyDataPages() {
-        return bucketsSize[emptyDataPagesBucket].intValue();
+        return bucketsSize[REUSE_BUCKET].intValue();
     }
 
     /** {@inheritDoc} */

http://git-wip-us.apache.org/repos/asf/ignite/blob/0bdfa20c/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/freelist/PagesList.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/freelist/PagesList.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/freelist/PagesList.java
index ed77674..905507e 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/freelist/PagesList.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/freelist/PagesList.java
@@ -35,6 +35,7 @@ import org.apache.ignite.internal.pagemem.wal.record.delta.PagesListInitNewPageR
 import org.apache.ignite.internal.pagemem.wal.record.delta.PagesListRemovePageRecord;
 import org.apache.ignite.internal.pagemem.wal.record.delta.PagesListSetNextRecord;
 import org.apache.ignite.internal.pagemem.wal.record.delta.PagesListSetPreviousRecord;
+import org.apache.ignite.internal.pagemem.wal.record.delta.RotatedIdPartRecord;
 import org.apache.ignite.internal.processors.cache.persistence.DataStructure;
 import org.apache.ignite.internal.processors.cache.persistence.freelist.io.PagesListMetaIO;
 import org.apache.ignite.internal.processors.cache.persistence.freelist.io.PagesListNodeIO;
@@ -55,6 +56,7 @@ import static java.lang.Boolean.FALSE;
 import static java.lang.Boolean.TRUE;
 import static org.apache.ignite.internal.pagemem.PageIdAllocator.FLAG_DATA;
 import static org.apache.ignite.internal.pagemem.PageIdAllocator.FLAG_IDX;
+import static org.apache.ignite.internal.pagemem.PageIdUtils.MAX_ITEMID_NUM;
 import static org.apache.ignite.internal.processors.cache.persistence.tree.io.PageIO.getPageId;
 
 /**
@@ -855,6 +857,8 @@ public abstract class PagesList extends DataStructure {
 
         try {
             while ((nextId = bag.pollFreePage()) != 0L) {
+                assert PageIdUtils.itemId(nextId) > 0 && PageIdUtils.itemId(nextId) <= MAX_ITEMID_NUM : U.hexLong(nextId);
+
                 int idx = io.addPage(prevAddr, nextId, pageSize());
 
                 if (idx == -1) { // Attempt to add page failed: the node page is full.
@@ -1070,6 +1074,10 @@ public abstract class PagesList extends DataStructure {
 
                         dirty = true;
 
+                        assert !isReuseBucket(bucket) ||
+                            PageIdUtils.itemId(pageId) > 0 && PageIdUtils.itemId(pageId) <= MAX_ITEMID_NUM
+                            : "Incorrectly recycled pageId in reuse bucket: " + U.hexLong(pageId);
+
                         dataPageId = pageId;
 
                         if (io.isEmpty(tailAddr)) {
@@ -1108,18 +1116,8 @@ public abstract class PagesList extends DataStructure {
 
                         decrementBucketSize(bucket);
 
-                        if (initIoVers != null) {
-                            dataPageId = PageIdUtils.changeType(tailId, FLAG_DATA);
-
-                            PageIO initIo = initIoVers.latest();
-
-                            initIo.initNewPage(tailAddr, dataPageId, pageSize());
-
-                            if (needWalDeltaRecord(tailId, tailPage, null)) {
-                                wal.log(new InitNewPageRecord(grpId, tailId, initIo.getType(),
-                                    initIo.getVersion(), dataPageId));
-                            }
-                        }
+                        if (initIoVers != null)
+                            dataPageId = initReusedPage(tailId, tailPage, tailAddr, 0, FLAG_DATA, initIoVers.latest());
                         else
                             dataPageId = recyclePage(tailId, tailPage, tailAddr, null);
 
@@ -1151,6 +1149,44 @@ public abstract class PagesList extends DataStructure {
     }
 
     /**
+     * Reused page must obtain correctly assembled page id, then initialized by proper {@link PageIO} instance
+     * and non-zero {@code itemId} of reused page id must be saved into special place.
+     *
+     * @param reusedPageId Reused page id.
+     * @param reusedPage Reused page.
+     * @param reusedPageAddr Reused page address.
+     * @param partId Partition id.
+     * @param flag Flag.
+     * @param initIo Initial io.
+     * @return Prepared page id.
+     */
+    protected final long initReusedPage(long reusedPageId, long reusedPage, long reusedPageAddr,
+        int partId, byte flag, PageIO initIo) throws IgniteCheckedException {
+
+        long newPageId = PageIdUtils.pageId(partId, flag, PageIdUtils.pageIndex(reusedPageId));
+
+        initIo.initNewPage(reusedPageAddr, newPageId, pageSize());
+
+        boolean needWalDeltaRecord = needWalDeltaRecord(reusedPageId, reusedPage, null);
+
+        if (needWalDeltaRecord) {
+            wal.log(new InitNewPageRecord(grpId, reusedPageId, initIo.getType(),
+                initIo.getVersion(), newPageId));
+        }
+
+        int itemId = PageIdUtils.itemId(reusedPageId);
+
+        if (itemId != 0) {
+            PageIO.setRotatedIdPart(reusedPageAddr, itemId);
+
+            if (needWalDeltaRecord)
+                wal.log(new RotatedIdPartRecord(grpId, newPageId, itemId));
+        }
+
+        return newPageId;
+    }
+
+    /**
      * @param dataId Data page ID.
      * @param dataPage Data page pointer.
      * @param dataAddr Data page address.

http://git-wip-us.apache.org/repos/asf/ignite/blob/0bdfa20c/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/tree/BPlusTree.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/tree/BPlusTree.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/tree/BPlusTree.java
index 4d05095..e30de5a 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/tree/BPlusTree.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/tree/BPlusTree.java
@@ -17,7 +17,6 @@
 
 package org.apache.ignite.internal.processors.cache.persistence.tree;
 
-import java.io.Externalizable;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -50,6 +49,7 @@ import org.apache.ignite.internal.processors.cache.persistence.tree.io.BPlusLeaf
 import org.apache.ignite.internal.processors.cache.persistence.tree.io.BPlusMetaIO;
 import org.apache.ignite.internal.processors.cache.persistence.tree.io.IOVersions;
 import org.apache.ignite.internal.processors.cache.persistence.tree.io.PageIO;
+import org.apache.ignite.internal.processors.cache.persistence.tree.reuse.LongListReuseBag;
 import org.apache.ignite.internal.processors.cache.persistence.tree.reuse.ReuseBag;
 import org.apache.ignite.internal.processors.cache.persistence.tree.reuse.ReuseList;
 import org.apache.ignite.internal.processors.cache.persistence.tree.util.PageHandler;
@@ -2156,7 +2156,7 @@ public abstract class BPlusTree<L, T extends L> extends DataStructure implements
         if (reuseList == null)
             return -1;
 
-        DestroyBag bag = new DestroyBag();
+        LongListReuseBag bag = new LongListReuseBag();
 
         long pagesCnt = 0;
 
@@ -4836,31 +4836,6 @@ public abstract class BPlusTree<L, T extends L> extends DataStructure implements
     }
 
     /**
-     * Reuse bag for destroy.
-     */
-    protected static final class DestroyBag extends GridLongList implements ReuseBag {
-        /** */
-        private static final long serialVersionUID = 0L;
-
-        /**
-         * Default constructor for {@link Externalizable}.
-         */
-        public DestroyBag() {
-            // No-op.
-        }
-
-        /** {@inheritDoc} */
-        @Override public void addFreePage(long pageId) {
-            add(pageId);
-        }
-
-        /** {@inheritDoc} */
-        @Override public long pollFreePage() {
-            return isEmpty() ? 0 : remove();
-        }
-    }
-
-    /**
      *
      */
     private static class TreeMetaData {

http://git-wip-us.apache.org/repos/asf/ignite/blob/0bdfa20c/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/tree/io/PageIO.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/tree/io/PageIO.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/tree/io/PageIO.java
index c940c39..4534bb5 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/tree/io/PageIO.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/tree/io/PageIO.java
@@ -109,16 +109,25 @@ public abstract class PageIO {
     public static final int PAGE_ID_OFF = CRC_OFF + 4;
 
     /** */
-    private static final int RESERVED_1_OFF = PAGE_ID_OFF + 8;
+    public static final int ROTATED_ID_PART_OFF = PAGE_ID_OFF + 8;
 
     /** */
-    private static final int RESERVED_2_OFF = RESERVED_1_OFF + 8;
+    private static final int RESERVED_BYTE_OFF = ROTATED_ID_PART_OFF + 1;
+
+    /** */
+    private static final int RESERVED_SHORT_OFF = RESERVED_BYTE_OFF + 1;
+
+    /** */
+    private static final int RESERVED_INT_OFF = RESERVED_SHORT_OFF + 2;
+
+    /** */
+    private static final int RESERVED_2_OFF = RESERVED_INT_OFF + 4;
 
     /** */
     private static final int RESERVED_3_OFF = RESERVED_2_OFF + 8;
 
     /** */
-    public static final int COMMON_HEADER_END = RESERVED_3_OFF + 8; // 40=type(2)+ver(2)+crc(4)+pageId(8)+reserved(3*8)
+    public static final int COMMON_HEADER_END = RESERVED_3_OFF + 8; // 40=type(2)+ver(2)+crc(4)+pageId(8)+rotatedIdPart(1)+reserved(1+2+4+2*8)
 
     /* All the page types. */
 
@@ -301,6 +310,24 @@ public abstract class PageIO {
 
     /**
      * @param pageAddr Page address.
+     * @return Rotated page ID part.
+     */
+    public static int getRotatedIdPart(long pageAddr) {
+        return PageUtils.getUnsignedByte(pageAddr, ROTATED_ID_PART_OFF);
+    }
+
+    /**
+     * @param pageAddr Page address.
+     * @param rotatedIdPart Rotated page ID part.
+     */
+    public static void setRotatedIdPart(long pageAddr, int rotatedIdPart) {
+        PageUtils.putUnsignedByte(pageAddr, ROTATED_ID_PART_OFF, rotatedIdPart);
+
+        assert getRotatedIdPart(pageAddr) == rotatedIdPart;
+    }
+
+    /**
+     * @param pageAddr Page address.
      * @return Checksum.
      */
     public static int getCrc(long pageAddr) {
@@ -415,7 +442,7 @@ public abstract class PageIO {
         setPageId(pageAddr, pageId);
         setCrc(pageAddr, 0);
 
-        PageUtils.putLong(pageAddr, RESERVED_1_OFF, 0L);
+        PageUtils.putLong(pageAddr, ROTATED_ID_PART_OFF, 0L); // 1 + reserved(1+2+4)
         PageUtils.putLong(pageAddr, RESERVED_2_OFF, 0L);
         PageUtils.putLong(pageAddr, RESERVED_3_OFF, 0L);
     }

http://git-wip-us.apache.org/repos/asf/ignite/blob/0bdfa20c/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/tree/reuse/LongListReuseBag.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/tree/reuse/LongListReuseBag.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/tree/reuse/LongListReuseBag.java
new file mode 100644
index 0000000..415c603
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/tree/reuse/LongListReuseBag.java
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.processors.cache.persistence.tree.reuse;
+
+import java.io.Externalizable;
+import org.apache.ignite.internal.util.GridLongList;
+
+/**
+ * {@link GridLongList}-based reuse bag.
+ */
+public final class LongListReuseBag extends GridLongList implements ReuseBag {
+    /** */
+    private static final long serialVersionUID = 0L;
+
+    /**
+     * Default constructor for {@link Externalizable}.
+     */
+    public LongListReuseBag() {
+        // No-op.
+    }
+
+    /** {@inheritDoc} */
+    @Override public void addFreePage(long pageId) {
+        add(pageId);
+    }
+
+    /** {@inheritDoc} */
+    @Override public long pollFreePage() {
+        return isEmpty() ? 0 : remove();
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/0bdfa20c/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/wal/record/RecordTypes.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/wal/record/RecordTypes.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/wal/record/RecordTypes.java
index 9654748..1807d1d 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/wal/record/RecordTypes.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/wal/record/RecordTypes.java
@@ -65,5 +65,6 @@ public final class RecordTypes {
         DELTA_TYPE_SET.add(WALRecord.RecordType.PAGE_LIST_META_RESET_COUNT_RECORD);
         DELTA_TYPE_SET.add(WALRecord.RecordType.DATA_PAGE_UPDATE_RECORD);
         DELTA_TYPE_SET.add(WALRecord.RecordType.BTREE_META_PAGE_INIT_ROOT2);
+        DELTA_TYPE_SET.add(WALRecord.RecordType.ROTATED_ID_PART_RECORD);
     }
 }

http://git-wip-us.apache.org/repos/asf/ignite/blob/0bdfa20c/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/wal/serializer/RecordDataV1Serializer.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/wal/serializer/RecordDataV1Serializer.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/wal/serializer/RecordDataV1Serializer.java
index e8d116b..f433d26 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/wal/serializer/RecordDataV1Serializer.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/wal/serializer/RecordDataV1Serializer.java
@@ -73,6 +73,7 @@ import org.apache.ignite.internal.pagemem.wal.record.delta.PartitionMetaStateRec
 import org.apache.ignite.internal.pagemem.wal.record.delta.RecycleRecord;
 import org.apache.ignite.internal.pagemem.wal.record.delta.RemoveRecord;
 import org.apache.ignite.internal.pagemem.wal.record.delta.ReplaceRecord;
+import org.apache.ignite.internal.pagemem.wal.record.delta.RotatedIdPartRecord;
 import org.apache.ignite.internal.pagemem.wal.record.delta.SplitExistingPageRecord;
 import org.apache.ignite.internal.pagemem.wal.record.delta.SplitForwardPageRecord;
 import org.apache.ignite.internal.pagemem.wal.record.delta.TrackingPageDeltaRecord;
@@ -286,6 +287,9 @@ public class RecordDataV1Serializer implements RecordDataSerializer {
             case PAGE_LIST_META_RESET_COUNT_RECORD:
                 return /*cacheId*/ 4 + /*pageId*/ 8;
 
+            case ROTATED_ID_PART_RECORD:
+                return 4 + 8 + 1;
+
             case SWITCH_SEGMENT_RECORD:
                 return 0;
 
@@ -835,6 +839,16 @@ public class RecordDataV1Serializer implements RecordDataSerializer {
                 res = new PageListMetaResetCountRecord(cacheId, pageId);
                 break;
 
+            case ROTATED_ID_PART_RECORD:
+                cacheId = in.readInt();
+                pageId = in.readLong();
+
+                byte rotatedIdPart = in.readByte();
+
+                res = new RotatedIdPartRecord(cacheId, pageId, rotatedIdPart);
+
+                break;
+
             case SWITCH_SEGMENT_RECORD:
                 throw new EOFException("END OF SEGMENT");
 
@@ -1351,6 +1365,16 @@ public class RecordDataV1Serializer implements RecordDataSerializer {
 
                 break;
 
+            case ROTATED_ID_PART_RECORD:
+                RotatedIdPartRecord rotatedIdPartRecord = (RotatedIdPartRecord) rec;
+
+                buf.putInt(rotatedIdPartRecord.groupId());
+                buf.putLong(rotatedIdPartRecord.pageId());
+
+                buf.put(rotatedIdPartRecord.rotatedIdPart());
+
+                break;
+
             case TX_RECORD:
                 txRecordSerializer.write((TxRecord)rec, buf);
 

http://git-wip-us.apache.org/repos/asf/ignite/blob/0bdfa20c/modules/core/src/test/java/org/apache/ignite/internal/pagemem/impl/PageIdUtilsSelfTest.java
----------------------------------------------------------------------
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/pagemem/impl/PageIdUtilsSelfTest.java b/modules/core/src/test/java/org/apache/ignite/internal/pagemem/impl/PageIdUtilsSelfTest.java
index 2984067..8b41944 100644
--- a/modules/core/src/test/java/org/apache/ignite/internal/pagemem/impl/PageIdUtilsSelfTest.java
+++ b/modules/core/src/test/java/org/apache/ignite/internal/pagemem/impl/PageIdUtilsSelfTest.java
@@ -34,7 +34,8 @@ public class PageIdUtilsSelfTest extends GridCommonAbstractTest {
         assertEquals(0x0102FFFFFFFFFFFFL, PageIdUtils.rotatePageId(0x0002FFFFFFFFFFFFL));
         assertEquals(0x0B02FFFFFFFFFFFFL, PageIdUtils.rotatePageId(0x0A02FFFFFFFFFFFFL));
         assertEquals(0x1002FFFFFFFFFFFFL, PageIdUtils.rotatePageId(0x0F02FFFFFFFFFFFFL));
-        assertEquals(0x0002FFFFFFFFFFFFL, PageIdUtils.rotatePageId(0xFF02FFFFFFFFFFFFL));
+        assertEquals(0x0102FFFFFFFFFFFFL, PageIdUtils.rotatePageId(0xFE02FFFFFFFFFFFFL));
+        assertEquals(0x0102FFFFFFFFFFFFL, PageIdUtils.rotatePageId(0xFF02FFFFFFFFFFFFL));
     }
 
     /**

http://git-wip-us.apache.org/repos/asf/ignite/blob/0bdfa20c/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/eviction/paged/PageEvictionPagesRecyclingAndReusingTest.java
----------------------------------------------------------------------
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/eviction/paged/PageEvictionPagesRecyclingAndReusingTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/eviction/paged/PageEvictionPagesRecyclingAndReusingTest.java
new file mode 100644
index 0000000..9c777fb
--- /dev/null
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/eviction/paged/PageEvictionPagesRecyclingAndReusingTest.java
@@ -0,0 +1,153 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.processors.cache.eviction.paged;
+
+import java.util.Random;
+import org.apache.ignite.IgniteCache;
+import org.apache.ignite.IgniteCheckedException;
+import org.apache.ignite.cache.CacheAtomicityMode;
+import org.apache.ignite.cache.CacheMode;
+import org.apache.ignite.cache.CacheWriteSynchronizationMode;
+import org.apache.ignite.configuration.CacheConfiguration;
+import org.apache.ignite.configuration.DataPageEvictionMode;
+import org.apache.ignite.configuration.IgniteConfiguration;
+import org.apache.ignite.internal.IgniteEx;
+import org.apache.ignite.internal.processors.cache.persistence.tree.reuse.ReuseList;
+
+/**
+ *
+ */
+public class PageEvictionPagesRecyclingAndReusingTest extends PageEvictionAbstractTest {
+    /** Test timeout. */
+    private static final long TEST_TIMEOUT = 10 * 60 * 1000;
+
+    /** Number of small entries. */
+    private static final int SMALL_ENTRIES = ENTRIES * 10;
+
+    /** {@inheritDoc} */
+    @Override protected IgniteConfiguration getConfiguration(String gridName) throws Exception {
+        return setEvictionMode(DataPageEvictionMode.RANDOM_LRU, super.getConfiguration(gridName));
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void afterTest() throws Exception {
+        stopAllGrids();
+    }
+
+    /** {@inheritDoc} */
+    @Override protected long getTestTimeout() {
+        return TEST_TIMEOUT;
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
+    public void testPagesRecyclingAndReusingAtomicReplicated() throws Exception {
+        testPagesRecyclingAndReusing(CacheAtomicityMode.ATOMIC, CacheMode.REPLICATED);
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
+    public void testPagesRecyclingAndReusingAtomicLocal() throws Exception {
+        testPagesRecyclingAndReusing(CacheAtomicityMode.ATOMIC, CacheMode.LOCAL);
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
+    public void testPagesRecyclingAndReusingTxReplicated() throws Exception {
+        testPagesRecyclingAndReusing(CacheAtomicityMode.TRANSACTIONAL, CacheMode.REPLICATED);
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
+    public void testPagesRecyclingAndReusingTxLocal() throws Exception {
+        testPagesRecyclingAndReusing(CacheAtomicityMode.TRANSACTIONAL, CacheMode.LOCAL);
+    }
+
+    /**
+     * @param atomicityMode Atomicity mode.
+     * @param cacheMode Cache mode.
+     */
+    private void testPagesRecyclingAndReusing(CacheAtomicityMode atomicityMode, CacheMode cacheMode) throws Exception {
+        IgniteEx ignite = startGrid(0);
+
+        CacheConfiguration<Object, Object> cfg = cacheConfig("evict-fair", null, cacheMode, atomicityMode,
+            CacheWriteSynchronizationMode.PRIMARY_SYNC);
+
+        IgniteCache<Object, Object> cache = ignite(0).getOrCreateCache(cfg);
+
+        ReuseList reuseList = ignite.context().cache().context().database().reuseList(null);
+
+        putRemoveCycles(cache, reuseList);
+
+        long recycledPagesCnt1 = reuseList.recycledPagesCount();
+
+        putRemoveCycles(cache, reuseList);
+
+        long recycledPagesCnt2 = reuseList.recycledPagesCount();
+
+        assert recycledPagesCnt1 == recycledPagesCnt2 : "Possible recycled pages leak!";
+    }
+
+    /**
+     * @param cache Cache.
+     * @param reuseList Reuse list.
+     */
+    private void putRemoveCycles(IgniteCache<Object, Object> cache, ReuseList reuseList) throws IgniteCheckedException {
+        for (int i = 1; i <= ENTRIES; i++) {
+            cache.put(i, new TestObject(PAGE_SIZE / 4 - 50));
+
+            if (i % (ENTRIES / 10) == 0)
+                System.out.println(">>> Entries put: " + i);
+        }
+
+        System.out.println("### Recycled pages count: " + reuseList.recycledPagesCount());
+
+        for (int i = 1; i <= ENTRIES; i++) {
+            cache.remove(i);
+
+            if (i % (ENTRIES / 10) == 0)
+                System.out.println(">>> Entries removed: " + i);
+        }
+
+        System.out.println("### Recycled pages count: " + reuseList.recycledPagesCount());
+
+        Random rnd = new Random();
+
+        for (int i = 1; i <= SMALL_ENTRIES; i++) {
+            cache.put(i, rnd.nextInt());
+
+            if (i % (SMALL_ENTRIES / 10) == 0)
+                System.out.println(">>> Small entries put: " + i);
+        }
+
+        System.out.println("### Recycled pages count: " + reuseList.recycledPagesCount());
+
+        for (int i = 1; i <= SMALL_ENTRIES; i++) {
+            cache.remove(i);
+
+            if (i % (SMALL_ENTRIES / 10) == 0)
+                System.out.println(">>> Small entries removed: " + i);
+        }
+
+        System.out.println("### Recycled pages count: " + reuseList.recycledPagesCount());
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/0bdfa20c/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/FillFactorMetricTest.java
----------------------------------------------------------------------
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/FillFactorMetricTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/FillFactorMetricTest.java
index cdea7bc..42eaf36 100644
--- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/FillFactorMetricTest.java
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/FillFactorMetricTest.java
@@ -202,9 +202,10 @@ public class FillFactorMetricTest extends GridCommonAbstractTest {
             // Wait for cache to be cleared
             clearFut.get();
 
-            // Fill factor will typically be 0.8, occupied pages mostly partition metadata
+            // Since refactoring of AbstractFreeList with recycling empty data pages,
+            // fill factor after cache cleaning will about 0.99, no more obsolete typically value 0.8
             for (float fillFactor : curFillFactor)
-                assertTrue("FillFactor too high: " + fillFactor, fillFactor < 0.85);
+                assertTrue("FillFactor too low: " + fillFactor, fillFactor > 0.9);
         }
 
         doneFlag.set(true);

http://git-wip-us.apache.org/repos/asf/ignite/blob/0bdfa20c/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteCacheEvictionSelfTestSuite.java
----------------------------------------------------------------------
diff --git a/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteCacheEvictionSelfTestSuite.java b/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteCacheEvictionSelfTestSuite.java
index 1d4fdf7..f2ac1c0 100644
--- a/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteCacheEvictionSelfTestSuite.java
+++ b/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteCacheEvictionSelfTestSuite.java
@@ -38,6 +38,7 @@ import org.apache.ignite.internal.processors.cache.eviction.lru.LruNearOnlyNearE
 import org.apache.ignite.internal.processors.cache.eviction.paged.PageEvictionDataStreamerTest;
 import org.apache.ignite.internal.processors.cache.eviction.paged.PageEvictionMetricTest;
 import org.apache.ignite.internal.processors.cache.eviction.paged.PageEvictionMultinodeMixedRegionsTest;
+import org.apache.ignite.internal.processors.cache.eviction.paged.PageEvictionPagesRecyclingAndReusingTest;
 import org.apache.ignite.internal.processors.cache.eviction.paged.PageEvictionReadThroughTest;
 import org.apache.ignite.internal.processors.cache.eviction.paged.PageEvictionTouchOrderTest;
 import org.apache.ignite.internal.processors.cache.eviction.paged.Random2LruNearEnabledPageEvictionMultinodeTest;
@@ -94,6 +95,8 @@ public class IgniteCacheEvictionSelfTestSuite extends TestSuite {
 
         suite.addTest(new TestSuite(PageEvictionMetricTest.class));
 
+        suite.addTest(new TestSuite(PageEvictionPagesRecyclingAndReusingTest.class));
+
         return suite;
     }
 }