You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by ag...@apache.org on 2017/04/12 07:33:55 UTC

[24/57] [abbrv] ignite git commit: IGNITE-4534 - Added offheap evictions

IGNITE-4534 - Added offheap evictions


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

Branch: refs/heads/ignite-3477-debug
Commit: ff5b3e16850e503b79a13c44b667140d23c1f080
Parents: baa3835
Author: Ivan Rakov <iv...@gmail.com>
Authored: Mon Apr 10 19:23:43 2017 +0300
Committer: Alexey Goncharuk <al...@gmail.com>
Committed: Mon Apr 10 19:23:43 2017 +0300

----------------------------------------------------------------------
 .../configuration/DataPageEvictionMode.java     |  32 +++
 .../MemoryPolicyConfiguration.java              |  70 ++++++
 .../ignite/internal/pagemem/PageSupport.java    |  10 +
 .../pagemem/impl/PageMemoryNoStoreImpl.java     |   8 +
 .../internal/pagemem/impl/PageNoStoreImpl.java  |   0
 .../cache/CacheOffheapEvictionManager.java      |   6 +-
 .../processors/cache/GridCacheAdapter.java      |   2 +
 .../processors/cache/GridCacheEntryEx.java      |   5 +-
 .../cache/GridCacheEvictionManager.java         |   2 +-
 .../processors/cache/GridCacheMapEntry.java     |  32 ++-
 .../cache/IgniteCacheOffheapManagerImpl.java    |  34 ++-
 .../processors/cache/database/CacheDataRow.java |   5 +
 .../cache/database/CacheDataRowAdapter.java     | 116 ++++++++-
 .../IgniteCacheDatabaseSharedManager.java       | 126 ++++++++--
 .../processors/cache/database/MemoryPolicy.java |  19 +-
 .../evict/FairFifoPageEvictionTracker.java      |  74 ++++++
 .../database/evict/NoOpPageEvictionTracker.java |  50 ++++
 .../evict/PageAbstractEvictionTracker.java      | 243 +++++++++++++++++++
 .../database/evict/PageEvictionTracker.java     |  52 ++++
 .../evict/Random2LruPageEvictionTracker.java    | 180 ++++++++++++++
 .../evict/RandomLruPageEvictionTracker.java     | 157 ++++++++++++
 .../cache/database/freelist/FreeListImpl.java   |  62 +++--
 .../cache/database/tree/io/DataPageIO.java      | 110 ++++++++-
 .../dht/atomic/GridDhtAtomicCache.java          |   2 +
 .../cache/distributed/near/GridNearTxLocal.java |   2 +
 .../distributed/near/GridNearTxRemote.java      |   4 +-
 .../local/atomic/GridLocalAtomicCache.java      |   6 +-
 .../cacheobject/IgniteCacheObjectProcessor.java |   7 -
 .../IgniteCacheObjectProcessorImpl.java         |  16 --
 .../processors/cache/GridCacheTestEntryEx.java  |   2 +-
 .../dht/GridCacheDhtEntrySelfTest.java          |   4 +-
 .../paged/PageEvictionAbstractTest.java         | 124 ++++++++++
 .../paged/PageEvictionMultinodeTest.java        | 110 +++++++++
 .../paged/PageEvictionReadThroughTest.java      | 140 +++++++++++
 .../paged/PageEvictionTouchOrderTest.java       | 109 +++++++++
 .../paged/PageEvictionWithRebalanceTest.java    |  81 +++++++
 .../Random2LruPageEvictionMultinodeTest.java    |  30 +++
 ...Random2LruPageEvictionWithRebalanceTest.java |  30 +++
 .../RandomLruPageEvictionMultinodeTest.java     |  30 +++
 .../RandomLruPageEvictionWithRebalanceTest.java |  30 +++
 .../cache/eviction/paged/TestObject.java        |  78 ++++++
 .../database/FreeListImplSelfTest.java          |  13 +-
 .../IgniteCacheEvictionSelfTestSuite.java       |  13 +
 .../processors/query/h2/opt/GridH2Row.java      |   5 +
 44 files changed, 2130 insertions(+), 101 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ignite/blob/ff5b3e16/modules/core/src/main/java/org/apache/ignite/configuration/DataPageEvictionMode.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/configuration/DataPageEvictionMode.java b/modules/core/src/main/java/org/apache/ignite/configuration/DataPageEvictionMode.java
new file mode 100644
index 0000000..bada68e
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/configuration/DataPageEvictionMode.java
@@ -0,0 +1,32 @@
+/*
+* 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.configuration;
+
+/**
+ * Enumeration defines data page eviction modes.
+ */
+public enum DataPageEvictionMode {
+    /** Disabled. */
+    DISABLED,
+
+    /** Random lru. */
+    RANDOM_LRU,
+
+    /** Random 2-lru. */
+    RANDOM_2_LRU
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/ff5b3e16/modules/core/src/main/java/org/apache/ignite/configuration/MemoryPolicyConfiguration.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/configuration/MemoryPolicyConfiguration.java b/modules/core/src/main/java/org/apache/ignite/configuration/MemoryPolicyConfiguration.java
index 2add64f..d6203c6 100644
--- a/modules/core/src/main/java/org/apache/ignite/configuration/MemoryPolicyConfiguration.java
+++ b/modules/core/src/main/java/org/apache/ignite/configuration/MemoryPolicyConfiguration.java
@@ -19,6 +19,8 @@ package org.apache.ignite.configuration;
 import java.io.Serializable;
 import org.apache.ignite.internal.pagemem.PageMemory;
 import org.apache.ignite.internal.processors.cache.database.MemoryPolicy;
+import org.apache.ignite.internal.processors.cache.database.freelist.FreeList;
+import org.apache.ignite.internal.processors.cache.database.tree.io.DataPageIO;
 
 /**
  * Configuration bean used for creating {@link MemoryPolicy} instances.
@@ -43,6 +45,18 @@ public final class MemoryPolicyConfiguration implements Serializable {
         return name;
     }
 
+    /** Algorithm for per-page eviction. If {@link DataPageEvictionMode#DISABLED} set, eviction is not performed. */
+    private DataPageEvictionMode pageEvictionMode = DataPageEvictionMode.DISABLED;
+
+    /** Allocation of new {@link DataPageIO} pages is stopped when this percentage of pages are allocated. */
+    private double evictionThreshold = 0.9;
+
+    /** Allocation of new {@link DataPageIO} pages is stopped by maintaining this amount of empty pages in
+     * corresponding {@link FreeList} bucket. Pages get into the bucket through evicting all data entries one by one.
+     * Higher load and contention require larger pool size.
+     */
+    private int emptyPagesPoolSize = 100;
+
     /**
      * @param name Unique name of MemoryPolicy.
      */
@@ -83,4 +97,60 @@ public final class MemoryPolicyConfiguration implements Serializable {
 
         return this;
     }
+
+    /**
+     * Gets data page eviction mode.
+     */
+    public DataPageEvictionMode getPageEvictionMode() {
+        return pageEvictionMode;
+    }
+
+    /**
+     * Sets data page eviction mode.
+     *
+     * @param evictionMode Eviction mode.
+     */
+    public MemoryPolicyConfiguration setPageEvictionMode(DataPageEvictionMode evictionMode) {
+        pageEvictionMode = evictionMode;
+
+        return this;
+    }
+
+    /**
+     * Gets data page eviction threshold.
+     *
+     * @return Data page eviction threshold.
+     */
+    public double getEvictionThreshold() {
+        return evictionThreshold;
+    }
+
+    /**
+     * Sets data page eviction threshold.
+     *
+     * @param evictionThreshold Eviction threshold.
+     */
+    public MemoryPolicyConfiguration setEvictionThreshold(double evictionThreshold) {
+        this.evictionThreshold = evictionThreshold;
+
+        return this;
+    }
+
+    /**
+     * Gets empty pages pool size.
+     */
+    public int getEmptyPagesPoolSize() {
+        return emptyPagesPoolSize;
+    }
+
+    /**
+     * Sets empty pages pool size.
+     *
+     * @param emptyPagesPoolSize Empty pages pool size.
+     */
+    public MemoryPolicyConfiguration setEmptyPagesPoolSize(int emptyPagesPoolSize) {
+        this.emptyPagesPoolSize = emptyPagesPoolSize;
+
+        return this;
+    }
 }

