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.ByteBuffAllocator;
038import org.apache.hadoop.hbase.io.hfile.BlockType.BlockCategory;
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.MediumTests;
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, MediumTests.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 getDeserializerIdentifier() {
080      return deserializedIdentifier;
081    }
082
083    @Override
084    public Cacheable deserialize(ByteBuff b, ByteBuffAllocator alloc) throws IOException {
085      LOG.info("Deserialized " + b);
086      return cacheable;
087    }
088  };
089
090  static class IndexCacheEntry extends DataCacheEntry {
091    private static IndexCacheEntry SINGLETON = new IndexCacheEntry();
092
093    public IndexCacheEntry() {
094      super(SINGLETON);
095    }
096
097    @Override
098    public BlockType getBlockType() {
099      return BlockType.ROOT_INDEX;
100    }
101  }
102
103  static class DataCacheEntry implements Cacheable {
104    private static final int SIZE = 1;
105    private static DataCacheEntry SINGLETON = new DataCacheEntry();
106    final CacheableDeserializer<Cacheable> deserializer;
107
108    DataCacheEntry() {
109      this(SINGLETON);
110    }
111
112    DataCacheEntry(final Cacheable c) {
113      this.deserializer = new Deserializer(c);
114    }
115
116    @Override
117    public String toString() {
118      return "size=" + SIZE + ", type=" + getBlockType();
119    };
120
121    @Override
122    public long heapSize() {
123      return SIZE;
124    }
125
126    @Override
127    public int getSerializedLength() {
128      return SIZE;
129    }
130
131    @Override
132    public void serialize(ByteBuffer destination, boolean includeNextBlockMetadata) {
133      LOG.info("Serialized " + this + " to " + destination);
134    }
135
136    @Override
137    public CacheableDeserializer<Cacheable> getDeserializer() {
138      return this.deserializer;
139    }
140
141    @Override
142    public BlockType getBlockType() {
143      return BlockType.DATA;
144    }
145  }
146
147  static class MetaCacheEntry extends DataCacheEntry {
148    @Override
149    public BlockType getBlockType() {
150      return BlockType.INTERMEDIATE_INDEX;
151    }
152  }
153
154  @Before
155  public void setUp() throws Exception {
156    this.conf = HBaseConfiguration.create();
157  }
158
159  /**
160   * @param bc       The block cache instance.
161   * @param cc       Cache config.
162   * @param doubling If true, addition of element ups counter by 2, not 1, because element added to
163   *                 onheap and offheap caches.
164   * @param sizing   True if we should run sizing test (doesn't always apply).
165   */
166  void basicBlockCacheOps(final BlockCache bc, final CacheConfig cc, final boolean doubling,
167    final boolean sizing) {
168    assertTrue(CacheConfig.DEFAULT_IN_MEMORY == cc.isInMemory());
169    BlockCacheKey bck = new BlockCacheKey("f", 0);
170    Cacheable c = new DataCacheEntry();
171    // Do asserts on block counting.
172    long initialBlockCount = bc.getBlockCount();
173    bc.cacheBlock(bck, c, cc.isInMemory());
174    assertEquals(doubling ? 2 : 1, bc.getBlockCount() - initialBlockCount);
175    bc.evictBlock(bck);
176    assertEquals(initialBlockCount, bc.getBlockCount());
177    // Do size accounting. Do it after the above 'warm-up' because it looks like some
178    // buffers do lazy allocation so sizes are off on first go around.
179    if (sizing) {
180      long originalSize = bc.getCurrentSize();
181      bc.cacheBlock(bck, c, cc.isInMemory());
182      assertTrue(bc.getCurrentSize() > originalSize);
183      bc.evictBlock(bck);
184      long size = bc.getCurrentSize();
185      assertEquals(originalSize, size);
186    }
187  }
188
189  @Test
190  public void testDisableCacheDataBlock() throws IOException {
191    Configuration conf = HBaseConfiguration.create();
192    CacheConfig cacheConfig = new CacheConfig(conf);
193    assertTrue(cacheConfig.shouldCacheBlockOnRead(BlockCategory.DATA));
194    assertFalse(cacheConfig.shouldCacheCompressed(BlockCategory.DATA));
195    assertFalse(cacheConfig.shouldCacheDataCompressed());
196    assertFalse(cacheConfig.shouldCacheDataOnWrite());
197    assertTrue(cacheConfig.shouldCacheDataOnRead());
198    assertTrue(cacheConfig.shouldCacheBlockOnRead(BlockCategory.INDEX));
199    assertTrue(cacheConfig.shouldCacheBlockOnRead(BlockCategory.META));
200    assertTrue(cacheConfig.shouldCacheBlockOnRead(BlockCategory.BLOOM));
201    assertFalse(cacheConfig.shouldCacheBloomsOnWrite());
202    assertFalse(cacheConfig.shouldCacheIndexesOnWrite());
203
204    conf.setBoolean(CacheConfig.CACHE_BLOCKS_ON_WRITE_KEY, true);
205    conf.setBoolean(CacheConfig.CACHE_DATA_BLOCKS_COMPRESSED_KEY, true);
206    conf.setBoolean(CacheConfig.CACHE_BLOOM_BLOCKS_ON_WRITE_KEY, true);
207    conf.setBoolean(CacheConfig.CACHE_INDEX_BLOCKS_ON_WRITE_KEY, true);
208
209    cacheConfig = new CacheConfig(conf);
210    assertTrue(cacheConfig.shouldCacheBlockOnRead(BlockCategory.DATA));
211    assertTrue(cacheConfig.shouldCacheCompressed(BlockCategory.DATA));
212    assertTrue(cacheConfig.shouldCacheDataCompressed());
213    assertTrue(cacheConfig.shouldCacheDataOnWrite());
214    assertTrue(cacheConfig.shouldCacheDataOnRead());
215    assertTrue(cacheConfig.shouldCacheBlockOnRead(BlockCategory.INDEX));
216    assertTrue(cacheConfig.shouldCacheBlockOnRead(BlockCategory.META));
217    assertTrue(cacheConfig.shouldCacheBlockOnRead(BlockCategory.BLOOM));
218    assertTrue(cacheConfig.shouldCacheBloomsOnWrite());
219    assertTrue(cacheConfig.shouldCacheIndexesOnWrite());
220
221    conf.setBoolean(CacheConfig.CACHE_DATA_ON_READ_KEY, false);
222    conf.setBoolean(CacheConfig.CACHE_BLOCKS_ON_WRITE_KEY, false);
223
224    cacheConfig = new CacheConfig(conf);
225    assertFalse(cacheConfig.shouldCacheBlockOnRead(BlockCategory.DATA));
226    assertFalse(cacheConfig.shouldCacheCompressed(BlockCategory.DATA));
227    assertFalse(cacheConfig.shouldCacheDataCompressed());
228    assertFalse(cacheConfig.shouldCacheDataOnWrite());
229    assertFalse(cacheConfig.shouldCacheDataOnRead());
230    assertTrue(cacheConfig.shouldCacheBlockOnRead(BlockCategory.INDEX));
231    assertFalse(cacheConfig.shouldCacheBlockOnRead(BlockCategory.META));
232    assertTrue(cacheConfig.shouldCacheBlockOnRead(BlockCategory.BLOOM));
233    assertTrue(cacheConfig.shouldCacheBloomsOnWrite());
234    assertTrue(cacheConfig.shouldCacheIndexesOnWrite());
235
236    conf.setBoolean(CacheConfig.CACHE_DATA_ON_READ_KEY, true);
237    conf.setBoolean(CacheConfig.CACHE_BLOCKS_ON_WRITE_KEY, false);
238
239    HColumnDescriptor family = new HColumnDescriptor("testDisableCacheDataBlock");
240    family.setBlockCacheEnabled(false);
241
242    cacheConfig = new CacheConfig(conf, family, null, ByteBuffAllocator.HEAP);
243    assertFalse(cacheConfig.shouldCacheBlockOnRead(BlockCategory.DATA));
244    assertFalse(cacheConfig.shouldCacheCompressed(BlockCategory.DATA));
245    assertFalse(cacheConfig.shouldCacheDataCompressed());
246    assertFalse(cacheConfig.shouldCacheDataOnWrite());
247    assertFalse(cacheConfig.shouldCacheDataOnRead());
248    assertTrue(cacheConfig.shouldCacheBlockOnRead(BlockCategory.INDEX));
249    assertFalse(cacheConfig.shouldCacheBlockOnRead(BlockCategory.META));
250    assertTrue(cacheConfig.shouldCacheBlockOnRead(BlockCategory.BLOOM));
251    assertTrue(cacheConfig.shouldCacheBloomsOnWrite());
252    assertTrue(cacheConfig.shouldCacheIndexesOnWrite());
253  }
254
255  @Test
256  public void testCacheConfigDefaultLRUBlockCache() {
257    CacheConfig cc = new CacheConfig(this.conf);
258    assertTrue(CacheConfig.DEFAULT_IN_MEMORY == cc.isInMemory());
259    BlockCache blockCache = BlockCacheFactory.createBlockCache(this.conf);
260    basicBlockCacheOps(blockCache, cc, false, true);
261    assertTrue(blockCache instanceof LruBlockCache);
262  }
263
264  /**
265   * Assert that the caches are deployed with CombinedBlockCache and of the appropriate sizes.
266   */
267  @Test
268  public void testOffHeapBucketCacheConfig() {
269    this.conf.set(HConstants.BUCKET_CACHE_IOENGINE_KEY, "offheap");
270    doBucketCacheConfigTest();
271  }
272
273  @Test
274  public void testFileBucketCacheConfig() throws IOException {
275    HBaseTestingUtility htu = new HBaseTestingUtility(this.conf);
276    try {
277      Path p = new Path(htu.getDataTestDir(), "bc.txt");
278      FileSystem fs = FileSystem.get(this.conf);
279      fs.create(p).close();
280      this.conf.set(HConstants.BUCKET_CACHE_IOENGINE_KEY, "file:" + p);
281      doBucketCacheConfigTest();
282    } finally {
283      htu.cleanupTestDir();
284    }
285  }
286
287  private void doBucketCacheConfigTest() {
288    final int bcSize = 100;
289    this.conf.setInt(HConstants.BUCKET_CACHE_SIZE_KEY, bcSize);
290    CacheConfig cc = new CacheConfig(this.conf);
291    BlockCache blockCache = BlockCacheFactory.createBlockCache(this.conf);
292    basicBlockCacheOps(blockCache, cc, false, false);
293    assertTrue(blockCache instanceof CombinedBlockCache);
294    // TODO: Assert sizes allocated are right and proportions.
295    CombinedBlockCache cbc = (CombinedBlockCache) blockCache;
296    BlockCache[] bcs = cbc.getBlockCaches();
297    assertTrue(bcs[0] instanceof LruBlockCache);
298    LruBlockCache lbc = (LruBlockCache) bcs[0];
299    assertEquals(MemorySizeUtil.getOnHeapCacheSize(this.conf), lbc.getMaxSize());
300    assertTrue(bcs[1] instanceof BucketCache);
301    BucketCache bc = (BucketCache) bcs[1];
302    // getMaxSize comes back in bytes but we specified size in MB
303    assertEquals(bcSize, bc.getMaxSize() / (1024 * 1024));
304  }
305
306  /**
307   * Assert that when BUCKET_CACHE_COMBINED_KEY is false, the non-default, that we deploy
308   * LruBlockCache as L1 with a BucketCache for L2.
309   */
310  @Test
311  public void testBucketCacheConfigL1L2Setup() {
312    this.conf.set(HConstants.BUCKET_CACHE_IOENGINE_KEY, "offheap");
313    // Make lru size is smaller than bcSize for sure. Need this to be true so when eviction
314    // from L1 happens, it does not fail because L2 can't take the eviction because block too big.
315    this.conf.setFloat(HConstants.HFILE_BLOCK_CACHE_SIZE_KEY, 0.001f);
316    MemoryUsage mu = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage();
317    long lruExpectedSize = MemorySizeUtil.getOnHeapCacheSize(this.conf);
318    final int bcSize = 100;
319    long bcExpectedSize = 100 * 1024 * 1024; // MB.
320    assertTrue(lruExpectedSize < bcExpectedSize);
321    this.conf.setInt(HConstants.BUCKET_CACHE_SIZE_KEY, bcSize);
322    CacheConfig cc = new CacheConfig(this.conf);
323    BlockCache blockCache = BlockCacheFactory.createBlockCache(this.conf);
324    basicBlockCacheOps(blockCache, cc, false, false);
325    assertTrue(blockCache instanceof CombinedBlockCache);
326    // TODO: Assert sizes allocated are right and proportions.
327    CombinedBlockCache cbc = (CombinedBlockCache) blockCache;
328    FirstLevelBlockCache lbc = cbc.l1Cache;
329    assertEquals(lruExpectedSize, lbc.getMaxSize());
330    BlockCache bc = cbc.l2Cache;
331    // getMaxSize comes back in bytes but we specified size in MB
332    assertEquals(bcExpectedSize, ((BucketCache) bc).getMaxSize());
333    // Test the L1+L2 deploy works as we'd expect with blocks evicted from L1 going to L2.
334    long initialL1BlockCount = lbc.getBlockCount();
335    long initialL2BlockCount = bc.getBlockCount();
336    Cacheable c = new DataCacheEntry();
337    BlockCacheKey bck = new BlockCacheKey("bck", 0);
338    lbc.cacheBlock(bck, c, false);
339    assertEquals(initialL1BlockCount + 1, lbc.getBlockCount());
340    assertEquals(initialL2BlockCount, bc.getBlockCount());
341    // Force evictions by putting in a block too big.
342    final long justTooBigSize = ((LruBlockCache) lbc).acceptableSize() + 1;
343    lbc.cacheBlock(new BlockCacheKey("bck2", 0), new DataCacheEntry() {
344      @Override
345      public long heapSize() {
346        return justTooBigSize;
347      }
348
349      @Override
350      public int getSerializedLength() {
351        return (int) heapSize();
352      }
353    });
354    // The eviction thread in lrublockcache needs to run.
355    while (initialL1BlockCount != lbc.getBlockCount())
356      Threads.sleep(10);
357    assertEquals(initialL1BlockCount, lbc.getBlockCount());
358  }
359
360  @Test
361  public void testL2CacheWithInvalidBucketSize() {
362    Configuration c = new Configuration(this.conf);
363    c.set(HConstants.BUCKET_CACHE_IOENGINE_KEY, "offheap");
364    c.set(BlockCacheFactory.BUCKET_CACHE_BUCKETS_KEY, "256,512,1024,2048,4000,4096");
365    c.setFloat(HConstants.BUCKET_CACHE_SIZE_KEY, 1024);
366    try {
367      BlockCacheFactory.createBlockCache(c);
368      fail("Should throw IllegalArgumentException when passing illegal value for bucket size");
369    } catch (IllegalArgumentException e) {
370    }
371  }
372
373  @Test
374  public void testIndexOnlyLruBlockCache() {
375    CacheConfig cc = new CacheConfig(this.conf);
376    conf.set(BlockCacheFactory.BLOCKCACHE_POLICY_KEY, "IndexOnlyLRU");
377    BlockCache blockCache = BlockCacheFactory.createBlockCache(this.conf);
378    assertTrue(blockCache instanceof IndexOnlyLruBlockCache);
379    // reject data block
380    long initialBlockCount = blockCache.getBlockCount();
381    BlockCacheKey bck = new BlockCacheKey("bck", 0);
382    Cacheable c = new DataCacheEntry();
383    blockCache.cacheBlock(bck, c, true);
384    // accept index block
385    Cacheable indexCacheEntry = new IndexCacheEntry();
386    blockCache.cacheBlock(bck, indexCacheEntry, true);
387    assertEquals(initialBlockCount + 1, blockCache.getBlockCount());
388  }
389
390  @Test
391  public void testGetOnHeapCacheSize() {
392    Configuration copyConf = new Configuration(conf);
393    long fixedSize = 1024 * 1024L;
394    long onHeapCacheSize = MemorySizeUtil.getOnHeapCacheSize(copyConf);
395    assertEquals(null, copyConf.get(HConstants.HFILE_ONHEAP_BLOCK_CACHE_FIXED_SIZE_KEY));
396    assertTrue(onHeapCacheSize > 0 && onHeapCacheSize != fixedSize);
397    // when HBASE_BLOCK_CACHE_FIXED_SIZE_KEY is set, it will be a fixed size
398    copyConf.setLong(HConstants.HFILE_ONHEAP_BLOCK_CACHE_FIXED_SIZE_KEY, fixedSize);
399    onHeapCacheSize = MemorySizeUtil.getOnHeapCacheSize(copyConf);
400    assertEquals(fixedSize, onHeapCacheSize);
401  }
402}