/*
* 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.worker.block.evictor;
import alluxio.ConfigurationTestUtils;
import alluxio.worker.block.BlockStoreEventListener;
import alluxio.worker.block.BlockStoreLocation;
import alluxio.worker.block.TieredBlockStoreTestUtils;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
/**
* Unit tests for specific behavior of {@link LRUEvictor} such as evicting/moving least recently
* used blocks and cascading LRU eviction.
*/
public class LRUEvictorTest extends EvictorTestBase {
/**
* Sets up all dependencies before a test runs.
*/
@Before
public final void before() throws Exception {
init(LRUEvictor.class.getName());
}
/**
* Resets the context of the worker after a test ran.
*/
@After
public void after() {
ConfigurationTestUtils.resetConfiguration();
}
// access the block to update evictor
private void access(long blockId) {
((BlockStoreEventListener) mEvictor).onAccessBlock(SESSION_ID, blockId);
}
/**
* Tests that the eviction in the bottom tier works.
*/
@Test
public void evictInBottomTier() throws Exception {
int bottomTierOrdinal =
TieredBlockStoreTestUtils.TIER_ORDINAL[TieredBlockStoreTestUtils.TIER_ORDINAL.length - 1];
// capacity increases with index
long[] bottomTierDirCapacity = TieredBlockStoreTestUtils.TIER_CAPACITY_BYTES[bottomTierOrdinal];
int nDir = bottomTierDirCapacity.length;
// fill in dirs from larger to smaller capacity with blockId equal to BLOCK_ID plus dir index
for (int i = nDir - 1; i >= 0; i--) {
cache(SESSION_ID, BLOCK_ID + i, bottomTierDirCapacity[i], bottomTierOrdinal, i);
}
BlockStoreLocation anyDirInBottomTier =
BlockStoreLocation.anyDirInTier(TieredBlockStoreTestUtils.TIER_ALIAS[bottomTierOrdinal]);
// request smallest capacity and update access time on the evicted block for nDir times, the dir
// to evict blocks from should be in the same order as caching
for (int i = nDir - 1; i >= 0; i--) {
EvictionPlan plan =
mEvictor.freeSpaceWithView(bottomTierDirCapacity[0], anyDirInBottomTier, mManagerView);
Assert.assertNotNull(plan);
Assert.assertTrue(plan.toMove().isEmpty());
Assert.assertEquals(1, plan.toEvict().size());
long toEvictBlockId = plan.toEvict().get(0).getFirst();
Assert.assertEquals(BLOCK_ID + i, toEvictBlockId);
access(toEvictBlockId);
}
}
/**
* Tests the cascading eviction with the first tier filled and the second tier empty resulting in
* no eviction.
*/
@Test
public void cascadingEvictionTest1() throws Exception {
// Two tiers, each dir in the second tier has more space than any dir in the first tier. Fill in
// the first tier, leave the second tier empty. Request space from the first tier, blocks should
// be moved from the first to the second tier without eviction.
int firstTierOrdinal = TieredBlockStoreTestUtils.TIER_ORDINAL[0];
long[] firstTierDirCapacity = TieredBlockStoreTestUtils.TIER_CAPACITY_BYTES[0];
int nDir = firstTierDirCapacity.length;
for (int i = 0; i < nDir; i++) {
cache(SESSION_ID, BLOCK_ID + i, firstTierDirCapacity[i], firstTierOrdinal, i);
}
BlockStoreLocation anyDirInFirstTier =
BlockStoreLocation.anyDirInTier(TieredBlockStoreTestUtils.TIER_ALIAS[firstTierOrdinal]);
long smallestCapacity = firstTierDirCapacity[0];
for (int i = 0; i < nDir; i++) {
EvictionPlan plan =
mEvictor.freeSpaceWithView(smallestCapacity, anyDirInFirstTier, mManagerView);
Assert.assertTrue(EvictorTestUtils.validCascadingPlan(smallestCapacity, plan, mMetaManager));
Assert.assertEquals(0, plan.toEvict().size());
Assert.assertEquals(1, plan.toMove().size());
long blockId = plan.toMove().get(0).getBlockId();
Assert.assertEquals(BLOCK_ID + i, blockId);
access(blockId);
}
}
/**
* Tests the cascading eviction with the first and second tier filled resulting in blocks in the
* second tier are evicted.
*/
@Test
public void cascadingEvictionTest2() throws Exception {
// Two tiers, the second tier has more dirs than the first tier and each dir in the second tier
// has more space than any dir in the first tier. Fill in all dirs and request space from the
// first tier, blocks should be moved from the first to the second tier, and some blocks in the
// second tier should be evicted to hold blocks moved from the first tier.
long blockId = BLOCK_ID;
for (int tierOrdinal : TieredBlockStoreTestUtils.TIER_ORDINAL) {
long[] tierCapacity = TieredBlockStoreTestUtils.TIER_CAPACITY_BYTES[tierOrdinal];
for (int dirIdx = 0; dirIdx < tierCapacity.length; dirIdx++) {
cache(SESSION_ID, blockId, tierCapacity[dirIdx], tierOrdinal, dirIdx);
blockId++;
}
}
BlockStoreLocation anyDirInFirstTier =
BlockStoreLocation.anyDirInTier(TieredBlockStoreTestUtils.TIER_ALIAS[0]);
int nDirInFirstTier = TieredBlockStoreTestUtils.TIER_CAPACITY_BYTES[0].length;
long smallestCapacity = TieredBlockStoreTestUtils.TIER_CAPACITY_BYTES[0][0];
for (int i = 0; i < nDirInFirstTier; i++) {
EvictionPlan plan =
mEvictor.freeSpaceWithView(smallestCapacity, anyDirInFirstTier, mManagerView);
Assert.assertTrue(EvictorTestUtils.validCascadingPlan(smallestCapacity, plan, mMetaManager));
// least recently used block in the first tier needs to be moved to the second tier
Assert.assertEquals(1, plan.toMove().size());
long blockIdMovedInFirstTier = plan.toMove().get(0).getBlockId();
Assert.assertEquals(BLOCK_ID + i, blockIdMovedInFirstTier);
// least recently used cached block in the second tier will be evicted to hold blocks moved
// from first tier
Assert.assertEquals(1, plan.toEvict().size());
long blockIdEvictedInSecondTier = plan.toEvict().get(0).getFirst();
Assert.assertEquals(BLOCK_ID + nDirInFirstTier + i, blockIdEvictedInSecondTier);
access(blockIdMovedInFirstTier);
access(blockIdEvictedInSecondTier);
}
}
/**
* Tests the behavior of moving blocks with the cascading eviction.
*/
@Test
public void cascadingEvictionTest3() throws Exception {
// First Tier 2000, 3000
// Second Tier 10000, 20000, 30000
int blockSize = 1000;
cache(SESSION_ID, 101, blockSize, 0, 0);
cache(SESSION_ID, 102, blockSize, 0, 0);
cache(SESSION_ID, 103, blockSize, 0, 1);
cache(SESSION_ID, 104, blockSize, 0, 1);
cache(SESSION_ID, 105, blockSize, 0, 1);
cache(SESSION_ID, 106, 9500, 1, 2);
// After caching blocks, the free space looks like
// First Tier 0, 0
// Second Tier 10000, 20000, 200500
BlockStoreLocation anyDirInFirstTier = BlockStoreLocation.anyDirInTier("MEM");
BlockStoreLocation firstDirSecondTier = new BlockStoreLocation("SSD", 0);
BlockStoreLocation secondDirSecondTier = new BlockStoreLocation("SSD", 1);
BlockStoreLocation thirdDirSecondTier = new BlockStoreLocation("SSD", 2);
EvictionPlan plan = mEvictor.freeSpaceWithView(blockSize * 2, anyDirInFirstTier, mManagerView);
Assert.assertNotNull(plan);
Assert.assertEquals(0, plan.toEvict().size());
Assert.assertEquals(2, plan.toMove().size());
// 2 blocks to move. The first one should be moved the 3rd dir as it has max free space.
long blockId = plan.toMove().get(0).getBlockId();
Assert.assertEquals(101, blockId);
BlockStoreLocation dstLocation = plan.toMove().get(0).getDstLocation();
Assert.assertEquals(thirdDirSecondTier, dstLocation);
// The second one should be moved the 2nd dir because after the first move the second dir
// has the max free space.
blockId = plan.toMove().get(1).getBlockId();
Assert.assertEquals(102, blockId);
dstLocation = plan.toMove().get(1).getDstLocation();
Assert.assertEquals(secondDirSecondTier, dstLocation);
cache(SESSION_ID, 107, 10000, 1, 0);
cache(SESSION_ID, 108, 20000, 1, 1);
cache(SESSION_ID, 109, 19000, 1, 2);
access(106);
// After caching more blocks, the free space looks like
// First Tier 0, 0
// Second Tier 0, 0, 1500
plan = mEvictor.freeSpaceWithView(blockSize * 3, anyDirInFirstTier, mManagerView);
Assert.assertNotNull(plan);
Assert.assertEquals(1, plan.toEvict().size());
Assert.assertEquals(3, plan.toMove().size());
blockId = plan.toEvict().get(0).getFirst();
Assert.assertEquals(107, blockId);
// 3 blocks to move. The first one should be moved the 3rd dir as it has max free space.
blockId = plan.toMove().get(0).getBlockId();
Assert.assertEquals(103, blockId);
dstLocation = plan.toMove().get(0).getDstLocation();
Assert.assertEquals(thirdDirSecondTier, dstLocation);
// The other two should be moved the 1st dir because the 1st dir has the max free space
// after evicting block 107.
blockId = plan.toMove().get(1).getBlockId();
Assert.assertEquals(104, blockId);
dstLocation = plan.toMove().get(1).getDstLocation();
Assert.assertEquals(firstDirSecondTier, dstLocation);
blockId = plan.toMove().get(2).getBlockId();
Assert.assertEquals(105, blockId);
dstLocation = plan.toMove().get(1).getDstLocation();
Assert.assertEquals(firstDirSecondTier, dstLocation);
}
}