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.junit.Assert.assertEquals;
021import static org.junit.Assert.assertNull;
022import static org.junit.Assert.assertTrue;
023
024import java.io.IOException;
025import java.lang.management.ManagementFactory;
026import java.util.ArrayList;
027import java.util.List;
028import org.apache.hadoop.conf.Configuration;
029import org.apache.hadoop.hbase.Cell;
030import org.apache.hadoop.hbase.CellComparator;
031import org.apache.hadoop.hbase.CellUtil;
032import org.apache.hadoop.hbase.HBaseClassTestRule;
033import org.apache.hadoop.hbase.HBaseConfiguration;
034import org.apache.hadoop.hbase.HBaseTestingUtility;
035import org.apache.hadoop.hbase.HColumnDescriptor;
036import org.apache.hadoop.hbase.HConstants;
037import org.apache.hadoop.hbase.HRegionInfo;
038import org.apache.hadoop.hbase.HTableDescriptor;
039import org.apache.hadoop.hbase.KeepDeletedCells;
040import org.apache.hadoop.hbase.KeyValue;
041import org.apache.hadoop.hbase.KeyValueTestUtil;
042import org.apache.hadoop.hbase.MemoryCompactionPolicy;
043import org.apache.hadoop.hbase.TableName;
044import org.apache.hadoop.hbase.client.Scan;
045import org.apache.hadoop.hbase.exceptions.IllegalArgumentIOException;
046import org.apache.hadoop.hbase.io.util.MemorySizeUtil;
047import org.apache.hadoop.hbase.testclassification.MediumTests;
048import org.apache.hadoop.hbase.testclassification.RegionServerTests;
049import org.apache.hadoop.hbase.util.Bytes;
050import org.apache.hadoop.hbase.util.EnvironmentEdge;
051import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
052import org.apache.hadoop.hbase.util.Threads;
053import org.apache.hadoop.hbase.wal.WAL;
054import org.junit.After;
055import org.junit.Before;
056import org.junit.ClassRule;
057import org.junit.Test;
058import org.junit.experimental.categories.Category;
059import org.slf4j.Logger;
060import org.slf4j.LoggerFactory;
061
062/**
063 * compacted memstore test case
064 */
065@Category({RegionServerTests.class, MediumTests.class})
066public class TestCompactingMemStore extends TestDefaultMemStore {
067
068  @ClassRule
069  public static final HBaseClassTestRule CLASS_RULE =
070      HBaseClassTestRule.forClass(TestCompactingMemStore.class);
071
072  private static final Logger LOG = LoggerFactory.getLogger(TestCompactingMemStore.class);
073  protected static ChunkCreator chunkCreator;
074  protected HRegion region;
075  protected RegionServicesForStores regionServicesForStores;
076  protected HStore store;
077
078  //////////////////////////////////////////////////////////////////////////////
079  // Helpers
080  //////////////////////////////////////////////////////////////////////////////
081  protected static byte[] makeQualifier(final int i1, final int i2) {
082    return Bytes.toBytes(Integer.toString(i1) + ";" +
083        Integer.toString(i2));
084  }
085
086  @After
087  public void tearDown() throws Exception {
088    chunkCreator.clearChunksInPool();
089  }
090
091  @Override
092  @Before
093  public void setUp() throws Exception {
094    compactingSetUp();
095    this.memstore = new MyCompactingMemStore(HBaseConfiguration.create(), CellComparator.getInstance(),
096        store, regionServicesForStores, MemoryCompactionPolicy.EAGER);
097    ((CompactingMemStore)memstore).setIndexType(CompactingMemStore.IndexType.ARRAY_MAP);
098  }
099
100  protected void compactingSetUp() throws Exception {
101    super.internalSetUp();
102    Configuration conf = new Configuration();
103    conf.setBoolean(MemStoreLAB.USEMSLAB_KEY, true);
104    conf.setFloat(MemStoreLAB.CHUNK_POOL_MAXSIZE_KEY, 0.2f);
105    conf.setInt(HRegion.MEMSTORE_PERIODIC_FLUSH_INTERVAL, 1000);
106    HBaseTestingUtility hbaseUtility = HBaseTestingUtility.createLocalHTU(conf);
107    HColumnDescriptor hcd = new HColumnDescriptor(FAMILY);
108    HTableDescriptor htd = new HTableDescriptor(TableName.valueOf("foobar"));
109    htd.addFamily(hcd);
110    HRegionInfo info =
111        new HRegionInfo(TableName.valueOf("foobar"), null, null, false);
112    WAL wal = hbaseUtility.createWal(conf, hbaseUtility.getDataTestDir(), info);
113    this.region = HRegion.createHRegion(info, hbaseUtility.getDataTestDir(), conf, htd, wal, true);
114    //this.region = hbaseUtility.createTestRegion("foobar", hcd);
115    this.regionServicesForStores = region.getRegionServicesForStores();
116    this.store = new HStore(region, hcd, conf);
117
118    long globalMemStoreLimit = (long) (ManagementFactory.getMemoryMXBean().getHeapMemoryUsage()
119        .getMax() * MemorySizeUtil.getGlobalMemStoreHeapPercent(conf, false));
120    chunkCreator = ChunkCreator.initialize(MemStoreLABImpl.CHUNK_SIZE_DEFAULT, false,
121        globalMemStoreLimit, 0.4f, MemStoreLAB.POOL_INITIAL_SIZE_DEFAULT,
122            null);
123    assertTrue(chunkCreator != null);
124  }
125
126  /**
127   * A simple test which verifies the 3 possible states when scanning across snapshot.
128   *
129   * @throws IOException
130   * @throws CloneNotSupportedException
131   */
132  @Override
133  @Test
134  public void testScanAcrossSnapshot2() throws IOException, CloneNotSupportedException {
135    // we are going to the scanning across snapshot with two kvs
136    // kv1 should always be returned before kv2
137    final byte[] one = Bytes.toBytes(1);
138    final byte[] two = Bytes.toBytes(2);
139    final byte[] f = Bytes.toBytes("f");
140    final byte[] q = Bytes.toBytes("q");
141    final byte[] v = Bytes.toBytes(3);
142
143    final KeyValue kv1 = new KeyValue(one, f, q, 10, v);
144    final KeyValue kv2 = new KeyValue(two, f, q, 10, v);
145
146    // use case 1: both kvs in kvset
147    this.memstore.add(kv1.clone(), null);
148    this.memstore.add(kv2.clone(), null);
149    verifyScanAcrossSnapshot2(kv1, kv2);
150
151    // use case 2: both kvs in snapshot
152    this.memstore.snapshot();
153    verifyScanAcrossSnapshot2(kv1, kv2);
154
155    // use case 3: first in snapshot second in kvset
156    this.memstore = new CompactingMemStore(HBaseConfiguration.create(),
157        CellComparator.getInstance(), store, regionServicesForStores,
158        MemoryCompactionPolicy.EAGER);
159    this.memstore.add(kv1.clone(), null);
160    // As compaction is starting in the background the repetition
161    // of the k1 might be removed BUT the scanners created earlier
162    // should look on the OLD MutableCellSetSegment, so this should be OK...
163    this.memstore.snapshot();
164    this.memstore.add(kv2.clone(), null);
165    verifyScanAcrossSnapshot2(kv1,kv2);
166  }
167
168  /**
169   * Test memstore snapshots
170   * @throws IOException
171   */
172  @Override
173  @Test
174  public void testSnapshotting() throws IOException {
175    final int snapshotCount = 5;
176    // Add some rows, run a snapshot. Do it a few times.
177    for (int i = 0; i < snapshotCount; i++) {
178      addRows(this.memstore);
179      runSnapshot(this.memstore, true);
180      assertEquals("History not being cleared", 0, this.memstore.getSnapshot().getCellsCount());
181    }
182  }
183
184
185  //////////////////////////////////////////////////////////////////////////////
186  // Get tests
187  //////////////////////////////////////////////////////////////////////////////
188
189  /** Test getNextRow from memstore
190   * @throws InterruptedException
191   */
192  @Override
193  @Test
194  public void testGetNextRow() throws Exception {
195    addRows(this.memstore);
196    // Add more versions to make it a little more interesting.
197    Thread.sleep(1);
198    addRows(this.memstore);
199    Cell closestToEmpty = ((CompactingMemStore)this.memstore).getNextRow(KeyValue.LOWESTKEY);
200    assertTrue(CellComparator.getInstance().compareRows(closestToEmpty,
201        new KeyValue(Bytes.toBytes(0), System.currentTimeMillis())) == 0);
202    for (int i = 0; i < ROW_COUNT; i++) {
203      Cell nr = ((CompactingMemStore)this.memstore).getNextRow(new KeyValue(Bytes.toBytes(i),
204          System.currentTimeMillis()));
205      if (i + 1 == ROW_COUNT) {
206        assertNull(nr);
207      } else {
208        assertTrue(CellComparator.getInstance().compareRows(nr,
209            new KeyValue(Bytes.toBytes(i + 1), System.currentTimeMillis())) == 0);
210      }
211    }
212    //starting from each row, validate results should contain the starting row
213    Configuration conf = HBaseConfiguration.create();
214    for (int startRowId = 0; startRowId < ROW_COUNT; startRowId++) {
215      ScanInfo scanInfo = new ScanInfo(conf, FAMILY, 0, 1, Integer.MAX_VALUE,
216          KeepDeletedCells.FALSE, HConstants.DEFAULT_BLOCKSIZE, 0, this.memstore.getComparator(), false);
217      try (InternalScanner scanner =
218          new StoreScanner(new Scan().withStartRow(Bytes.toBytes(startRowId)), scanInfo, null,
219              memstore.getScanners(0))) {
220        List<Cell> results = new ArrayList<>();
221        for (int i = 0; scanner.next(results); i++) {
222          int rowId = startRowId + i;
223          Cell left = results.get(0);
224          byte[] row1 = Bytes.toBytes(rowId);
225          assertTrue("Row name",
226            CellComparator.getInstance().compareRows(left, row1, 0, row1.length) == 0);
227          assertEquals("Count of columns", QUALIFIER_COUNT, results.size());
228          List<Cell> row = new ArrayList<>();
229          for (Cell kv : results) {
230            row.add(kv);
231          }
232          isExpectedRowWithoutTimestamps(rowId, row);
233          // Clear out set. Otherwise row results accumulate.
234          results.clear();
235        }
236      }
237    }
238  }
239
240  @Override
241  @Test
242  public void testGet_memstoreAndSnapShot() throws IOException {
243    byte[] row = Bytes.toBytes("testrow");
244    byte[] fam = Bytes.toBytes("testfamily");
245    byte[] qf1 = Bytes.toBytes("testqualifier1");
246    byte[] qf2 = Bytes.toBytes("testqualifier2");
247    byte[] qf3 = Bytes.toBytes("testqualifier3");
248    byte[] qf4 = Bytes.toBytes("testqualifier4");
249    byte[] qf5 = Bytes.toBytes("testqualifier5");
250    byte[] val = Bytes.toBytes("testval");
251
252    //Setting up memstore
253    memstore.add(new KeyValue(row, fam, qf1, val), null);
254    memstore.add(new KeyValue(row, fam, qf2, val), null);
255    memstore.add(new KeyValue(row, fam, qf3, val), null);
256    //Pushing to pipeline
257    ((CompactingMemStore)memstore).flushInMemory();
258    assertEquals(0, memstore.getSnapshot().getCellsCount());
259    //Creating a snapshot
260    memstore.snapshot();
261    assertEquals(3, memstore.getSnapshot().getCellsCount());
262    //Adding value to "new" memstore
263    assertEquals(0, memstore.getActive().getCellsCount());
264    memstore.add(new KeyValue(row, fam, qf4, val), null);
265    memstore.add(new KeyValue(row, fam, qf5, val), null);
266    assertEquals(2, memstore.getActive().getCellsCount());
267  }
268
269  ////////////////////////////////////
270  // Test for periodic memstore flushes
271  // based on time of oldest edit
272  ////////////////////////////////////
273
274  /**
275   * Add keyvalues with a fixed memstoreTs, and checks that memstore size is decreased
276   * as older keyvalues are deleted from the memstore.
277   *
278   * @throws Exception
279   */
280  @Override
281  @Test
282  public void testUpsertMemstoreSize() throws Exception {
283    MemStoreSize oldSize = memstore.size();
284
285    List<Cell> l = new ArrayList<>();
286    KeyValue kv1 = KeyValueTestUtil.create("r", "f", "q", 100, "v");
287    KeyValue kv2 = KeyValueTestUtil.create("r", "f", "q", 101, "v");
288    KeyValue kv3 = KeyValueTestUtil.create("r", "f", "q", 102, "v");
289
290    kv1.setSequenceId(1);
291    kv2.setSequenceId(1);
292    kv3.setSequenceId(1);
293    l.add(kv1);
294    l.add(kv2);
295    l.add(kv3);
296
297    this.memstore.upsert(l, 2, null);// readpoint is 2
298    MemStoreSize newSize = this.memstore.size();
299    assert (newSize.getDataSize() > oldSize.getDataSize());
300    //The kv1 should be removed.
301    assert (memstore.getActive().getCellsCount() == 2);
302
303    KeyValue kv4 = KeyValueTestUtil.create("r", "f", "q", 104, "v");
304    kv4.setSequenceId(1);
305    l.clear();
306    l.add(kv4);
307    this.memstore.upsert(l, 3, null);
308    assertEquals(newSize, this.memstore.size());
309    //The kv2 should be removed.
310    assert (memstore.getActive().getCellsCount() == 2);
311    //this.memstore = null;
312  }
313
314  /**
315   * Tests that the timeOfOldestEdit is updated correctly for the
316   * various edit operations in memstore.
317   * @throws Exception
318   */
319  @Override
320  @Test
321  public void testUpdateToTimeOfOldestEdit() throws Exception {
322    try {
323      EnvironmentEdgeForMemstoreTest edge = new EnvironmentEdgeForMemstoreTest();
324      EnvironmentEdgeManager.injectEdge(edge);
325      long t = memstore.timeOfOldestEdit();
326      assertEquals(Long.MAX_VALUE, t);
327
328      // test the case that the timeOfOldestEdit is updated after a KV add
329      memstore.add(KeyValueTestUtil.create("r", "f", "q", 100, "v"), null);
330      t = memstore.timeOfOldestEdit();
331      assertTrue(t == 1234);
332      // The method will also assert
333      // the value is reset to Long.MAX_VALUE
334      t = runSnapshot(memstore, true);
335
336      // test the case that the timeOfOldestEdit is updated after a KV delete
337      memstore.add(KeyValueTestUtil.create("r", "f", "q", 100, KeyValue.Type.Delete, "v"), null);
338      t = memstore.timeOfOldestEdit();
339      assertTrue(t == 1234);
340     t = runSnapshot(memstore, true);
341
342      // test the case that the timeOfOldestEdit is updated after a KV upsert
343      List<Cell> l = new ArrayList<>();
344      KeyValue kv1 = KeyValueTestUtil.create("r", "f", "q", 100, "v");
345      kv1.setSequenceId(100);
346      l.add(kv1);
347      memstore.upsert(l, 1000, null);
348      t = memstore.timeOfOldestEdit();
349      assertTrue(t == 1234);
350    } finally {
351      EnvironmentEdgeManager.reset();
352    }
353  }
354
355  private long runSnapshot(final AbstractMemStore hmc, boolean useForce)
356      throws IOException {
357    // Save off old state.
358    long oldHistorySize = hmc.getSnapshot().getDataSize();
359    long prevTimeStamp = hmc.timeOfOldestEdit();
360
361    hmc.snapshot();
362    MemStoreSnapshot snapshot = hmc.snapshot();
363    if (useForce) {
364      // Make some assertions about what just happened.
365      assertTrue("History size has not increased", oldHistorySize < snapshot.getDataSize());
366      long t = hmc.timeOfOldestEdit();
367      assertTrue("Time of oldest edit is not Long.MAX_VALUE", t == Long.MAX_VALUE);
368      hmc.clearSnapshot(snapshot.getId());
369    } else {
370      long t = hmc.timeOfOldestEdit();
371      assertTrue("Time of oldest edit didn't remain the same", t == prevTimeStamp);
372    }
373    return prevTimeStamp;
374  }
375
376  private void isExpectedRowWithoutTimestamps(final int rowIndex,
377      List<Cell> kvs) {
378    int i = 0;
379    for (Cell kv : kvs) {
380      byte[] expectedColname = makeQualifier(rowIndex, i++);
381      assertTrue("Column name", CellUtil.matchingQualifier(kv, expectedColname));
382      // Value is column name as bytes.  Usually result is
383      // 100 bytes in size at least. This is the default size
384      // for BytesWriteable.  For comparison, convert bytes to
385      // String and trim to remove trailing null bytes.
386      assertTrue("Content", CellUtil.matchingValue(kv, expectedColname));
387    }
388  }
389
390  @Test
391  public void testPuttingBackChunksAfterFlushing() throws IOException {
392    byte[] row = Bytes.toBytes("testrow");
393    byte[] fam = Bytes.toBytes("testfamily");
394    byte[] qf1 = Bytes.toBytes("testqualifier1");
395    byte[] qf2 = Bytes.toBytes("testqualifier2");
396    byte[] qf3 = Bytes.toBytes("testqualifier3");
397    byte[] qf4 = Bytes.toBytes("testqualifier4");
398    byte[] qf5 = Bytes.toBytes("testqualifier5");
399    byte[] val = Bytes.toBytes("testval");
400
401    // Setting up memstore
402    memstore.add(new KeyValue(row, fam, qf1, val), null);
403    memstore.add(new KeyValue(row, fam, qf2, val), null);
404    memstore.add(new KeyValue(row, fam, qf3, val), null);
405
406    // Creating a snapshot
407    MemStoreSnapshot snapshot = memstore.snapshot();
408    assertEquals(3, memstore.getSnapshot().getCellsCount());
409
410    // Adding value to "new" memstore
411    assertEquals(0, memstore.getActive().getCellsCount());
412    memstore.add(new KeyValue(row, fam, qf4, val), null);
413    memstore.add(new KeyValue(row, fam, qf5, val), null);
414    assertEquals(2, memstore.getActive().getCellsCount());
415    // close the scanners
416    for(KeyValueScanner scanner : snapshot.getScanners()) {
417      scanner.close();
418    }
419    memstore.clearSnapshot(snapshot.getId());
420
421    int chunkCount = chunkCreator.getPoolSize();
422    assertTrue(chunkCount > 0);
423
424  }
425
426  @Test
427  public void testPuttingBackChunksWithOpeningScanner()
428      throws IOException {
429    byte[] row = Bytes.toBytes("testrow");
430    byte[] fam = Bytes.toBytes("testfamily");
431    byte[] qf1 = Bytes.toBytes("testqualifier1");
432    byte[] qf2 = Bytes.toBytes("testqualifier2");
433    byte[] qf3 = Bytes.toBytes("testqualifier3");
434    byte[] qf4 = Bytes.toBytes("testqualifier4");
435    byte[] qf5 = Bytes.toBytes("testqualifier5");
436    byte[] qf6 = Bytes.toBytes("testqualifier6");
437    byte[] qf7 = Bytes.toBytes("testqualifier7");
438    byte[] val = Bytes.toBytes("testval");
439
440    // Setting up memstore
441    memstore.add(new KeyValue(row, fam, qf1, val), null);
442    memstore.add(new KeyValue(row, fam, qf2, val), null);
443    memstore.add(new KeyValue(row, fam, qf3, val), null);
444
445    // Creating a snapshot
446    MemStoreSnapshot snapshot = memstore.snapshot();
447    assertEquals(3, memstore.getSnapshot().getCellsCount());
448
449    // Adding value to "new" memstore
450    assertEquals(0, memstore.getActive().getCellsCount());
451    memstore.add(new KeyValue(row, fam, qf4, val), null);
452    memstore.add(new KeyValue(row, fam, qf5, val), null);
453    assertEquals(2, memstore.getActive().getCellsCount());
454
455    // opening scanner before clear the snapshot
456    List<KeyValueScanner> scanners = memstore.getScanners(0);
457    // Shouldn't putting back the chunks to pool,since some scanners are opening
458    // based on their data
459    // close the scanners
460    for(KeyValueScanner scanner : snapshot.getScanners()) {
461      scanner.close();
462    }
463    memstore.clearSnapshot(snapshot.getId());
464
465    assertTrue(chunkCreator.getPoolSize() == 0);
466
467    // Chunks will be put back to pool after close scanners;
468    for (KeyValueScanner scanner : scanners) {
469      scanner.close();
470    }
471    assertTrue(chunkCreator.getPoolSize() > 0);
472
473    // clear chunks
474    chunkCreator.clearChunksInPool();
475
476    // Creating another snapshot
477
478    snapshot = memstore.snapshot();
479    // Adding more value
480    memstore.add(new KeyValue(row, fam, qf6, val), null);
481    memstore.add(new KeyValue(row, fam, qf7, val), null);
482    // opening scanners
483    scanners = memstore.getScanners(0);
484    // close scanners before clear the snapshot
485    for (KeyValueScanner scanner : scanners) {
486      scanner.close();
487    }
488    // Since no opening scanner, the chunks of snapshot should be put back to
489    // pool
490    // close the scanners
491    for(KeyValueScanner scanner : snapshot.getScanners()) {
492      scanner.close();
493    }
494    memstore.clearSnapshot(snapshot.getId());
495    assertTrue(chunkCreator.getPoolSize() > 0);
496  }
497
498  @Test
499  public void testPuttingBackChunksWithOpeningPipelineScanner()
500      throws IOException {
501
502    // set memstore to do data compaction and not to use the speculative scan
503    MemoryCompactionPolicy compactionType = MemoryCompactionPolicy.EAGER;
504    memstore.getConfiguration().set(CompactingMemStore.COMPACTING_MEMSTORE_TYPE_KEY,
505        String.valueOf(compactionType));
506    ((MyCompactingMemStore)memstore).initiateType(compactionType, memstore.getConfiguration());
507
508    byte[] row = Bytes.toBytes("testrow");
509    byte[] fam = Bytes.toBytes("testfamily");
510    byte[] qf1 = Bytes.toBytes("testqualifier1");
511    byte[] qf2 = Bytes.toBytes("testqualifier2");
512    byte[] qf3 = Bytes.toBytes("testqualifier3");
513    byte[] val = Bytes.toBytes("testval");
514
515    // Setting up memstore
516    memstore.add(new KeyValue(row, fam, qf1, 1, val), null);
517    memstore.add(new KeyValue(row, fam, qf2, 1, val), null);
518    memstore.add(new KeyValue(row, fam, qf3, 1, val), null);
519
520    // Creating a pipeline
521    ((MyCompactingMemStore)memstore).disableCompaction();
522    ((CompactingMemStore)memstore).flushInMemory();
523
524    // Adding value to "new" memstore
525    assertEquals(0, memstore.getActive().getCellsCount());
526    memstore.add(new KeyValue(row, fam, qf1, 2, val), null);
527    memstore.add(new KeyValue(row, fam, qf2, 2, val), null);
528    assertEquals(2, memstore.getActive().getCellsCount());
529
530    // pipeline bucket 2
531    ((CompactingMemStore)memstore).flushInMemory();
532    // opening scanner before force flushing
533    List<KeyValueScanner> scanners = memstore.getScanners(0);
534    // Shouldn't putting back the chunks to pool,since some scanners are opening
535    // based on their data
536    ((MyCompactingMemStore)memstore).enableCompaction();
537    // trigger compaction
538    ((CompactingMemStore)memstore).flushInMemory();
539
540    // Adding value to "new" memstore
541    assertEquals(0, memstore.getActive().getCellsCount());
542    memstore.add(new KeyValue(row, fam, qf3, 3, val), null);
543    memstore.add(new KeyValue(row, fam, qf2, 3, val), null);
544    memstore.add(new KeyValue(row, fam, qf1, 3, val), null);
545    assertEquals(3, memstore.getActive().getCellsCount());
546
547    assertTrue(chunkCreator.getPoolSize() == 0);
548
549    // Chunks will be put back to pool after close scanners;
550    for (KeyValueScanner scanner : scanners) {
551      scanner.close();
552    }
553    assertTrue(chunkCreator.getPoolSize() > 0);
554
555    // clear chunks
556    chunkCreator.clearChunksInPool();
557
558    // Creating another snapshot
559
560    MemStoreSnapshot snapshot = memstore.snapshot();
561    // close the scanners
562    for(KeyValueScanner scanner : snapshot.getScanners()) {
563      scanner.close();
564    }
565    memstore.clearSnapshot(snapshot.getId());
566
567    snapshot = memstore.snapshot();
568    // Adding more value
569    memstore.add(new KeyValue(row, fam, qf2, 4, val), null);
570    memstore.add(new KeyValue(row, fam, qf3, 4, val), null);
571    // opening scanners
572    scanners = memstore.getScanners(0);
573    // close scanners before clear the snapshot
574    for (KeyValueScanner scanner : scanners) {
575      scanner.close();
576    }
577    // Since no opening scanner, the chunks of snapshot should be put back to
578    // pool
579    // close the scanners
580    for(KeyValueScanner scanner : snapshot.getScanners()) {
581      scanner.close();
582    }
583    memstore.clearSnapshot(snapshot.getId());
584    assertTrue(chunkCreator.getPoolSize() > 0);
585  }
586
587  //////////////////////////////////////////////////////////////////////////////
588  // Compaction tests
589  //////////////////////////////////////////////////////////////////////////////
590  @Test
591  public void testCompaction1Bucket() throws IOException {
592
593    // set memstore to do basic structure flattening, the "eager" option is tested in
594    // TestCompactingToCellFlatMapMemStore
595    MemoryCompactionPolicy compactionType = MemoryCompactionPolicy.BASIC;
596    memstore.getConfiguration()
597        .set(CompactingMemStore.COMPACTING_MEMSTORE_TYPE_KEY, String.valueOf(compactionType));
598    ((MyCompactingMemStore)memstore).initiateType(compactionType, memstore.getConfiguration());
599
600    String[] keys1 = { "A", "A", "B", "C" }; //A1, A2, B3, C4
601
602    // test 1 bucket
603    int totalCellsLen = addRowsByKeys(memstore, keys1);
604    int oneCellOnCSLMHeapSize = 120;
605    int oneCellOnCAHeapSize = 88;
606    long totalHeapSize = MutableSegment.DEEP_OVERHEAD + 4 * oneCellOnCSLMHeapSize;
607    assertEquals(totalCellsLen, regionServicesForStores.getMemStoreSize());
608    assertEquals(totalHeapSize, ((CompactingMemStore)memstore).heapSize());
609
610    ((CompactingMemStore)memstore).flushInMemory(); // push keys to pipeline and compact
611    assertEquals(0, memstore.getSnapshot().getCellsCount());
612    // There is no compaction, as the compacting memstore type is basic.
613    // totalCellsLen remains the same
614    totalHeapSize = MutableSegment.DEEP_OVERHEAD + CellArrayImmutableSegment.DEEP_OVERHEAD_CAM
615        + 4 * oneCellOnCAHeapSize;
616    assertEquals(totalCellsLen, regionServicesForStores.getMemStoreSize());
617    assertEquals(totalHeapSize, ((CompactingMemStore)memstore).heapSize());
618
619    MemStoreSize mss = memstore.getFlushableSize();
620    MemStoreSnapshot snapshot = memstore.snapshot(); // push keys to snapshot
621    // simulate flusher
622    region.decrMemStoreSize(mss);
623    ImmutableSegment s = memstore.getSnapshot();
624    assertEquals(4, s.getCellsCount());
625    assertEquals(0, regionServicesForStores.getMemStoreSize());
626
627    memstore.clearSnapshot(snapshot.getId());
628  }
629
630  @Test
631  public void testCompaction2Buckets() throws IOException {
632
633    // set memstore to do basic structure flattening, the "eager" option is tested in
634    // TestCompactingToCellFlatMapMemStore
635    MemoryCompactionPolicy compactionType = MemoryCompactionPolicy.BASIC;
636    memstore.getConfiguration().set(CompactingMemStore.COMPACTING_MEMSTORE_TYPE_KEY,
637        String.valueOf(compactionType));
638    memstore.getConfiguration().set(MemStoreCompactionStrategy.COMPACTING_MEMSTORE_THRESHOLD_KEY,
639        String.valueOf(1));
640    ((MyCompactingMemStore)memstore).initiateType(compactionType, memstore.getConfiguration());
641    String[] keys1 = { "A", "A", "B", "C" };
642    String[] keys2 = { "A", "B", "D" };
643
644    int totalCellsLen1 = addRowsByKeys(memstore, keys1);
645    int oneCellOnCSLMHeapSize = 120;
646    int oneCellOnCAHeapSize = 88;
647    long totalHeapSize = MutableSegment.DEEP_OVERHEAD + 4 * oneCellOnCSLMHeapSize;
648
649    assertEquals(totalCellsLen1, regionServicesForStores.getMemStoreSize());
650    assertEquals(totalHeapSize, ((CompactingMemStore)memstore).heapSize());
651
652    ((CompactingMemStore)memstore).flushInMemory(); // push keys to pipeline and compact
653    int counter = 0;
654    for ( Segment s : memstore.getSegments()) {
655      counter += s.getCellsCount();
656    }
657    assertEquals(4, counter);
658    assertEquals(0, memstore.getSnapshot().getCellsCount());
659    // There is no compaction, as the compacting memstore type is basic.
660    // totalCellsLen remains the same
661    assertEquals(totalCellsLen1, regionServicesForStores.getMemStoreSize());
662    totalHeapSize = MutableSegment.DEEP_OVERHEAD + CellArrayImmutableSegment.DEEP_OVERHEAD_CAM
663        + 4 * oneCellOnCAHeapSize;
664    assertEquals(totalHeapSize, ((CompactingMemStore)memstore).heapSize());
665
666    int totalCellsLen2 = addRowsByKeys(memstore, keys2);
667    totalHeapSize += 3 * oneCellOnCSLMHeapSize;
668    assertEquals(totalCellsLen1 + totalCellsLen2, regionServicesForStores.getMemStoreSize());
669    assertEquals(totalHeapSize, ((CompactingMemStore) memstore).heapSize());
670
671    MemStoreSize mss = memstore.getFlushableSize();
672    ((CompactingMemStore)memstore).flushInMemory(); // push keys to pipeline and compact
673    assertEquals(0, memstore.getSnapshot().getCellsCount());
674    assertEquals(totalCellsLen1 + totalCellsLen2, regionServicesForStores.getMemStoreSize());
675    totalHeapSize = MutableSegment.DEEP_OVERHEAD + CellArrayImmutableSegment.DEEP_OVERHEAD_CAM
676        + 7 * oneCellOnCAHeapSize;
677    assertEquals(totalHeapSize, ((CompactingMemStore)memstore).heapSize());
678
679    mss = memstore.getFlushableSize();
680    MemStoreSnapshot snapshot = memstore.snapshot(); // push keys to snapshot
681    // simulate flusher
682    region.decrMemStoreSize(mss.getDataSize(), mss.getHeapSize(), mss.getOffHeapSize(),
683      mss.getCellsCount());
684    ImmutableSegment s = memstore.getSnapshot();
685    assertEquals(7, s.getCellsCount());
686    assertEquals(0, regionServicesForStores.getMemStoreSize());
687
688    memstore.clearSnapshot(snapshot.getId());
689  }
690
691  @Test
692  public void testCompaction3Buckets() throws IOException {
693
694    // set memstore to do data compaction and not to use the speculative scan
695    MemoryCompactionPolicy compactionType = MemoryCompactionPolicy.EAGER;
696    memstore.getConfiguration().set(CompactingMemStore.COMPACTING_MEMSTORE_TYPE_KEY,
697        String.valueOf(compactionType));
698    ((MyCompactingMemStore)memstore).initiateType(compactionType, memstore.getConfiguration());
699    String[] keys1 = { "A", "A", "B", "C" };
700    String[] keys2 = { "A", "B", "D" };
701    String[] keys3 = { "D", "B", "B" };
702
703    int totalCellsLen1 = addRowsByKeys(memstore, keys1);// Adding 4 cells.
704    int oneCellOnCSLMHeapSize = 120;
705    int oneCellOnCAHeapSize = 88;
706    assertEquals(totalCellsLen1, region.getMemStoreDataSize());
707    long totalHeapSize = MutableSegment.DEEP_OVERHEAD + 4 * oneCellOnCSLMHeapSize;
708    assertEquals(totalHeapSize, ((CompactingMemStore)memstore).heapSize());
709    ((CompactingMemStore)memstore).flushInMemory(); // push keys to pipeline and compact
710
711    assertEquals(0, memstore.getSnapshot().getCellsCount());
712    // One cell is duplicated and the compaction will remove it. All cells of same time so adjusting
713    // totalCellsLen
714    totalCellsLen1 = (totalCellsLen1 * 3) / 4;
715    assertEquals(totalCellsLen1, regionServicesForStores.getMemStoreSize());
716    // In memory flush to make a CellArrayMap instead of CSLM. See the overhead diff.
717    totalHeapSize = MutableSegment.DEEP_OVERHEAD + CellArrayImmutableSegment.DEEP_OVERHEAD_CAM
718        + 3 * oneCellOnCAHeapSize;
719    assertEquals(totalHeapSize, ((CompactingMemStore)memstore).heapSize());
720
721    int totalCellsLen2 = addRowsByKeys(memstore, keys2);// Adding 3 more cells.
722    long totalHeapSize2 = totalHeapSize + 3 * oneCellOnCSLMHeapSize;
723
724    assertEquals(totalCellsLen1 + totalCellsLen2, regionServicesForStores.getMemStoreSize());
725    assertEquals(totalHeapSize2, ((CompactingMemStore) memstore).heapSize());
726
727    ((MyCompactingMemStore) memstore).disableCompaction();
728    MemStoreSize mss = memstore.getFlushableSize();
729    ((CompactingMemStore)memstore).flushInMemory(); // push keys to pipeline without compaction
730    assertEquals(0, memstore.getSnapshot().getCellsCount());
731    // No change in the cells data size. ie. memstore size. as there is no compaction.
732    assertEquals(totalCellsLen1 + totalCellsLen2, regionServicesForStores.getMemStoreSize());
733    assertEquals(totalHeapSize2 + CellArrayImmutableSegment.DEEP_OVERHEAD_CAM,
734        ((CompactingMemStore) memstore).heapSize());
735
736    int totalCellsLen3 = addRowsByKeys(memstore, keys3);// 3 more cells added
737    assertEquals(totalCellsLen1 + totalCellsLen2 + totalCellsLen3,
738        regionServicesForStores.getMemStoreSize());
739    long totalHeapSize3 = totalHeapSize2 + CellArrayImmutableSegment.DEEP_OVERHEAD_CAM
740        + 3 * oneCellOnCSLMHeapSize;
741    assertEquals(totalHeapSize3, ((CompactingMemStore) memstore).heapSize());
742
743    ((MyCompactingMemStore)memstore).enableCompaction();
744    mss = memstore.getFlushableSize();
745    ((CompactingMemStore)memstore).flushInMemory(); // push keys to pipeline and compact
746    assertEquals(0, memstore.getSnapshot().getCellsCount());
747    // active flushed to pipeline and all 3 segments compacted. Will get rid of duplicated cells.
748    // Out of total 10, only 4 cells are unique
749    totalCellsLen2 = totalCellsLen2 / 3;// 2 out of 3 cells are duplicated
750    totalCellsLen3 = 0;// All duplicated cells.
751    assertEquals(totalCellsLen1 + totalCellsLen2 + totalCellsLen3,
752        regionServicesForStores.getMemStoreSize());
753    // Only 4 unique cells left
754    assertEquals(4 * oneCellOnCAHeapSize + MutableSegment.DEEP_OVERHEAD
755        + CellArrayImmutableSegment.DEEP_OVERHEAD_CAM, ((CompactingMemStore) memstore).heapSize());
756
757    mss = memstore.getFlushableSize();
758    MemStoreSnapshot snapshot = memstore.snapshot(); // push keys to snapshot
759    // simulate flusher
760    region.decrMemStoreSize(mss.getDataSize(), mss.getHeapSize(), mss.getOffHeapSize(),
761      mss.getCellsCount());
762    ImmutableSegment s = memstore.getSnapshot();
763    assertEquals(4, s.getCellsCount());
764    assertEquals(0, regionServicesForStores.getMemStoreSize());
765
766    memstore.clearSnapshot(snapshot.getId());
767  }
768
769  @Test
770  public void testMagicCompaction3Buckets() throws IOException {
771
772    MemoryCompactionPolicy compactionType = MemoryCompactionPolicy.ADAPTIVE;
773    memstore.getConfiguration().set(CompactingMemStore.COMPACTING_MEMSTORE_TYPE_KEY,
774        String.valueOf(compactionType));
775    memstore.getConfiguration().setDouble(
776        AdaptiveMemStoreCompactionStrategy.ADAPTIVE_COMPACTION_THRESHOLD_KEY, 0.45);
777    memstore.getConfiguration().setInt(
778        AdaptiveMemStoreCompactionStrategy.COMPACTING_MEMSTORE_THRESHOLD_KEY, 2);
779    memstore.getConfiguration().setInt(CompactingMemStore.IN_MEMORY_FLUSH_THRESHOLD_FACTOR_KEY, 1);
780    ((MyCompactingMemStore) memstore).initiateType(compactionType, memstore.getConfiguration());
781
782    String[] keys1 = { "A", "B", "D" };
783    String[] keys2 = { "A" };
784    String[] keys3 = { "A", "A", "B", "C" };
785    String[] keys4 = { "D", "B", "B" };
786
787    int totalCellsLen1 = addRowsByKeys(memstore, keys1);// Adding 3 cells.
788    int oneCellOnCSLMHeapSize = 120;
789    assertEquals(totalCellsLen1, region.getMemStoreDataSize());
790    long totalHeapSize = MutableSegment.DEEP_OVERHEAD + 3 * oneCellOnCSLMHeapSize;
791    assertEquals(totalHeapSize, memstore.heapSize());
792
793    ((CompactingMemStore)memstore).flushInMemory(); // push keys to pipeline - flatten
794    assertEquals(3, ((CompactingMemStore) memstore).getImmutableSegments().getNumOfCells());
795    assertEquals(1.0,
796        ((CompactingMemStore) memstore).getImmutableSegments().getEstimatedUniquesFrac(), 0);
797    assertEquals(0, memstore.getSnapshot().getCellsCount());
798
799    addRowsByKeys(memstore, keys2);// Adding 1 more cell - flatten.
800    ((CompactingMemStore)memstore).flushInMemory(); // push keys to pipeline without compaction
801    assertEquals(4, ((CompactingMemStore) memstore).getImmutableSegments().getNumOfCells());
802    assertEquals(1.0,
803        ((CompactingMemStore) memstore).getImmutableSegments().getEstimatedUniquesFrac(), 0);
804    assertEquals(0, memstore.getSnapshot().getCellsCount());
805
806    addRowsByKeys(memstore, keys3);// Adding 4 more cells - merge.
807    ((CompactingMemStore)memstore).flushInMemory(); // push keys to pipeline without compaction
808    assertEquals(8, ((CompactingMemStore) memstore).getImmutableSegments().getNumOfCells());
809    assertEquals((4.0 / 8.0),
810        ((CompactingMemStore) memstore).getImmutableSegments().getEstimatedUniquesFrac(), 0);
811    assertEquals(0, memstore.getSnapshot().getCellsCount());
812
813    addRowsByKeys(memstore, keys4);// 3 more cells added - compact (or not)
814    ((CompactingMemStore)memstore).flushInMemory(); // push keys to pipeline and compact
815    int numCells = ((CompactingMemStore) memstore).getImmutableSegments().getNumOfCells();
816    assertTrue(4 == numCells || 11 == numCells);
817    assertEquals(0, memstore.getSnapshot().getCellsCount());
818
819    MemStoreSize mss = memstore.getFlushableSize();
820    MemStoreSnapshot snapshot = memstore.snapshot(); // push keys to snapshot
821    // simulate flusher
822    region.decrMemStoreSize(mss);
823    ImmutableSegment s = memstore.getSnapshot();
824    numCells = s.getCellsCount();
825    assertTrue(4 == numCells || 11 == numCells);
826    assertEquals(0, regionServicesForStores.getMemStoreSize());
827
828    memstore.clearSnapshot(snapshot.getId());
829  }
830
831  protected int addRowsByKeys(final AbstractMemStore hmc, String[] keys) {
832    byte[] fam = Bytes.toBytes("testfamily");
833    byte[] qf = Bytes.toBytes("testqualifier");
834    long size = hmc.getActive().getDataSize();
835    long heapOverhead = hmc.getActive().getHeapSize();
836    int cellsCount = hmc.getActive().getCellsCount();
837    int totalLen = 0;
838    for (int i = 0; i < keys.length; i++) {
839      long timestamp = System.currentTimeMillis();
840      Threads.sleep(1); // to make sure each kv gets a different ts
841      byte[] row = Bytes.toBytes(keys[i]);
842      byte[] val = Bytes.toBytes(keys[i] + i);
843      KeyValue kv = new KeyValue(row, fam, qf, timestamp, val);
844      totalLen += kv.getLength();
845      hmc.add(kv, null);
846      LOG.debug("added kv: " + kv.getKeyString() + ", timestamp:" + kv.getTimestamp());
847    }
848    regionServicesForStores.addMemStoreSize(hmc.getActive().getDataSize() - size,
849      hmc.getActive().getHeapSize() - heapOverhead, 0,
850      hmc.getActive().getCellsCount() - cellsCount);
851    return totalLen;
852  }
853
854  // for controlling the val size when adding a new cell
855  protected int addRowsByKeys(final AbstractMemStore hmc, String[] keys, byte[] val) {
856    byte[] fam = Bytes.toBytes("testfamily");
857    byte[] qf = Bytes.toBytes("testqualifier");
858    long size = hmc.getActive().getDataSize();
859    long heapOverhead = hmc.getActive().getHeapSize();
860    int cellsCount = hmc.getActive().getCellsCount();
861    int totalLen = 0;
862    for (int i = 0; i < keys.length; i++) {
863      long timestamp = System.currentTimeMillis();
864      Threads.sleep(1); // to make sure each kv gets a different ts
865      byte[] row = Bytes.toBytes(keys[i]);
866      KeyValue kv = new KeyValue(row, fam, qf, timestamp, val);
867      totalLen += kv.getLength();
868      hmc.add(kv, null);
869      LOG.debug("added kv: " + kv.getKeyString() + ", timestamp:" + kv.getTimestamp());
870    }
871    regionServicesForStores.addMemStoreSize(hmc.getActive().getDataSize() - size,
872      hmc.getActive().getHeapSize() - heapOverhead, 0, cellsCount);
873    return totalLen;
874  }
875
876  private class EnvironmentEdgeForMemstoreTest implements EnvironmentEdge {
877    long t = 1234;
878
879    @Override
880    public long currentTime() {
881            return t;
882        }
883      public void setCurrentTimeMillis(long t) {
884        this.t = t;
885      }
886    }
887
888  static protected class MyCompactingMemStore extends CompactingMemStore {
889
890    public MyCompactingMemStore(Configuration conf, CellComparator c, HStore store,
891        RegionServicesForStores regionServices, MemoryCompactionPolicy compactionPolicy)
892        throws IOException {
893      super(conf, c, store, regionServices, compactionPolicy);
894    }
895
896    void disableCompaction() {
897      allowCompaction.set(false);
898    }
899
900    void enableCompaction() {
901      allowCompaction.set(true);
902    }
903    void initiateType(MemoryCompactionPolicy compactionType, Configuration conf)
904        throws IllegalArgumentIOException {
905      compactor.initiateCompactionStrategy(compactionType, conf, "CF_TEST");
906    }
907
908  }
909}