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.apache.hadoop.hbase.HBaseTestingUtility.START_KEY; 021import static org.apache.hadoop.hbase.HBaseTestingUtility.START_KEY_BYTES; 022import static org.apache.hadoop.hbase.HBaseTestingUtility.fam1; 023import static org.apache.hadoop.hbase.regionserver.Store.PRIORITY_USER; 024import static org.junit.Assert.assertEquals; 025import static org.junit.Assert.assertNotNull; 026import static org.junit.Assert.assertNull; 027import static org.junit.Assert.assertTrue; 028 029import java.io.IOException; 030import java.util.ArrayList; 031import java.util.Collection; 032import java.util.HashMap; 033import java.util.List; 034import java.util.Map; 035import java.util.Map.Entry; 036import org.apache.hadoop.conf.Configuration; 037import org.apache.hadoop.hbase.Cell; 038import org.apache.hadoop.hbase.CellUtil; 039import org.apache.hadoop.hbase.HBaseClassTestRule; 040import org.apache.hadoop.hbase.HBaseTestCase; 041import org.apache.hadoop.hbase.HBaseTestingUtility; 042import org.apache.hadoop.hbase.HConstants; 043import org.apache.hadoop.hbase.HTableDescriptor; 044import org.apache.hadoop.hbase.KeepDeletedCells; 045import org.apache.hadoop.hbase.client.Delete; 046import org.apache.hadoop.hbase.client.Get; 047import org.apache.hadoop.hbase.client.Result; 048import org.apache.hadoop.hbase.client.Scan; 049import org.apache.hadoop.hbase.client.Table; 050import org.apache.hadoop.hbase.io.encoding.DataBlockEncoding; 051import org.apache.hadoop.hbase.io.hfile.HFileDataBlockEncoder; 052import org.apache.hadoop.hbase.io.hfile.HFileDataBlockEncoderImpl; 053import org.apache.hadoop.hbase.io.hfile.HFileScanner; 054import org.apache.hadoop.hbase.regionserver.compactions.CompactionLifeCycleTracker; 055import org.apache.hadoop.hbase.regionserver.compactions.CompactionProgress; 056import org.apache.hadoop.hbase.regionserver.compactions.CompactionRequestImpl; 057import org.apache.hadoop.hbase.regionserver.compactions.RatioBasedCompactionPolicy; 058import org.apache.hadoop.hbase.testclassification.LargeTests; 059import org.apache.hadoop.hbase.testclassification.RegionServerTests; 060import org.apache.hadoop.hbase.util.Bytes; 061import org.apache.hadoop.hbase.wal.WAL; 062import org.junit.After; 063import org.junit.Before; 064import org.junit.ClassRule; 065import org.junit.Rule; 066import org.junit.Test; 067import org.junit.experimental.categories.Category; 068import org.junit.rules.TestName; 069import org.junit.runner.RunWith; 070import org.junit.runners.Parameterized; 071import org.slf4j.Logger; 072import org.slf4j.LoggerFactory; 073 074/** 075 * Test major compactions 076 */ 077@Category({RegionServerTests.class, LargeTests.class}) 078@RunWith(Parameterized.class) 079public class TestMajorCompaction { 080 081 @ClassRule 082 public static final HBaseClassTestRule CLASS_RULE = 083 HBaseClassTestRule.forClass(TestMajorCompaction.class); 084 085 @Parameterized.Parameters 086 public static Object[] data() { 087 return new Object[] { "NONE", "BASIC", "EAGER" }; 088 } 089 @Rule public TestName name; 090 private static final Logger LOG = LoggerFactory.getLogger(TestMajorCompaction.class.getName()); 091 private static final HBaseTestingUtility UTIL = HBaseTestingUtility.createLocalHTU(); 092 protected Configuration conf = UTIL.getConfiguration(); 093 094 private HRegion r = null; 095 private HTableDescriptor htd = null; 096 private static final byte [] COLUMN_FAMILY = fam1; 097 private final byte [] STARTROW = Bytes.toBytes(START_KEY); 098 private static final byte [] COLUMN_FAMILY_TEXT = COLUMN_FAMILY; 099 private int compactionThreshold; 100 private byte[] secondRowBytes, thirdRowBytes; 101 private static final long MAX_FILES_TO_COMPACT = 10; 102 103 /** constructor */ 104 public TestMajorCompaction(String compType) { 105 super(); 106 name = new TestName(); 107 // Set cache flush size to 1MB 108 conf.setInt(HConstants.HREGION_MEMSTORE_FLUSH_SIZE, 1024*1024); 109 conf.setInt(HConstants.HREGION_MEMSTORE_BLOCK_MULTIPLIER, 100); 110 compactionThreshold = conf.getInt("hbase.hstore.compactionThreshold", 3); 111 conf.set(CompactingMemStore.COMPACTING_MEMSTORE_TYPE_KEY, String.valueOf(compType)); 112 113 secondRowBytes = START_KEY_BYTES.clone(); 114 // Increment the least significant character so we get to next row. 115 secondRowBytes[START_KEY_BYTES.length - 1]++; 116 thirdRowBytes = START_KEY_BYTES.clone(); 117 thirdRowBytes[START_KEY_BYTES.length - 1] = 118 (byte) (thirdRowBytes[START_KEY_BYTES.length - 1] + 2); 119 } 120 121 @Before 122 public void setUp() throws Exception { 123 this.htd = UTIL.createTableDescriptor(name.getMethodName().replace('[','i').replace(']','i')); 124 this.r = UTIL.createLocalHRegion(htd, null, null); 125 } 126 127 @After 128 public void tearDown() throws Exception { 129 WAL wal = ((HRegion)r).getWAL(); 130 ((HRegion)r).close(); 131 wal.close(); 132 } 133 134 /** 135 * Test that on a major compaction, if all cells are expired or deleted, then 136 * we'll end up with no product. Make sure scanner over region returns 137 * right answer in this case - and that it just basically works. 138 * @throws IOException exception encountered 139 */ 140 @Test 141 public void testMajorCompactingToNoOutput() throws IOException { 142 testMajorCompactingWithDeletes(KeepDeletedCells.FALSE); 143 } 144 145 /** 146 * Test that on a major compaction,Deleted cells are retained if keep deleted cells is set to true 147 * @throws IOException exception encountered 148 */ 149 @Test 150 public void testMajorCompactingWithKeepDeletedCells() throws IOException { 151 testMajorCompactingWithDeletes(KeepDeletedCells.TRUE); 152 } 153 154 /** 155 * Run compaction and flushing memstore 156 * Assert deletes get cleaned up. 157 * @throws Exception 158 */ 159 @Test 160 public void testMajorCompaction() throws Exception { 161 majorCompaction(); 162 } 163 164 @Test 165 public void testDataBlockEncodingInCacheOnly() throws Exception { 166 majorCompactionWithDataBlockEncoding(true); 167 } 168 169 @Test 170 public void testDataBlockEncodingEverywhere() throws Exception { 171 majorCompactionWithDataBlockEncoding(false); 172 } 173 174 public void majorCompactionWithDataBlockEncoding(boolean inCacheOnly) 175 throws Exception { 176 Map<HStore, HFileDataBlockEncoder> replaceBlockCache = new HashMap<>(); 177 for (HStore store : r.getStores()) { 178 HFileDataBlockEncoder blockEncoder = store.getDataBlockEncoder(); 179 replaceBlockCache.put(store, blockEncoder); 180 final DataBlockEncoding inCache = DataBlockEncoding.PREFIX; 181 final DataBlockEncoding onDisk = inCacheOnly ? DataBlockEncoding.NONE : 182 inCache; 183 ((HStore)store).setDataBlockEncoderInTest(new HFileDataBlockEncoderImpl(onDisk)); 184 } 185 186 majorCompaction(); 187 188 // restore settings 189 for (Entry<HStore, HFileDataBlockEncoder> entry : replaceBlockCache.entrySet()) { 190 ((HStore)entry.getKey()).setDataBlockEncoderInTest(entry.getValue()); 191 } 192 } 193 194 private void majorCompaction() throws Exception { 195 createStoreFile(r); 196 for (int i = 0; i < compactionThreshold; i++) { 197 createStoreFile(r); 198 } 199 // Add more content. 200 HBaseTestCase.addContent(new RegionAsTable(r), Bytes.toString(COLUMN_FAMILY)); 201 202 // Now there are about 5 versions of each column. 203 // Default is that there only 3 (MAXVERSIONS) versions allowed per column. 204 // 205 // Assert == 3 when we ask for versions. 206 Result result = r.get(new Get(STARTROW).addFamily(COLUMN_FAMILY_TEXT).readVersions(100)); 207 assertEquals(compactionThreshold, result.size()); 208 209 // see if CompactionProgress is in place but null 210 for (HStore store : r.getStores()) { 211 assertNull(store.getCompactionProgress()); 212 } 213 214 r.flush(true); 215 r.compact(true); 216 217 // see if CompactionProgress has done its thing on at least one store 218 int storeCount = 0; 219 for (HStore store : r.getStores()) { 220 CompactionProgress progress = store.getCompactionProgress(); 221 if( progress != null ) { 222 ++storeCount; 223 assertTrue(progress.currentCompactedKVs > 0); 224 assertTrue(progress.getTotalCompactingKVs() > 0); 225 } 226 assertTrue(storeCount > 0); 227 } 228 229 // look at the second row 230 // Increment the least significant character so we get to next row. 231 byte [] secondRowBytes = START_KEY_BYTES.clone(); 232 secondRowBytes[START_KEY_BYTES.length - 1]++; 233 234 // Always 3 versions if that is what max versions is. 235 result = r.get(new Get(secondRowBytes).addFamily(COLUMN_FAMILY_TEXT).readVersions(100)); 236 LOG.debug("Row " + Bytes.toStringBinary(secondRowBytes) + " after " + 237 "initial compaction: " + result); 238 assertEquals("Invalid number of versions of row " 239 + Bytes.toStringBinary(secondRowBytes) + ".", compactionThreshold, 240 result.size()); 241 242 // Now add deletes to memstore and then flush it. 243 // That will put us over 244 // the compaction threshold of 3 store files. Compacting these store files 245 // should result in a compacted store file that has no references to the 246 // deleted row. 247 LOG.debug("Adding deletes to memstore and flushing"); 248 Delete delete = new Delete(secondRowBytes, System.currentTimeMillis()); 249 byte [][] famAndQf = {COLUMN_FAMILY, null}; 250 delete.addFamily(famAndQf[0]); 251 r.delete(delete); 252 253 // Assert deleted. 254 result = r.get(new Get(secondRowBytes).addFamily(COLUMN_FAMILY_TEXT).readVersions(100)); 255 assertTrue("Second row should have been deleted", result.isEmpty()); 256 257 r.flush(true); 258 259 result = r.get(new Get(secondRowBytes).addFamily(COLUMN_FAMILY_TEXT).readVersions(100)); 260 assertTrue("Second row should have been deleted", result.isEmpty()); 261 262 // Add a bit of data and flush. Start adding at 'bbb'. 263 createSmallerStoreFile(this.r); 264 r.flush(true); 265 // Assert that the second row is still deleted. 266 result = r.get(new Get(secondRowBytes).addFamily(COLUMN_FAMILY_TEXT).readVersions(100)); 267 assertTrue("Second row should still be deleted", result.isEmpty()); 268 269 // Force major compaction. 270 r.compact(true); 271 assertEquals(1, r.getStore(COLUMN_FAMILY_TEXT).getStorefiles().size()); 272 273 result = r.get(new Get(secondRowBytes).addFamily(COLUMN_FAMILY_TEXT).readVersions(100)); 274 assertTrue("Second row should still be deleted", result.isEmpty()); 275 276 // Make sure the store files do have some 'aaa' keys in them -- exactly 3. 277 // Also, that compacted store files do not have any secondRowBytes because 278 // they were deleted. 279 verifyCounts(3,0); 280 281 // Multiple versions allowed for an entry, so the delete isn't enough 282 // Lower TTL and expire to ensure that all our entries have been wiped 283 final int ttl = 1000; 284 for (HStore store : r.getStores()) { 285 ScanInfo old = store.getScanInfo(); 286 ScanInfo si = old.customize(old.getMaxVersions(), ttl, old.getKeepDeletedCells()); 287 store.setScanInfo(si); 288 } 289 Thread.sleep(1000); 290 291 r.compact(true); 292 int count = count(); 293 assertEquals("Should not see anything after TTL has expired", 0, count); 294 } 295 296 @Test 297 public void testTimeBasedMajorCompaction() throws Exception { 298 // create 2 storefiles and force a major compaction to reset the time 299 int delay = 10 * 1000; // 10 sec 300 float jitterPct = 0.20f; // 20% 301 conf.setLong(HConstants.MAJOR_COMPACTION_PERIOD, delay); 302 conf.setFloat("hbase.hregion.majorcompaction.jitter", jitterPct); 303 304 HStore s = ((HStore) r.getStore(COLUMN_FAMILY)); 305 s.storeEngine.getCompactionPolicy().setConf(conf); 306 try { 307 createStoreFile(r); 308 createStoreFile(r); 309 r.compact(true); 310 311 // add one more file & verify that a regular compaction won't work 312 createStoreFile(r); 313 r.compact(false); 314 assertEquals(2, s.getStorefilesCount()); 315 316 // ensure that major compaction time is deterministic 317 RatioBasedCompactionPolicy 318 c = (RatioBasedCompactionPolicy)s.storeEngine.getCompactionPolicy(); 319 Collection<HStoreFile> storeFiles = s.getStorefiles(); 320 long mcTime = c.getNextMajorCompactTime(storeFiles); 321 for (int i = 0; i < 10; ++i) { 322 assertEquals(mcTime, c.getNextMajorCompactTime(storeFiles)); 323 } 324 325 // ensure that the major compaction time is within the variance 326 long jitter = Math.round(delay * jitterPct); 327 assertTrue(delay - jitter <= mcTime && mcTime <= delay + jitter); 328 329 // wait until the time-based compaction interval 330 Thread.sleep(mcTime); 331 332 // trigger a compaction request and ensure that it's upgraded to major 333 r.compact(false); 334 assertEquals(1, s.getStorefilesCount()); 335 } finally { 336 // reset the timed compaction settings 337 conf.setLong(HConstants.MAJOR_COMPACTION_PERIOD, 1000*60*60*24); 338 conf.setFloat("hbase.hregion.majorcompaction.jitter", 0.20F); 339 // run a major to reset the cache 340 createStoreFile(r); 341 r.compact(true); 342 assertEquals(1, s.getStorefilesCount()); 343 } 344 } 345 346 private void verifyCounts(int countRow1, int countRow2) throws Exception { 347 int count1 = 0; 348 int count2 = 0; 349 for (HStoreFile f: r.getStore(COLUMN_FAMILY_TEXT).getStorefiles()) { 350 HFileScanner scanner = f.getReader().getScanner(false, false); 351 scanner.seekTo(); 352 do { 353 byte [] row = CellUtil.cloneRow(scanner.getCell()); 354 if (Bytes.equals(row, STARTROW)) { 355 count1++; 356 } else if(Bytes.equals(row, secondRowBytes)) { 357 count2++; 358 } 359 } while(scanner.next()); 360 } 361 assertEquals(countRow1,count1); 362 assertEquals(countRow2,count2); 363 } 364 365 366 private int count() throws IOException { 367 int count = 0; 368 for (HStoreFile f: r.getStore(COLUMN_FAMILY_TEXT).getStorefiles()) { 369 HFileScanner scanner = f.getReader().getScanner(false, false); 370 if (!scanner.seekTo()) { 371 continue; 372 } 373 do { 374 count++; 375 } while(scanner.next()); 376 } 377 return count; 378 } 379 380 private void createStoreFile(final HRegion region) throws IOException { 381 createStoreFile(region, Bytes.toString(COLUMN_FAMILY)); 382 } 383 384 private void createStoreFile(final HRegion region, String family) throws IOException { 385 Table loader = new RegionAsTable(region); 386 HBaseTestCase.addContent(loader, family); 387 region.flush(true); 388 } 389 390 private void createSmallerStoreFile(final HRegion region) throws IOException { 391 Table loader = new RegionAsTable(region); 392 HBaseTestCase.addContent(loader, Bytes.toString(COLUMN_FAMILY), Bytes.toBytes("" + 393 "bbb"), null); 394 region.flush(true); 395 } 396 397 /** 398 * Test for HBASE-5920 - Test user requested major compactions always occurring 399 */ 400 @Test 401 public void testNonUserMajorCompactionRequest() throws Exception { 402 HStore store = r.getStore(COLUMN_FAMILY); 403 createStoreFile(r); 404 for (int i = 0; i < MAX_FILES_TO_COMPACT + 1; i++) { 405 createStoreFile(r); 406 } 407 store.triggerMajorCompaction(); 408 409 CompactionRequestImpl request = store.requestCompaction().get().getRequest(); 410 assertNotNull("Expected to receive a compaction request", request); 411 assertEquals( 412 "System-requested major compaction should not occur if there are too many store files", 413 false, 414 request.isMajor()); 415 } 416 417 /** 418 * Test for HBASE-5920 419 */ 420 @Test 421 public void testUserMajorCompactionRequest() throws IOException{ 422 HStore store = r.getStore(COLUMN_FAMILY); 423 createStoreFile(r); 424 for (int i = 0; i < MAX_FILES_TO_COMPACT + 1; i++) { 425 createStoreFile(r); 426 } 427 store.triggerMajorCompaction(); 428 CompactionRequestImpl request = 429 store.requestCompaction(PRIORITY_USER, CompactionLifeCycleTracker.DUMMY, null).get() 430 .getRequest(); 431 assertNotNull("Expected to receive a compaction request", request); 432 assertEquals( 433 "User-requested major compaction should always occur, even if there are too many store files", 434 true, 435 request.isMajor()); 436 } 437 438 /** 439 * Test that on a major compaction, if all cells are expired or deleted, then we'll end up with no 440 * product. Make sure scanner over region returns right answer in this case - and that it just 441 * basically works. 442 * @throws IOException 443 */ 444 @Test 445 public void testMajorCompactingToNoOutputWithReverseScan() throws IOException { 446 createStoreFile(r); 447 for (int i = 0; i < compactionThreshold; i++) { 448 createStoreFile(r); 449 } 450 // Now delete everything. 451 Scan scan = new Scan(); 452 scan.setReversed(true); 453 InternalScanner s = r.getScanner(scan); 454 do { 455 List<Cell> results = new ArrayList<>(); 456 boolean result = s.next(results); 457 assertTrue(!results.isEmpty()); 458 r.delete(new Delete(CellUtil.cloneRow(results.get(0)))); 459 if (!result) { 460 break; 461 } 462 } while (true); 463 s.close(); 464 // Flush 465 r.flush(true); 466 // Major compact. 467 r.compact(true); 468 scan = new Scan(); 469 scan.setReversed(true); 470 s = r.getScanner(scan); 471 int counter = 0; 472 do { 473 List<Cell> results = new ArrayList<>(); 474 boolean result = s.next(results); 475 if (!result) { 476 break; 477 } 478 counter++; 479 } while (true); 480 s.close(); 481 assertEquals(0, counter); 482 } 483 484 private void testMajorCompactingWithDeletes(KeepDeletedCells keepDeletedCells) 485 throws IOException { 486 createStoreFile(r); 487 for (int i = 0; i < compactionThreshold; i++) { 488 createStoreFile(r); 489 } 490 // Now delete everything. 491 InternalScanner s = r.getScanner(new Scan()); 492 int originalCount = 0; 493 do { 494 List<Cell> results = new ArrayList<>(); 495 boolean result = s.next(results); 496 r.delete(new Delete(CellUtil.cloneRow(results.get(0)))); 497 if (!result) break; 498 originalCount++; 499 } while (true); 500 s.close(); 501 // Flush 502 r.flush(true); 503 504 for (HStore store : this.r.stores.values()) { 505 ScanInfo old = store.getScanInfo(); 506 ScanInfo si = old.customize(old.getMaxVersions(), old.getTtl(), keepDeletedCells); 507 store.setScanInfo(si); 508 } 509 // Major compact. 510 r.compact(true); 511 s = r.getScanner(new Scan().setRaw(true)); 512 int counter = 0; 513 do { 514 List<Cell> results = new ArrayList<>(); 515 boolean result = s.next(results); 516 if (!result) break; 517 counter++; 518 } while (true); 519 assertEquals(keepDeletedCells == KeepDeletedCells.TRUE ? originalCount : 0, counter); 520 521 } 522}