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.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.HBaseTestingUtil;
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  HBaseTestingUtil hbu = new HBaseTestingUtil();
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      HBaseTestingUtil.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.readAllVersions();
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.readAllVersions();
182      g.addColumn(c0, c0);
183      r = region.get(g); // this'll use ExplicitColumnTracker
184      checkResult(r, c0, T3, T2, T1);
185    } finally {
186      HBaseTestingUtil.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.readAllVersions();
224      Result r = region.get(g); // this'll use ScanWildcardColumnTracker
225      checkResult(r, c0, T3);
226
227      g = new Get(T1);
228      g.readAllVersions();
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.readAllVersions();
240      r = region.get(g); // this'll use ScanWildcardColumnTracker
241      checkResult(r, c0, T3);
242
243      g = new Get(T1);
244      g.readAllVersions();
245      g.addColumn(c0, c0);
246      r = region.get(g); // this'll use ExplicitColumnTracker
247      checkResult(r, c0, T3);
248    } finally {
249      HBaseTestingUtil.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.readAllVersions();
301      r = region.get(g); // this'll use ScanWildcardColumnTracker
302      checkResult(r, c0, T4, T3);
303
304      g = new Get(T1);
305      g.readAllVersions();
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.readAllVersions();
318      r = region.get(g); // this'll use ScanWildcardColumnTracker
319      checkResult(r, c0, T5, T4);
320
321      g = new Get(T1);
322      g.readAllVersions();
323      g.addColumn(c0, c0);
324      r = region.get(g); // this'll use ExplicitColumnTracker
325      checkResult(r, c0, T5, T4);
326    } finally {
327      HBaseTestingUtil.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.readAllVersions();
385      r = region.get(g); // this'll use ScanWildcardColumnTracker
386      checkResult(r, c0, T4, T3);
387
388      g = new Get(T1);
389      g.readAllVersions();
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      HBaseTestingUtil.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.readAllVersions();
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.readAllVersions();
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.readAllVersions();
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.readAllVersions();
506      r = region.get(g);
507      checkResult(r, c0, T2);
508    } finally {
509      HBaseTestingUtil.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      HBaseTestingUtil.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}