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