http://git-wip-us.apache.org/repos/asf/ignite/blob/ff5b3e16/modules/core/src/main/java/org/apache/ignite/internal/pagemem/PageSupport.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/pagemem/PageSupport.java b/modules/core/src/main/java/org/apache/ignite/internal/pagemem/PageSupport.java
index 8076f28..0f39058 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/pagemem/PageSupport.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/pagemem/PageSupport.java
@@ -53,6 +53,16 @@ public interface PageSupport {
     public long readLock(int cacheId, long pageId, long page);
 
     /**
+     * Obtains read lock without checking page tag.
+     *
+     * @param cacheId Cache ID.
+     * @param pageId Page ID.
+     * @param page Page pointer.
+     * @return Pointer for reading the page.
+     */
+    public long readLockForce(int cacheId, long pageId, long page);
+
+    /**
      * Releases locked page.
      *
      * @param cacheId Cache ID.

http://git-wip-us.apache.org/repos/asf/ignite/blob/ff5b3e16/modules/core/src/main/java/org/apache/ignite/internal/pagemem/impl/PageMemoryNoStoreImpl.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/pagemem/impl/PageMemoryNoStoreImpl.java b/modules/core/src/main/java/org/apache/ignite/internal/pagemem/impl/PageMemoryNoStoreImpl.java
index f24113c..7134cff 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/pagemem/impl/PageMemoryNoStoreImpl.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/pagemem/impl/PageMemoryNoStoreImpl.java
@@ -416,6 +416,14 @@ public class PageMemoryNoStoreImpl implements PageMemory {
     }
 
     /** {@inheritDoc} */
+    public long readLockForce(int cacheId, long pageId, long page) {
+        if (rwLock.readLock(page + LOCK_OFFSET, -1))
+            return page + PAGE_OVERHEAD;
+
+        return 0L;
+    }
+
+    /** {@inheritDoc} */
     @Override public void readUnlock(int cacheId, long pageId, long page) {
         rwLock.readUnlock(page + LOCK_OFFSET);
     }

http://git-wip-us.apache.org/repos/asf/ignite/blob/ff5b3e16/modules/core/src/main/java/org/apache/ignite/internal/pagemem/impl/PageNoStoreImpl.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/pagemem/impl/PageNoStoreImpl.java b/modules/core/src/main/java/org/apache/ignite/internal/pagemem/impl/PageNoStoreImpl.java
new file mode 100644
index 0000000..e69de29

http://git-wip-us.apache.org/repos/asf/ignite/blob/ff5b3e16/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/CacheOffheapEvictionManager.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/CacheOffheapEvictionManager.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/CacheOffheapEvictionManager.java
index 99df39d..f8e9f32 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/CacheOffheapEvictionManager.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/CacheOffheapEvictionManager.java
@@ -27,10 +27,6 @@ import org.apache.ignite.internal.util.typedef.internal.U;
 import org.jetbrains.annotations.Nullable;
 
 /**
- * TODO GG-11140.
- *
- * Temporary implementation, ignores configured EvictionPolicy, evictions to be reconsidered as
- * part of GG-11140.
  *
  */
 public class CacheOffheapEvictionManager extends GridCacheManagerAdapter implements CacheEvictionManager {
@@ -51,7 +47,7 @@ public class CacheOffheapEvictionManager extends GridCacheManagerAdapter impleme
                 return;
             }
 
-            boolean evicted = e.evictInternal(GridCacheVersionManager.EVICT_VER, null);
+            boolean evicted = e.evictInternal(GridCacheVersionManager.EVICT_VER, null, false);
 
             if (evicted)
                 cctx.cache().removeEntry(e);

http://git-wip-us.apache.org/repos/asf/ignite/blob/ff5b3e16/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheAdapter.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheAdapter.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheAdapter.java
index 9a6ff11..d791b7c 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheAdapter.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheAdapter.java
@@ -2073,6 +2073,8 @@ public abstract class GridCacheAdapter<K, V> implements IgniteInternalCache<K, V
                                             GridCacheEntryEx entry = null;
 
                                             try {
+                                                ctx.shared().database().ensureFreeSpace(ctx.memoryPolicy());
+
                                                 entry = entryEx(key);
 
                                                 entry.unswap();

http://git-wip-us.apache.org/repos/asf/ignite/blob/ff5b3e16/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheEntryEx.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheEntryEx.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheEntryEx.java
index 99f9744..2066342 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheEntryEx.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheEntryEx.java
@@ -213,11 +213,12 @@ public interface GridCacheEntryEx {
     /**
      * @param obsoleteVer Version for eviction.
      * @param filter Optional filter.
+     * @param evictOffheap Evict offheap value flag.
      * @return {@code True} if entry could be evicted.
      * @throws IgniteCheckedException In case of error.
      */
-    public boolean evictInternal(GridCacheVersion obsoleteVer, @Nullable CacheEntryPredicate[] filter)
-        throws IgniteCheckedException;
+    public boolean evictInternal(GridCacheVersion obsoleteVer, @Nullable CacheEntryPredicate[] filter,
+        boolean evictOffheap) throws IgniteCheckedException;
 
     /**
      * Evicts entry when batch evict is performed. When called, does not write entry data to swap, but instead

http://git-wip-us.apache.org/repos/asf/ignite/blob/ff5b3e16/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheEvictionManager.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheEvictionManager.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheEvictionManager.java
index 26f37a7..0deae07 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheEvictionManager.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheEvictionManager.java
@@ -135,7 +135,7 @@ public class GridCacheEvictionManager extends GridCacheManagerAdapter implements
 
         boolean hasVal = recordable && entry.hasValue();
 
-        boolean evicted = entry.evictInternal(obsoleteVer, filter);
+        boolean evicted = entry.evictInternal(obsoleteVer, filter, false);
 
         if (evicted) {
             // Remove manually evicted entry from policy.

http://git-wip-us.apache.org/repos/asf/ignite/blob/ff5b3e16/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheMapEntry.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheMapEntry.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheMapEntry.java
index 7fad9f5..9e2cd70 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheMapEntry.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheMapEntry.java
@@ -43,6 +43,7 @@ import org.apache.ignite.internal.processors.affinity.AffinityTopologyVersion;
 import org.apache.ignite.internal.processors.cache.GridCacheUpdateAtomicResult.UpdateOutcome;
 import org.apache.ignite.internal.processors.cache.database.CacheDataRow;
 import org.apache.ignite.internal.processors.cache.database.CacheDataRowAdapter;
+import org.apache.ignite.internal.processors.cache.database.MemoryPolicy;
 import org.apache.ignite.internal.processors.cache.distributed.dht.GridDhtCacheEntry;
 import org.apache.ignite.internal.processors.cache.distributed.dht.GridDhtLocalPartition;
 import org.apache.ignite.internal.processors.cache.distributed.dht.atomic.GridDhtAtomicAbstractUpdateFuture;
@@ -809,6 +810,8 @@ public abstract class GridCacheMapEntry extends GridMetadataAwareAdapter impleme
         boolean touch = false;
 
         try {
+            ensureFreeSpace();
+
             synchronized (this) {
                 long ttl = ttlExtras();
 
@@ -908,6 +911,8 @@ public abstract class GridCacheMapEntry extends GridMetadataAwareAdapter impleme
 
         long updateCntr0;
 
+        ensureFreeSpace();
+
         synchronized (this) {
             checkObsolete();
 
@@ -1641,6 +1646,9 @@ public abstract class GridCacheMapEntry extends GridMetadataAwareAdapter impleme
 
         AtomicCacheUpdateClosure c;
 
+        if (!primary && !isNear())
+            ensureFreeSpace();
+
         synchronized (this) {
             checkObsolete();
 
@@ -2567,6 +2575,8 @@ public abstract class GridCacheMapEntry extends GridMetadataAwareAdapter impleme
         GridDrType drType,
         boolean fromStore
     ) throws IgniteCheckedException, GridCacheEntryRemovedException {
+        ensureFreeSpace();
+
         synchronized (this) {
             checkObsolete();
 
@@ -3378,6 +3388,16 @@ public abstract class GridCacheMapEntry extends GridMetadataAwareAdapter impleme
     }
 
     /**
+     * Evicts necessary number of data pages if per-page eviction is configured in current {@link MemoryPolicy}.
+     */
+    private void ensureFreeSpace() throws IgniteCheckedException {
+        // Deadlock alert: evicting data page causes removing (and locking) all entries on the page one by one.
+        assert !Thread.holdsLock(this);
+
+        cctx.shared().database().ensureFreeSpace(cctx.memoryPolicy());
+    }
+
+    /**
      * @return Entry which holds key, value and version.
      */
     private synchronized <K, V> CacheEntryImplEx<K, V> wrapVersionedWithValue() {
@@ -3387,8 +3407,12 @@ public abstract class GridCacheMapEntry extends GridMetadataAwareAdapter impleme
     }
 
     /** {@inheritDoc} */
-    @Override public boolean evictInternal(GridCacheVersion obsoleteVer, @Nullable CacheEntryPredicate[] filter)
+    @Override public boolean evictInternal(
+        GridCacheVersion obsoleteVer,
+        @Nullable CacheEntryPredicate[] filter,
+        boolean evictOffheap)
         throws IgniteCheckedException {
+
         boolean marked = false;
 
         try {
@@ -3411,6 +3435,9 @@ public abstract class GridCacheMapEntry extends GridMetadataAwareAdapter impleme
                         // Nullify value after swap.
                         value(null);
 
+                        if (evictOffheap)
+                            removeValue();
+
                         marked = true;
 
                         return true;
@@ -3451,6 +3478,9 @@ public abstract class GridCacheMapEntry extends GridMetadataAwareAdapter impleme
                             // Nullify value after swap.
                             value(null);
 
+                            if (evictOffheap)
+                                removeValue();
+
                             marked = true;
 
                             return true;

http://git-wip-us.apache.org/repos/asf/ignite/blob/ff5b3e16/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/IgniteCacheOffheapManagerImpl.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/IgniteCacheOffheapManagerImpl.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/IgniteCacheOffheapManagerImpl.java
index e022e57..73edbe1 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/IgniteCacheOffheapManagerImpl.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/IgniteCacheOffheapManagerImpl.java
@@ -28,6 +28,7 @@ import javax.cache.Cache;
 import org.apache.ignite.IgniteCheckedException;
 import org.apache.ignite.IgniteException;
 import org.apache.ignite.cluster.ClusterNode;
+import org.apache.ignite.configuration.DataPageEvictionMode;
 import org.apache.ignite.internal.NodeStoppingException;
 import org.apache.ignite.internal.pagemem.FullPageId;
 import org.apache.ignite.internal.pagemem.PageIdUtils;
@@ -37,7 +38,6 @@ import org.apache.ignite.internal.processors.affinity.AffinityTopologyVersion;
 import org.apache.ignite.internal.processors.cache.database.CacheDataRow;
 import org.apache.ignite.internal.processors.cache.database.CacheDataRowAdapter;
 import org.apache.ignite.internal.processors.cache.database.CacheSearchRow;
-import org.apache.ignite.internal.processors.cache.database.IgniteCacheDatabaseSharedManager;
 import org.apache.ignite.internal.processors.cache.database.RootPage;
 import org.apache.ignite.internal.processors.cache.database.RowStore;
 import org.apache.ignite.internal.processors.cache.database.freelist.FreeList;
@@ -965,8 +965,12 @@ public class IgniteCacheOffheapManagerImpl extends GridCacheManagerAdapter imple
             CacheObject val,
             GridCacheVersion ver,
             long expireTime,
-            @Nullable CacheDataRow oldRow) throws IgniteCheckedException {
-            DataRow dataRow = new DataRow(key, val, ver, partId, expireTime);
+            @Nullable CacheDataRow oldRow) throws IgniteCheckedException
+        {
+            int cacheId = cctx.memoryPolicy().config().getPageEvictionMode() == DataPageEvictionMode.DISABLED ?
+                0 : cctx.cacheId();
+
+            DataRow dataRow = new DataRow(key, val, ver, partId, expireTime, cacheId);
 
             if (canUpdateOldRow(oldRow, dataRow) && rowStore.updateRow(oldRow.link(), dataRow))
                 dataRow.link(oldRow.link());
@@ -997,7 +1001,10 @@ public class IgniteCacheOffheapManagerImpl extends GridCacheManagerAdapter imple
                 throw new NodeStoppingException("Operation has been cancelled (node is stopping).");
 
             try {
-                DataRow dataRow = new DataRow(key, val, ver, p, expireTime);
+                int cacheId = cctx.memoryPolicy().config().getPageEvictionMode() != DataPageEvictionMode.DISABLED ?
+                    cctx.cacheId() : 0;
+
+                DataRow dataRow = new DataRow(key, val, ver, p, expireTime, cacheId);
 
                 CacheObjectContext coCtx = cctx.cacheObjectContext();
 
@@ -1140,9 +1147,12 @@ public class IgniteCacheOffheapManagerImpl extends GridCacheManagerAdapter imple
 
             CacheDataRow row = dataTree.findOne(new SearchRow(key), CacheDataRowAdapter.RowData.NO_KEY);
 
-            if (row != null)
+            if (row != null) {
                 row.key(key);
 
+                cctx.memoryPolicy().evictionTracker().touchPage(row.link());
+            }
+
             return row;
         }
 
@@ -1345,7 +1355,7 @@ public class IgniteCacheOffheapManagerImpl extends GridCacheManagerAdapter imple
          * @param part Partition.
          * @param expireTime Expire time.
          */
-        DataRow(KeyCacheObject key, CacheObject val, GridCacheVersion ver, int part, long expireTime) {
+        DataRow(KeyCacheObject key, CacheObject val, GridCacheVersion ver, int part, long expireTime, int cacheId) {
             super(0);
 
             this.hash = key.hashCode();
@@ -1354,6 +1364,7 @@ public class IgniteCacheOffheapManagerImpl extends GridCacheManagerAdapter imple
             this.ver = ver;
             this.part = part;
             this.expireTime = expireTime;
+            this.cacheId = cacheId;
         }
 
         /** {@inheritDoc} */
@@ -1473,6 +1484,9 @@ public class IgniteCacheOffheapManagerImpl extends GridCacheManagerAdapter imple
                     if (data.nextLink() == 0) {
                         long addr = pageAddr + data.offset();
 
+                        if (cctx.memoryPolicy().config().getPageEvictionMode() != DataPageEvictionMode.DISABLED)
+                            addr += 4; // Skip cache id.
+
                         final int len = PageUtils.getInt(addr, 0);
 
                         int lenCmp = Integer.compare(len, bytes.length);
@@ -1672,6 +1686,14 @@ public class IgniteCacheOffheapManagerImpl extends GridCacheManagerAdapter imple
         @Override public int getHash(long pageAddr, int idx) {
             return PageUtils.getInt(pageAddr, offset(idx) + 8);
         }
+
+        /** {@inheritDoc} */
+        @Override public void visit(long pageAddr, IgniteInClosure<CacheSearchRow> c) {
+            int cnt = getCount(pageAddr);
+
+            for (int i = 0; i < cnt; i++)
+                c.apply(new CacheDataRowAdapter(getLink(pageAddr, i)));
+        }
     }
 
     /**

http://git-wip-us.apache.org/repos/asf/ignite/blob/ff5b3e16/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/database/CacheDataRow.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/database/CacheDataRow.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/database/CacheDataRow.java
index cc26b21..e0076d5 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/database/CacheDataRow.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/database/CacheDataRow.java
@@ -36,6 +36,11 @@ public interface CacheDataRow extends CacheSearchRow {
     public GridCacheVersion version();
 
     /**
+     * @return Cache id. Stored only if memory policy with configured per-page eviction is used.
+     */
+    public int cacheId();
+
+    /**
      * @return Expire time.
      */
     public long expireTime();

http://git-wip-us.apache.org/repos/asf/ignite/blob/ff5b3e16/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/database/CacheDataRowAdapter.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/database/CacheDataRowAdapter.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/database/CacheDataRowAdapter.java
index eca59d6..afeada5 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/database/CacheDataRowAdapter.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/database/CacheDataRowAdapter.java
@@ -19,12 +19,14 @@ package org.apache.ignite.internal.processors.cache.database;
 
 import java.nio.ByteBuffer;
 import org.apache.ignite.IgniteCheckedException;
+import org.apache.ignite.configuration.DataPageEvictionMode;
 import org.apache.ignite.internal.pagemem.PageIdUtils;
 import org.apache.ignite.internal.pagemem.PageMemory;
 import org.apache.ignite.internal.pagemem.PageUtils;
 import org.apache.ignite.internal.processors.cache.CacheObject;
 import org.apache.ignite.internal.processors.cache.CacheObjectContext;
 import org.apache.ignite.internal.processors.cache.GridCacheContext;
+import org.apache.ignite.internal.processors.cache.GridCacheSharedContext;
 import org.apache.ignite.internal.processors.cache.IncompleteCacheObject;
 import org.apache.ignite.internal.processors.cache.IncompleteObject;
 import org.apache.ignite.internal.processors.cache.KeyCacheObject;
@@ -36,6 +38,7 @@ import org.apache.ignite.internal.util.tostring.GridToStringExclude;
 import org.apache.ignite.internal.util.tostring.GridToStringInclude;
 import org.apache.ignite.internal.util.typedef.internal.S;
 import org.apache.ignite.internal.util.typedef.internal.U;
+import org.jetbrains.annotations.Nullable;
 
 import static org.apache.ignite.internal.pagemem.PageIdUtils.itemId;
 import static org.apache.ignite.internal.pagemem.PageIdUtils.pageId;
@@ -63,6 +66,10 @@ public class CacheDataRowAdapter implements CacheDataRow {
     @GridToStringInclude
     protected GridCacheVersion ver;
 
+    /** */
+    @GridToStringInclude
+    protected int cacheId;
+
     /**
      * @param link Link.
      */
@@ -92,14 +99,35 @@ public class CacheDataRowAdapter implements CacheDataRow {
      * @throws IgniteCheckedException If failed.
      */
     public final void initFromLink(GridCacheContext<?, ?> cctx, RowData rowData) throws IgniteCheckedException {
-        assert cctx != null : "cctx";
+        initFromLink(cctx, cctx.shared(), cctx.memoryPolicy().pageMemory(), rowData);
+    }
+
+    /**
+     * Read row from data pages.
+     * Can be called with cctx == null, if cache instance is unknown, but its ID is stored in the data row.
+     *
+     * @param cctx Cctx.
+     * @param sharedCtx Shared context.
+     * @param pageMem Page memory.
+     * @param rowData Row data.
+     */
+    public final void initFromLink(
+        @Nullable GridCacheContext<?, ?> cctx,
+        GridCacheSharedContext<?, ?> sharedCtx,
+        PageMemory pageMem,
+        RowData rowData)
+        throws IgniteCheckedException {
         assert link != 0 : "link";
         assert key == null : "key";
 
-        final CacheObjectContext coctx = cctx.cacheObjectContext();
-        final PageMemory pageMem = cctx.memoryPolicy().pageMemory();
+        CacheObjectContext coctx = null;
+
+        if (cctx != null) {
+            cacheId = cctx.memoryPolicy().config().getPageEvictionMode() == DataPageEvictionMode.DISABLED ?
+                cctx.cacheId() : 0; // Force cacheId reading for evictable memory policies.
 
-        final int cacheId = cctx.cacheId();
+            coctx = cctx.cacheObjectContext();
+        }
 
         long nextLink = link;
         IncompleteObject<?> incomplete = null;
@@ -126,7 +154,7 @@ public class CacheDataRowAdapter implements CacheDataRow {
                     if (first) {
                         if (nextLink == 0) {
                             // Fast path for a single page row.
-                            readFullRow(coctx, pageAddr + data.offset(), rowData);
+                            readFullRow(sharedCtx, coctx, pageAddr + data.offset(), rowData);
 
                             return;
                         }
@@ -141,7 +169,7 @@ public class CacheDataRowAdapter implements CacheDataRow {
 
                     boolean keyOnly = rowData == RowData.KEY_ONLY;
 
-                    incomplete = readFragment(coctx, buf, keyOnly, incomplete);
+                    incomplete = readFragment(sharedCtx, coctx, buf, keyOnly, incomplete);
 
                     if (keyOnly && key != null)
                         return;
@@ -168,11 +196,24 @@ public class CacheDataRowAdapter implements CacheDataRow {
      * @return Read object.
      */
     private IncompleteObject<?> readFragment(
+        GridCacheSharedContext<?, ?> sharedCtx,
         CacheObjectContext coctx,
         ByteBuffer buf,
         boolean keyOnly,
         IncompleteObject<?> incomplete
     ) throws IgniteCheckedException {
+        if (cacheId == 0) {
+            incomplete = readIncompleteCacheId(buf, incomplete);
+
+            if (cacheId == 0)
+                return incomplete;
+
+            incomplete = null;
+        }
+
+        if (coctx == null)
+            coctx = sharedCtx.cacheContext(cacheId).cacheObjectContext();
+
         // Read key.
         if (key == null) {
             incomplete = readIncompleteKey(coctx, buf, (IncompleteCacheObject)incomplete);
@@ -215,9 +256,23 @@ public class CacheDataRowAdapter implements CacheDataRow {
      * @param rowData Required row data.
      * @throws IgniteCheckedException If failed.
      */
-    private void readFullRow(CacheObjectContext coctx, long addr, RowData rowData) throws IgniteCheckedException {
+    private void readFullRow(
+        GridCacheSharedContext<?, ?> sharedCtx,
+        CacheObjectContext coctx,
+        long addr,
+        RowData rowData)
+        throws IgniteCheckedException {
         int off = 0;
 
+        if (cacheId == 0) {
+            cacheId = PageUtils.getInt(addr, off);
+
+            off += 4;
+        }
+
+        if (coctx == null)
+            coctx = sharedCtx.cacheContext(cacheId).cacheObjectContext();
+
         int len = PageUtils.getInt(addr, off);
         off += 4;
 
@@ -255,6 +310,44 @@ public class CacheDataRowAdapter implements CacheDataRow {
     }
 
     /**
+     * @param buf Buffer.
+     * @param incomplete Incomplete.
+     */
+    private IncompleteObject<?> readIncompleteCacheId(
+        ByteBuffer buf,
+        IncompleteObject<?> incomplete
+    ) {
+        if (incomplete == null) {
+            int remaining = buf.remaining();
+
+            if (remaining == 0)
+                return null;
+
+            int size = 4;
+
+            if (remaining >= size) {
+                cacheId = buf.getInt();
+
+                return null;
+            }
+
+            incomplete = new IncompleteObject<>(new byte[size]);
+        }
+
+        incomplete.readData(buf);
+
+        if (incomplete.isReady()) {
+            final ByteBuffer timeBuf = ByteBuffer.wrap(incomplete.data());
+
+            timeBuf.order(buf.order());
+
+            cacheId = timeBuf.getInt();
+        }
+
+        return incomplete;
+    }
+
+    /**
      * @param coctx Cache object context.
      * @param buf Buffer.
      * @param incomplete Incomplete object.
@@ -313,7 +406,7 @@ public class CacheDataRowAdapter implements CacheDataRow {
     private IncompleteObject<?> readIncompleteExpireTime(
         ByteBuffer buf,
         IncompleteObject<?> incomplete
-    ) throws IgniteCheckedException {
+    ) {
         if (incomplete == null) {
             int remaining = buf.remaining();
 
@@ -414,13 +507,18 @@ public class CacheDataRowAdapter implements CacheDataRow {
     /**
      * @param key Key.
      */
-    public void key(KeyCacheObject key) {
+    @Override public void key(KeyCacheObject key) {
         assert key != null;
 
         this.key = key;
     }
 
     /** {@inheritDoc} */
+    @Override public int cacheId() {
+        return cacheId;
+    }
+
+    /** {@inheritDoc} */
     @Override public CacheObject value() {
         assert val != null : "Value is not ready: " + this;
 

http://git-wip-us.apache.org/repos/asf/ignite/blob/ff5b3e16/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/database/IgniteCacheDatabaseSharedManager.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/database/IgniteCacheDatabaseSharedManager.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/database/IgniteCacheDatabaseSharedManager.java
index d61130b..2d2295c 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/database/IgniteCacheDatabaseSharedManager.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/database/IgniteCacheDatabaseSharedManager.java
@@ -29,6 +29,7 @@ import org.apache.ignite.IgniteLogger;
 import org.apache.ignite.MemoryMetrics;
 import org.apache.ignite.cluster.ClusterNode;
 import org.apache.ignite.configuration.IgniteConfiguration;
+import org.apache.ignite.configuration.DataPageEvictionMode;
 import org.apache.ignite.configuration.MemoryConfiguration;
 import org.apache.ignite.configuration.MemoryPolicyConfiguration;
 import org.apache.ignite.internal.GridKernalContext;
@@ -40,7 +41,13 @@ import org.apache.ignite.internal.pagemem.PageMemory;
 import org.apache.ignite.internal.pagemem.snapshot.StartFullSnapshotAckDiscoveryMessage;
 import org.apache.ignite.internal.pagemem.impl.PageMemoryNoStoreImpl;
 import org.apache.ignite.internal.processors.cache.GridCacheContext;
+import org.apache.ignite.internal.processors.cache.GridCacheMapEntry;
 import org.apache.ignite.internal.processors.cache.GridCacheSharedManagerAdapter;
+import org.apache.ignite.internal.processors.cache.database.evict.FairFifoPageEvictionTracker;
+import org.apache.ignite.internal.processors.cache.database.evict.NoOpPageEvictionTracker;
+import org.apache.ignite.internal.processors.cache.database.evict.PageEvictionTracker;
+import org.apache.ignite.internal.processors.cache.database.evict.Random2LruPageEvictionTracker;
+import org.apache.ignite.internal.processors.cache.database.evict.RandomLruPageEvictionTracker;
 import org.apache.ignite.internal.processors.cache.database.freelist.FreeList;
 import org.apache.ignite.internal.processors.cache.database.freelist.FreeListImpl;
 import org.apache.ignite.internal.processors.cache.database.tree.reuse.ReuseList;
@@ -102,7 +109,7 @@ public class IgniteCacheDatabaseSharedManager extends GridCacheSharedManagerAdap
 
             initPageMemoryPolicies(dbCfg);
 
-            startPageMemoryPools();
+            startMemoryPolicies();
 
             initPageMemoryDataStructures(dbCfg);
         }
@@ -123,8 +130,8 @@ public class IgniteCacheDatabaseSharedManager extends GridCacheSharedManagerAdap
 
             FreeListImpl freeList = new FreeListImpl(0,
                     cctx.igniteInstanceName(),
-                    memPlc.pageMemory(),
                     memMetrics,
+                    memPlc,
                     null,
                     cctx.wal(),
                     0L,
@@ -148,9 +155,12 @@ public class IgniteCacheDatabaseSharedManager extends GridCacheSharedManagerAdap
     /**
      *
      */
-    private void startPageMemoryPools() {
-        for (MemoryPolicy memPlc : memPlcMap.values())
+    private void startMemoryPolicies() {
+        for (MemoryPolicy memPlc : memPlcMap.values()) {
             memPlc.pageMemory().start();
+
+            memPlc.evictionTracker().start();
+        }
     }
 
     /**
@@ -205,9 +215,7 @@ public class IgniteCacheDatabaseSharedManager extends GridCacheSharedManagerAdap
             for (MemoryPolicyConfiguration memPlcCfg : memPlcsCfgs) {
                 MemoryMetricsImpl memMetrics = new MemoryMetricsImpl(memPlcCfg);
 
-                PageMemory pageMem = initMemory(dbCfg, memPlcCfg, memMetrics);
-
-                MemoryPolicy memPlc = new MemoryPolicy(pageMem, memMetrics, memPlcCfg);
+                MemoryPolicy memPlc = initMemory(dbCfg, memPlcCfg, memMetrics);
 
                 memPlcMap.put(memPlcCfg.getName(), memPlc);
 
@@ -222,7 +230,7 @@ public class IgniteCacheDatabaseSharedManager extends GridCacheSharedManagerAdap
 
         MemoryMetricsImpl sysMemMetrics = new MemoryMetricsImpl(sysPlcCfg);
 
-        memPlcMap.put(SYSTEM_MEMORY_POLICY_NAME, new MemoryPolicy(initMemory(dbCfg, sysPlcCfg, sysMemMetrics), sysMemMetrics, sysPlcCfg));
+        memPlcMap.put(SYSTEM_MEMORY_POLICY_NAME, initMemory(dbCfg, sysPlcCfg, sysMemMetrics));
 
         memMetricsMap.put(SYSTEM_MEMORY_POLICY_NAME, sysMemMetrics);
     }
@@ -253,9 +261,7 @@ public class IgniteCacheDatabaseSharedManager extends GridCacheSharedManagerAdap
      * @param memMetrics MemoryMetrics instance.
      */
     private MemoryPolicy createDefaultMemoryPolicy(MemoryConfiguration dbCfg, MemoryPolicyConfiguration memPlcCfg, MemoryMetricsImpl memMetrics) {
-        PageMemory pageMem = initMemory(dbCfg, memPlcCfg, memMetrics);
-
-        return new MemoryPolicy(pageMem, memMetrics, memPlcCfg);
+        return initMemory(dbCfg, memPlcCfg, memMetrics);
     }
 
     /**
@@ -285,6 +291,8 @@ public class IgniteCacheDatabaseSharedManager extends GridCacheSharedManagerAdap
                 checkPolicyName(plcCfg.getName(), plcNames);
 
                 checkPolicySize(plcCfg);
+
+                checkPolicyEvictionProperties(plcCfg, dbCfg);
             }
         }
 
@@ -307,6 +315,7 @@ public class IgniteCacheDatabaseSharedManager extends GridCacheSharedManagerAdap
 
     /**
      * @param plcCfg MemoryPolicyConfiguration to validate.
+     * @throws IgniteCheckedException If config is invalid.
      */
     private static void checkPolicySize(MemoryPolicyConfiguration plcCfg) throws IgniteCheckedException {
         if (plcCfg.getSize() < MIN_PAGE_MEMORY_SIZE)
@@ -314,8 +323,35 @@ public class IgniteCacheDatabaseSharedManager extends GridCacheSharedManagerAdap
     }
 
     /**
+     * @param plcCfg MemoryPolicyConfiguration to validate.
+     * @param dbCfg Memory configuration.
+     * @throws IgniteCheckedException If config is invalid.
+     */
+    protected void checkPolicyEvictionProperties(MemoryPolicyConfiguration plcCfg, MemoryConfiguration dbCfg)
+        throws IgniteCheckedException {
+        if (plcCfg.getPageEvictionMode() == DataPageEvictionMode.DISABLED)
+            return;
+
+        if (plcCfg.getEvictionThreshold() < 0.5 || plcCfg.getEvictionThreshold() > 0.999) {
+            throw new IgniteCheckedException("Page eviction threshold must be between 0.5 and 0.999: " +
+                plcCfg.getName());
+        }
+
+        if (plcCfg.getEmptyPagesPoolSize() <= 10)
+            throw new IgniteCheckedException("Evicted pages pool size should be greater than 10: " + plcCfg.getName());
+
+        long maxPoolSize = plcCfg.getSize() / dbCfg.getPageSize() / 10;
+
+        if (plcCfg.getEmptyPagesPoolSize() >= maxPoolSize) {
+            throw new IgniteCheckedException("Evicted pages pool size should be lesser than " + maxPoolSize +
+                ": " + plcCfg.getName());
+        }
+    }
+
+    /**
      * @param plcName MemoryPolicy name to validate.
      * @param observedNames Names of MemoryPolicies observed before.
+     * @throws IgniteCheckedException If config is invalid.
      */
     private static void checkPolicyName(String plcName, Set<String> observedNames) throws IgniteCheckedException {
         if (plcName == null || plcName.isEmpty())
@@ -406,8 +442,11 @@ public class IgniteCacheDatabaseSharedManager extends GridCacheSharedManagerAdap
     /** {@inheritDoc} */
     @Override protected void stop0(boolean cancel) {
         if (memPlcMap != null) {
-            for (MemoryPolicy memPlc : memPlcMap.values())
+            for (MemoryPolicy memPlc : memPlcMap.values()) {
                 memPlc.pageMemory().stop();
+
+                memPlc.evictionTracker().stop();
+            }
         }
     }
 
@@ -512,12 +551,49 @@ public class IgniteCacheDatabaseSharedManager extends GridCacheSharedManagerAdap
     }
 
     /**
+     * See {@link GridCacheMapEntry#ensureFreeSpace()}
+     *
+     * @param memPlc Memory policy.
+     */
+    public void ensureFreeSpace(MemoryPolicy memPlc) throws IgniteCheckedException {
+        if (memPlc == null)
+            return;
+
+        MemoryPolicyConfiguration plcCfg = memPlc.config();
+
+        if (plcCfg.getPageEvictionMode() == DataPageEvictionMode.DISABLED)
+            return;
+
+        long memorySize = plcCfg.getSize();
+
+        PageMemory pageMem = memPlc.pageMemory();
+
+        int sysPageSize = pageMem.systemPageSize();
+
+        FreeListImpl freeListImpl = freeListMap.get(plcCfg.getName());
+
+        for (;;) {
+            long allocatedPagesCnt = pageMem.loadedPages();
+
+            int emptyDataPagesCnt = freeListImpl.emptyDataPages();
+
+            boolean shouldEvict = allocatedPagesCnt > (memorySize / sysPageSize * plcCfg.getEvictionThreshold()) &&
+                emptyDataPagesCnt < plcCfg.getEmptyPagesPoolSize();
+
+            if (shouldEvict)
+                memPlc.evictionTracker().evictDataPage();
+            else
+                break;
+        }
+    }
+
+    /**
      * @param dbCfg memory configuration with common parameters.
      * @param plc memory policy with PageMemory specific parameters.
      * @param memMetrics {@link MemoryMetrics} object to collect memory usage metrics.
-     * @return Page memory instance.
+     * @return Memory policy instance.
      */
-    private PageMemory initMemory(MemoryConfiguration dbCfg, MemoryPolicyConfiguration plc, MemoryMetricsImpl memMetrics) {
+    private MemoryPolicy initMemory(MemoryConfiguration dbCfg, MemoryPolicyConfiguration plc, MemoryMetricsImpl memMetrics) {
         long[] sizes = calculateFragmentSizes(
                 dbCfg.getConcurrencyLevel(),
                 plc.getSize());
@@ -532,7 +608,27 @@ public class IgniteCacheDatabaseSharedManager extends GridCacheSharedManagerAdap
                 true,
                 sizes);
 
-        return createPageMemory(memProvider, dbCfg.getPageSize(), memMetrics);
+        PageMemory pageMem = createPageMemory(memProvider, dbCfg.getPageSize(), memMetrics);
+
+        return new MemoryPolicy(pageMem, plc, memMetrics, createPageEvictionTracker(plc, pageMem));
+    }
+
+    /**
+     * @param plc Memory Policy Configuration.
+     * @param pageMem Page memory.
+     */
+    private PageEvictionTracker createPageEvictionTracker(MemoryPolicyConfiguration plc, PageMemory pageMem) {
+        if (Boolean.getBoolean("override.fair.fifo.page.eviction.tracker"))
+            return new FairFifoPageEvictionTracker(pageMem, plc, cctx);
+
+        switch (plc.getPageEvictionMode()) {
+            case RANDOM_LRU:
+                return new RandomLruPageEvictionTracker(pageMem, plc, cctx);
+            case RANDOM_2_LRU:
+                return new Random2LruPageEvictionTracker(pageMem, plc, cctx);
+            default:
+                return new NoOpPageEvictionTracker();
+        }
     }
 
     /**

http://git-wip-us.apache.org/repos/asf/ignite/blob/ff5b3e16/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/database/MemoryPolicy.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/database/MemoryPolicy.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/database/MemoryPolicy.java
index be23b38..90e5ac1 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/database/MemoryPolicy.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/database/MemoryPolicy.java
@@ -19,6 +19,7 @@ package org.apache.ignite.internal.processors.cache.database;
 import org.apache.ignite.MemoryMetrics;
 import org.apache.ignite.configuration.MemoryPolicyConfiguration;
 import org.apache.ignite.internal.pagemem.PageMemory;
+import org.apache.ignite.internal.processors.cache.database.evict.PageEvictionTracker;
 
 /**
  * Memory policy provides access to objects configured with {@link MemoryPolicyConfiguration} configuration.
@@ -33,15 +34,24 @@ public class MemoryPolicy {
     /** */
     private final MemoryPolicyConfiguration cfg;
 
+    /** */
+    private final PageEvictionTracker evictionTracker;
+
     /**
      * @param pageMem PageMemory instance.
      * @param memMetrics MemoryMetrics instance.
      * @param cfg Configuration of given MemoryPolicy.
+     * @param evictionTracker Eviction tracker.
      */
-    public MemoryPolicy(PageMemory pageMem, MemoryMetrics memMetrics, MemoryPolicyConfiguration cfg) {
+    public MemoryPolicy(
+        PageMemory pageMem,
+        MemoryPolicyConfiguration cfg,
+        MemoryMetrics memMetrics,
+        PageEvictionTracker evictionTracker) {
         this.pageMem = pageMem;
         this.memMetrics = memMetrics;
         this.cfg = cfg;
+        this.evictionTracker = evictionTracker;
     }
 
     /**
@@ -64,4 +74,11 @@ public class MemoryPolicy {
     public MemoryMetrics memoryMetrics() {
         return memMetrics;
     }
+
+    /**
+     *
+     */
+    public PageEvictionTracker evictionTracker() {
+        return evictionTracker;
+    }
 }

http://git-wip-us.apache.org/repos/asf/ignite/blob/ff5b3e16/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/database/evict/FairFifoPageEvictionTracker.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/database/evict/FairFifoPageEvictionTracker.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/database/evict/FairFifoPageEvictionTracker.java
new file mode 100644
index 0000000..8847013
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/database/evict/FairFifoPageEvictionTracker.java
@@ -0,0 +1,74 @@
+/*
+* 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.database.evict;
+
+import java.util.LinkedList;
+import org.apache.ignite.IgniteCheckedException;
+import org.apache.ignite.IgniteException;
+import org.apache.ignite.configuration.MemoryPolicyConfiguration;
+import org.apache.ignite.internal.pagemem.PageIdUtils;
+import org.apache.ignite.internal.pagemem.PageMemory;
+import org.apache.ignite.internal.processors.cache.GridCacheSharedContext;
+
+/**
+ * On-heap FIFO page eviction tracker. Only for test purposes.
+ */
+public class FairFifoPageEvictionTracker extends PageAbstractEvictionTracker {
+    /** Page usage deque. */
+    private final LinkedList<Integer> pageUsageList = new LinkedList<>();
+
+    /**
+     * @param pageMem Page memory.
+     * @param plcCfg Memory policy configuration.
+     * @param sharedCtx Shared context.
+     */
+    public FairFifoPageEvictionTracker(PageMemory pageMem,
+        MemoryPolicyConfiguration plcCfg,
+        GridCacheSharedContext sharedCtx) {
+        super(pageMem, plcCfg, sharedCtx);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void start() throws IgniteException {
+        // No-op.
+    }
+
+    /** {@inheritDoc} */
+    @Override public void stop() throws IgniteException {
+        // No-op.
+    }
+
+    /** {@inheritDoc} */
+    @Override public synchronized void touchPage(long pageId) throws IgniteCheckedException {
+        pageUsageList.addLast(PageIdUtils.pageIndex(pageId));
+    }
+
+    /** {@inheritDoc} */
+    @Override public synchronized void evictDataPage() throws IgniteCheckedException {
+        evictDataPage(pageUsageList.pollFirst());
+    }
+
+    /** {@inheritDoc} */
+    @Override public synchronized void forgetPage(long pageId) throws IgniteCheckedException {
+        // No-op.
+    }
+
+    /** {@inheritDoc} */
+    @Override protected synchronized boolean checkTouch(long pageId) {
+        return true;
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/ff5b3e16/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/database/evict/NoOpPageEvictionTracker.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/database/evict/NoOpPageEvictionTracker.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/database/evict/NoOpPageEvictionTracker.java
new file mode 100644
index 0000000..ba466bf
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/database/evict/NoOpPageEvictionTracker.java
@@ -0,0 +1,50 @@
+/*
+* 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.database.evict;
+
+import org.apache.ignite.IgniteCheckedException;
+import org.apache.ignite.IgniteException;
+
+/**
+ *
+ */
+public class NoOpPageEvictionTracker implements PageEvictionTracker {
+    /** {@inheritDoc} */
+    @Override public void start() throws IgniteException {
+        // No-op.
+    }
+
+    /** {@inheritDoc} */
+    @Override public void stop() throws IgniteException {
+        // No-op.
+    }
+
+    /** {@inheritDoc} */
+    @Override public void touchPage(long pageId) throws IgniteCheckedException {
+        // No-op.
+    }
+
+    /** {@inheritDoc} */
+    @Override public void evictDataPage() throws IgniteCheckedException {
+        // No-op.
+    }
+
+    /** {@inheritDoc} */
+    @Override public void forgetPage(long pageId) throws IgniteCheckedException {
+        // No-op.
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/ff5b3e16/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/database/evict/PageAbstractEvictionTracker.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/database/evict/PageAbstractEvictionTracker.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/database/evict/PageAbstractEvictionTracker.java
new file mode 100644
index 0000000..88de545
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/database/evict/PageAbstractEvictionTracker.java
@@ -0,0 +1,243 @@
+/*
+* 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.database.evict;
+
+import java.util.List;
+import org.apache.ignite.IgniteCheckedException;
+import org.apache.ignite.configuration.MemoryConfiguration;
+import org.apache.ignite.configuration.MemoryPolicyConfiguration;
+import org.apache.ignite.internal.pagemem.PageIdUtils;
+import org.apache.ignite.internal.pagemem.PageMemory;
+import org.apache.ignite.internal.processors.cache.GridCacheContext;
+import org.apache.ignite.internal.processors.cache.GridCacheEntryEx;
+import org.apache.ignite.internal.processors.cache.GridCacheSharedContext;
+import org.apache.ignite.internal.processors.cache.database.CacheDataRowAdapter;
+import org.apache.ignite.internal.processors.cache.database.tree.io.DataPageIO;
+import org.apache.ignite.internal.processors.cache.database.tree.io.PageIO;
+import org.apache.ignite.internal.processors.cache.version.GridCacheVersionManager;
+import org.apache.ignite.internal.util.typedef.internal.U;
+
+/**
+ *
+ */
+public abstract class PageAbstractEvictionTracker implements PageEvictionTracker {
+    /** This number of least significant bits is dropped from timestamp. */
+    private static final int COMPACT_TS_SHIFT = 8; // Enough if grid works for less than 17 years.
+
+    /** Millis in day. */
+    private final static int DAY = 24 * 60 * 60 * 1000;
+
+    /** Page memory. */
+    protected final PageMemory pageMem;
+
+    /** Tracking array size. */
+    final int trackingSize;
+
+    /** Base compact timestamp. */
+    private final long baseCompactTs;
+
+    /** Shared context. */
+    private final GridCacheSharedContext sharedCtx;
+
+    /* TODO: IGNITE-4921: Will be removed after segments refactoring >>>> */
+    protected final int segBits;
+    protected final int idxBits;
+    protected final int segMask;
+    protected final int idxMask;
+    protected final int segmentPageCount;
+    /* <<<< */
+
+    /**
+     * @param pageMem Page memory.
+     * @param plcCfg Memory policy configuration.
+     * @param sharedCtx Shared context.
+     */
+    PageAbstractEvictionTracker(
+        PageMemory pageMem,
+        MemoryPolicyConfiguration plcCfg,
+        GridCacheSharedContext sharedCtx
+    ) {
+        this.pageMem = pageMem;
+
+        this.sharedCtx = sharedCtx;
+
+        MemoryConfiguration memCfg = sharedCtx.kernalContext().config().getMemoryConfiguration();
+
+        /* TODO: IGNITE-4921: Will be removed after segments refactoring >>>> */
+        int concurrencyLevel = memCfg.getConcurrencyLevel();
+
+        if (concurrencyLevel < 1)
+            concurrencyLevel = Runtime.getRuntime().availableProcessors();
+
+        int pageSize = memCfg.getPageSize();
+
+        long segSize = plcCfg.getSize() / concurrencyLevel;
+
+        if (segSize < 1024 * 1024)
+            segSize = 1024 * 1024;
+
+        segmentPageCount = (int)(segSize / pageSize);
+
+        segBits = Integer.SIZE - Integer.numberOfLeadingZeros(concurrencyLevel - 1);
+
+        idxBits = PageIdUtils.PAGE_IDX_SIZE - segBits;
+
+        segMask = ~(-1 << segBits);
+
+        idxMask = ~(-1 << idxBits);
+        /* <<<< */
+
+        trackingSize = segmentPageCount << segBits;
+
+        baseCompactTs = (U.currentTimeMillis() - DAY) >> COMPACT_TS_SHIFT;
+        // We subtract day to avoid fail in case of daylight shift or timezone change.
+    }
+
+    /**
+     * @param pageIdx Page index.
+     * @return true if at least one data row has been evicted
+     * @throws IgniteCheckedException If failed.
+     */
+    final boolean evictDataPage(int pageIdx) throws IgniteCheckedException {
+        long fakePageId = PageIdUtils.pageId(0, (byte)0, pageIdx);
+
+        long page = pageMem.acquirePage(0, fakePageId);
+
+        List<CacheDataRowAdapter> rowsToEvict;
+
+        try {
+            long pageAddr = pageMem.readLockForce(0, fakePageId, page);
+
+            try {
+                if (PageIO.getType(pageAddr) != PageIO.T_DATA)
+                    return false; // Can't evict: page has been recycled into non-data page.
+
+                DataPageIO io = DataPageIO.VERSIONS.forPage(pageAddr);
+
+                long realPageId = PageIO.getPageId(pageAddr);
+
+                if (!checkTouch(realPageId))
+                    return false; // Can't evict: another thread concurrently invoked forgetPage()
+
+                rowsToEvict = io.forAllItems(pageAddr, new DataPageIO.CC<CacheDataRowAdapter>() {
+                    @Override public CacheDataRowAdapter apply(long link) throws IgniteCheckedException {
+                        CacheDataRowAdapter row = new CacheDataRowAdapter(link);
+
+                        row.initFromLink(null, sharedCtx, pageMem, CacheDataRowAdapter.RowData.KEY_ONLY);
+
+                        assert row.cacheId() != 0 : "Cache ID should be stored in rows of evictable cache";
+
+                        return row;
+                    }
+                });
+            }
+            finally {
+                pageMem.readUnlock(0, fakePageId, page);
+            }
+        }
+        finally {
+            pageMem.releasePage(0, fakePageId, page);
+        }
+
+        boolean evictionDone = false;
+
+        for (CacheDataRowAdapter dataRow : rowsToEvict) {
+            GridCacheContext<?, ?> cacheCtx = sharedCtx.cacheContext(dataRow.cacheId());
+
+            if (!cacheCtx.userCache())
+                continue;
+
+            GridCacheEntryEx entryEx = cacheCtx.cache().entryEx(dataRow.key());
+
+            evictionDone |= entryEx.evictInternal(GridCacheVersionManager.EVICT_VER, null, true);
+        }
+
+        return evictionDone;
+    }
+
+    /**
+     * @param pageId Page ID.
+     * @return true if page was touched at least once.
+     */
+    protected abstract boolean checkTouch(long pageId);
+
+    /**
+     * @param epochMilli Time millis.
+     * @return Compact timestamp. Comparable and fits in 4 bytes.
+     */
+    final long compactTimestamp(long epochMilli) {
+        return (epochMilli >> COMPACT_TS_SHIFT) - baseCompactTs;
+    }
+
+    /**
+     * Resolves position in tracking array by page index.
+     *
+     * @param pageIdx Page index.
+     * @return Position of page in tracking array.
+     */
+    int trackingIdx(int pageIdx) {
+        int inSegmentPageIdx = inSegmentPageIdx(pageIdx);
+
+        assert inSegmentPageIdx < segmentPageCount : inSegmentPageIdx;
+
+        int trackingIdx = segmentIdx(pageIdx) * segmentPageCount + inSegmentPageIdx;
+
+        assert trackingIdx < trackingSize : trackingIdx;
+
+        return trackingIdx;
+    }
+
+    /**
+     * Reverse of {@link #trackingIdx(int)}.
+     *
+     * @param trackingIdx Tracking index.
+     * @return Page index.
+     */
+    int pageIdx(int trackingIdx) {
+        assert trackingIdx < trackingSize;
+
+        long res = 0;
+
+        long segIdx = trackingIdx / segmentPageCount;
+        long pageIdx = trackingIdx % segmentPageCount;
+
+        res = (res << segBits) | (segIdx & segMask);
+        res = (res << idxBits) | (pageIdx & idxMask);
+
+        assert (res & (-1L << 32)) == 0 : res;
+
+        return (int)res;
+    }
+
+    /* TODO: IGNITE-4921: Will be removed after segments refactoring >>>> */
+    /**
+     * @param pageIdx Page index.
+     * @return Number of segment.
+     */
+    private int segmentIdx(int pageIdx) {
+        return (pageIdx >> idxBits) & segMask;
+    }
+
+    /**
+     * @param pageIdx Page index.
+     * @return Number of page inside segment.
+     */
+    private int inSegmentPageIdx(int pageIdx) {
+        return pageIdx & idxMask;
+    }
+    /* <<<< */
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/ff5b3e16/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/database/evict/PageEvictionTracker.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/database/evict/PageEvictionTracker.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/database/evict/PageEvictionTracker.java
new file mode 100644
index 0000000..b13dcf8
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/database/evict/PageEvictionTracker.java
@@ -0,0 +1,52 @@
+/*
+* 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.database.evict;
+
+import org.apache.ignite.IgniteCheckedException;
+import org.apache.ignite.lifecycle.LifecycleAware;
+
+/**
+ * Entry point for per-page eviction. Accepts information about touching data pages,
+ * capable of evicting "the least needed" page (according to implemented eviction algorithm).
+ */
+public interface PageEvictionTracker extends LifecycleAware {
+    /**
+     * Call this method when data page is accessed.
+     *
+     * @param pageId Page id.
+     * @throws IgniteCheckedException In case of page memory error.
+     */
+    public void touchPage(long pageId) throws IgniteCheckedException;
+
+    /**
+     * Evicts one data page.
+     * In most cases, all entries will be removed from the page.
+     * Method guarantees removing at least one entry from "evicted" data page. Removing all entries may be
+     * not possible, as some of them can be used by active transactions.
+     *
+     * @throws IgniteCheckedException In case of page memory error.
+     */
+    public void evictDataPage() throws IgniteCheckedException;
+
+    /**
+     * Call this method when last entry is removed from data page.
+     *
+     * @param pageId Page id.
+     * @throws IgniteCheckedException In case of page memory error.
+     */
+    public void forgetPage(long pageId) throws IgniteCheckedException;
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/ff5b3e16/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/database/evict/Random2LruPageEvictionTracker.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/database/evict/Random2LruPageEvictionTracker.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/database/evict/Random2LruPageEvictionTracker.java
new file mode 100644
index 0000000..f0ad813
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/database/evict/Random2LruPageEvictionTracker.java
@@ -0,0 +1,180 @@
+/*
+* 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.database.evict;
+
+import java.util.concurrent.ThreadLocalRandom;
+import org.apache.ignite.IgniteCheckedException;
+import org.apache.ignite.IgniteException;
+import org.apache.ignite.IgniteLogger;
+import org.apache.ignite.configuration.MemoryConfiguration;
+import org.apache.ignite.configuration.MemoryPolicyConfiguration;
+import org.apache.ignite.internal.pagemem.PageIdUtils;
+import org.apache.ignite.internal.pagemem.PageMemory;
+import org.apache.ignite.internal.processors.cache.GridCacheSharedContext;
+import org.apache.ignite.internal.util.GridUnsafe;
+import org.apache.ignite.internal.util.typedef.internal.LT;
+import org.apache.ignite.internal.util.typedef.internal.U;
+
+/**
+ *
+ */
+public class Random2LruPageEvictionTracker extends PageAbstractEvictionTracker {
+    /** Evict attempts limit. */
+    private static final int EVICT_ATTEMPTS_LIMIT = 30;
+
+    /** LRU Sample size. */
+    private static final int SAMPLE_SIZE = 5;
+
+    /** Maximum sample search spin count */
+    private static final int SAMPLE_SPIN_LIMIT = SAMPLE_SIZE * 1000;
+
+    /** Logger. */
+    private final IgniteLogger log;
+
+    /** Tracking array ptr. */
+    private long trackingArrPtr;
+
+    /**
+     * @param pageMem Page memory.
+     * @param plcCfg Policy config.
+     * @param sharedCtx Shared context.
+     */
+    public Random2LruPageEvictionTracker(
+        PageMemory pageMem,
+        MemoryPolicyConfiguration plcCfg,
+        GridCacheSharedContext<?, ?> sharedCtx
+    ) {
+        super(pageMem, plcCfg, sharedCtx);
+
+        MemoryConfiguration memCfg = sharedCtx.kernalContext().config().getMemoryConfiguration();
+
+        assert plcCfg.getSize() / memCfg.getPageSize() < Integer.MAX_VALUE;
+
+        log = sharedCtx.logger(getClass());
+    }
+
+    /** {@inheritDoc} */
+    @Override public void start() throws IgniteException {
+        trackingArrPtr = GridUnsafe.allocateMemory(trackingSize * 8);
+
+        GridUnsafe.setMemory(trackingArrPtr, trackingSize * 8, (byte)0);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void stop() throws IgniteException {
+        GridUnsafe.freeMemory(trackingArrPtr);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void touchPage(long pageId) throws IgniteCheckedException {
+        int pageIdx = PageIdUtils.pageIndex(pageId);
+
+        long latestTs = compactTimestamp(U.currentTimeMillis());
+
+        assert latestTs >= 0 && latestTs < Integer.MAX_VALUE;
+
+        boolean success;
+
+        do {
+            int trackingIdx = trackingIdx(pageIdx);
+
+            int firstTs = GridUnsafe.getIntVolatile(null, trackingArrPtr + trackingIdx * 8);
+
+            int secondTs = GridUnsafe.getIntVolatile(null, trackingArrPtr + trackingIdx * 8 + 4);
+
+            if (firstTs <= secondTs)
+                success = GridUnsafe.compareAndSwapInt(null, trackingArrPtr + trackingIdx * 8, firstTs, (int)latestTs);
+            else {
+                success = GridUnsafe.compareAndSwapInt(
+                    null, trackingArrPtr + trackingIdx * 8 + 4, secondTs, (int)latestTs);
+            }
+        } while (!success);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void evictDataPage() throws IgniteCheckedException {
+        ThreadLocalRandom rnd = ThreadLocalRandom.current();
+
+        int evictAttemptsCnt = 0;
+
+        while (evictAttemptsCnt < EVICT_ATTEMPTS_LIMIT) {
+            int lruTrackingIdx = -1;
+
+            int lruCompactTs = Integer.MAX_VALUE;
+
+            int dataPagesCnt = 0;
+
+            int sampleSpinCnt = 0;
+
+            while (dataPagesCnt < SAMPLE_SIZE) {
+                int trackingIdx = rnd.nextInt(trackingSize);
+
+                int firstTs = GridUnsafe.getIntVolatile(null, trackingArrPtr + trackingIdx * 8);
+
+                int secondTs = GridUnsafe.getIntVolatile(null, trackingArrPtr + trackingIdx * 8 + 4);
+
+                int minTs = Math.min(firstTs, secondTs);
+
+                int maxTs = Math.max(firstTs, secondTs);
+
+                if (maxTs != 0) {
+                    // We chose data page with at least one touch.
+                    if (minTs < lruCompactTs) {
+                        lruTrackingIdx = trackingIdx;
+
+                        lruCompactTs = minTs;
+                    }
+
+                    dataPagesCnt++;
+                }
+
+                sampleSpinCnt++;
+
+                if (sampleSpinCnt > SAMPLE_SPIN_LIMIT) {
+                    LT.warn(log, "Too many attempts to choose data page: " + SAMPLE_SPIN_LIMIT);
+
+                    return;
+                }
+            }
+
+            if (evictDataPage(pageIdx(lruTrackingIdx)))
+                return;
+
+            evictAttemptsCnt++;
+        }
+
+        LT.warn(log, "Too many failed attempts to evict page: " + EVICT_ATTEMPTS_LIMIT);
+    }
+
+    /** {@inheritDoc} */
+    @Override protected boolean checkTouch(long pageId) {
+        int trackingIdx = trackingIdx(PageIdUtils.pageIndex(pageId));
+
+        int firstTs = GridUnsafe.getIntVolatile(null, trackingArrPtr + trackingIdx * 8);
+
+        return firstTs != 0;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void forgetPage(long pageId) {
+        int pageIdx = PageIdUtils.pageIndex(pageId);
+
+        int trackingIdx = trackingIdx(pageIdx);
+
+        GridUnsafe.putLongVolatile(null, trackingArrPtr + trackingIdx * 8, 0L);
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/ff5b3e16/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/database/evict/RandomLruPageEvictionTracker.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/database/evict/RandomLruPageEvictionTracker.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/database/evict/RandomLruPageEvictionTracker.java
new file mode 100644
index 0000000..8818b1c
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/database/evict/RandomLruPageEvictionTracker.java
@@ -0,0 +1,157 @@
+/*
+* 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.database.evict;
+
+import java.util.concurrent.ThreadLocalRandom;
+import org.apache.ignite.IgniteCheckedException;
+import org.apache.ignite.IgniteException;
+import org.apache.ignite.IgniteLogger;
+import org.apache.ignite.configuration.MemoryConfiguration;
+import org.apache.ignite.configuration.MemoryPolicyConfiguration;
+import org.apache.ignite.internal.pagemem.PageIdUtils;
+import org.apache.ignite.internal.pagemem.PageMemory;
+import org.apache.ignite.internal.processors.cache.GridCacheSharedContext;
+import org.apache.ignite.internal.util.GridUnsafe;
+import org.apache.ignite.internal.util.typedef.internal.LT;
+import org.apache.ignite.internal.util.typedef.internal.U;
+
+/**
+ *
+ */
+public class RandomLruPageEvictionTracker extends PageAbstractEvictionTracker {
+    /** Evict attempts limit. */
+    private static final int EVICT_ATTEMPTS_LIMIT = 30;
+
+    /** LRU Sample size. */
+    private static final int SAMPLE_SIZE = 5;
+
+    /** Maximum sample search spin count */
+    private static final int SAMPLE_SPIN_LIMIT = SAMPLE_SIZE * 1000;
+
+    /** Logger. */
+    private final IgniteLogger log;
+
+    /** Tracking array ptr. */
+    private long trackingArrPtr;
+
+    /**
+     * @param pageMem Page memory.
+     * @param plcCfg Policy config.
+     * @param sharedCtx Shared context.
+     */
+    public RandomLruPageEvictionTracker(
+        PageMemory pageMem,
+        MemoryPolicyConfiguration plcCfg,
+        GridCacheSharedContext<?, ?> sharedCtx
+    ) {
+        super(pageMem, plcCfg, sharedCtx);
+
+        MemoryConfiguration memCfg = sharedCtx.kernalContext().config().getMemoryConfiguration();
+
+        assert plcCfg.getSize() / memCfg.getPageSize() < Integer.MAX_VALUE;
+
+        log = sharedCtx.logger(getClass());
+    }
+
+    /** {@inheritDoc} */
+    @Override public void start() throws IgniteException {
+        trackingArrPtr = GridUnsafe.allocateMemory(trackingSize * 4);
+
+        GridUnsafe.setMemory(trackingArrPtr, trackingSize * 4, (byte)0);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void stop() throws IgniteException {
+        GridUnsafe.freeMemory(trackingArrPtr);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void touchPage(long pageId) throws IgniteCheckedException {
+        int pageIdx = PageIdUtils.pageIndex(pageId);
+
+        long res = compactTimestamp(U.currentTimeMillis());
+        
+        assert res >= 0 && res < Integer.MAX_VALUE;
+        
+        GridUnsafe.putIntVolatile(null, trackingArrPtr + trackingIdx(pageIdx) * 4, (int)res);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void evictDataPage() throws IgniteCheckedException {
+        ThreadLocalRandom rnd = ThreadLocalRandom.current();
+
+        int evictAttemptsCnt = 0;
+
+        while (evictAttemptsCnt < EVICT_ATTEMPTS_LIMIT) {
+            int lruTrackingIdx = -1;
+
+            int lruCompactTs = Integer.MAX_VALUE;
+
+            int dataPagesCnt = 0;
+
+            int sampleSpinCnt = 0;
+
+            while (dataPagesCnt < SAMPLE_SIZE) {
+                int sampleTrackingIdx = rnd.nextInt(trackingSize);
+
+                int compactTs = GridUnsafe.getIntVolatile(null, trackingArrPtr + sampleTrackingIdx * 4);
+
+                if (compactTs != 0) {
+                    // We chose data page with at least one touch.
+                    if (compactTs < lruCompactTs) {
+                        lruTrackingIdx = sampleTrackingIdx;
+
+                        lruCompactTs = compactTs;
+                    }
+
+                    dataPagesCnt++;
+                }
+
+                sampleSpinCnt++;
+
+                if (sampleSpinCnt > SAMPLE_SPIN_LIMIT) {
+                    LT.warn(log, "Too many attempts to choose data page: " + SAMPLE_SPIN_LIMIT);
+
+                    return;
+                }
+            }
+
+            if (evictDataPage(pageIdx(lruTrackingIdx)))
+                return;
+
+            evictAttemptsCnt++;
+        }
+
+        LT.warn(log, "Too many failed attempts to evict page: " + EVICT_ATTEMPTS_LIMIT);
+    }
+
+    /** {@inheritDoc} */
+    @Override protected boolean checkTouch(long pageId) {
+        int trackingIdx = trackingIdx(PageIdUtils.pageIndex(pageId));
+
+        int ts = GridUnsafe.getIntVolatile(null, trackingArrPtr + trackingIdx * 4);
+
+        return ts != 0;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void forgetPage(long pageId) {
+        int pageIdx = PageIdUtils.pageIndex(pageId);
+
+        GridUnsafe.putIntVolatile(null, trackingArrPtr + trackingIdx(pageIdx) * 4, 0);
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/ff5b3e16/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/database/freelist/FreeListImpl.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/database/freelist/FreeListImpl.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/database/freelist/FreeListImpl.java
index d433172..cb68f7b 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/database/freelist/FreeListImpl.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/database/freelist/FreeListImpl.java
@@ -22,7 +22,6 @@ import org.apache.ignite.IgniteCheckedException;
 import org.apache.ignite.IgniteLogger;
 import org.apache.ignite.internal.pagemem.PageIdAllocator;
 import org.apache.ignite.internal.pagemem.PageIdUtils;
-import org.apache.ignite.internal.pagemem.PageMemory;
 import org.apache.ignite.internal.pagemem.PageUtils;
 import org.apache.ignite.internal.pagemem.wal.IgniteWriteAheadLogManager;
 import org.apache.ignite.internal.pagemem.wal.record.delta.DataPageInsertFragmentRecord;
@@ -31,6 +30,8 @@ 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.database.CacheDataRow;
 import org.apache.ignite.internal.processors.cache.database.MemoryMetricsImpl;
+import org.apache.ignite.internal.processors.cache.database.MemoryPolicy;
+import org.apache.ignite.internal.processors.cache.database.evict.PageEvictionTracker;
 import org.apache.ignite.internal.processors.cache.database.tree.io.CacheVersionIO;
 import org.apache.ignite.internal.processors.cache.database.tree.io.DataPageIO;
 import org.apache.ignite.internal.processors.cache.database.tree.io.DataPagePayload;
@@ -71,17 +72,22 @@ public class FreeListImpl extends PagesList implements FreeList, ReuseList {
     private final int MIN_SIZE_FOR_DATA_PAGE;
 
     /** */
+    private final int emptyDataPagesBucket;
+
+    /** */
     private final PageHandler<CacheDataRow, Boolean> updateRow = new UpdateRowHandler();
 
     /** */
     private final MemoryMetricsImpl memMetrics;
 
+    /** */
+    private final PageEvictionTracker evictionTracker;
+
     /**
      *
      */
     private final class UpdateRowHandler extends PageHandler<CacheDataRow, Boolean> {
-        @Override
-        public Boolean run(
+        @Override public Boolean run(
             int cacheId,
             long pageId,
             long page,
@@ -97,6 +103,8 @@ public class FreeListImpl extends PagesList implements FreeList, ReuseList {
 
             boolean updated = io.updateRow(pageAddr, itemId, pageSize(), null, row, rowSize);
 
+            evictionTracker.touchPage(pageId);
+
             if (updated && needWalDeltaRecord(pageId, page, walPlc)) {
                 // TODO This record must contain only a reference to a logical WAL record with the actual data.
                 byte[] payload = new byte[rowSize];
@@ -125,8 +133,7 @@ public class FreeListImpl extends PagesList implements FreeList, ReuseList {
      *
      */
     private final class WriteRowHandler extends PageHandler<CacheDataRow, Integer> {
-        @Override
-        public Integer run(
+        @Override public Integer run(
             int cacheId,
             long pageId,
             long page,
@@ -156,6 +163,9 @@ public class FreeListImpl extends PagesList implements FreeList, ReuseList {
                 put(null, pageId, page, pageAddr, bucket);
             }
 
+            if (written == rowSize)
+                evictionTracker.touchPage(pageId);
+
             // Avoid boxing with garbage generation for usual case.
             return written == rowSize ? COMPLETE : written;
         }
@@ -287,6 +297,9 @@ public class FreeListImpl extends PagesList implements FreeList, ReuseList {
                 }
                 else
                     put(null, pageId, page, pageAddr, newBucket);
+
+                if (io.isEmpty(pageAddr))
+                    evictionTracker.forgetPage(pageId);
             }
 
             // For common case boxed 0L will be cached inside of Long, so no garbage will be produced.
@@ -297,8 +310,8 @@ public class FreeListImpl extends PagesList implements FreeList, ReuseList {
     /**
      * @param cacheId Cache ID.
      * @param name Name (for debug purpose).
-     * @param pageMem Page memory.
      * @param memMetrics Memory metrics.
+     * @param memPlc Memory policy.
      * @param reuseList Reuse list or {@code null} if this free list will be a reuse list for itself.
      * @param wal Write ahead log manager.
      * @param metaPageId Metadata page ID.
@@ -308,13 +321,14 @@ public class FreeListImpl extends PagesList implements FreeList, ReuseList {
     public FreeListImpl(
         int cacheId,
         String name,
-        PageMemory pageMem,
         MemoryMetricsImpl memMetrics,
+        MemoryPolicy memPlc,
         ReuseList reuseList,
         IgniteWriteAheadLogManager wal,
         long metaPageId,
         boolean initNew) throws IgniteCheckedException {
-        super(cacheId, name, pageMem, BUCKETS, wal, metaPageId);
+        super(cacheId, name, memPlc.pageMemory(), BUCKETS, wal, metaPageId);
+        this.evictionTracker = memPlc.evictionTracker();
         this.reuseList = reuseList == null ? this : reuseList;
         int pageSize = pageMem.pageSize();
 
@@ -337,6 +351,8 @@ public class FreeListImpl extends PagesList implements FreeList, ReuseList {
 
         this.memMetrics = memMetrics;
 
+        emptyDataPagesBucket = bucket(MIN_SIZE_FOR_DATA_PAGE, false);
+
         init(metaPageId, initNew);
     }
 
@@ -446,25 +462,26 @@ public class FreeListImpl extends PagesList implements FreeList, ReuseList {
 
             int freeSpace = Math.min(MIN_SIZE_FOR_DATA_PAGE, rowSize - written);
 
-            int bucket = bucket(freeSpace, false);
+            long pageId = 0L;
+
+            if (freeSpace == MIN_SIZE_FOR_DATA_PAGE)
+                pageId = takeEmptyPage(emptyDataPagesBucket, DataPageIO.VERSIONS);
 
-            long pageId = 0;
             boolean reuseBucket = false;
 
             // TODO: properly handle reuse bucket.
-            for (int b = bucket + 1; b < BUCKETS - 1; b++) {
-                pageId = takeEmptyPage(b, DataPageIO.VERSIONS);
+            if (pageId == 0L) {
+                for (int b = bucket(freeSpace, false) + 1; b < BUCKETS - 1; b++) {
+                    pageId = takeEmptyPage(b, DataPageIO.VERSIONS);
 
-                if (pageId != 0L) {
-                    reuseBucket = isReuseBucket(b);
+                    if (pageId != 0L) {
+                        reuseBucket = isReuseBucket(b);
 
-                    break;
+                        break;
+                    }
                 }
             }
 
-            if (pageId == 0L)
-                pageId = takeEmptyPage(bucket, DataPageIO.VERSIONS);
-
             boolean allocated = pageId == 0L;
 
             if (allocated)
@@ -531,6 +548,13 @@ public class FreeListImpl extends PagesList implements FreeList, ReuseList {
         return bucket == REUSE_BUCKET;
     }
 
+    /**
+     * @return Number of empty data pages in free list.
+     */
+    public int emptyDataPages() {
+        return bucketsSize[emptyDataPagesBucket].intValue();
+    }
+
     /** {@inheritDoc} */
     @Override public void addForRecycle(ReuseBag bag) throws IgniteCheckedException {
         assert reuseList == this: "not allowed to be a reuse list";
@@ -561,7 +585,7 @@ public class FreeListImpl extends PagesList implements FreeList, ReuseList {
         int keyLen = row.key().valueBytesLength(null);
         int valLen = row.value().valueBytesLength(null);
 
-        return keyLen + valLen + CacheVersionIO.size(row.version(), false) + 8;
+        return keyLen + valLen + CacheVersionIO.size(row.version(), false) + 8 + (row.cacheId() == 0 ? 0 : 4);
     }
 
     /** {@inheritDoc} */