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 junit.framework.TestCase.assertTrue; 021import static org.junit.Assert.assertEquals; 022 023import java.io.IOException; 024import java.util.ArrayList; 025import java.util.Arrays; 026import java.util.List; 027import org.apache.hadoop.conf.Configuration; 028import org.apache.hadoop.fs.Path; 029import org.apache.hadoop.hbase.Cell; 030import org.apache.hadoop.hbase.CellUtil; 031import org.apache.hadoop.hbase.HBaseClassTestRule; 032import org.apache.hadoop.hbase.HBaseTestingUtility; 033import org.apache.hadoop.hbase.HColumnDescriptor; 034import org.apache.hadoop.hbase.HRegionInfo; 035import org.apache.hadoop.hbase.HTableDescriptor; 036import org.apache.hadoop.hbase.TableName; 037import org.apache.hadoop.hbase.client.Delete; 038import org.apache.hadoop.hbase.client.Durability; 039import org.apache.hadoop.hbase.client.Get; 040import org.apache.hadoop.hbase.client.Put; 041import org.apache.hadoop.hbase.client.Scan; 042import org.apache.hadoop.hbase.io.hfile.BlockCache; 043import org.apache.hadoop.hbase.io.hfile.CacheConfig; 044import org.apache.hadoop.hbase.io.hfile.HFile; 045import org.apache.hadoop.hbase.testclassification.MediumTests; 046import org.apache.hadoop.hbase.testclassification.RegionServerTests; 047import org.apache.hadoop.hbase.util.Bytes; 048import org.apache.hadoop.hbase.util.EnvironmentEdgeManagerTestHelper; 049import org.junit.*; 050import org.junit.ClassRule; 051import org.junit.experimental.categories.Category; 052import org.junit.rules.TestName; 053import org.slf4j.Logger; 054import org.slf4j.LoggerFactory; 055 056@Category({RegionServerTests.class, MediumTests.class}) 057public class TestBlocksRead { 058 059 @ClassRule 060 public static final HBaseClassTestRule CLASS_RULE = 061 HBaseClassTestRule.forClass(TestBlocksRead.class); 062 063 private static final Logger LOG = LoggerFactory.getLogger(TestBlocksRead.class); 064 @Rule public TestName testName = new TestName(); 065 066 static final BloomType[] BLOOM_TYPE = new BloomType[] { BloomType.ROWCOL, 067 BloomType.ROW, BloomType.NONE }; 068 069 private static BlockCache blockCache; 070 HRegion region = null; 071 private static HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility(); 072 private final String DIR = TEST_UTIL.getDataTestDir("TestBlocksRead").toString(); 073 private Configuration conf = TEST_UTIL.getConfiguration(); 074 075 @BeforeClass 076 public static void setUp() throws Exception { 077 // disable compactions in this test. 078 TEST_UTIL.getConfiguration().setInt("hbase.hstore.compactionThreshold", 10000); 079 CacheConfig.instantiateBlockCache(TEST_UTIL.getConfiguration()); 080 } 081 082 @AfterClass 083 public static void tearDown() throws Exception { 084 EnvironmentEdgeManagerTestHelper.reset(); 085 } 086 087 /** 088 * Callers must afterward call {@link HBaseTestingUtility#closeRegionAndWAL(HRegion)} 089 * @param tableName 090 * @param callingMethod 091 * @param conf 092 * @param family 093 * @throws IOException 094 * @return created and initialized region. 095 */ 096 private HRegion initHRegion(byte[] tableName, String callingMethod, 097 Configuration conf, String family) throws IOException { 098 HTableDescriptor htd = new HTableDescriptor(TableName.valueOf(tableName)); 099 HColumnDescriptor familyDesc; 100 for (int i = 0; i < BLOOM_TYPE.length; i++) { 101 BloomType bloomType = BLOOM_TYPE[i]; 102 familyDesc = new HColumnDescriptor(family + "_" + bloomType) 103 .setBlocksize(1) 104 .setBloomFilterType(BLOOM_TYPE[i]); 105 htd.addFamily(familyDesc); 106 } 107 108 HRegionInfo info = new HRegionInfo(htd.getTableName(), null, null, false); 109 Path path = new Path(DIR + callingMethod); 110 HRegion r = HBaseTestingUtility.createRegionAndWAL(info, path, conf, htd); 111 blockCache = new CacheConfig(conf).getBlockCache(); 112 return r; 113 } 114 115 private void putData(String family, String row, String col, long version) 116 throws IOException { 117 for (int i = 0; i < BLOOM_TYPE.length; i++) { 118 putData(Bytes.toBytes(family + "_" + BLOOM_TYPE[i]), row, col, version, 119 version); 120 } 121 } 122 123 // generates a value to put for a row/col/version. 124 private static byte[] genValue(String row, String col, long version) { 125 return Bytes.toBytes("Value:" + row + "#" + col + "#" + version); 126 } 127 128 private void putData(byte[] cf, String row, String col, long versionStart, 129 long versionEnd) throws IOException { 130 byte columnBytes[] = Bytes.toBytes(col); 131 Put put = new Put(Bytes.toBytes(row)); 132 put.setDurability(Durability.SKIP_WAL); 133 134 for (long version = versionStart; version <= versionEnd; version++) { 135 put.addColumn(cf, columnBytes, version, genValue(row, col, version)); 136 } 137 region.put(put); 138 } 139 140 private Cell[] getData(String family, String row, List<String> columns, 141 int expBlocks) throws IOException { 142 return getData(family, row, columns, expBlocks, expBlocks, expBlocks); 143 } 144 145 private Cell[] getData(String family, String row, List<String> columns, 146 int expBlocksRowCol, int expBlocksRow, int expBlocksNone) 147 throws IOException { 148 int[] expBlocks = new int[] { expBlocksRowCol, expBlocksRow, expBlocksNone }; 149 Cell[] kvs = null; 150 151 for (int i = 0; i < BLOOM_TYPE.length; i++) { 152 BloomType bloomType = BLOOM_TYPE[i]; 153 byte[] cf = Bytes.toBytes(family + "_" + bloomType); 154 long blocksStart = getBlkAccessCount(cf); 155 Get get = new Get(Bytes.toBytes(row)); 156 157 for (String column : columns) { 158 get.addColumn(cf, Bytes.toBytes(column)); 159 } 160 161 kvs = region.get(get).rawCells(); 162 long blocksEnd = getBlkAccessCount(cf); 163 if (expBlocks[i] != -1) { 164 assertEquals("Blocks Read Check for Bloom: " + bloomType, expBlocks[i], 165 blocksEnd - blocksStart); 166 } 167 System.out.println("Blocks Read for Bloom: " + bloomType + " = " 168 + (blocksEnd - blocksStart) + "Expected = " + expBlocks[i]); 169 } 170 return kvs; 171 } 172 173 private Cell[] getData(String family, String row, String column, 174 int expBlocks) throws IOException { 175 return getData(family, row, Arrays.asList(column), expBlocks, expBlocks, 176 expBlocks); 177 } 178 179 private Cell[] getData(String family, String row, String column, 180 int expBlocksRowCol, int expBlocksRow, int expBlocksNone) 181 throws IOException { 182 return getData(family, row, Arrays.asList(column), expBlocksRowCol, 183 expBlocksRow, expBlocksNone); 184 } 185 186 private void deleteFamily(String family, String row, long version) 187 throws IOException { 188 Delete del = new Delete(Bytes.toBytes(row)); 189 del.addFamily(Bytes.toBytes(family + "_ROWCOL"), version); 190 del.addFamily(Bytes.toBytes(family + "_ROW"), version); 191 del.addFamily(Bytes.toBytes(family + "_NONE"), version); 192 region.delete(del); 193 } 194 195 private static void verifyData(Cell kv, String expectedRow, 196 String expectedCol, long expectedVersion) { 197 assertTrue("RowCheck", CellUtil.matchingRows(kv, Bytes.toBytes(expectedRow))); 198 assertTrue("ColumnCheck", CellUtil.matchingQualifier(kv, Bytes.toBytes(expectedCol))); 199 assertEquals("TSCheck", expectedVersion, kv.getTimestamp()); 200 assertTrue("ValueCheck", CellUtil.matchingValue(kv, genValue(expectedRow, expectedCol, expectedVersion))); 201 } 202 203 private static long getBlkAccessCount(byte[] cf) { 204 return HFile.DATABLOCK_READ_COUNT.sum(); 205 } 206 207 private static long getBlkCount() { 208 return blockCache.getBlockCount(); 209 } 210 211 /** 212 * Test # of blocks read for some simple seek cases. 213 * 214 * @throws Exception 215 */ 216 @Test 217 public void testBlocksRead() throws Exception { 218 byte[] TABLE = Bytes.toBytes("testBlocksRead"); 219 String FAMILY = "cf1"; 220 Cell kvs[]; 221 this.region = initHRegion(TABLE, testName.getMethodName(), conf, FAMILY); 222 223 try { 224 putData(FAMILY, "row", "col1", 1); 225 putData(FAMILY, "row", "col2", 2); 226 putData(FAMILY, "row", "col3", 3); 227 putData(FAMILY, "row", "col4", 4); 228 putData(FAMILY, "row", "col5", 5); 229 putData(FAMILY, "row", "col6", 6); 230 putData(FAMILY, "row", "col7", 7); 231 region.flush(true); 232 233 // Expected block reads: 1 234 // The top block has the KV we are 235 // interested. So only 1 seek is needed. 236 kvs = getData(FAMILY, "row", "col1", 1); 237 assertEquals(1, kvs.length); 238 verifyData(kvs[0], "row", "col1", 1); 239 240 // Expected block reads: 2 241 // The top block and next block has the KVs we are 242 // interested. So only 2 seek is needed. 243 kvs = getData(FAMILY, "row", Arrays.asList("col1", "col2"), 2); 244 assertEquals(2, kvs.length); 245 verifyData(kvs[0], "row", "col1", 1); 246 verifyData(kvs[1], "row", "col2", 2); 247 248 // Expected block reads: 3 249 // The first 2 seeks is to find out col2. [HBASE-4443] 250 // One additional seek for col3 251 // So 3 seeks are needed. 252 kvs = getData(FAMILY, "row", Arrays.asList("col2", "col3"), 2); 253 assertEquals(2, kvs.length); 254 verifyData(kvs[0], "row", "col2", 2); 255 verifyData(kvs[1], "row", "col3", 3); 256 257 // Expected block reads: 1. [HBASE-4443]&[HBASE-7845] 258 kvs = getData(FAMILY, "row", Arrays.asList("col5"), 1); 259 assertEquals(1, kvs.length); 260 verifyData(kvs[0], "row", "col5", 5); 261 } finally { 262 HBaseTestingUtility.closeRegionAndWAL(this.region); 263 this.region = null; 264 } 265 } 266 267 /** 268 * Test # of blocks read (targeted at some of the cases Lazy Seek optimizes). 269 * 270 * @throws Exception 271 */ 272 @Test 273 public void testLazySeekBlocksRead() throws Exception { 274 byte[] TABLE = Bytes.toBytes("testLazySeekBlocksRead"); 275 String FAMILY = "cf1"; 276 Cell kvs[]; 277 this.region = initHRegion(TABLE, testName.getMethodName(), conf, FAMILY); 278 279 try { 280 // File 1 281 putData(FAMILY, "row", "col1", 1); 282 putData(FAMILY, "row", "col2", 2); 283 region.flush(true); 284 285 // File 2 286 putData(FAMILY, "row", "col1", 3); 287 putData(FAMILY, "row", "col2", 4); 288 region.flush(true); 289 290 // Expected blocks read: 1. 291 // File 2's top block is also the KV we are 292 // interested. So only 1 seek is needed. 293 kvs = getData(FAMILY, "row", Arrays.asList("col1"), 1); 294 assertEquals(1, kvs.length); 295 verifyData(kvs[0], "row", "col1", 3); 296 297 // Expected blocks read: 2 298 // File 2's top block has the "col1" KV we are 299 // interested. We also need "col2" which is in a block 300 // of its own. So, we need that block as well. 301 kvs = getData(FAMILY, "row", Arrays.asList("col1", "col2"), 2); 302 assertEquals(2, kvs.length); 303 verifyData(kvs[0], "row", "col1", 3); 304 verifyData(kvs[1], "row", "col2", 4); 305 306 // File 3: Add another column 307 putData(FAMILY, "row", "col3", 5); 308 region.flush(true); 309 310 // Expected blocks read: 1 311 // File 3's top block has the "col3" KV we are 312 // interested. So only 1 seek is needed. 313 kvs = getData(FAMILY, "row", "col3", 1); 314 assertEquals(1, kvs.length); 315 verifyData(kvs[0], "row", "col3", 5); 316 317 // Get a column from older file. 318 // For ROWCOL Bloom filter: Expected blocks read: 1. 319 // For ROW Bloom filter: Expected blocks read: 2. 320 // For NONE Bloom filter: Expected blocks read: 2. 321 kvs = getData(FAMILY, "row", Arrays.asList("col1"), 1, 2, 2); 322 assertEquals(1, kvs.length); 323 verifyData(kvs[0], "row", "col1", 3); 324 325 // File 4: Delete the entire row. 326 deleteFamily(FAMILY, "row", 6); 327 region.flush(true); 328 329 // For ROWCOL Bloom filter: Expected blocks read: 2. 330 // For ROW Bloom filter: Expected blocks read: 3. 331 // For NONE Bloom filter: Expected blocks read: 3. 332 kvs = getData(FAMILY, "row", "col1", 2, 3, 3); 333 assertEquals(0, kvs.length); 334 kvs = getData(FAMILY, "row", "col2", 2, 3, 3); 335 assertEquals(0, kvs.length); 336 kvs = getData(FAMILY, "row", "col3", 2); 337 assertEquals(0, kvs.length); 338 kvs = getData(FAMILY, "row", Arrays.asList("col1", "col2", "col3"), 4); 339 assertEquals(0, kvs.length); 340 341 // File 5: Delete 342 deleteFamily(FAMILY, "row", 10); 343 region.flush(true); 344 345 // File 6: some more puts, but with timestamps older than the 346 // previous delete. 347 putData(FAMILY, "row", "col1", 7); 348 putData(FAMILY, "row", "col2", 8); 349 putData(FAMILY, "row", "col3", 9); 350 region.flush(true); 351 352 // Baseline expected blocks read: 6. [HBASE-4532] 353 kvs = getData(FAMILY, "row", Arrays.asList("col1", "col2", "col3"), 6, 7, 7); 354 assertEquals(0, kvs.length); 355 356 // File 7: Put back new data 357 putData(FAMILY, "row", "col1", 11); 358 putData(FAMILY, "row", "col2", 12); 359 putData(FAMILY, "row", "col3", 13); 360 region.flush(true); 361 362 363 // Expected blocks read: 8. [HBASE-4585, HBASE-13109] 364 kvs = getData(FAMILY, "row", Arrays.asList("col1", "col2", "col3"), 8, 9, 9); 365 assertEquals(3, kvs.length); 366 verifyData(kvs[0], "row", "col1", 11); 367 verifyData(kvs[1], "row", "col2", 12); 368 verifyData(kvs[2], "row", "col3", 13); 369 } finally { 370 HBaseTestingUtility.closeRegionAndWAL(this.region); 371 this.region = null; 372 } 373 } 374 375 /** 376 * Test # of blocks read to ensure disabling cache-fill on Scan works. 377 * @throws Exception 378 */ 379 @Test 380 public void testBlocksStoredWhenCachingDisabled() throws Exception { 381 byte [] TABLE = Bytes.toBytes("testBlocksReadWhenCachingDisabled"); 382 String FAMILY = "cf1"; 383 384 this.region = initHRegion(TABLE, testName.getMethodName(), conf, FAMILY); 385 386 try { 387 putData(FAMILY, "row", "col1", 1); 388 putData(FAMILY, "row", "col2", 2); 389 region.flush(true); 390 391 // Execute a scan with caching turned off 392 // Expected blocks stored: 0 393 long blocksStart = getBlkCount(); 394 Scan scan = new Scan(); 395 scan.setCacheBlocks(false); 396 RegionScanner rs = region.getScanner(scan); 397 List<Cell> result = new ArrayList<>(2); 398 rs.next(result); 399 assertEquals(2 * BLOOM_TYPE.length, result.size()); 400 rs.close(); 401 long blocksEnd = getBlkCount(); 402 403 assertEquals(blocksStart, blocksEnd); 404 405 // Execute with caching turned on 406 // Expected blocks stored: 2 407 blocksStart = blocksEnd; 408 scan.setCacheBlocks(true); 409 rs = region.getScanner(scan); 410 result = new ArrayList<>(2); 411 rs.next(result); 412 assertEquals(2 * BLOOM_TYPE.length, result.size()); 413 rs.close(); 414 blocksEnd = getBlkCount(); 415 416 assertEquals(2 * BLOOM_TYPE.length, blocksEnd - blocksStart); 417 } finally { 418 HBaseTestingUtility.closeRegionAndWAL(this.region); 419 this.region = null; 420 } 421 } 422 423 @Test 424 public void testLazySeekBlocksReadWithDelete() throws Exception { 425 byte[] TABLE = Bytes.toBytes("testLazySeekBlocksReadWithDelete"); 426 String FAMILY = "cf1"; 427 Cell kvs[]; 428 this.region = initHRegion(TABLE, testName.getMethodName(), conf, FAMILY); 429 try { 430 deleteFamily(FAMILY, "row", 200); 431 for (int i = 0; i < 100; i++) { 432 putData(FAMILY, "row", "col" + i, i); 433 } 434 putData(FAMILY, "row", "col99", 201); 435 region.flush(true); 436 437 kvs = getData(FAMILY, "row", Arrays.asList("col0"), 2); 438 assertEquals(0, kvs.length); 439 440 kvs = getData(FAMILY, "row", Arrays.asList("col99"), 2); 441 assertEquals(1, kvs.length); 442 verifyData(kvs[0], "row", "col99", 201); 443 } finally { 444 HBaseTestingUtility.closeRegionAndWAL(this.region); 445 this.region = null; 446 } 447 } 448 449}