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