Skip to content

Commit

Permalink
cherry-pick and resolve location conflict: Add the two-choice random …
Browse files Browse the repository at this point in the history
…cache eviction policy

The PR adds the two-choice random cache eviction policy. The algorithm
selects two random page IDs and evicts the one least recently used.

From some evaluation (https://danluu.com/2choices-eviction/), the
two-choice random policy has competitive performance compared with LRU.

Users can configure the new cache eviction policy.

pr-link: Alluxio#16828
change-id: cid-f9a140208b3a6938ddc70766abfb3d9f90c722f0
  • Loading branch information
ChunxuTang authored and jiacheliu3 committed May 16, 2023
1 parent ae84f7c commit e15d0b0
Show file tree
Hide file tree
Showing 2 changed files with 175 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/*
* The Alluxio Open Foundation licenses this work under the Apache License, version 2.0
* (the "License"). You may not use this work except in compliance with the License, which is
* available at www.apache.org/licenses/LICENSE-2.0
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied, as more fully set forth in the License.
*
* See the NOTICE file distributed with this work for information regarding copyright ownership.
*/

package alluxio.client.file.cache.evictor;

import alluxio.client.file.cache.PageId;

import java.time.Instant;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.function.Predicate;
import javax.annotation.Nullable;
import javax.annotation.concurrent.ThreadSafe;

/**
* Two Choice Random client-side cache eviction policy.
* It selects two random page IDs and evicts the one least-recently used.
*/
@ThreadSafe
public class TwoChoiceRandomEvictor implements CacheEvictor {
private final Map<PageId, Long> mCache = Collections.synchronizedMap(new HashMap<>());

/**
* Constructor.
* @param options
*/
public TwoChoiceRandomEvictor(CacheEvictorOptions options) {
}

@Override
public void updateOnGet(PageId pageId) {
mCache.put(pageId, Instant.now().toEpochMilli());
}

@Override
public void updateOnPut(PageId pageId) {
mCache.put(pageId, Instant.now().toEpochMilli());
}

@Override
public void updateOnDelete(PageId pageId) {
mCache.remove(pageId);
}

@Nullable
@Override
public PageId evict() {
synchronized (mCache) {
if (mCache.isEmpty()) {
return null;
}

// TODO(chunxu): improve the performance here
List<PageId> keys = new ArrayList<>(mCache.keySet());
Random rand = new Random();
PageId key1 = keys.get(rand.nextInt(keys.size()));
PageId key2 = keys.get(rand.nextInt(keys.size()));
if (mCache.get(key1) < mCache.get(key2)) {
return key1;
}
return key2;
}
}

@Nullable
@Override
public PageId evictMatching(Predicate<PageId> criterion) {
synchronized (mCache) {
for (PageId candidate : mCache.keySet()) {
if (criterion.test(candidate)) {
return candidate;
}
}
return null;
}
}

@Override
public void reset() {
mCache.clear();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/*
* The Alluxio Open Foundation licenses this work under the Apache License, version 2.0
* (the "License"). You may not use this work except in compliance with the License, which is
* available at www.apache.org/licenses/LICENSE-2.0
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied, as more fully set forth in the License.
*
* See the NOTICE file distributed with this work for information regarding copyright ownership.
*/

package alluxio.client.file.cache;

import alluxio.client.file.cache.evictor.CacheEvictorOptions;
import alluxio.client.file.cache.evictor.TwoChoiceRandomEvictor;

import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;

/**
* Tests for the {@link TwoChoiceRandomEvictor} class.
*/
public class TwoChoiceRandomEvictorTest {
private TwoChoiceRandomEvictor mEvictor;
private final PageId mFirst = new PageId("1L", 2L);
private final PageId mSecond = new PageId("3L", 4L);
private final PageId mThird = new PageId("5L", 6L);

/**
* Sets up the instances.
*/
@Before
public void before() {
mEvictor = new TwoChoiceRandomEvictor(new CacheEvictorOptions());
}

@Test
public void evictGetOrder() {
mEvictor.updateOnGet(mFirst);
Assert.assertEquals(mFirst, mEvictor.evict());
mEvictor.updateOnGet(mSecond);
Assert.assertEquals(mSecond, mEvictor.evict());
}

@Test
public void evictPutOrder() {
mEvictor.updateOnPut(mFirst);
Assert.assertEquals(mFirst, mEvictor.evict());
mEvictor.updateOnPut(mSecond);
mEvictor.updateOnPut(mFirst);
PageId evictedPage = mEvictor.evict();
Assert.assertTrue(evictedPage.equals(mFirst) || evictedPage.equals(mSecond));
}

@Test
public void evictAfterDelete() {
mEvictor.updateOnPut(mFirst);
mEvictor.updateOnPut(mSecond);
mEvictor.updateOnPut(mThird);
mEvictor.updateOnDelete(mSecond);
mEvictor.updateOnDelete(mThird);
Assert.assertEquals(mFirst, mEvictor.evict());
}

@Test
public void evictEmpty() {
Assert.assertNull(mEvictor.evict());
}

@Test
public void evictAllGone() {
mEvictor.updateOnPut(mFirst);
mEvictor.updateOnPut(mSecond);
mEvictor.updateOnPut(mThird);
mEvictor.updateOnDelete(mFirst);
mEvictor.updateOnDelete(mSecond);
mEvictor.updateOnDelete(mThird);
Assert.assertNull(mEvictor.evict());
}
}

0 comments on commit e15d0b0

Please sign in to comment.