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