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