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.assertArrayEquals;
022import static org.junit.Assert.assertEquals;
023import static org.junit.Assert.assertFalse;
024import static org.junit.Assert.assertTrue;
025import static org.junit.Assert.fail;
026
027import java.io.IOException;
028import java.util.ArrayList;
029import java.util.List;
030import org.apache.hadoop.hbase.Cell;
031import org.apache.hadoop.hbase.CellUtil;
032import org.apache.hadoop.hbase.HBaseClassTestRule;
033import org.apache.hadoop.hbase.HBaseTestingUtility;
034import org.apache.hadoop.hbase.HConstants;
035import org.apache.hadoop.hbase.HTableDescriptor;
036import org.apache.hadoop.hbase.KeepDeletedCells;
037import org.apache.hadoop.hbase.PrivateCellUtil;
038import org.apache.hadoop.hbase.client.Delete;
039import org.apache.hadoop.hbase.client.Get;
040import org.apache.hadoop.hbase.client.Put;
041import org.apache.hadoop.hbase.client.Result;
042import org.apache.hadoop.hbase.client.Scan;
043import org.apache.hadoop.hbase.testclassification.MediumTests;
044import org.apache.hadoop.hbase.testclassification.RegionServerTests;
045import org.apache.hadoop.hbase.util.Bytes;
046import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
047import org.apache.hadoop.hbase.util.EnvironmentEdgeManagerTestHelper;
048import org.apache.hadoop.hbase.util.IncrementingEnvironmentEdge;
049import org.junit.After;
050import org.junit.Before;
051import org.junit.ClassRule;
052import org.junit.Rule;
053import org.junit.Test;
054import org.junit.experimental.categories.Category;
055import org.junit.rules.TestName;
056
057@Category({ RegionServerTests.class, MediumTests.class })
058public class TestKeepDeletes {
059
060  @ClassRule
061  public static final HBaseClassTestRule CLASS_RULE =
062    HBaseClassTestRule.forClass(TestKeepDeletes.class);
063
064  HBaseTestingUtility hbu = HBaseTestingUtility.createLocalHTU();
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  private final byte[] T6 = Bytes.toBytes("6");
072
073  private final byte[] c0 = COLUMNS[0];
074  private final byte[] c1 = COLUMNS[1];
075
076  @Rule
077  public TestName name = new TestName();
078
079  @Before
080  public void setUp() throws Exception {
081    /*
082     * HBASE-6832: [WINDOWS] Tests should use explicit timestamp for Puts, and not rely on implicit
083     * RS timing. Use an explicit timer (IncrementingEnvironmentEdge) so that the put, delete
084     * compact timestamps are tracked. Otherwise, forced major compaction will not purge Delete's
085     * having the same timestamp. see ScanQueryMatcher.match(): if (retainDeletesInOutput ||
086     * (!isUserScan && (EnvironmentEdgeManager.currentTime() - timestamp) <= timeToPurgeDeletes) ...
087     * )
088     */
089    EnvironmentEdgeManagerTestHelper.injectEdge(new IncrementingEnvironmentEdge());
090  }
091
092  @After
093  public void tearDown() throws Exception {
094    EnvironmentEdgeManager.reset();
095  }
096
097  /**
098   * Make sure that deleted rows are retained. Family delete markers are deleted. Column Delete
099   * markers are versioned Time range scan of deleted rows are possible
100   */
101  @Test
102  public void testBasicScenario() throws Exception {
103    // keep 3 versions, rows do not expire
104    HTableDescriptor htd = hbu.createTableDescriptor(name.getMethodName(), 0, 3, HConstants.FOREVER,
105      KeepDeletedCells.TRUE);
106    HRegion region = hbu.createLocalHRegion(htd, null, null);
107
108    long ts = EnvironmentEdgeManager.currentTime();
109    Put p = new Put(T1, ts);
110    p.addColumn(c0, c0, T1);
111    region.put(p);
112    p = new Put(T1, ts + 1);
113    p.addColumn(c0, c0, T2);
114    region.put(p);
115    p = new Put(T1, ts + 2);
116    p.addColumn(c0, c0, T3);
117    region.put(p);
118    p = new Put(T1, ts + 4);
119    p.addColumn(c0, c0, T4);
120    region.put(p);
121
122    // now place a delete marker at ts+2
123    Delete d = new Delete(T1, ts + 2);
124    region.delete(d);
125
126    // a raw scan can see the delete markers
127    // (one for each column family)
128    assertEquals(3, countDeleteMarkers(region));
129
130    // get something *before* the delete marker
131    Get g = new Get(T1);
132    g.setMaxVersions();
133    g.setTimeRange(0L, ts + 2);
134    Result r = region.get(g);
135    checkResult(r, c0, c0, T2, T1);
136
137    // flush
138    region.flush(true);
139
140    // yep, T2 still there, T1 gone
141    r = region.get(g);
142    checkResult(r, c0, c0, T2);
143
144    // major compact
145    region.compact(true);
146    region.compact(true);
147
148    // one delete marker left (the others did not
149    // have older puts)
150    assertEquals(1, countDeleteMarkers(region));
151
152    // still there (even after multiple compactions)
153    r = region.get(g);
154    checkResult(r, c0, c0, T2);
155
156    // a timerange that includes the delete marker won't see past rows
157    g.setTimeRange(0L, ts + 4);
158    r = region.get(g);
159    assertTrue(r.isEmpty());
160
161    // two more puts, this will expire the older puts.
162    p = new Put(T1, ts + 5);
163    p.addColumn(c0, c0, T5);
164    region.put(p);
165    p = new Put(T1, ts + 6);
166    p.addColumn(c0, c0, T6);
167    region.put(p);
168
169    // also add an old put again
170    // (which is past the max versions)
171    p = new Put(T1, ts);
172    p.addColumn(c0, c0, T1);
173    region.put(p);
174    r = region.get(g);
175    assertTrue(r.isEmpty());
176
177    region.flush(true);
178    region.compact(true);
179    region.compact(true);
180
181    // verify that the delete marker itself was collected
182    region.put(p);
183    r = region.get(g);
184    checkResult(r, c0, c0, T1);
185    assertEquals(0, countDeleteMarkers(region));
186
187    HBaseTestingUtility.closeRegionAndWAL(region);
188  }
189
190  /**
191   * Even when the store does not keep deletes a "raw" scan will return everything it can find
192   * (unless discarding cells is guaranteed to have no effect). Assuming this the desired behavior.
193   * Could also disallow "raw" scanning if the store does not have KEEP_DELETED_CELLS enabled. (can
194   * be changed easily)
195   */
196  @Test
197  public void testRawScanWithoutKeepingDeletes() throws Exception {
198    // KEEP_DELETED_CELLS is NOT enabled
199    HTableDescriptor htd = hbu.createTableDescriptor(name.getMethodName(), 0, 3, HConstants.FOREVER,
200      KeepDeletedCells.FALSE);
201    HRegion region = hbu.createLocalHRegion(htd, null, null);
202
203    long ts = EnvironmentEdgeManager.currentTime();
204    Put p = new Put(T1, ts);
205    p.addColumn(c0, c0, T1);
206    region.put(p);
207
208    Delete d = new Delete(T1, ts);
209    d.addColumn(c0, c0, ts);
210    region.delete(d);
211
212    // scan still returns delete markers and deletes rows
213    Scan s = new Scan();
214    s.setRaw(true);
215    s.setMaxVersions();
216    InternalScanner scan = region.getScanner(s);
217    List<Cell> kvs = new ArrayList<>();
218    scan.next(kvs);
219    assertEquals(2, kvs.size());
220
221    region.flush(true);
222    region.compact(true);
223
224    // after compaction they are gone
225    // (note that this a test with a Store without
226    // KEEP_DELETED_CELLS)
227    s = new Scan();
228    s.setRaw(true);
229    s.setMaxVersions();
230    scan = region.getScanner(s);
231    kvs = new ArrayList<>();
232    scan.next(kvs);
233    assertTrue(kvs.isEmpty());
234
235    HBaseTestingUtility.closeRegionAndWAL(region);
236  }
237
238  /**
239   * basic verification of existing behavior
240   */
241  @Test
242  public void testWithoutKeepingDeletes() throws Exception {
243    // KEEP_DELETED_CELLS is NOT enabled
244    HTableDescriptor htd = hbu.createTableDescriptor(name.getMethodName(), 0, 3, HConstants.FOREVER,
245      KeepDeletedCells.FALSE);
246    HRegion region = hbu.createLocalHRegion(htd, null, null);
247
248    long ts = EnvironmentEdgeManager.currentTime();
249    Put p = new Put(T1, ts);
250    p.addColumn(c0, c0, T1);
251    region.put(p);
252
253    Get gOne = new Get(T1);
254    gOne.setMaxVersions();
255    gOne.setTimeRange(0L, ts + 1);
256    Result rOne = region.get(gOne);
257    assertFalse(rOne.isEmpty());
258
259    Delete d = new Delete(T1, ts + 2);
260    d.addColumn(c0, c0, ts);
261    region.delete(d);
262
263    // "past" get does not see rows behind delete marker
264    Get g = new Get(T1);
265    g.setMaxVersions();
266    g.setTimeRange(0L, ts + 1);
267    Result r = region.get(g);
268    assertTrue(r.isEmpty());
269
270    // "past" scan does not see rows behind delete marker
271    Scan s = new Scan();
272    s.setMaxVersions();
273    s.setTimeRange(0L, ts + 1);
274    InternalScanner scanner = region.getScanner(s);
275    List<Cell> kvs = new ArrayList<>();
276    while (scanner.next(kvs)) {
277      continue;
278    }
279    assertTrue(kvs.isEmpty());
280
281    // flushing and minor compaction keep delete markers
282    region.flush(true);
283    region.compact(false);
284    assertEquals(1, countDeleteMarkers(region));
285    region.compact(true);
286    // major compaction deleted it
287    assertEquals(0, countDeleteMarkers(region));
288
289    HBaseTestingUtility.closeRegionAndWAL(region);
290  }
291
292  /**
293   * The ExplicitColumnTracker does not support "raw" scanning.
294   */
295  @Test
296  public void testRawScanWithColumns() throws Exception {
297    HTableDescriptor htd = hbu.createTableDescriptor(name.getMethodName(), 0, 3, HConstants.FOREVER,
298      KeepDeletedCells.TRUE);
299    Region region = hbu.createLocalHRegion(htd, null, null);
300
301    Scan s = new Scan();
302    s.setRaw(true);
303    s.setMaxVersions();
304    s.addColumn(c0, c0);
305
306    try {
307      region.getScanner(s);
308      fail("raw scanner with columns should have failed");
309    } catch (org.apache.hadoop.hbase.DoNotRetryIOException dnre) {
310      // ok!
311    }
312
313    HBaseTestingUtility.closeRegionAndWAL(region);
314  }
315
316  /**
317   * Verify that "raw" scanning mode return delete markers and deletes rows.
318   */
319  @Test
320  public void testRawScan() throws Exception {
321    HTableDescriptor htd = hbu.createTableDescriptor(name.getMethodName(), 0, 3, HConstants.FOREVER,
322      KeepDeletedCells.TRUE);
323    Region region = hbu.createLocalHRegion(htd, null, null);
324
325    long ts = EnvironmentEdgeManager.currentTime();
326    Put p = new Put(T1, ts);
327    p.addColumn(c0, c0, T1);
328    region.put(p);
329    p = new Put(T1, ts + 2);
330    p.addColumn(c0, c0, T2);
331    region.put(p);
332    p = new Put(T1, ts + 4);
333    p.addColumn(c0, c0, T3);
334    region.put(p);
335
336    Delete d = new Delete(T1, ts + 1);
337    region.delete(d);
338
339    d = new Delete(T1, ts + 2);
340    d.addColumn(c0, c0, ts + 2);
341    region.delete(d);
342
343    d = new Delete(T1, ts + 3);
344    d.addColumns(c0, c0, ts + 3);
345    region.delete(d);
346
347    Scan s = new Scan();
348    s.setRaw(true);
349    s.setMaxVersions();
350    InternalScanner scan = region.getScanner(s);
351    List<Cell> kvs = new ArrayList<>();
352    scan.next(kvs);
353    assertEquals(8, kvs.size());
354    assertTrue(PrivateCellUtil.isDeleteFamily(kvs.get(0)));
355    assertArrayEquals(CellUtil.cloneValue(kvs.get(1)), T3);
356    assertTrue(CellUtil.isDelete(kvs.get(2)));
357    assertTrue(CellUtil.isDelete(kvs.get(3))); // .isDeleteType());
358    assertArrayEquals(CellUtil.cloneValue(kvs.get(4)), T2);
359    assertArrayEquals(CellUtil.cloneValue(kvs.get(5)), T1);
360    // we have 3 CFs, so there are two more delete markers
361    assertTrue(PrivateCellUtil.isDeleteFamily(kvs.get(6)));
362    assertTrue(PrivateCellUtil.isDeleteFamily(kvs.get(7)));
363
364    // verify that raw scans honor the passed timerange
365    s = new Scan();
366    s.setRaw(true);
367    s.setMaxVersions();
368    s.setTimeRange(0, 1);
369    scan = region.getScanner(s);
370    kvs = new ArrayList<>();
371    scan.next(kvs);
372    // nothing in this interval, not even delete markers
373    assertTrue(kvs.isEmpty());
374
375    // filter new delete markers
376    s = new Scan();
377    s.setRaw(true);
378    s.setMaxVersions();
379    s.setTimeRange(0, ts + 2);
380    scan = region.getScanner(s);
381    kvs = new ArrayList<>();
382    scan.next(kvs);
383    assertEquals(4, kvs.size());
384    assertTrue(PrivateCellUtil.isDeleteFamily(kvs.get(0)));
385    assertArrayEquals(CellUtil.cloneValue(kvs.get(1)), T1);
386    // we have 3 CFs
387    assertTrue(PrivateCellUtil.isDeleteFamily(kvs.get(2)));
388    assertTrue(PrivateCellUtil.isDeleteFamily(kvs.get(3)));
389
390    // filter old delete markers
391    s = new Scan();
392    s.setRaw(true);
393    s.setMaxVersions();
394    s.setTimeRange(ts + 3, ts + 5);
395    scan = region.getScanner(s);
396    kvs = new ArrayList<>();
397    scan.next(kvs);
398    assertEquals(2, kvs.size());
399    assertArrayEquals(CellUtil.cloneValue(kvs.get(0)), T3);
400    assertTrue(CellUtil.isDelete(kvs.get(1)));
401
402    HBaseTestingUtility.closeRegionAndWAL(region);
403  }
404
405  /**
406   * Verify that delete markers are removed from an otherwise empty store.
407   */
408  @Test
409  public void testDeleteMarkerExpirationEmptyStore() throws Exception {
410    HTableDescriptor htd = hbu.createTableDescriptor(name.getMethodName(), 0, 1, HConstants.FOREVER,
411      KeepDeletedCells.TRUE);
412    HRegion region = hbu.createLocalHRegion(htd, null, null);
413
414    long ts = EnvironmentEdgeManager.currentTime();
415
416    Delete d = new Delete(T1, ts);
417    d.addColumns(c0, c0, ts);
418    region.delete(d);
419
420    d = new Delete(T1, ts);
421    d.addFamily(c0);
422    region.delete(d);
423
424    d = new Delete(T1, ts);
425    d.addColumn(c0, c0, ts + 1);
426    region.delete(d);
427
428    d = new Delete(T1, ts);
429    d.addColumn(c0, c0, ts + 2);
430    region.delete(d);
431
432    // 1 family marker, 1 column marker, 2 version markers
433    assertEquals(4, countDeleteMarkers(region));
434
435    // neither flush nor minor compaction removes any marker
436    region.flush(true);
437    assertEquals(4, countDeleteMarkers(region));
438    region.compact(false);
439    assertEquals(4, countDeleteMarkers(region));
440
441    // major compaction removes all, since there are no puts they affect
442    region.compact(true);
443    assertEquals(0, countDeleteMarkers(region));
444
445    HBaseTestingUtility.closeRegionAndWAL(region);
446  }
447
448  /**
449   * Test delete marker removal from store files.
450   */
451  @Test
452  public void testDeleteMarkerExpiration() throws Exception {
453    HTableDescriptor htd = hbu.createTableDescriptor(name.getMethodName(), 0, 1, HConstants.FOREVER,
454      KeepDeletedCells.TRUE);
455    HRegion region = hbu.createLocalHRegion(htd, null, null);
456
457    long ts = EnvironmentEdgeManager.currentTime();
458
459    Put p = new Put(T1, ts);
460    p.addColumn(c0, c0, T1);
461    region.put(p);
462
463    // a put into another store (CF) should have no effect
464    p = new Put(T1, ts - 10);
465    p.addColumn(c1, c0, T1);
466    region.put(p);
467
468    // all the following deletes affect the put
469    Delete d = new Delete(T1, ts);
470    d.addColumns(c0, c0, ts);
471    region.delete(d);
472
473    d = new Delete(T1, ts);
474    d.addFamily(c0, ts);
475    region.delete(d);
476
477    d = new Delete(T1, ts);
478    d.addColumn(c0, c0, ts + 1);
479    region.delete(d);
480
481    d = new Delete(T1, ts);
482    d.addColumn(c0, c0, ts + 2);
483    region.delete(d);
484
485    // 1 family marker, 1 column marker, 2 version markers
486    assertEquals(4, countDeleteMarkers(region));
487
488    region.flush(true);
489    assertEquals(4, countDeleteMarkers(region));
490    region.compact(false);
491    assertEquals(4, countDeleteMarkers(region));
492
493    // another put will push out the earlier put...
494    p = new Put(T1, ts + 3);
495    p.addColumn(c0, c0, T1);
496    region.put(p);
497
498    region.flush(true);
499    // no markers are collected, since there is an affected put
500    region.compact(true);
501    assertEquals(4, countDeleteMarkers(region));
502
503    // the last collections collected the earlier put
504    // so after this collection all markers
505    region.compact(true);
506    assertEquals(0, countDeleteMarkers(region));
507
508    HBaseTestingUtility.closeRegionAndWAL(region);
509  }
510
511  /**
512   * Test delete marker removal from store files.
513   */
514  @Test
515  public void testWithOldRow() throws Exception {
516    HTableDescriptor htd = hbu.createTableDescriptor(name.getMethodName(), 0, 1, HConstants.FOREVER,
517      KeepDeletedCells.TRUE);
518    HRegion region = hbu.createLocalHRegion(htd, null, null);
519
520    long ts = EnvironmentEdgeManager.currentTime();
521
522    Put p = new Put(T1, ts);
523    p.addColumn(c0, c0, T1);
524    region.put(p);
525
526    // a put another (older) row in the same store
527    p = new Put(T2, ts - 10);
528    p.addColumn(c0, c0, T1);
529    region.put(p);
530
531    // all the following deletes affect the put
532    Delete d = new Delete(T1, ts);
533    d.addColumns(c0, c0, ts);
534    region.delete(d);
535
536    d = new Delete(T1, ts);
537    d.addFamily(c0, ts);
538    region.delete(d);
539
540    d = new Delete(T1, ts);
541    d.addColumn(c0, c0, ts + 1);
542    region.delete(d);
543
544    d = new Delete(T1, ts);
545    d.addColumn(c0, c0, ts + 2);
546    region.delete(d);
547
548    // 1 family marker, 1 column marker, 2 version markers
549    assertEquals(4, countDeleteMarkers(region));
550
551    region.flush(true);
552    assertEquals(4, countDeleteMarkers(region));
553    region.compact(false);
554    assertEquals(4, countDeleteMarkers(region));
555
556    // another put will push out the earlier put...
557    p = new Put(T1, ts + 3);
558    p.addColumn(c0, c0, T1);
559    region.put(p);
560
561    region.flush(true);
562    // no markers are collected, since there is an affected put
563    region.compact(true);
564    assertEquals(4, countDeleteMarkers(region));
565
566    // all markers remain, since we have the older row
567    // and we haven't pushed the inlined markers past MAX_VERSIONS
568    region.compact(true);
569    assertEquals(4, countDeleteMarkers(region));
570
571    // another put will push out the earlier put...
572    p = new Put(T1, ts + 4);
573    p.addColumn(c0, c0, T1);
574    region.put(p);
575
576    // this pushed out the column and version marker
577    // but the family markers remains. THIS IS A PROBLEM!
578    region.compact(true);
579    assertEquals(1, countDeleteMarkers(region));
580
581    // no amount of compacting is getting this of this one
582    // KEEP_DELETED_CELLS=>TTL is an option to avoid this.
583    region.compact(true);
584    assertEquals(1, countDeleteMarkers(region));
585
586    HBaseTestingUtility.closeRegionAndWAL(region);
587  }
588
589  /**
590   * Verify correct range demarcation
591   */
592  @Test
593  public void testRanges() throws Exception {
594    HTableDescriptor htd = hbu.createTableDescriptor(name.getMethodName(), 0, 3, HConstants.FOREVER,
595      KeepDeletedCells.TRUE);
596    Region region = hbu.createLocalHRegion(htd, null, null);
597
598    long ts = EnvironmentEdgeManager.currentTime();
599    Put p = new Put(T1, ts);
600    p.addColumn(c0, c0, T1);
601    p.addColumn(c0, c1, T1);
602    p.addColumn(c1, c0, T1);
603    p.addColumn(c1, c1, T1);
604    region.put(p);
605
606    p = new Put(T2, ts);
607    p.addColumn(c0, c0, T1);
608    p.addColumn(c0, c1, T1);
609    p.addColumn(c1, c0, T1);
610    p.addColumn(c1, c1, T1);
611    region.put(p);
612
613    p = new Put(T1, ts + 1);
614    p.addColumn(c0, c0, T2);
615    p.addColumn(c0, c1, T2);
616    p.addColumn(c1, c0, T2);
617    p.addColumn(c1, c1, T2);
618    region.put(p);
619
620    p = new Put(T2, ts + 1);
621    p.addColumn(c0, c0, T2);
622    p.addColumn(c0, c1, T2);
623    p.addColumn(c1, c0, T2);
624    p.addColumn(c1, c1, T2);
625    region.put(p);
626
627    Delete d = new Delete(T1, ts + 2);
628    d.addColumns(c0, c0, ts + 2);
629    region.delete(d);
630
631    d = new Delete(T1, ts + 2);
632    d.addFamily(c1, ts + 2);
633    region.delete(d);
634
635    d = new Delete(T2, ts + 2);
636    d.addFamily(c0, ts + 2);
637    region.delete(d);
638
639    // add an older delete, to make sure it is filtered
640    d = new Delete(T1, ts - 10);
641    d.addFamily(c1, ts - 10);
642    region.delete(d);
643
644    // ts + 2 does NOT include the delete at ts+2
645    checkGet(region, T1, c0, c0, ts + 2, T2, T1);
646    checkGet(region, T1, c0, c1, ts + 2, T2, T1);
647    checkGet(region, T1, c1, c0, ts + 2, T2, T1);
648    checkGet(region, T1, c1, c1, ts + 2, T2, T1);
649
650    checkGet(region, T2, c0, c0, ts + 2, T2, T1);
651    checkGet(region, T2, c0, c1, ts + 2, T2, T1);
652    checkGet(region, T2, c1, c0, ts + 2, T2, T1);
653    checkGet(region, T2, c1, c1, ts + 2, T2, T1);
654
655    // ts + 3 does
656    checkGet(region, T1, c0, c0, ts + 3);
657    checkGet(region, T1, c0, c1, ts + 3, T2, T1);
658    checkGet(region, T1, c1, c0, ts + 3);
659    checkGet(region, T1, c1, c1, ts + 3);
660
661    checkGet(region, T2, c0, c0, ts + 3);
662    checkGet(region, T2, c0, c1, ts + 3);
663    checkGet(region, T2, c1, c0, ts + 3, T2, T1);
664    checkGet(region, T2, c1, c1, ts + 3, T2, T1);
665
666    HBaseTestingUtility.closeRegionAndWAL(region);
667  }
668
669  /**
670   * Verify that column/version delete makers are sorted with their respective puts and removed
671   * correctly by versioning (i.e. not relying on the store earliestPutTS).
672   */
673  @Test
674  public void testDeleteMarkerVersioning() throws Exception {
675    HTableDescriptor htd = hbu.createTableDescriptor(name.getMethodName(), 0, 1, HConstants.FOREVER,
676      KeepDeletedCells.TRUE);
677    HRegion region = hbu.createLocalHRegion(htd, null, null);
678
679    long ts = EnvironmentEdgeManager.currentTime();
680    Put p = new Put(T1, ts);
681    p.addColumn(c0, c0, T1);
682    region.put(p);
683
684    // this prevents marker collection based on earliestPut
685    // (cannot keep earliest put per column in the store file)
686    p = new Put(T1, ts - 10);
687    p.addColumn(c0, c1, T1);
688    region.put(p);
689
690    Delete d = new Delete(T1, ts);
691    // test corner case (Put and Delete have same TS)
692    d.addColumns(c0, c0, ts);
693    region.delete(d);
694
695    d = new Delete(T1, ts + 1);
696    d.addColumn(c0, c0, ts + 1);
697    region.delete(d);
698
699    d = new Delete(T1, ts + 3);
700    d.addColumn(c0, c0, ts + 3);
701    region.delete(d);
702
703    region.flush(true);
704    region.compact(true);
705    region.compact(true);
706    assertEquals(3, countDeleteMarkers(region));
707
708    // add two more puts, since max version is 1
709    // the 2nd put (and all delete markers following)
710    // will be removed.
711    p = new Put(T1, ts + 2);
712    p.addColumn(c0, c0, T2);
713    region.put(p);
714
715    // delete, put, delete, delete, put
716    assertEquals(3, countDeleteMarkers(region));
717
718    p = new Put(T1, ts + 3);
719    p.addColumn(c0, c0, T3);
720    region.put(p);
721
722    // This is potentially questionable behavior.
723    // This could be changed by not letting the ScanQueryMatcher
724    // return SEEK_NEXT_COL if a put is past VERSIONS, but instead
725    // return SKIP if the store has KEEP_DELETED_CELLS set.
726    //
727    // As it stands, the 1 here is correct here.
728    // There are two puts, VERSIONS is one, so after the 1st put the scanner
729    // knows that there can be no more KVs (put or delete) that have any effect.
730    //
731    // delete, put, put | delete, delete
732    assertEquals(1, countDeleteMarkers(region));
733
734    // flush cache only sees what is in the memstore
735    region.flush(true);
736
737    // Here we have the three markers again, because the flush above
738    // removed the 2nd put before the file is written.
739    // So there's only one put, and hence the deletes already in the store
740    // files cannot be removed safely.
741    // delete, put, delete, delete
742    assertEquals(3, countDeleteMarkers(region));
743
744    region.compact(true);
745    assertEquals(3, countDeleteMarkers(region));
746
747    // add one more put
748    p = new Put(T1, ts + 4);
749    p.addColumn(c0, c0, T4);
750    region.put(p);
751
752    region.flush(true);
753    // one trailing delete marker remains (but only one)
754    // because delete markers do not increase the version count
755    assertEquals(1, countDeleteMarkers(region));
756    region.compact(true);
757    region.compact(true);
758    assertEquals(1, countDeleteMarkers(region));
759
760    HBaseTestingUtility.closeRegionAndWAL(region);
761  }
762
763  /**
764   * Verify scenarios with multiple CFs and columns
765   */
766  @Test
767  public void testWithMixedCFs() throws Exception {
768    HTableDescriptor htd = hbu.createTableDescriptor(name.getMethodName(), 0, 1, HConstants.FOREVER,
769      KeepDeletedCells.TRUE);
770    Region region = hbu.createLocalHRegion(htd, null, null);
771
772    long ts = EnvironmentEdgeManager.currentTime();
773
774    Put p = new Put(T1, ts);
775    p.addColumn(c0, c0, T1);
776    p.addColumn(c0, c1, T1);
777    p.addColumn(c1, c0, T1);
778    p.addColumn(c1, c1, T1);
779    region.put(p);
780
781    p = new Put(T2, ts + 1);
782    p.addColumn(c0, c0, T2);
783    p.addColumn(c0, c1, T2);
784    p.addColumn(c1, c0, T2);
785    p.addColumn(c1, c1, T2);
786    region.put(p);
787
788    // family markers are each family
789    Delete d = new Delete(T1, ts + 1);
790    region.delete(d);
791
792    d = new Delete(T2, ts + 2);
793    region.delete(d);
794
795    Scan s = new Scan(T1);
796    s.setTimeRange(0, ts + 1);
797    InternalScanner scanner = region.getScanner(s);
798    List<Cell> kvs = new ArrayList<>();
799    scanner.next(kvs);
800    assertEquals(4, kvs.size());
801    scanner.close();
802
803    s = new Scan(T2);
804    s.setTimeRange(0, ts + 2);
805    scanner = region.getScanner(s);
806    kvs = new ArrayList<>();
807    scanner.next(kvs);
808    assertEquals(4, kvs.size());
809    scanner.close();
810
811    HBaseTestingUtility.closeRegionAndWAL(region);
812  }
813
814  /**
815   * Test keeping deleted rows together with min versions set
816   */
817  @Test
818  public void testWithMinVersions() throws Exception {
819    HTableDescriptor htd =
820      hbu.createTableDescriptor(name.getMethodName(), 3, 1000, 1, KeepDeletedCells.TRUE);
821    HRegion region = hbu.createLocalHRegion(htd, null, null);
822
823    long ts = EnvironmentEdgeManager.currentTime() - 2000; // 2s in the past
824
825    Put p = new Put(T1, ts);
826    p.addColumn(c0, c0, T3);
827    region.put(p);
828    p = new Put(T1, ts - 1);
829    p.addColumn(c0, c0, T2);
830    region.put(p);
831    p = new Put(T1, ts - 3);
832    p.addColumn(c0, c0, T1);
833    region.put(p);
834    p = new Put(T1, ts - 4);
835    p.addColumn(c0, c0, T0);
836    region.put(p);
837
838    // all puts now are just retained because of min versions = 3
839
840    // place a family delete marker
841    Delete d = new Delete(T1, ts - 1);
842    region.delete(d);
843    // and a column delete marker
844    d = new Delete(T1, ts - 2);
845    d.addColumns(c0, c0, ts - 1);
846    region.delete(d);
847
848    Get g = new Get(T1);
849    g.setMaxVersions();
850    g.setTimeRange(0L, ts - 2);
851    Result r = region.get(g);
852    checkResult(r, c0, c0, T1, T0);
853
854    // 3 families, one column delete marker
855    assertEquals(4, countDeleteMarkers(region));
856
857    region.flush(true);
858    // no delete marker removes by the flush
859    assertEquals(4, countDeleteMarkers(region));
860
861    r = region.get(g);
862    checkResult(r, c0, c0, T1);
863    p = new Put(T1, ts + 1);
864    p.addColumn(c0, c0, T4);
865    region.put(p);
866    region.flush(true);
867
868    assertEquals(4, countDeleteMarkers(region));
869
870    r = region.get(g);
871    checkResult(r, c0, c0, T1);
872
873    // this will push out the last put before
874    // family delete marker
875    p = new Put(T1, ts + 2);
876    p.addColumn(c0, c0, T5);
877    region.put(p);
878
879    region.flush(true);
880    region.compact(true);
881    // the two family markers without puts are gone
882    assertEquals(2, countDeleteMarkers(region));
883
884    // the last compactStores updated the earliestPutTs,
885    // so after the next compaction the last family delete marker is also gone
886    region.compact(true);
887    assertEquals(0, countDeleteMarkers(region));
888
889    HBaseTestingUtility.closeRegionAndWAL(region);
890  }
891
892  /**
893   * Test keeping deleted rows together with min versions set
894   */
895  @Test
896  public void testWithTTL() throws Exception {
897    HTableDescriptor htd =
898      hbu.createTableDescriptor(name.getMethodName(), 1, 1000, 1, KeepDeletedCells.TTL);
899    HRegion region = hbu.createLocalHRegion(htd, null, null);
900
901    long ts = EnvironmentEdgeManager.currentTime() - 2000; // 2s in the past
902
903    Put p = new Put(T1, ts);
904    p.addColumn(c0, c0, T3);
905    region.put(p);
906
907    // place an old row, to make the family marker expires anyway
908    p = new Put(T2, ts - 10);
909    p.addColumn(c0, c0, T1);
910    region.put(p);
911
912    checkGet(region, T1, c0, c0, ts + 1, T3);
913    // place a family delete marker
914    Delete d = new Delete(T1, ts + 2);
915    region.delete(d);
916
917    checkGet(region, T1, c0, c0, ts + 1, T3);
918
919    // 3 families, one column delete marker
920    assertEquals(3, countDeleteMarkers(region));
921
922    region.flush(true);
923    // no delete marker removes by the flush
924    assertEquals(3, countDeleteMarkers(region));
925
926    // but the Put is gone
927    checkGet(region, T1, c0, c0, ts + 1);
928
929    region.compact(true);
930    // all delete marker gone
931    assertEquals(0, countDeleteMarkers(region));
932
933    HBaseTestingUtility.closeRegionAndWAL(region);
934  }
935
936  private void checkGet(Region region, byte[] row, byte[] fam, byte[] col, long time,
937    byte[]... vals) throws IOException {
938    Get g = new Get(row);
939    g.addColumn(fam, col);
940    g.setMaxVersions();
941    g.setTimeRange(0L, time);
942    Result r = region.get(g);
943    checkResult(r, fam, col, vals);
944
945  }
946
947  private int countDeleteMarkers(HRegion region) throws IOException {
948    Scan s = new Scan();
949    s.setRaw(true);
950    // use max versions from the store(s)
951    s.setMaxVersions(region.getStores().iterator().next().getScanInfo().getMaxVersions());
952    InternalScanner scan = region.getScanner(s);
953    List<Cell> kvs = new ArrayList<>();
954    int res = 0;
955    boolean hasMore;
956    do {
957      hasMore = scan.next(kvs);
958      for (Cell kv : kvs) {
959        if (CellUtil.isDelete(kv)) {
960          res++;
961        }
962      }
963      kvs.clear();
964    } while (hasMore);
965    scan.close();
966    return res;
967  }
968
969  private void checkResult(Result r, byte[] fam, byte[] col, byte[]... vals) {
970    assertEquals(r.size(), vals.length);
971    List<Cell> kvs = r.getColumnCells(fam, col);
972    assertEquals(kvs.size(), vals.length);
973    for (int i = 0; i < vals.length; i++) {
974      assertArrayEquals(CellUtil.cloneValue(kvs.get(i)), vals[i]);
975    }
976  }
977
978}