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.assertNotNull;
022import static org.junit.Assert.assertNull;
023import static org.junit.Assert.assertTrue;
024
025import java.io.IOException;
026import java.util.Arrays;
027import java.util.List;
028import org.apache.hadoop.conf.Configuration;
029import org.apache.hadoop.fs.Path;
030import org.apache.hadoop.hbase.HBaseClassTestRule;
031import org.apache.hadoop.hbase.HBaseConfiguration;
032import org.apache.hadoop.hbase.HBaseTestingUtility;
033import org.apache.hadoop.hbase.HColumnDescriptor;
034import org.apache.hadoop.hbase.HConstants;
035import org.apache.hadoop.hbase.HRegionInfo;
036import org.apache.hadoop.hbase.HTableDescriptor;
037import org.apache.hadoop.hbase.MiniHBaseCluster;
038import org.apache.hadoop.hbase.NamespaceDescriptor;
039import org.apache.hadoop.hbase.TableName;
040import org.apache.hadoop.hbase.Waiter;
041import org.apache.hadoop.hbase.client.Admin;
042import org.apache.hadoop.hbase.client.Connection;
043import org.apache.hadoop.hbase.client.ConnectionFactory;
044import org.apache.hadoop.hbase.client.Get;
045import org.apache.hadoop.hbase.client.Put;
046import org.apache.hadoop.hbase.client.Result;
047import org.apache.hadoop.hbase.client.Table;
048import org.apache.hadoop.hbase.testclassification.LargeTests;
049import org.apache.hadoop.hbase.testclassification.RegionServerTests;
050import org.apache.hadoop.hbase.util.Bytes;
051import org.apache.hadoop.hbase.util.JVMClusterUtil;
052import org.apache.hadoop.hbase.util.Pair;
053import org.apache.hadoop.hbase.wal.AbstractFSWALProvider;
054import org.apache.hadoop.hbase.wal.WAL;
055import org.junit.ClassRule;
056import org.junit.Test;
057import org.junit.experimental.categories.Category;
058import org.slf4j.Logger;
059import org.slf4j.LoggerFactory;
060
061import org.apache.hbase.thirdparty.com.google.common.hash.Hashing;
062
063/**
064 * This test verifies the correctness of the Per Column Family flushing strategy
065 */
066@Category({ RegionServerTests.class, LargeTests.class })
067public class TestPerColumnFamilyFlush {
068
069  @ClassRule
070  public static final HBaseClassTestRule CLASS_RULE =
071    HBaseClassTestRule.forClass(TestPerColumnFamilyFlush.class);
072
073  private static final Logger LOG = LoggerFactory.getLogger(TestPerColumnFamilyFlush.class);
074
075  private static final HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
076
077  private static final Path DIR = TEST_UTIL.getDataTestDir("TestHRegion");
078
079  public static final TableName TABLENAME = TableName.valueOf("TestPerColumnFamilyFlush", "t1");
080
081  public static final byte[][] FAMILIES = { Bytes.toBytes("f1"), Bytes.toBytes("f2"),
082    Bytes.toBytes("f3"), Bytes.toBytes("f4"), Bytes.toBytes("f5") };
083
084  public static final byte[] FAMILY1 = FAMILIES[0];
085
086  public static final byte[] FAMILY2 = FAMILIES[1];
087
088  public static final byte[] FAMILY3 = FAMILIES[2];
089
090  private HRegion initHRegion(String callingMethod, Configuration conf) throws IOException {
091    HTableDescriptor htd = new HTableDescriptor(TABLENAME);
092    for (byte[] family : FAMILIES) {
093      htd.addFamily(new HColumnDescriptor(family));
094    }
095    HRegionInfo info = new HRegionInfo(TABLENAME, null, null, false);
096    Path path = new Path(DIR, callingMethod);
097    return HBaseTestingUtility.createRegionAndWAL(info, path, conf, htd);
098  }
099
100  // A helper function to create puts.
101  private Put createPut(int familyNum, int putNum) {
102    byte[] qf = Bytes.toBytes("q" + familyNum);
103    byte[] row = Bytes.toBytes("row" + familyNum + "-" + putNum);
104    byte[] val = Bytes.toBytes("val" + familyNum + "-" + putNum);
105    Put p = new Put(row);
106    p.addColumn(FAMILIES[familyNum - 1], qf, val);
107    return p;
108  }
109
110  // A helper function to create puts.
111  private Get createGet(int familyNum, int putNum) {
112    byte[] row = Bytes.toBytes("row" + familyNum + "-" + putNum);
113    return new Get(row);
114  }
115
116  // A helper function to verify edits.
117  void verifyEdit(int familyNum, int putNum, Table table) throws IOException {
118    Result r = table.get(createGet(familyNum, putNum));
119    byte[] family = FAMILIES[familyNum - 1];
120    byte[] qf = Bytes.toBytes("q" + familyNum);
121    byte[] val = Bytes.toBytes("val" + familyNum + "-" + putNum);
122    assertNotNull(("Missing Put#" + putNum + " for CF# " + familyNum), r.getFamilyMap(family));
123    assertNotNull(("Missing Put#" + putNum + " for CF# " + familyNum),
124      r.getFamilyMap(family).get(qf));
125    assertTrue(("Incorrect value for Put#" + putNum + " for CF# " + familyNum),
126      Arrays.equals(r.getFamilyMap(family).get(qf), val));
127  }
128
129  @Test
130  public void testSelectiveFlushWhenEnabled() throws IOException {
131    // Set up the configuration, use new one to not conflict with minicluster in other tests
132    Configuration conf = new HBaseTestingUtility().getConfiguration();
133    conf.setLong(HConstants.HREGION_MEMSTORE_FLUSH_SIZE, 200 * 1024);
134    conf.set(FlushPolicyFactory.HBASE_FLUSH_POLICY_KEY, FlushAllLargeStoresPolicy.class.getName());
135    conf.setLong(FlushLargeStoresPolicy.HREGION_COLUMNFAMILY_FLUSH_SIZE_LOWER_BOUND_MIN, 40 * 1024);
136    // Intialize the region
137    HRegion region = initHRegion("testSelectiveFlushWithDataCompaction", conf);
138    // Add 1200 entries for CF1, 100 for CF2 and 50 for CF3
139    for (int i = 1; i <= 1200; i++) {
140      region.put(createPut(1, i));
141
142      if (i <= 100) {
143        region.put(createPut(2, i));
144        if (i <= 50) {
145          region.put(createPut(3, i));
146        }
147      }
148    }
149
150    long totalMemstoreSize = region.getMemStoreDataSize();
151
152    // Find the smallest LSNs for edits wrt to each CF.
153    long smallestSeqCF1 = region.getOldestSeqIdOfStore(FAMILY1);
154    long smallestSeqCF2 = region.getOldestSeqIdOfStore(FAMILY2);
155    long smallestSeqCF3 = region.getOldestSeqIdOfStore(FAMILY3);
156
157    // Find the sizes of the memstores of each CF.
158    MemStoreSize cf1MemstoreSize = region.getStore(FAMILY1).getMemStoreSize();
159    MemStoreSize cf2MemstoreSize = region.getStore(FAMILY2).getMemStoreSize();
160    MemStoreSize cf3MemstoreSize = region.getStore(FAMILY3).getMemStoreSize();
161
162    // Get the overall smallest LSN in the region's memstores.
163    long smallestSeqInRegionCurrentMemstore =
164      getWAL(region).getEarliestMemStoreSeqNum(region.getRegionInfo().getEncodedNameAsBytes());
165
166    // The overall smallest LSN in the region's memstores should be the same as
167    // the LSN of the smallest edit in CF1
168    assertEquals(smallestSeqCF1, smallestSeqInRegionCurrentMemstore);
169
170    // Some other sanity checks.
171    assertTrue(smallestSeqCF1 < smallestSeqCF2);
172    assertTrue(smallestSeqCF2 < smallestSeqCF3);
173    assertTrue(cf1MemstoreSize.getDataSize() > 0);
174    assertTrue(cf2MemstoreSize.getDataSize() > 0);
175    assertTrue(cf3MemstoreSize.getDataSize() > 0);
176
177    // The total memstore size should be the same as the sum of the sizes of
178    // memstores of CF1, CF2 and CF3.
179    assertEquals(totalMemstoreSize, cf1MemstoreSize.getDataSize() + cf2MemstoreSize.getDataSize()
180      + cf3MemstoreSize.getDataSize());
181
182    // Flush!
183    region.flush(false);
184
185    // Will use these to check if anything changed.
186    MemStoreSize oldCF2MemstoreSize = cf2MemstoreSize;
187    MemStoreSize oldCF3MemstoreSize = cf3MemstoreSize;
188
189    // Recalculate everything
190    cf1MemstoreSize = region.getStore(FAMILY1).getMemStoreSize();
191    cf2MemstoreSize = region.getStore(FAMILY2).getMemStoreSize();
192    cf3MemstoreSize = region.getStore(FAMILY3).getMemStoreSize();
193    totalMemstoreSize = region.getMemStoreDataSize();
194    smallestSeqInRegionCurrentMemstore =
195      getWAL(region).getEarliestMemStoreSeqNum(region.getRegionInfo().getEncodedNameAsBytes());
196
197    // We should have cleared out only CF1, since we chose the flush thresholds
198    // and number of puts accordingly.
199    assertEquals(0, cf1MemstoreSize.getDataSize());
200    assertEquals(MutableSegment.DEEP_OVERHEAD, cf1MemstoreSize.getHeapSize());
201    // Nothing should have happened to CF2, ...
202    assertEquals(cf2MemstoreSize, oldCF2MemstoreSize);
203    // ... or CF3
204    assertEquals(cf3MemstoreSize, oldCF3MemstoreSize);
205    // Now the smallest LSN in the region should be the same as the smallest
206    // LSN in the memstore of CF2.
207    assertEquals(smallestSeqInRegionCurrentMemstore, smallestSeqCF2);
208    // Of course, this should hold too.
209    assertEquals(totalMemstoreSize, cf2MemstoreSize.getDataSize() + cf3MemstoreSize.getDataSize());
210
211    // Now add more puts (mostly for CF2), so that we only flush CF2 this time.
212    for (int i = 1200; i < 2400; i++) {
213      region.put(createPut(2, i));
214
215      // Add only 100 puts for CF3
216      if (i - 1200 < 100) {
217        region.put(createPut(3, i));
218      }
219    }
220
221    // How much does the CF3 memstore occupy? Will be used later.
222    oldCF3MemstoreSize = region.getStore(FAMILY3).getMemStoreSize();
223
224    // Flush again
225    region.flush(false);
226
227    // Recalculate everything
228    cf1MemstoreSize = region.getStore(FAMILY1).getMemStoreSize();
229    cf2MemstoreSize = region.getStore(FAMILY2).getMemStoreSize();
230    cf3MemstoreSize = region.getStore(FAMILY3).getMemStoreSize();
231    totalMemstoreSize = region.getMemStoreDataSize();
232    smallestSeqInRegionCurrentMemstore =
233      getWAL(region).getEarliestMemStoreSeqNum(region.getRegionInfo().getEncodedNameAsBytes());
234
235    // CF1 and CF2, both should be absent.
236    assertEquals(0, cf1MemstoreSize.getDataSize());
237    assertEquals(MutableSegment.DEEP_OVERHEAD, cf1MemstoreSize.getHeapSize());
238    assertEquals(0, cf2MemstoreSize.getDataSize());
239    assertEquals(MutableSegment.DEEP_OVERHEAD, cf2MemstoreSize.getHeapSize());
240    // CF3 shouldn't have been touched.
241    assertEquals(cf3MemstoreSize, oldCF3MemstoreSize);
242    assertEquals(totalMemstoreSize, cf3MemstoreSize.getDataSize());
243
244    // What happens when we hit the memstore limit, but we are not able to find
245    // any Column Family above the threshold?
246    // In that case, we should flush all the CFs.
247
248    // Clearing the existing memstores.
249    region.flush(true);
250
251    // The memstore limit is 200*1024 and the column family flush threshold is
252    // around 50*1024. We try to just hit the memstore limit with each CF's
253    // memstore being below the CF flush threshold.
254    for (int i = 1; i <= 300; i++) {
255      region.put(createPut(1, i));
256      region.put(createPut(2, i));
257      region.put(createPut(3, i));
258      region.put(createPut(4, i));
259      region.put(createPut(5, i));
260    }
261
262    region.flush(false);
263
264    // Since we won't find any CF above the threshold, and hence no specific
265    // store to flush, we should flush all the memstores.
266    assertEquals(0, region.getMemStoreDataSize());
267    HBaseTestingUtility.closeRegionAndWAL(region);
268  }
269
270  @Test
271  public void testSelectiveFlushWhenNotEnabled() throws IOException {
272    // Set up the configuration, use new one to not conflict with minicluster in other tests
273    Configuration conf = new HBaseTestingUtility().getConfiguration();
274    conf.setLong(HConstants.HREGION_MEMSTORE_FLUSH_SIZE, 200 * 1024);
275    conf.set(FlushPolicyFactory.HBASE_FLUSH_POLICY_KEY, FlushAllStoresPolicy.class.getName());
276
277    // Intialize the HRegion
278    HRegion region = initHRegion("testSelectiveFlushWhenNotEnabled", conf);
279    // Add 1200 entries for CF1, 100 for CF2 and 50 for CF3
280    for (int i = 1; i <= 1200; i++) {
281      region.put(createPut(1, i));
282      if (i <= 100) {
283        region.put(createPut(2, i));
284        if (i <= 50) {
285          region.put(createPut(3, i));
286        }
287      }
288    }
289
290    long totalMemstoreSize = region.getMemStoreDataSize();
291
292    // Find the sizes of the memstores of each CF.
293    MemStoreSize cf1MemstoreSize = region.getStore(FAMILY1).getMemStoreSize();
294    MemStoreSize cf2MemstoreSize = region.getStore(FAMILY2).getMemStoreSize();
295    MemStoreSize cf3MemstoreSize = region.getStore(FAMILY3).getMemStoreSize();
296
297    // Some other sanity checks.
298    assertTrue(cf1MemstoreSize.getDataSize() > 0);
299    assertTrue(cf2MemstoreSize.getDataSize() > 0);
300    assertTrue(cf3MemstoreSize.getDataSize() > 0);
301
302    // The total memstore size should be the same as the sum of the sizes of
303    // memstores of CF1, CF2 and CF3.
304    assertEquals(totalMemstoreSize, cf1MemstoreSize.getDataSize() + cf2MemstoreSize.getDataSize()
305      + cf3MemstoreSize.getDataSize());
306
307    // Flush!
308    region.flush(false);
309
310    cf1MemstoreSize = region.getStore(FAMILY1).getMemStoreSize();
311    cf2MemstoreSize = region.getStore(FAMILY2).getMemStoreSize();
312    cf3MemstoreSize = region.getStore(FAMILY3).getMemStoreSize();
313    totalMemstoreSize = region.getMemStoreDataSize();
314    long smallestSeqInRegionCurrentMemstore =
315      region.getWAL().getEarliestMemStoreSeqNum(region.getRegionInfo().getEncodedNameAsBytes());
316
317    // Everything should have been cleared
318    assertEquals(0, cf1MemstoreSize.getDataSize());
319    assertEquals(MutableSegment.DEEP_OVERHEAD, cf1MemstoreSize.getHeapSize());
320    assertEquals(0, cf2MemstoreSize.getDataSize());
321    assertEquals(MutableSegment.DEEP_OVERHEAD, cf2MemstoreSize.getHeapSize());
322    assertEquals(0, cf3MemstoreSize.getDataSize());
323    assertEquals(MutableSegment.DEEP_OVERHEAD, cf3MemstoreSize.getHeapSize());
324    assertEquals(0, totalMemstoreSize);
325    assertEquals(HConstants.NO_SEQNUM, smallestSeqInRegionCurrentMemstore);
326    HBaseTestingUtility.closeRegionAndWAL(region);
327  }
328
329  // Find the (first) region which has the specified name.
330  private static Pair<HRegion, HRegionServer> getRegionWithName(TableName tableName) {
331    MiniHBaseCluster cluster = TEST_UTIL.getMiniHBaseCluster();
332    List<JVMClusterUtil.RegionServerThread> rsts = cluster.getRegionServerThreads();
333    for (int i = 0; i < cluster.getRegionServerThreads().size(); i++) {
334      HRegionServer hrs = rsts.get(i).getRegionServer();
335      for (HRegion region : hrs.getRegions(tableName)) {
336        return Pair.newPair(region, hrs);
337      }
338    }
339    return null;
340  }
341
342  private void doTestLogReplay() throws Exception {
343    Configuration conf = TEST_UTIL.getConfiguration();
344    conf.setLong(HConstants.HREGION_MEMSTORE_FLUSH_SIZE, 10000);
345    // Carefully chosen limits so that the memstore just flushes when we're done
346    conf.set(FlushPolicyFactory.HBASE_FLUSH_POLICY_KEY, FlushAllLargeStoresPolicy.class.getName());
347    conf.setLong(FlushLargeStoresPolicy.HREGION_COLUMNFAMILY_FLUSH_SIZE_LOWER_BOUND_MIN, 2500);
348    final int numRegionServers = 4;
349    try {
350      TEST_UTIL.startMiniCluster(numRegionServers);
351      TEST_UTIL.getAdmin()
352        .createNamespace(NamespaceDescriptor.create(TABLENAME.getNamespaceAsString()).build());
353      Table table = TEST_UTIL.createTable(TABLENAME, FAMILIES);
354      HTableDescriptor htd = table.getTableDescriptor();
355
356      for (byte[] family : FAMILIES) {
357        if (!htd.hasFamily(family)) {
358          htd.addFamily(new HColumnDescriptor(family));
359        }
360      }
361
362      // Add 100 edits for CF1, 20 for CF2, 20 for CF3.
363      // These will all be interleaved in the log.
364      for (int i = 1; i <= 80; i++) {
365        table.put(createPut(1, i));
366        if (i <= 10) {
367          table.put(createPut(2, i));
368          table.put(createPut(3, i));
369        }
370      }
371      Thread.sleep(1000);
372
373      Pair<HRegion, HRegionServer> desiredRegionAndServer = getRegionWithName(TABLENAME);
374      HRegion desiredRegion = desiredRegionAndServer.getFirst();
375      assertTrue("Could not find a region which hosts the new region.", desiredRegion != null);
376
377      // Flush the region selectively.
378      desiredRegion.flush(false);
379
380      long totalMemstoreSize;
381      long cf1MemstoreSize, cf2MemstoreSize, cf3MemstoreSize;
382      totalMemstoreSize = desiredRegion.getMemStoreDataSize();
383
384      // Find the sizes of the memstores of each CF.
385      cf1MemstoreSize = desiredRegion.getStore(FAMILY1).getMemStoreSize().getDataSize();
386      cf2MemstoreSize = desiredRegion.getStore(FAMILY2).getMemStoreSize().getDataSize();
387      cf3MemstoreSize = desiredRegion.getStore(FAMILY3).getMemStoreSize().getDataSize();
388
389      // CF1 Should have been flushed
390      assertEquals(0, cf1MemstoreSize);
391      // CF2 and CF3 shouldn't have been flushed.
392      // TODO: This test doesn't allow for this case:
393      // " Since none of the CFs were above the size, flushing all."
394      // i.e. a flush happens before we get to here and its a flush-all.
395      assertTrue(cf2MemstoreSize >= 0);
396      assertTrue(cf3MemstoreSize >= 0);
397      assertEquals(totalMemstoreSize, cf2MemstoreSize + cf3MemstoreSize);
398
399      // Wait for the RS report to go across to the master, so that the master
400      // is aware of which sequence ids have been flushed, before we kill the RS.
401      // If in production, the RS dies before the report goes across, we will
402      // safely replay all the edits.
403      Thread.sleep(2000);
404
405      // Abort the region server where we have the region hosted.
406      HRegionServer rs = desiredRegionAndServer.getSecond();
407      rs.abort("testing");
408
409      // The aborted region server's regions will be eventually assigned to some
410      // other region server, and the get RPC call (inside verifyEdit()) will
411      // retry for some time till the regions come back up.
412
413      // Verify that all the edits are safe.
414      for (int i = 1; i <= 80; i++) {
415        verifyEdit(1, i, table);
416        if (i <= 10) {
417          verifyEdit(2, i, table);
418          verifyEdit(3, i, table);
419        }
420      }
421    } finally {
422      TEST_UTIL.shutdownMiniCluster();
423    }
424  }
425
426  // Test Log Replay with Distributed log split on.
427  @Test
428  public void testLogReplayWithDistributedLogSplit() throws Exception {
429    doTestLogReplay();
430  }
431
432  private WAL getWAL(Region region) {
433    return ((HRegion) region).getWAL();
434  }
435
436  private int getNumRolledLogFiles(Region region) {
437    return AbstractFSWALProvider.getNumRolledLogFiles(getWAL(region));
438  }
439
440  /**
441   * When a log roll is about to happen, we do a flush of the regions who will be affected by the
442   * log roll. These flushes cannot be a selective flushes, otherwise we cannot roll the logs. This
443   * test ensures that we do a full-flush in that scenario.
444   */
445  @Test
446  public void testFlushingWhenLogRolling() throws Exception {
447    TableName tableName = TableName.valueOf("testFlushingWhenLogRolling");
448    Configuration conf = TEST_UTIL.getConfiguration();
449    conf.setLong(HConstants.HREGION_MEMSTORE_FLUSH_SIZE, 128 * 1024 * 1024);
450    conf.set(FlushPolicyFactory.HBASE_FLUSH_POLICY_KEY, FlushAllLargeStoresPolicy.class.getName());
451    long cfFlushSizeLowerBound = 2048;
452    conf.setLong(FlushLargeStoresPolicy.HREGION_COLUMNFAMILY_FLUSH_SIZE_LOWER_BOUND_MIN,
453      cfFlushSizeLowerBound);
454
455    // One hour, prevent periodic rolling
456    conf.setLong("hbase.regionserver.logroll.period", 60L * 60 * 1000);
457    // prevent rolling by size
458    conf.setLong("hbase.regionserver.hlog.blocksize", 128L * 1024 * 1024);
459    // Make it 10 as max logs before a flush comes on.
460    final int maxLogs = 10;
461    conf.setInt("hbase.regionserver.maxlogs", maxLogs);
462
463    final int numRegionServers = 1;
464    TEST_UTIL.startMiniCluster(numRegionServers);
465    try {
466      Table table = TEST_UTIL.createTable(tableName, FAMILIES);
467      // Force flush the namespace table so edits to it are not hanging around as oldest
468      // edits. Otherwise, below, when we make maximum number of WAL files, then it will be
469      // the namespace region that is flushed and not the below 'desiredRegion'.
470      try (Admin admin = TEST_UTIL.getConnection().getAdmin()) {
471        admin.flush(TableName.NAMESPACE_TABLE_NAME);
472      }
473      Pair<HRegion, HRegionServer> desiredRegionAndServer = getRegionWithName(tableName);
474      final HRegion desiredRegion = desiredRegionAndServer.getFirst();
475      assertTrue("Could not find a region which hosts the new region.", desiredRegion != null);
476      LOG.info("Writing to region=" + desiredRegion);
477
478      // Add one row for both CFs.
479      for (int i = 1; i <= 3; i++) {
480        table.put(createPut(i, 0));
481      }
482      // Now only add row to CF1, make sure when we force a flush, CF1 is larger than the lower
483      // bound and CF2 and CF3 are smaller than the lower bound.
484      for (int i = 0; i < maxLogs; i++) {
485        for (int j = 0; j < 100; j++) {
486          table.put(createPut(1, i * 100 + j));
487        }
488        // Roll the WAL. The log file count is less than maxLogs so no flush is triggered.
489        int currentNumRolledLogFiles = getNumRolledLogFiles(desiredRegion);
490        assertNull(getWAL(desiredRegion).rollWriter());
491        TEST_UTIL.waitFor(60000,
492          () -> getNumRolledLogFiles(desiredRegion) > currentNumRolledLogFiles);
493      }
494      assertEquals(maxLogs, getNumRolledLogFiles(desiredRegion));
495      assertTrue(
496        desiredRegion.getStore(FAMILY1).getMemStoreSize().getHeapSize() > cfFlushSizeLowerBound);
497      assertTrue(
498        desiredRegion.getStore(FAMILY2).getMemStoreSize().getHeapSize() < cfFlushSizeLowerBound);
499      assertTrue(
500        desiredRegion.getStore(FAMILY3).getMemStoreSize().getHeapSize() < cfFlushSizeLowerBound);
501      table.put(createPut(1, 12345678));
502      // Make numRolledLogFiles greater than maxLogs
503      desiredRegionAndServer.getSecond().getWalRoller().requestRollAll();
504      // Wait for some time till the flush caused by log rolling happens.
505      TEST_UTIL.waitFor(30000, new Waiter.ExplainingPredicate<Exception>() {
506
507        @Override
508        public boolean evaluate() throws Exception {
509          return desiredRegion.getMemStoreDataSize() == 0;
510        }
511
512        @Override
513        public String explainFailure() throws Exception {
514          long memstoreSize = desiredRegion.getMemStoreDataSize();
515          if (memstoreSize > 0) {
516            return "Still have unflushed entries in memstore, memstore size is " + memstoreSize;
517          }
518          return "Unknown";
519        }
520      });
521      LOG.info("Finished waiting on flush after too many WALs...");
522      // Individual families should have been flushed.
523      assertEquals(MutableSegment.DEEP_OVERHEAD,
524        desiredRegion.getStore(FAMILY1).getMemStoreSize().getHeapSize());
525      assertEquals(MutableSegment.DEEP_OVERHEAD,
526        desiredRegion.getStore(FAMILY2).getMemStoreSize().getHeapSize());
527      assertEquals(MutableSegment.DEEP_OVERHEAD,
528        desiredRegion.getStore(FAMILY3).getMemStoreSize().getHeapSize());
529      // let WAL cleanOldLogs
530      assertNull(getWAL(desiredRegion).rollWriter(true));
531      TEST_UTIL.waitFor(60000, () -> getNumRolledLogFiles(desiredRegion) < maxLogs);
532    } finally {
533      TEST_UTIL.shutdownMiniCluster();
534    }
535  }
536
537  private void doPut(Table table, long memstoreFlushSize) throws IOException, InterruptedException {
538    Region region = getRegionWithName(table.getName()).getFirst();
539    // cf1 4B per row, cf2 40B per row and cf3 400B per row
540    byte[] qf = Bytes.toBytes("qf");
541    for (int i = 0; i < 10000; i++) {
542      Put put = new Put(Bytes.toBytes("row-" + i));
543      byte[] value1 = new byte[100];
544      Bytes.random(value1);
545      put.addColumn(FAMILY1, qf, value1);
546      byte[] value2 = new byte[200];
547      Bytes.random(value2);
548      put.addColumn(FAMILY2, qf, value2);
549      byte[] value3 = new byte[400];
550      Bytes.random(value3);
551      put.addColumn(FAMILY3, qf, value3);
552      table.put(put);
553      // slow down to let regionserver flush region.
554      while (region.getMemStoreHeapSize() > memstoreFlushSize) {
555        Thread.sleep(100);
556      }
557    }
558  }
559
560  // Under the same write load, small stores should have less store files when
561  // percolumnfamilyflush enabled.
562  @Test
563  public void testCompareStoreFileCount() throws Exception {
564    long memstoreFlushSize = 1024L * 1024;
565    Configuration conf = TEST_UTIL.getConfiguration();
566    conf.setLong(HConstants.HREGION_MEMSTORE_FLUSH_SIZE, memstoreFlushSize);
567    conf.set(FlushPolicyFactory.HBASE_FLUSH_POLICY_KEY, FlushAllStoresPolicy.class.getName());
568    conf.setInt(HStore.BLOCKING_STOREFILES_KEY, 10000);
569    conf.set(HConstants.HBASE_REGION_SPLIT_POLICY_KEY,
570      ConstantSizeRegionSplitPolicy.class.getName());
571
572    HTableDescriptor htd = new HTableDescriptor(TABLENAME);
573    htd.setCompactionEnabled(false);
574    htd.addFamily(new HColumnDescriptor(FAMILY1));
575    htd.addFamily(new HColumnDescriptor(FAMILY2));
576    htd.addFamily(new HColumnDescriptor(FAMILY3));
577
578    LOG.info("==============Test with selective flush disabled===============");
579    int cf1StoreFileCount = -1;
580    int cf2StoreFileCount = -1;
581    int cf3StoreFileCount = -1;
582    int cf1StoreFileCount1 = -1;
583    int cf2StoreFileCount1 = -1;
584    int cf3StoreFileCount1 = -1;
585    try {
586      TEST_UTIL.startMiniCluster(1);
587      TEST_UTIL.getAdmin()
588        .createNamespace(NamespaceDescriptor.create(TABLENAME.getNamespaceAsString()).build());
589      TEST_UTIL.getAdmin().createTable(htd);
590      TEST_UTIL.waitTableAvailable(TABLENAME);
591      Connection conn = ConnectionFactory.createConnection(conf);
592      Table table = conn.getTable(TABLENAME);
593      doPut(table, memstoreFlushSize);
594      table.close();
595      conn.close();
596
597      Region region = getRegionWithName(TABLENAME).getFirst();
598      cf1StoreFileCount = region.getStore(FAMILY1).getStorefilesCount();
599      cf2StoreFileCount = region.getStore(FAMILY2).getStorefilesCount();
600      cf3StoreFileCount = region.getStore(FAMILY3).getStorefilesCount();
601    } finally {
602      TEST_UTIL.shutdownMiniCluster();
603    }
604
605    LOG.info("==============Test with selective flush enabled===============");
606    conf.set(FlushPolicyFactory.HBASE_FLUSH_POLICY_KEY, FlushAllLargeStoresPolicy.class.getName());
607    // default value of per-cf flush lower bound is too big, set to a small enough value
608    conf.setLong(FlushLargeStoresPolicy.HREGION_COLUMNFAMILY_FLUSH_SIZE_LOWER_BOUND_MIN, 0);
609    try {
610      TEST_UTIL.startMiniCluster(1);
611      TEST_UTIL.getAdmin()
612        .createNamespace(NamespaceDescriptor.create(TABLENAME.getNamespaceAsString()).build());
613      TEST_UTIL.getAdmin().createTable(htd);
614      Connection conn = ConnectionFactory.createConnection(conf);
615      Table table = conn.getTable(TABLENAME);
616      doPut(table, memstoreFlushSize);
617      table.close();
618      conn.close();
619
620      Region region = getRegionWithName(TABLENAME).getFirst();
621      cf1StoreFileCount1 = region.getStore(FAMILY1).getStorefilesCount();
622      cf2StoreFileCount1 = region.getStore(FAMILY2).getStorefilesCount();
623      cf3StoreFileCount1 = region.getStore(FAMILY3).getStorefilesCount();
624    } finally {
625      TEST_UTIL.shutdownMiniCluster();
626    }
627
628    LOG.info("disable selective flush: " + Bytes.toString(FAMILY1) + "=>" + cf1StoreFileCount + ", "
629      + Bytes.toString(FAMILY2) + "=>" + cf2StoreFileCount + ", " + Bytes.toString(FAMILY3) + "=>"
630      + cf3StoreFileCount);
631    LOG.info("enable selective flush: " + Bytes.toString(FAMILY1) + "=>" + cf1StoreFileCount1 + ", "
632      + Bytes.toString(FAMILY2) + "=>" + cf2StoreFileCount1 + ", " + Bytes.toString(FAMILY3) + "=>"
633      + cf3StoreFileCount1);
634    // small CF will have less store files.
635    assertTrue(cf1StoreFileCount1 < cf1StoreFileCount);
636    assertTrue(cf2StoreFileCount1 < cf2StoreFileCount);
637  }
638
639  public static void main(String[] args) throws Exception {
640    int numRegions = Integer.parseInt(args[0]);
641    long numRows = Long.parseLong(args[1]);
642
643    HTableDescriptor htd = new HTableDescriptor(TABLENAME);
644    htd.setMaxFileSize(10L * 1024 * 1024 * 1024);
645    htd.setValue(HTableDescriptor.SPLIT_POLICY, ConstantSizeRegionSplitPolicy.class.getName());
646    htd.addFamily(new HColumnDescriptor(FAMILY1));
647    htd.addFamily(new HColumnDescriptor(FAMILY2));
648    htd.addFamily(new HColumnDescriptor(FAMILY3));
649
650    Configuration conf = HBaseConfiguration.create();
651    Connection conn = ConnectionFactory.createConnection(conf);
652    Admin admin = conn.getAdmin();
653    if (admin.tableExists(TABLENAME)) {
654      admin.disableTable(TABLENAME);
655      admin.deleteTable(TABLENAME);
656    }
657    if (numRegions >= 3) {
658      byte[] startKey = new byte[16];
659      byte[] endKey = new byte[16];
660      Arrays.fill(endKey, (byte) 0xFF);
661      admin.createTable(htd, startKey, endKey, numRegions);
662    } else {
663      admin.createTable(htd);
664    }
665    admin.close();
666
667    Table table = conn.getTable(TABLENAME);
668    byte[] qf = Bytes.toBytes("qf");
669    byte[] value1 = new byte[16];
670    byte[] value2 = new byte[256];
671    byte[] value3 = new byte[4096];
672    for (long i = 0; i < numRows; i++) {
673      Put put = new Put(Hashing.md5().hashLong(i).asBytes());
674      Bytes.random(value1);
675      Bytes.random(value2);
676      Bytes.random(value3);
677      put.addColumn(FAMILY1, qf, value1);
678      put.addColumn(FAMILY2, qf, value2);
679      put.addColumn(FAMILY3, qf, value3);
680      table.put(put);
681      if (i % 10000 == 0) {
682        LOG.info(i + " rows put");
683      }
684    }
685    table.close();
686    conn.close();
687  }
688}