001/**
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.hbase.io.hfile;
019
020import static org.junit.Assert.assertEquals;
021import static org.junit.Assert.assertFalse;
022import static org.junit.Assert.assertTrue;
023import static org.junit.Assert.fail;
024
025import java.io.IOException;
026import java.lang.management.ManagementFactory;
027import java.lang.management.MemoryUsage;
028import java.nio.ByteBuffer;
029import org.apache.hadoop.conf.Configuration;
030import org.apache.hadoop.fs.FileSystem;
031import org.apache.hadoop.fs.Path;
032import org.apache.hadoop.hbase.HBaseClassTestRule;
033import org.apache.hadoop.hbase.HBaseConfiguration;
034import org.apache.hadoop.hbase.HBaseTestingUtility;
035import org.apache.hadoop.hbase.HColumnDescriptor;
036import org.apache.hadoop.hbase.HConstants;
037import org.apache.hadoop.hbase.io.hfile.BlockType.BlockCategory;
038import org.apache.hadoop.hbase.io.hfile.Cacheable.MemoryType;
039import org.apache.hadoop.hbase.io.hfile.bucket.BucketCache;
040import org.apache.hadoop.hbase.io.util.MemorySizeUtil;
041import org.apache.hadoop.hbase.nio.ByteBuff;
042import org.apache.hadoop.hbase.testclassification.IOTests;
043import org.apache.hadoop.hbase.testclassification.LargeTests;
044import org.apache.hadoop.hbase.util.Threads;
045import org.junit.Before;
046import org.junit.ClassRule;
047import org.junit.Test;
048import org.junit.experimental.categories.Category;
049import org.slf4j.Logger;
050import org.slf4j.LoggerFactory;
051
052/**
053 * Tests that {@link CacheConfig} does as expected.
054 */
055// This test is marked as a large test though it runs in a short amount of time
056// (seconds).  It is large because it depends on being able to reset the global
057// blockcache instance which is in a global variable.  Experience has it that
058// tests clash on the global variable if this test is run as small sized test.
059@Category({IOTests.class, LargeTests.class})
060public class TestCacheConfig {
061
062  @ClassRule
063  public static final HBaseClassTestRule CLASS_RULE =
064      HBaseClassTestRule.forClass(TestCacheConfig.class);
065
066  private static final Logger LOG = LoggerFactory.getLogger(TestCacheConfig.class);
067  private Configuration conf;
068
069  static class Deserializer implements CacheableDeserializer<Cacheable> {
070    private final Cacheable cacheable;
071    private int deserializedIdentifier = 0;
072
073    Deserializer(final Cacheable c) {
074      deserializedIdentifier = CacheableDeserializerIdManager.registerDeserializer(this);
075      this.cacheable = c;
076    }
077
078    @Override
079    public int getDeserialiserIdentifier() {
080      return deserializedIdentifier;
081    }
082
083    @Override
084    public Cacheable deserialize(ByteBuff b, boolean reuse, MemoryType memType) throws IOException {
085      LOG.info("Deserialized " + b + ", reuse=" + reuse);
086      return cacheable;
087    }
088
089    @Override
090    public Cacheable deserialize(ByteBuff b) throws IOException {
091      LOG.info("Deserialized " + b);
092      return cacheable;
093    }
094  }
095
096  static class IndexCacheEntry extends DataCacheEntry {
097    private static IndexCacheEntry SINGLETON = new IndexCacheEntry();
098
099    public IndexCacheEntry() {
100      super(SINGLETON);
101    }
102
103    @Override
104    public BlockType getBlockType() {
105      return BlockType.ROOT_INDEX;
106    }
107  }
108
109  static class DataCacheEntry implements Cacheable {
110    private static final int SIZE = 1;
111    private static DataCacheEntry SINGLETON = new DataCacheEntry();
112    final CacheableDeserializer<Cacheable> deserializer;
113
114    DataCacheEntry() {
115      this(SINGLETON);
116    }
117
118    DataCacheEntry(final Cacheable c) {
119      this.deserializer = new Deserializer(c);
120    }
121
122    @Override
123    public String toString() {
124      return "size=" + SIZE + ", type=" + getBlockType();
125    }
126
127    @Override
128    public long heapSize() {
129      return SIZE;
130    }
131
132    @Override
133    public int getSerializedLength() {
134      return SIZE;
135    }
136
137    @Override
138    public void serialize(ByteBuffer destination, boolean includeNextBlockMetadata) {
139      LOG.info("Serialized " + this + " to " + destination);
140    }
141
142    @Override
143    public CacheableDeserializer<Cacheable> getDeserializer() {
144      return this.deserializer;
145    }
146
147    @Override
148    public BlockType getBlockType() {
149      return BlockType.DATA;
150    }
151
152    @Override
153    public MemoryType getMemoryType() {
154      return MemoryType.EXCLUSIVE;
155    }
156  }
157
158  static class MetaCacheEntry extends DataCacheEntry {
159    @Override
160    public BlockType getBlockType() {
161      return BlockType.INTERMEDIATE_INDEX;
162    }
163  }
164
165  @Before
166  public void setUp() throws Exception {
167    this.conf = HBaseConfiguration.create();
168  }
169
170  /**
171   * @param bc The block cache instance.
172   * @param cc Cache config.
173   * @param doubling If true, addition of element ups counter by 2, not 1, because element added
174   * to onheap and offheap caches.
175   * @param sizing True if we should run sizing test (doesn't always apply).
176   */
177  void basicBlockCacheOps(final BlockCache bc, final CacheConfig cc, final boolean doubling,
178      final boolean sizing) {
179    assertTrue(CacheConfig.DEFAULT_IN_MEMORY == cc.isInMemory());
180    BlockCacheKey bck = new BlockCacheKey("f", 0);
181    Cacheable c = new DataCacheEntry();
182    // Do asserts on block counting.
183    long initialBlockCount = bc.getBlockCount();
184    bc.cacheBlock(bck, c, cc.isInMemory());
185    assertEquals(doubling ? 2 : 1, bc.getBlockCount() - initialBlockCount);
186    bc.evictBlock(bck);
187    assertEquals(initialBlockCount, bc.getBlockCount());
188    // Do size accounting.  Do it after the above 'warm-up' because it looks like some
189    // buffers do lazy allocation so sizes are off on first go around.
190    if (sizing) {
191      long originalSize = bc.getCurrentSize();
192      bc.cacheBlock(bck, c, cc.isInMemory());
193      assertTrue(bc.getCurrentSize() > originalSize);
194      bc.evictBlock(bck);
195      long size = bc.getCurrentSize();
196      assertEquals(originalSize, size);
197    }
198  }
199
200  @Test
201  public void testDisableCacheDataBlock() throws IOException {
202    Configuration conf = HBaseConfiguration.create();
203    CacheConfig cacheConfig = new CacheConfig(conf);
204    assertTrue(cacheConfig.shouldCacheBlockOnRead(BlockCategory.DATA));
205    assertFalse(cacheConfig.shouldCacheCompressed(BlockCategory.DATA));
206    assertFalse(cacheConfig.shouldCacheDataCompressed());
207    assertFalse(cacheConfig.shouldCacheDataOnWrite());
208    assertTrue(cacheConfig.shouldCacheDataOnRead());
209    assertTrue(cacheConfig.shouldCacheBlockOnRead(BlockCategory.INDEX));
210    assertTrue(cacheConfig.shouldCacheBlockOnRead(BlockCategory.META));
211    assertTrue(cacheConfig.shouldCacheBlockOnRead(BlockCategory.BLOOM));
212    assertFalse(cacheConfig.shouldCacheBloomsOnWrite());
213    assertFalse(cacheConfig.shouldCacheIndexesOnWrite());
214
215    conf.setBoolean(CacheConfig.CACHE_BLOCKS_ON_WRITE_KEY, true);
216    conf.setBoolean(CacheConfig.CACHE_DATA_BLOCKS_COMPRESSED_KEY, true);
217    conf.setBoolean(CacheConfig.CACHE_BLOOM_BLOCKS_ON_WRITE_KEY, true);
218    conf.setBoolean(CacheConfig.CACHE_INDEX_BLOCKS_ON_WRITE_KEY, true);
219
220    cacheConfig = new CacheConfig(conf);
221    assertTrue(cacheConfig.shouldCacheBlockOnRead(BlockCategory.DATA));
222    assertTrue(cacheConfig.shouldCacheCompressed(BlockCategory.DATA));
223    assertTrue(cacheConfig.shouldCacheDataCompressed());
224    assertTrue(cacheConfig.shouldCacheDataOnWrite());
225    assertTrue(cacheConfig.shouldCacheDataOnRead());
226    assertTrue(cacheConfig.shouldCacheBlockOnRead(BlockCategory.INDEX));
227    assertTrue(cacheConfig.shouldCacheBlockOnRead(BlockCategory.META));
228    assertTrue(cacheConfig.shouldCacheBlockOnRead(BlockCategory.BLOOM));
229    assertTrue(cacheConfig.shouldCacheBloomsOnWrite());
230    assertTrue(cacheConfig.shouldCacheIndexesOnWrite());
231
232    conf.setBoolean(CacheConfig.CACHE_DATA_ON_READ_KEY, false);
233    conf.setBoolean(CacheConfig.CACHE_BLOCKS_ON_WRITE_KEY, false);
234
235    cacheConfig = new CacheConfig(conf);
236    assertFalse(cacheConfig.shouldCacheBlockOnRead(BlockCategory.DATA));
237    assertFalse(cacheConfig.shouldCacheCompressed(BlockCategory.DATA));
238    assertFalse(cacheConfig.shouldCacheDataCompressed());
239    assertFalse(cacheConfig.shouldCacheDataOnWrite());
240    assertFalse(cacheConfig.shouldCacheDataOnRead());
241    assertTrue(cacheConfig.shouldCacheBlockOnRead(BlockCategory.INDEX));
242    assertFalse(cacheConfig.shouldCacheBlockOnRead(BlockCategory.META));
243    assertTrue(cacheConfig.shouldCacheBlockOnRead(BlockCategory.BLOOM));
244    assertTrue(cacheConfig.shouldCacheBloomsOnWrite());
245    assertTrue(cacheConfig.shouldCacheIndexesOnWrite());
246
247    conf.setBoolean(CacheConfig.CACHE_DATA_ON_READ_KEY, true);
248    conf.setBoolean(CacheConfig.CACHE_BLOCKS_ON_WRITE_KEY, false);
249
250    HColumnDescriptor family = new HColumnDescriptor("testDisableCacheDataBlock");
251    family.setBlockCacheEnabled(false);
252
253    cacheConfig = new CacheConfig(conf, family, null);
254    assertFalse(cacheConfig.shouldCacheBlockOnRead(BlockCategory.DATA));
255    assertFalse(cacheConfig.shouldCacheCompressed(BlockCategory.DATA));
256    assertFalse(cacheConfig.shouldCacheDataCompressed());
257    assertFalse(cacheConfig.shouldCacheDataOnWrite());
258    assertFalse(cacheConfig.shouldCacheDataOnRead());
259    assertTrue(cacheConfig.shouldCacheBlockOnRead(BlockCategory.INDEX));
260    assertFalse(cacheConfig.shouldCacheBlockOnRead(BlockCategory.META));
261    assertTrue(cacheConfig.shouldCacheBlockOnRead(BlockCategory.BLOOM));
262    assertTrue(cacheConfig.shouldCacheBloomsOnWrite());
263    assertTrue(cacheConfig.shouldCacheIndexesOnWrite());
264  }
265
266  @Test
267  public void testCacheConfigDefaultLRUBlockCache() {
268    CacheConfig cc = new CacheConfig(this.conf);
269    assertTrue(CacheConfig.DEFAULT_IN_MEMORY == cc.isInMemory());
270    BlockCache blockCache = BlockCacheFactory.createBlockCache(this.conf);
271    basicBlockCacheOps(blockCache, cc, false, true);
272    assertTrue(blockCache instanceof LruBlockCache);
273  }
274
275  /**
276   * Assert that the caches are deployed with CombinedBlockCache and of the appropriate sizes.
277   */
278  @Test
279  public void testOffHeapBucketCacheConfig() {
280    this.conf.set(HConstants.BUCKET_CACHE_IOENGINE_KEY, "offheap");
281    doBucketCacheConfigTest();
282  }
283
284  @Test
285  public void testFileBucketCacheConfig() throws IOException {
286    HBaseTestingUtility htu = new HBaseTestingUtility(this.conf);
287    try {
288      Path p = new Path(htu.getDataTestDir(), "bc.txt");
289      FileSystem fs = FileSystem.get(this.conf);
290      fs.create(p).close();
291      this.conf.set(HConstants.BUCKET_CACHE_IOENGINE_KEY, "file:" + p);
292      doBucketCacheConfigTest();
293    } finally {
294      htu.cleanupTestDir();
295    }
296  }
297
298  private void doBucketCacheConfigTest() {
299    final int bcSize = 100;
300    this.conf.setInt(HConstants.BUCKET_CACHE_SIZE_KEY, bcSize);
301    CacheConfig cc = new CacheConfig(this.conf);
302    BlockCache blockCache = BlockCacheFactory.createBlockCache(this.conf);
303    basicBlockCacheOps(blockCache, cc, false, false);
304    assertTrue(blockCache instanceof CombinedBlockCache);
305    // TODO: Assert sizes allocated are right and proportions.
306    CombinedBlockCache cbc = (CombinedBlockCache) blockCache;
307    BlockCache[] bcs = cbc.getBlockCaches();
308    assertTrue(bcs[0] instanceof LruBlockCache);
309    LruBlockCache lbc = (LruBlockCache) bcs[0];
310    assertEquals(MemorySizeUtil.getOnHeapCacheSize(this.conf), lbc.getMaxSize());
311    assertTrue(bcs[1] instanceof BucketCache);
312    BucketCache bc = (BucketCache) bcs[1];
313    // getMaxSize comes back in bytes but we specified size in MB
314    assertEquals(bcSize, bc.getMaxSize() / (1024 * 1024));
315  }
316
317  /**
318   * Assert that when BUCKET_CACHE_COMBINED_KEY is false, the non-default, that we deploy
319   * LruBlockCache as L1 with a BucketCache for L2.
320   */
321  @Test
322  public void testBucketCacheConfigL1L2Setup() {
323    this.conf.set(HConstants.BUCKET_CACHE_IOENGINE_KEY, "offheap");
324    // Make lru size is smaller than bcSize for sure.  Need this to be true so when eviction
325    // from L1 happens, it does not fail because L2 can't take the eviction because block too big.
326    this.conf.setFloat(HConstants.HFILE_BLOCK_CACHE_SIZE_KEY, 0.001f);
327    MemoryUsage mu = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage();
328    long lruExpectedSize = MemorySizeUtil.getOnHeapCacheSize(this.conf);
329    final int bcSize = 100;
330    long bcExpectedSize = 100 * 1024 * 1024; // MB.
331    assertTrue(lruExpectedSize < bcExpectedSize);
332    this.conf.setInt(HConstants.BUCKET_CACHE_SIZE_KEY, bcSize);
333    CacheConfig cc = new CacheConfig(this.conf);
334    BlockCache blockCache = BlockCacheFactory.createBlockCache(this.conf);
335    basicBlockCacheOps(blockCache, cc, false, false);
336    assertTrue(blockCache instanceof CombinedBlockCache);
337    // TODO: Assert sizes allocated are right and proportions.
338    CombinedBlockCache cbc = (CombinedBlockCache) blockCache;
339    FirstLevelBlockCache lbc = cbc.l1Cache;
340    assertEquals(lruExpectedSize, lbc.getMaxSize());
341    BlockCache bc = cbc.l2Cache;
342    // getMaxSize comes back in bytes but we specified size in MB
343    assertEquals(bcExpectedSize, ((BucketCache) bc).getMaxSize());
344    // Test the L1+L2 deploy works as we'd expect with blocks evicted from L1 going to L2.
345    long initialL1BlockCount = lbc.getBlockCount();
346    long initialL2BlockCount = bc.getBlockCount();
347    Cacheable c = new DataCacheEntry();
348    BlockCacheKey bck = new BlockCacheKey("bck", 0);
349    lbc.cacheBlock(bck, c, false);
350    assertEquals(initialL1BlockCount + 1, lbc.getBlockCount());
351    assertEquals(initialL2BlockCount, bc.getBlockCount());
352    // Force evictions by putting in a block too big.
353    final long justTooBigSize = ((LruBlockCache)lbc).acceptableSize() + 1;
354    lbc.cacheBlock(new BlockCacheKey("bck2", 0), new DataCacheEntry() {
355      @Override
356      public long heapSize() {
357        return justTooBigSize;
358      }
359
360      @Override
361      public int getSerializedLength() {
362        return (int)heapSize();
363      }
364    });
365    // The eviction thread in lrublockcache needs to run.
366    while (initialL1BlockCount != lbc.getBlockCount()) Threads.sleep(10);
367    assertEquals(initialL1BlockCount, lbc.getBlockCount());
368  }
369
370  @Test
371  public void testL2CacheWithInvalidBucketSize() {
372    Configuration c = new Configuration(this.conf);
373    c.set(HConstants.BUCKET_CACHE_IOENGINE_KEY, "offheap");
374    c.set(BlockCacheFactory.BUCKET_CACHE_BUCKETS_KEY, "256,512,1024,2048,4000,4096");
375    c.setFloat(HConstants.BUCKET_CACHE_SIZE_KEY, 1024);
376    try {
377      BlockCacheFactory.createBlockCache(c);
378      fail("Should throw IllegalArgumentException when passing illegal value for bucket size");
379    } catch (IllegalArgumentException e) {
380    }
381  }
382}