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.COLUMNS; 021import static org.junit.Assert.assertEquals; 022import static org.junit.Assert.assertTrue; 023 024import java.io.IOException; 025import java.util.ArrayList; 026import java.util.List; 027import org.apache.hadoop.hbase.Cell; 028import org.apache.hadoop.hbase.CellUtil; 029import org.apache.hadoop.hbase.HBaseClassTestRule; 030import org.apache.hadoop.hbase.HBaseTestingUtility; 031import org.apache.hadoop.hbase.KeepDeletedCells; 032import org.apache.hadoop.hbase.TableName; 033import org.apache.hadoop.hbase.client.ColumnFamilyDescriptor; 034import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder; 035import org.apache.hadoop.hbase.client.Delete; 036import org.apache.hadoop.hbase.client.Get; 037import org.apache.hadoop.hbase.client.Put; 038import org.apache.hadoop.hbase.client.Result; 039import org.apache.hadoop.hbase.client.TableDescriptor; 040import org.apache.hadoop.hbase.client.TableDescriptorBuilder; 041import org.apache.hadoop.hbase.filter.TimestampsFilter; 042import org.apache.hadoop.hbase.testclassification.MediumTests; 043import org.apache.hadoop.hbase.testclassification.RegionServerTests; 044import org.apache.hadoop.hbase.util.Bytes; 045import org.apache.hadoop.hbase.util.EnvironmentEdgeManager; 046import org.apache.hadoop.hbase.util.ManualEnvironmentEdge; 047import org.junit.Assert; 048import org.junit.ClassRule; 049import org.junit.Rule; 050import org.junit.Test; 051import org.junit.experimental.categories.Category; 052import org.junit.rules.TestName; 053 054/** 055 * Test Minimum Versions feature (HBASE-4071). 056 */ 057@Category({RegionServerTests.class, MediumTests.class}) 058public class TestMinVersions { 059 060 @ClassRule 061 public static final HBaseClassTestRule CLASS_RULE = 062 HBaseClassTestRule.forClass(TestMinVersions.class); 063 064 HBaseTestingUtility hbu = new HBaseTestingUtility(); 065 private final byte[] T0 = Bytes.toBytes("0"); 066 private final byte[] T1 = Bytes.toBytes("1"); 067 private final byte[] T2 = Bytes.toBytes("2"); 068 private final byte[] T3 = Bytes.toBytes("3"); 069 private final byte[] T4 = Bytes.toBytes("4"); 070 private final byte[] T5 = Bytes.toBytes("5"); 071 072 private final byte[] c0 = COLUMNS[0]; 073 074 @Rule public TestName name = new TestName(); 075 076 /** 077 * Verify behavior of getClosestBefore(...) 078 */ 079 @Test 080 public void testGetClosestBefore() throws Exception { 081 082 ColumnFamilyDescriptor cfd = 083 ColumnFamilyDescriptorBuilder.newBuilder(c0) 084 .setMinVersions(1).setMaxVersions(1000).setTimeToLive(1). 085 setKeepDeletedCells(KeepDeletedCells.FALSE).build(); 086 087 TableDescriptor htd = TableDescriptorBuilder. 088 newBuilder(TableName.valueOf(name.getMethodName())).setColumnFamily(cfd).build(); 089 HRegion region = hbu.createLocalHRegion(htd, null, null); 090 try { 091 092 // 2s in the past 093 long ts = EnvironmentEdgeManager.currentTime() - 2000; 094 095 Put p = new Put(T1, ts); 096 p.addColumn(c0, c0, T1); 097 region.put(p); 098 099 p = new Put(T1, ts+1); 100 p.addColumn(c0, c0, T4); 101 region.put(p); 102 103 p = new Put(T3, ts); 104 p.addColumn(c0, c0, T3); 105 region.put(p); 106 107 // now make sure that getClosestBefore(...) get can 108 // rows that would be expired without minVersion. 109 // also make sure it gets the latest version 110 Result r = hbu.getClosestRowBefore(region, T1, c0); 111 checkResult(r, c0, T4); 112 113 r = hbu.getClosestRowBefore(region, T2, c0); 114 checkResult(r, c0, T4); 115 116 // now flush/compact 117 region.flush(true); 118 region.compact(true); 119 120 r = hbu.getClosestRowBefore(region, T1, c0); 121 checkResult(r, c0, T4); 122 123 r = hbu.getClosestRowBefore(region, T2, c0); 124 checkResult(r, c0, T4); 125 } finally { 126 HBaseTestingUtility.closeRegionAndWAL(region); 127 } 128 } 129 130 /** 131 * Test mixed memstore and storefile scanning 132 * with minimum versions. 133 */ 134 @Test 135 public void testStoreMemStore() throws Exception { 136 // keep 3 versions minimum 137 138 ColumnFamilyDescriptor cfd = 139 ColumnFamilyDescriptorBuilder.newBuilder(c0) 140 .setMinVersions(3).setMaxVersions(1000).setTimeToLive(1). 141 setKeepDeletedCells(KeepDeletedCells.FALSE).build(); 142 143 TableDescriptor htd = TableDescriptorBuilder. 144 newBuilder(TableName.valueOf(name.getMethodName())).setColumnFamily(cfd).build(); 145 146 HRegion region = hbu.createLocalHRegion(htd, null, null); 147 // 2s in the past 148 long ts = EnvironmentEdgeManager.currentTime() - 2000; 149 150 try { 151 Put p = new Put(T1, ts-1); 152 p.addColumn(c0, c0, T2); 153 region.put(p); 154 155 p = new Put(T1, ts-3); 156 p.addColumn(c0, c0, T0); 157 region.put(p); 158 159 // now flush/compact 160 region.flush(true); 161 region.compact(true); 162 163 p = new Put(T1, ts); 164 p.addColumn(c0, c0, T3); 165 region.put(p); 166 167 p = new Put(T1, ts-2); 168 p.addColumn(c0, c0, T1); 169 region.put(p); 170 171 p = new Put(T1, ts-3); 172 p.addColumn(c0, c0, T0); 173 region.put(p); 174 175 // newest version in the memstore 176 // the 2nd oldest in the store file 177 // and the 3rd, 4th oldest also in the memstore 178 179 Get g = new Get(T1); 180 g.setMaxVersions(); 181 Result r = region.get(g); // this'll use ScanWildcardColumnTracker 182 checkResult(r, c0, T3,T2,T1); 183 184 g = new Get(T1); 185 g.setMaxVersions(); 186 g.addColumn(c0, c0); 187 r = region.get(g); // this'll use ExplicitColumnTracker 188 checkResult(r, c0, T3,T2,T1); 189 } finally { 190 HBaseTestingUtility.closeRegionAndWAL(region); 191 } 192 } 193 194 /** 195 * Make sure the Deletes behave as expected with minimum versions 196 */ 197 @Test 198 public void testDelete() throws Exception { 199 ColumnFamilyDescriptor cfd = 200 ColumnFamilyDescriptorBuilder.newBuilder(c0) 201 .setMinVersions(3).setMaxVersions(1000).setTimeToLive(1). 202 setKeepDeletedCells(KeepDeletedCells.FALSE).build(); 203 204 TableDescriptor htd = TableDescriptorBuilder. 205 newBuilder(TableName.valueOf(name.getMethodName())).setColumnFamily(cfd).build(); 206 207 HRegion region = hbu.createLocalHRegion(htd, null, null); 208 209 // 2s in the past 210 long ts = EnvironmentEdgeManager.currentTime() - 2000; 211 212 try { 213 Put p = new Put(T1, ts-2); 214 p.addColumn(c0, c0, T1); 215 region.put(p); 216 217 p = new Put(T1, ts-1); 218 p.addColumn(c0, c0, T2); 219 region.put(p); 220 221 p = new Put(T1, ts); 222 p.addColumn(c0, c0, T3); 223 region.put(p); 224 225 Delete d = new Delete(T1, ts-1); 226 region.delete(d); 227 228 Get g = new Get(T1); 229 g.setMaxVersions(); 230 Result r = region.get(g); // this'll use ScanWildcardColumnTracker 231 checkResult(r, c0, T3); 232 233 g = new Get(T1); 234 g.setMaxVersions(); 235 g.addColumn(c0, c0); 236 r = region.get(g); // this'll use ExplicitColumnTracker 237 checkResult(r, c0, T3); 238 239 // now flush/compact 240 region.flush(true); 241 region.compact(true); 242 243 // try again 244 g = new Get(T1); 245 g.setMaxVersions(); 246 r = region.get(g); // this'll use ScanWildcardColumnTracker 247 checkResult(r, c0, T3); 248 249 g = new Get(T1); 250 g.setMaxVersions(); 251 g.addColumn(c0, c0); 252 r = region.get(g); // this'll use ExplicitColumnTracker 253 checkResult(r, c0, T3); 254 } finally { 255 HBaseTestingUtility.closeRegionAndWAL(region); 256 } 257 } 258 259 /** 260 * Make sure the memstor behaves correctly with minimum versions 261 */ 262 @Test 263 public void testMemStore() throws Exception { 264 ColumnFamilyDescriptor cfd = 265 ColumnFamilyDescriptorBuilder.newBuilder(c0) 266 .setMinVersions(2).setMaxVersions(1000).setTimeToLive(1). 267 setKeepDeletedCells(KeepDeletedCells.FALSE).build(); 268 269 TableDescriptor htd = TableDescriptorBuilder. 270 newBuilder(TableName.valueOf(name.getMethodName())).setColumnFamily(cfd).build(); 271 HRegion region = hbu.createLocalHRegion(htd, null, null); 272 273 // 2s in the past 274 long ts = EnvironmentEdgeManager.currentTime() - 2000; 275 276 try { 277 // 2nd version 278 Put p = new Put(T1, ts-2); 279 p.addColumn(c0, c0, T2); 280 region.put(p); 281 282 // 3rd version 283 p = new Put(T1, ts-1); 284 p.addColumn(c0, c0, T3); 285 region.put(p); 286 287 // 4th version 288 p = new Put(T1, ts); 289 p.addColumn(c0, c0, T4); 290 region.put(p); 291 292 // now flush/compact 293 region.flush(true); 294 region.compact(true); 295 296 // now put the first version (backdated) 297 p = new Put(T1, ts-3); 298 p.addColumn(c0, c0, T1); 299 region.put(p); 300 301 // now the latest change is in the memstore, 302 // but it is not the latest version 303 304 Result r = region.get(new Get(T1)); 305 checkResult(r, c0, T4); 306 307 Get g = new Get(T1); 308 g.setMaxVersions(); 309 r = region.get(g); // this'll use ScanWildcardColumnTracker 310 checkResult(r, c0, T4,T3); 311 312 g = new Get(T1); 313 g.setMaxVersions(); 314 g.addColumn(c0, c0); 315 r = region.get(g); // this'll use ExplicitColumnTracker 316 checkResult(r, c0, T4,T3); 317 318 p = new Put(T1, ts+1); 319 p.addColumn(c0, c0, T5); 320 region.put(p); 321 322 // now the latest version is in the memstore 323 324 g = new Get(T1); 325 g.setMaxVersions(); 326 r = region.get(g); // this'll use ScanWildcardColumnTracker 327 checkResult(r, c0, T5,T4); 328 329 g = new Get(T1); 330 g.setMaxVersions(); 331 g.addColumn(c0, c0); 332 r = region.get(g); // this'll use ExplicitColumnTracker 333 checkResult(r, c0, T5,T4); 334 } finally { 335 HBaseTestingUtility.closeRegionAndWAL(region); 336 } 337 } 338 339 /** 340 * Verify basic minimum versions functionality 341 */ 342 @Test 343 public void testBaseCase() throws Exception { 344 // 2 version minimum, 1000 versions maximum, ttl = 1s 345 ColumnFamilyDescriptor cfd = 346 ColumnFamilyDescriptorBuilder.newBuilder(c0) 347 .setMinVersions(2).setMaxVersions(1000).setTimeToLive(1). 348 setKeepDeletedCells(KeepDeletedCells.FALSE).build(); 349 350 TableDescriptor htd = TableDescriptorBuilder. 351 newBuilder(TableName.valueOf(name.getMethodName())).setColumnFamily(cfd).build(); 352 HRegion region = hbu.createLocalHRegion(htd, null, null); 353 try { 354 355 // 2s in the past 356 long ts = EnvironmentEdgeManager.currentTime() - 2000; 357 358 // 1st version 359 Put p = new Put(T1, ts-3); 360 p.addColumn(c0, c0, T1); 361 region.put(p); 362 363 // 2nd version 364 p = new Put(T1, ts-2); 365 p.addColumn(c0, c0, T2); 366 region.put(p); 367 368 // 3rd version 369 p = new Put(T1, ts-1); 370 p.addColumn(c0, c0, T3); 371 region.put(p); 372 373 // 4th version 374 p = new Put(T1, ts); 375 p.addColumn(c0, c0, T4); 376 region.put(p); 377 378 Result r = region.get(new Get(T1)); 379 checkResult(r, c0, T4); 380 381 Get g = new Get(T1); 382 g.setTimeRange(0L, ts+1); 383 r = region.get(g); 384 checkResult(r, c0, T4); 385 386 // oldest version still exists 387 g.setTimeRange(0L, ts-2); 388 r = region.get(g); 389 checkResult(r, c0, T1); 390 391 // gets see only available versions 392 // even before compactions 393 g = new Get(T1); 394 g.setMaxVersions(); 395 r = region.get(g); // this'll use ScanWildcardColumnTracker 396 checkResult(r, c0, T4,T3); 397 398 g = new Get(T1); 399 g.setMaxVersions(); 400 g.addColumn(c0, c0); 401 r = region.get(g); // this'll use ExplicitColumnTracker 402 checkResult(r, c0, T4,T3); 403 404 // now flush 405 region.flush(true); 406 407 // with HBASE-4241 a flush will eliminate the expired rows 408 g = new Get(T1); 409 g.setTimeRange(0L, ts-2); 410 r = region.get(g); 411 assertTrue(r.isEmpty()); 412 413 // major compaction 414 region.compact(true); 415 416 // after compaction the 4th version is still available 417 g = new Get(T1); 418 g.setTimeRange(0L, ts+1); 419 r = region.get(g); 420 checkResult(r, c0, T4); 421 422 // so is the 3rd 423 g.setTimeRange(0L, ts); 424 r = region.get(g); 425 checkResult(r, c0, T3); 426 427 // but the 2nd and earlier versions are gone 428 g.setTimeRange(0L, ts-1); 429 r = region.get(g); 430 assertTrue(r.isEmpty()); 431 } finally { 432 HBaseTestingUtility.closeRegionAndWAL(region); 433 } 434 } 435 436 /** 437 * Verify that basic filters still behave correctly with 438 * minimum versions enabled. 439 */ 440 @Test 441 public void testFilters() throws Exception { 442 final byte [] c1 = COLUMNS[1]; 443 ColumnFamilyDescriptor cfd = 444 ColumnFamilyDescriptorBuilder.newBuilder(c0) 445 .setMinVersions(2).setMaxVersions(1000).setTimeToLive(1). 446 setKeepDeletedCells(KeepDeletedCells.FALSE).build(); 447 448 ColumnFamilyDescriptor cfd2 = 449 ColumnFamilyDescriptorBuilder.newBuilder(c1) 450 .setMinVersions(2).setMaxVersions(1000).setTimeToLive(1). 451 setKeepDeletedCells(KeepDeletedCells.FALSE).build(); 452 List<ColumnFamilyDescriptor> cfdList = new ArrayList(); 453 cfdList.add(cfd); 454 cfdList.add(cfd2); 455 456 TableDescriptor htd = TableDescriptorBuilder. 457 newBuilder(TableName.valueOf(name.getMethodName())).setColumnFamilies(cfdList).build(); 458 HRegion region = hbu.createLocalHRegion(htd, null, null); 459 460 // 2s in the past 461 long ts = EnvironmentEdgeManager.currentTime() - 2000; 462 try { 463 464 Put p = new Put(T1, ts-3); 465 p.addColumn(c0, c0, T0); 466 p.addColumn(c1, c1, T0); 467 region.put(p); 468 469 p = new Put(T1, ts-2); 470 p.addColumn(c0, c0, T1); 471 p.addColumn(c1, c1, T1); 472 region.put(p); 473 474 p = new Put(T1, ts-1); 475 p.addColumn(c0, c0, T2); 476 p.addColumn(c1, c1, T2); 477 region.put(p); 478 479 p = new Put(T1, ts); 480 p.addColumn(c0, c0, T3); 481 p.addColumn(c1, c1, T3); 482 region.put(p); 483 484 List<Long> tss = new ArrayList<>(); 485 tss.add(ts-1); 486 tss.add(ts-2); 487 488 // Sholud only get T2, versions is 2, so T1 is gone from user view. 489 Get g = new Get(T1); 490 g.addColumn(c1,c1); 491 g.setFilter(new TimestampsFilter(tss)); 492 g.setMaxVersions(); 493 Result r = region.get(g); 494 checkResult(r, c1, T2); 495 496 // Sholud only get T2, versions is 2, so T1 is gone from user view. 497 g = new Get(T1); 498 g.addColumn(c0,c0); 499 g.setFilter(new TimestampsFilter(tss)); 500 g.setMaxVersions(); 501 r = region.get(g); 502 checkResult(r, c0, T2); 503 504 // now flush/compact 505 region.flush(true); 506 region.compact(true); 507 508 // After flush/compact, the result should be consistent with previous result 509 g = new Get(T1); 510 g.addColumn(c1,c1); 511 g.setFilter(new TimestampsFilter(tss)); 512 g.setMaxVersions(); 513 r = region.get(g); 514 checkResult(r, c1, T2); 515 516 // After flush/compact, the result should be consistent with previous result 517 g = new Get(T1); 518 g.addColumn(c0,c0); 519 g.setFilter(new TimestampsFilter(tss)); 520 g.setMaxVersions(); 521 r = region.get(g); 522 checkResult(r, c0, T2); 523 } finally { 524 HBaseTestingUtility.closeRegionAndWAL(region); 525 } 526 } 527 528 @Test 529 public void testMinVersionsWithKeepDeletedCellsTTL() throws Exception { 530 int ttl = 4; 531 ColumnFamilyDescriptor cfd = 532 ColumnFamilyDescriptorBuilder.newBuilder(c0) 533 .setVersionsWithTimeToLive(ttl, 2).build(); 534 verifyVersionedCellKeyValues(ttl, cfd); 535 536 cfd = ColumnFamilyDescriptorBuilder.newBuilder(c0) 537 .setMinVersions(2) 538 .setMaxVersions(Integer.MAX_VALUE) 539 .setTimeToLive(ttl) 540 .setKeepDeletedCells(KeepDeletedCells.TTL) 541 .build(); 542 verifyVersionedCellKeyValues(ttl, cfd); 543 } 544 545 private void verifyVersionedCellKeyValues(int ttl, ColumnFamilyDescriptor cfd) 546 throws IOException { 547 TableDescriptor htd = TableDescriptorBuilder. 548 newBuilder(TableName.valueOf(name.getMethodName())).setColumnFamily(cfd).build(); 549 550 HRegion region = hbu.createLocalHRegion(htd, null, null); 551 552 try { 553 long startTS = EnvironmentEdgeManager.currentTime(); 554 ManualEnvironmentEdge injectEdge = new ManualEnvironmentEdge(); 555 injectEdge.setValue(startTS); 556 EnvironmentEdgeManager.injectEdge(injectEdge); 557 558 long ts = startTS - 2000; 559 putFourVersions(region, ts); 560 561 Get get; 562 Result result; 563 564 //check we can still see all versions before compaction 565 get = new Get(T1); 566 get.readAllVersions(); 567 get.setTimeRange(0, ts); 568 result = region.get(get); 569 checkResult(result, c0, T4, T3, T2, T1); 570 571 region.flush(true); 572 region.compact(true); 573 Assert.assertEquals(startTS, EnvironmentEdgeManager.currentTime()); 574 long expiredTime = EnvironmentEdgeManager.currentTime() - ts - 4; 575 Assert.assertTrue("TTL for T1 has expired", expiredTime < (ttl * 1000)); 576 //check that nothing was purged yet 577 verifyBeforeCompaction(region, ts); 578 579 injectEdge.incValue(ttl * 1000); 580 581 region.flush(true); 582 region.compact(true); 583 verifyAfterTtl(region, ts); 584 } finally { 585 HBaseTestingUtility.closeRegionAndWAL(region); 586 } 587 } 588 589 private void verifyAfterTtl(HRegion region, long ts) throws IOException { 590 Get get; 591 Result result; 592 //check that after compaction (which is after TTL) that only T1 && T2 were purged 593 get = new Get(T1); 594 get.readAllVersions(); 595 get.setTimeRange(0, ts); 596 result = region.get(get); 597 checkResult(result, c0, T4, T3); 598 599 get = new Get(T1); 600 get.readAllVersions(); 601 get.setTimeRange(0, ts - 1); 602 result = region.get(get); 603 checkResult(result, c0, T3); 604 605 get = new Get(T1); 606 get.readAllVersions(); 607 get.setTimestamp(ts - 2); 608 result = region.get(get); 609 checkResult(result, c0, T3); 610 611 get = new Get(T1); 612 get.readAllVersions(); 613 get.setTimestamp(ts - 3); 614 result = region.get(get); 615 Assert.assertEquals(result.getColumnCells(c0, c0).size(), 0); 616 617 get = new Get(T1); 618 get.readAllVersions(); 619 get.setTimeRange(0, ts - 2); 620 result = region.get(get); 621 Assert.assertEquals(result.getColumnCells(c0, c0).size(), 0); 622 } 623 624 private void verifyBeforeCompaction(HRegion region, long ts) throws IOException { 625 Get get; 626 Result result; 627 get = new Get(T1); 628 get.readAllVersions(); 629 get.setTimeRange(0, ts); 630 result = region.get(get); 631 checkResult(result, c0, T4, T3, T2, T1); 632 633 get = new Get(T1); 634 get.readAllVersions(); 635 get.setTimeRange(0, ts - 1); 636 result = region.get(get); 637 checkResult(result, c0, T3, T2, T1); 638 639 get = new Get(T1); 640 get.readAllVersions(); 641 get.setTimeRange(0, ts - 2); 642 result = region.get(get); 643 checkResult(result, c0, T2, T1); 644 645 get = new Get(T1); 646 get.readAllVersions(); 647 get.setTimeRange(0, ts - 3); 648 result = region.get(get); 649 checkResult(result, c0, T1); 650 } 651 652 private void putFourVersions(HRegion region, long ts) throws IOException { 653 // 1st version 654 Put put = new Put(T1, ts - 4); 655 put.addColumn(c0, c0, T1); 656 region.put(put); 657 658 // 2nd version 659 put = new Put(T1, ts - 3); 660 put.addColumn(c0, c0, T2); 661 region.put(put); 662 663 // 3rd version 664 put = new Put(T1, ts - 2); 665 put.addColumn(c0, c0, T3); 666 region.put(put); 667 668 // 4th version 669 put = new Put(T1, ts - 1); 670 put.addColumn(c0, c0, T4); 671 region.put(put); 672 } 673 674 private void checkResult(Result r, byte[] col, byte[] ... vals) { 675 assertEquals(vals.length, r.size()); 676 List<Cell> kvs = r.getColumnCells(col, col); 677 assertEquals(kvs.size(), vals.length); 678 for (int i=0;i<vals.length;i++) { 679 String expected = Bytes.toString(vals[i]); 680 String actual = Bytes.toString(CellUtil.cloneValue(kvs.get(i))); 681 assertTrue(expected + " was expected but doesn't match " + actual, 682 CellUtil.matchingValue(kvs.get(i), vals[i])); 683 } 684 } 685 686} 687