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.regionserver; 019 020import static org.junit.jupiter.api.Assertions.assertTrue; 021 022import java.io.IOException; 023import java.util.Random; 024import java.util.stream.Stream; 025import org.apache.hadoop.conf.Configuration; 026import org.apache.hadoop.fs.FileSystem; 027import org.apache.hadoop.fs.Path; 028import org.apache.hadoop.hbase.HBaseParameterizedTestTemplate; 029import org.apache.hadoop.hbase.HBaseTestingUtil; 030import org.apache.hadoop.hbase.KeyValue; 031import org.apache.hadoop.hbase.TableName; 032import org.apache.hadoop.hbase.client.ColumnFamilyDescriptor; 033import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder; 034import org.apache.hadoop.hbase.client.RegionInfo; 035import org.apache.hadoop.hbase.client.RegionInfoBuilder; 036import org.apache.hadoop.hbase.client.TableDescriptor; 037import org.apache.hadoop.hbase.client.TableDescriptorBuilder; 038import org.apache.hadoop.hbase.fs.HFileSystem; 039import org.apache.hadoop.hbase.io.encoding.DataBlockEncoding; 040import org.apache.hadoop.hbase.io.hfile.BlockCache; 041import org.apache.hadoop.hbase.io.hfile.BlockCacheFactory; 042import org.apache.hadoop.hbase.io.hfile.BlockCacheKey; 043import org.apache.hadoop.hbase.io.hfile.BlockType; 044import org.apache.hadoop.hbase.io.hfile.CacheConfig; 045import org.apache.hadoop.hbase.io.hfile.HFile; 046import org.apache.hadoop.hbase.io.hfile.HFileBlock; 047import org.apache.hadoop.hbase.io.hfile.HFileScanner; 048import org.apache.hadoop.hbase.io.hfile.RandomKeyValueUtil; 049import org.apache.hadoop.hbase.testclassification.RegionServerTests; 050import org.apache.hadoop.hbase.testclassification.SmallTests; 051import org.apache.hadoop.hbase.util.Bytes; 052import org.apache.hadoop.hbase.util.CommonFSUtils; 053import org.apache.hadoop.hbase.wal.AbstractFSWALProvider; 054import org.junit.jupiter.api.AfterEach; 055import org.junit.jupiter.api.BeforeEach; 056import org.junit.jupiter.api.Tag; 057import org.junit.jupiter.api.TestInfo; 058import org.junit.jupiter.api.TestTemplate; 059import org.junit.jupiter.params.provider.Arguments; 060import org.slf4j.Logger; 061import org.slf4j.LoggerFactory; 062 063/** 064 * Tests {@link HFile} cache-on-write functionality for data blocks, non-root index blocks, and 065 * Bloom filter blocks, as specified by the column family. 066 */ 067@Tag(RegionServerTests.TAG) 068@Tag(SmallTests.TAG) 069@HBaseParameterizedTestTemplate(name = "{index}: cacheOnWrite={0}") 070public class TestCacheOnWriteInSchema { 071 072 private static final Logger LOG = LoggerFactory.getLogger(TestCacheOnWriteInSchema.class); 073 074 private static final HBaseTestingUtil TEST_UTIL = new HBaseTestingUtil(); 075 private static final String DIR = TEST_UTIL.getDataTestDir("TestCacheOnWriteInSchema").toString(); 076 private static byte[] family = Bytes.toBytes("family"); 077 private static final int NUM_KV = 25000; 078 private static final Random rand = new Random(12983177L); 079 /** The number of valid key types possible in a store file */ 080 private static final int NUM_VALID_KEY_TYPES = KeyValue.Type.values().length - 2; 081 082 private enum CacheOnWriteType { 083 DATA_BLOCKS(BlockType.DATA, BlockType.ENCODED_DATA), 084 BLOOM_BLOCKS(BlockType.BLOOM_CHUNK), 085 INDEX_BLOCKS(BlockType.LEAF_INDEX, BlockType.INTERMEDIATE_INDEX); 086 087 private final BlockType blockType1; 088 private final BlockType blockType2; 089 090 CacheOnWriteType(BlockType blockType) { 091 this(blockType, blockType); 092 } 093 094 CacheOnWriteType(BlockType blockType1, BlockType blockType2) { 095 this.blockType1 = blockType1; 096 this.blockType2 = blockType2; 097 } 098 099 public boolean shouldBeCached(BlockType blockType) { 100 return blockType == blockType1 || blockType == blockType2; 101 } 102 103 public ColumnFamilyDescriptorBuilder modifyFamilySchema(ColumnFamilyDescriptorBuilder builder) { 104 switch (this) { 105 case DATA_BLOCKS: 106 builder.setCacheDataOnWrite(true); 107 break; 108 case BLOOM_BLOCKS: 109 builder.setCacheBloomsOnWrite(true); 110 break; 111 case INDEX_BLOCKS: 112 builder.setCacheIndexesOnWrite(true); 113 break; 114 } 115 return builder; 116 } 117 } 118 119 private final CacheOnWriteType cowType; 120 private Configuration conf; 121 private final String testDescription; 122 private HRegion region; 123 private HStore store; 124 private FileSystem fs; 125 126 public TestCacheOnWriteInSchema(CacheOnWriteType cowType) { 127 this.cowType = cowType; 128 testDescription = "[cacheOnWrite=" + cowType + "]"; 129 System.out.println(testDescription); 130 } 131 132 public static Stream<Arguments> parameters() { 133 return Stream.of(CacheOnWriteType.values()).map(Arguments::of); 134 } 135 136 @BeforeEach 137 public void setUp(TestInfo testInfo) throws IOException { 138 String name = testInfo.getTestMethod().get().getName() + "_" + cowType; 139 byte[] table = Bytes.toBytes(name); 140 141 conf = TEST_UTIL.getConfiguration(); 142 conf.setInt(HFile.FORMAT_VERSION_KEY, HFile.MAX_FORMAT_VERSION); 143 conf.setBoolean(CacheConfig.CACHE_BLOCKS_ON_WRITE_KEY, false); 144 conf.setBoolean(CacheConfig.CACHE_INDEX_BLOCKS_ON_WRITE_KEY, false); 145 conf.setBoolean(CacheConfig.CACHE_BLOOM_BLOCKS_ON_WRITE_KEY, false); 146 fs = HFileSystem.get(conf); 147 148 // Create the schema 149 ColumnFamilyDescriptor hcd = cowType 150 .modifyFamilySchema( 151 ColumnFamilyDescriptorBuilder.newBuilder(family).setBloomFilterType(BloomType.ROWCOL)) 152 .build(); 153 TableDescriptor htd = 154 TableDescriptorBuilder.newBuilder(TableName.valueOf(table)).setColumnFamily(hcd).build(); 155 156 // Create a store based on the schema 157 String id = TestCacheOnWriteInSchema.class.getName(); 158 Path logdir = 159 new Path(CommonFSUtils.getRootDir(conf), AbstractFSWALProvider.getWALDirectoryName(id)); 160 fs.delete(logdir, true); 161 162 RegionInfo info = RegionInfoBuilder.newBuilder(htd.getTableName()).build(); 163 164 region = HBaseTestingUtil.createRegionAndWAL(info, logdir, conf, htd, 165 BlockCacheFactory.createBlockCache(conf)); 166 store = region.getStore(hcd.getName()); 167 } 168 169 @AfterEach 170 public void tearDown() throws IOException { 171 IOException ex = null; 172 try { 173 HBaseTestingUtil.closeRegionAndWAL(region); 174 } catch (IOException e) { 175 LOG.warn("Caught Exception", e); 176 ex = e; 177 } 178 try { 179 fs.delete(new Path(DIR), true); 180 } catch (IOException e) { 181 LOG.error("Could not delete " + DIR, e); 182 ex = e; 183 } 184 if (ex != null) { 185 throw ex; 186 } 187 } 188 189 @TestTemplate 190 public void testCacheOnWriteInSchema() throws IOException { 191 // Write some random data into the store 192 StoreFileWriter writer = store.getStoreEngine() 193 .createWriter(CreateStoreFileWriterParams.create().maxKeyCount(Integer.MAX_VALUE) 194 .compression(HFile.DEFAULT_COMPRESSION_ALGORITHM).isCompaction(false) 195 .includeMVCCReadpoint(true).includesTag(false).shouldDropBehind(false)); 196 writeStoreFile(writer); 197 writer.close(); 198 // Verify the block types of interest were cached on write 199 readStoreFile(writer.getPath()); 200 } 201 202 private void readStoreFile(Path path) throws IOException { 203 CacheConfig cacheConf = store.getCacheConfig(); 204 BlockCache cache = cacheConf.getBlockCache().get(); 205 StoreFileInfo storeFileInfo = StoreFileInfo.createStoreFileInfoForHFile(conf, fs, path, true); 206 HStoreFile sf = new HStoreFile(storeFileInfo, BloomType.ROWCOL, cacheConf); 207 sf.initReader(); 208 HFile.Reader reader = sf.getReader().getHFileReader(); 209 try { 210 // Open a scanner with (on read) caching disabled 211 HFileScanner scanner = reader.getScanner(conf, false, false); 212 assertTrue(scanner.seekTo(), testDescription); 213 // Cribbed from io.hfile.TestCacheOnWrite 214 long offset = 0; 215 while (offset < reader.getTrailer().getLoadOnOpenDataOffset()) { 216 // Flags: don't cache the block, use pread, this is not a compaction. 217 // Also, pass null for expected block type to avoid checking it. 218 HFileBlock block = 219 reader.readBlock(offset, -1, false, true, false, true, null, DataBlockEncoding.NONE); 220 BlockCacheKey blockCacheKey = new BlockCacheKey(reader.getName(), offset); 221 boolean isCached = cache.getBlock(blockCacheKey, true, false, true) != null; 222 boolean shouldBeCached = cowType.shouldBeCached(block.getBlockType()); 223 final BlockType blockType = block.getBlockType(); 224 225 if ( 226 shouldBeCached != isCached 227 && (cowType.blockType1.equals(blockType) || cowType.blockType2.equals(blockType)) 228 ) { 229 throw new AssertionError("shouldBeCached: " + shouldBeCached + "\n" + "isCached: " 230 + isCached + "\n" + "Test description: " + testDescription + "\n" + "block: " + block 231 + "\n" + "blockCacheKey: " + blockCacheKey); 232 } 233 offset += block.getOnDiskSizeWithHeader(); 234 } 235 } finally { 236 reader.close(); 237 } 238 } 239 240 private static KeyValue.Type generateKeyType(Random rand) { 241 if (rand.nextBoolean()) { 242 // Let's make half of KVs puts. 243 return KeyValue.Type.Put; 244 } else { 245 KeyValue.Type keyType = KeyValue.Type.values()[1 + rand.nextInt(NUM_VALID_KEY_TYPES)]; 246 if (keyType == KeyValue.Type.Minimum || keyType == KeyValue.Type.Maximum) { 247 throw new RuntimeException("Generated an invalid key type: " + keyType + ". " 248 + "Probably the layout of KeyValue.Type has changed."); 249 } 250 return keyType; 251 } 252 } 253 254 private void writeStoreFile(StoreFileWriter writer) throws IOException { 255 final int rowLen = 32; 256 for (int i = 0; i < NUM_KV; ++i) { 257 byte[] k = RandomKeyValueUtil.randomOrderedKey(rand, i); 258 byte[] v = RandomKeyValueUtil.randomValue(rand); 259 int cfLen = rand.nextInt(k.length - rowLen + 1); 260 KeyValue kv = new KeyValue(k, 0, rowLen, k, rowLen, cfLen, k, rowLen + cfLen, 261 k.length - rowLen - cfLen, rand.nextLong(), generateKeyType(rand), v, 0, v.length); 262 writer.append(kv); 263 } 264 } 265}