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 cacheConfig = new CacheConfig(conf);
213    assertTrue(cacheConfig.shouldCacheBlockOnRead(BlockCategory.DATA));
214    assertFalse(cacheConfig.shouldCacheCompressed(BlockCategory.DATA));
215    assertFalse(cacheConfig.shouldCacheDataCompressed());
216    assertFalse(cacheConfig.shouldCacheDataOnWrite());
217    assertTrue(cacheConfig.shouldCacheDataOnRead());
218    assertTrue(cacheConfig.shouldCacheBlockOnRead(BlockCategory.INDEX));
219    assertTrue(cacheConfig.shouldCacheBlockOnRead(BlockCategory.META));
220    assertTrue(cacheConfig.shouldCacheBlockOnRead(BlockCategory.BLOOM));
221    assertFalse(cacheConfig.shouldCacheBloomsOnWrite());
222    assertFalse(cacheConfig.shouldCacheIndexesOnWrite());
223
224    conf.setBoolean(CacheConfig.CACHE_BLOCKS_ON_WRITE_KEY, true);
225    conf.setBoolean(CacheConfig.CACHE_DATA_BLOCKS_COMPRESSED_KEY, true);
226    conf.setBoolean(CacheConfig.CACHE_BLOOM_BLOCKS_ON_WRITE_KEY, true);
227    conf.setBoolean(CacheConfig.CACHE_INDEX_BLOCKS_ON_WRITE_KEY, true);
228
229    cacheConfig = new CacheConfig(conf);
230    assertTrue(cacheConfig.shouldCacheBlockOnRead(BlockCategory.DATA));
231    assertTrue(cacheConfig.shouldCacheCompressed(BlockCategory.DATA));
232    assertTrue(cacheConfig.shouldCacheDataCompressed());
233    assertTrue(cacheConfig.shouldCacheDataOnWrite());
234    assertTrue(cacheConfig.shouldCacheDataOnRead());
235    assertTrue(cacheConfig.shouldCacheBlockOnRead(BlockCategory.INDEX));
236    assertTrue(cacheConfig.shouldCacheBlockOnRead(BlockCategory.META));
237    assertTrue(cacheConfig.shouldCacheBlockOnRead(BlockCategory.BLOOM));
238    assertTrue(cacheConfig.shouldCacheBloomsOnWrite());
239    assertTrue(cacheConfig.shouldCacheIndexesOnWrite());
240
241    conf.setBoolean(CacheConfig.CACHE_DATA_ON_READ_KEY, false);
242    conf.setBoolean(CacheConfig.CACHE_BLOCKS_ON_WRITE_KEY, false);
243
244    cacheConfig = new CacheConfig(conf);
245    assertFalse(cacheConfig.shouldCacheBlockOnRead(BlockCategory.DATA));
246    assertFalse(cacheConfig.shouldCacheCompressed(BlockCategory.DATA));
247    assertFalse(cacheConfig.shouldCacheDataCompressed());
248    assertFalse(cacheConfig.shouldCacheDataOnWrite());
249    assertFalse(cacheConfig.shouldCacheDataOnRead());
250    assertTrue(cacheConfig.shouldCacheBlockOnRead(BlockCategory.INDEX));
251    assertFalse(cacheConfig.shouldCacheBlockOnRead(BlockCategory.META));
252    assertTrue(cacheConfig.shouldCacheBlockOnRead(BlockCategory.BLOOM));
253    assertTrue(cacheConfig.shouldCacheBloomsOnWrite());
254    assertTrue(cacheConfig.shouldCacheIndexesOnWrite());
255
256    conf.setBoolean(CacheConfig.CACHE_DATA_ON_READ_KEY, true);
257    conf.setBoolean(CacheConfig.CACHE_BLOCKS_ON_WRITE_KEY, false);
258
259    HColumnDescriptor family = new HColumnDescriptor("testDisableCacheDataBlock");
260    family.setBlockCacheEnabled(false);
261
262    cacheConfig = new CacheConfig(conf, family);
263    assertFalse(cacheConfig.shouldCacheBlockOnRead(BlockCategory.DATA));
264    assertFalse(cacheConfig.shouldCacheCompressed(BlockCategory.DATA));
265    assertFalse(cacheConfig.shouldCacheDataCompressed());
266    assertFalse(cacheConfig.shouldCacheDataOnWrite());
267    assertFalse(cacheConfig.shouldCacheDataOnRead());
268    assertTrue(cacheConfig.shouldCacheBlockOnRead(BlockCategory.INDEX));
269    assertFalse(cacheConfig.shouldCacheBlockOnRead(BlockCategory.META));
270    assertTrue(cacheConfig.shouldCacheBlockOnRead(BlockCategory.BLOOM));
271    assertTrue(cacheConfig.shouldCacheBloomsOnWrite());
272    assertTrue(cacheConfig.shouldCacheIndexesOnWrite());
273  }
274
275  @Test
276  public void testCacheConfigDefaultLRUBlockCache() {
277    CacheConfig cc = new CacheConfig(this.conf);
278    assertTrue(cc.isBlockCacheEnabled());
279    assertTrue(CacheConfig.DEFAULT_IN_MEMORY == cc.isInMemory());
280    basicBlockCacheOps(cc, false, true);
281    assertTrue(cc.getBlockCache() instanceof LruBlockCache);
282  }
283
284  /**
285   * Assert that the caches are deployed with CombinedBlockCache and of the appropriate sizes.
286   */
287  @Test
288  public void testOffHeapBucketCacheConfig() {
289    this.conf.set(HConstants.BUCKET_CACHE_IOENGINE_KEY, "offheap");
290    doBucketCacheConfigTest();
291  }
292
293  @Test
294  public void testFileBucketCacheConfig() throws IOException {
295    HBaseTestingUtility htu = new HBaseTestingUtility(this.conf);
296    try {
297      Path p = new Path(htu.getDataTestDir(), "bc.txt");
298      FileSystem fs = FileSystem.get(this.conf);
299      fs.create(p).close();
300      this.conf.set(HConstants.BUCKET_CACHE_IOENGINE_KEY, "file:" + p);
301      doBucketCacheConfigTest();
302    } finally {
303      htu.cleanupTestDir();
304    }
305  }
306
307  private void doBucketCacheConfigTest() {
308    final int bcSize = 100;
309    this.conf.setInt(HConstants.BUCKET_CACHE_SIZE_KEY, bcSize);
310    CacheConfig cc = new CacheConfig(this.conf);
311    basicBlockCacheOps(cc, false, false);
312    assertTrue(cc.getBlockCache() instanceof CombinedBlockCache);
313    // TODO: Assert sizes allocated are right and proportions.
314    CombinedBlockCache cbc = (CombinedBlockCache)cc.getBlockCache();
315    BlockCache [] bcs = cbc.getBlockCaches();
316    assertTrue(bcs[0] instanceof LruBlockCache);
317    LruBlockCache lbc = (LruBlockCache)bcs[0];
318    assertEquals(MemorySizeUtil.getOnHeapCacheSize(this.conf), lbc.getMaxSize());
319    assertTrue(bcs[1] instanceof BucketCache);
320    BucketCache bc = (BucketCache)bcs[1];
321    // getMaxSize comes back in bytes but we specified size in MB
322    assertEquals(bcSize, bc.getMaxSize() / (1024 * 1024));
323  }
324
325  /**
326   * Assert that when BUCKET_CACHE_COMBINED_KEY is false, the non-default, that we deploy
327   * LruBlockCache as L1 with a BucketCache for L2.
328   */
329  @Test
330  public void testBucketCacheConfigL1L2Setup() {
331    this.conf.set(HConstants.BUCKET_CACHE_IOENGINE_KEY, "offheap");
332    // Make lru size is smaller than bcSize for sure.  Need this to be true so when eviction
333    // from L1 happens, it does not fail because L2 can't take the eviction because block too big.
334    this.conf.setFloat(HConstants.HFILE_BLOCK_CACHE_SIZE_KEY, 0.001f);
335    MemoryUsage mu = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage();
336    long lruExpectedSize = MemorySizeUtil.getOnHeapCacheSize(this.conf);
337    final int bcSize = 100;
338    long bcExpectedSize = 100 * 1024 * 1024; // MB.
339    assertTrue(lruExpectedSize < bcExpectedSize);
340    this.conf.setInt(HConstants.BUCKET_CACHE_SIZE_KEY, bcSize);
341    CacheConfig cc = new CacheConfig(this.conf);
342    basicBlockCacheOps(cc, false, false);
343    assertTrue(cc.getBlockCache() instanceof CombinedBlockCache);
344    // TODO: Assert sizes allocated are right and proportions.
345    CombinedBlockCache cbc = (CombinedBlockCache)cc.getBlockCache();
346    LruBlockCache lbc = cbc.onHeapCache;
347    assertEquals(lruExpectedSize, lbc.getMaxSize());
348    BlockCache bc = cbc.l2Cache;
349    // getMaxSize comes back in bytes but we specified size in MB
350    assertEquals(bcExpectedSize, ((BucketCache) bc).getMaxSize());
351    // Test the L1+L2 deploy works as we'd expect with blocks evicted from L1 going to L2.
352    long initialL1BlockCount = lbc.getBlockCount();
353    long initialL2BlockCount = bc.getBlockCount();
354    Cacheable c = new DataCacheEntry();
355    BlockCacheKey bck = new BlockCacheKey("bck", 0);
356    lbc.cacheBlock(bck, c, false);
357    assertEquals(initialL1BlockCount + 1, lbc.getBlockCount());
358    assertEquals(initialL2BlockCount, bc.getBlockCount());
359    // Force evictions by putting in a block too big.
360    final long justTooBigSize = lbc.acceptableSize() + 1;
361    lbc.cacheBlock(new BlockCacheKey("bck2", 0), new DataCacheEntry() {
362      @Override
363      public long heapSize() {
364        return justTooBigSize;
365      }
366
367      @Override
368      public int getSerializedLength() {
369        return (int)heapSize();
370      }
371    });
372    // The eviction thread in lrublockcache needs to run.
373    while (initialL1BlockCount != lbc.getBlockCount()) Threads.sleep(10);
374    assertEquals(initialL1BlockCount, lbc.getBlockCount());
375  }
376
377  @Test
378  public void testL2CacheWithInvalidBucketSize() {
379    Configuration c = new Configuration(this.conf);
380    c.set(HConstants.BUCKET_CACHE_IOENGINE_KEY, "offheap");
381    c.set(CacheConfig.BUCKET_CACHE_BUCKETS_KEY, "256,512,1024,2048,4000,4096");
382    c.setFloat(HConstants.BUCKET_CACHE_SIZE_KEY, 1024);
383    try {
384      CacheConfig.getBucketCache(c);
385      fail("Should throw IllegalArgumentException when passing illegal value for bucket size");
386    } catch (IllegalArgumentException e) {
387    }
388  }
389}