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