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.assertFalse;
022import static org.junit.Assert.assertNull;
023import static org.junit.Assert.assertTrue;
024import static org.mockito.Mockito.doReturn;
025import static org.mockito.Mockito.mock;
026import static org.mockito.Mockito.when;
027
028import java.io.IOException;
029import java.util.ArrayList;
030import java.util.List;
031import java.util.Optional;
032import org.apache.hadoop.conf.Configuration;
033import org.apache.hadoop.hbase.HBaseClassTestRule;
034import org.apache.hadoop.hbase.HBaseConfiguration;
035import org.apache.hadoop.hbase.HConstants;
036import org.apache.hadoop.hbase.TableName;
037import org.apache.hadoop.hbase.client.RegionInfo;
038import org.apache.hadoop.hbase.client.RegionInfoBuilder;
039import org.apache.hadoop.hbase.client.TableDescriptor;
040import org.apache.hadoop.hbase.client.TableDescriptorBuilder;
041import org.apache.hadoop.hbase.testclassification.RegionServerTests;
042import org.apache.hadoop.hbase.testclassification.SmallTests;
043import org.apache.hadoop.hbase.util.Bytes;
044import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
045import org.junit.Before;
046import org.junit.ClassRule;
047import org.junit.Test;
048import org.junit.experimental.categories.Category;
049
050@Category({ RegionServerTests.class, SmallTests.class })
051public class TestRegionSplitPolicy {
052
053  @ClassRule
054  public static final HBaseClassTestRule CLASS_RULE =
055    HBaseClassTestRule.forClass(TestRegionSplitPolicy.class);
056
057  private Configuration conf;
058  private HRegion mockRegion;
059  private List<HStore> stores;
060  private static final TableName TABLENAME = TableName.valueOf("t");
061
062  @Before
063  public void setupMocks() {
064    conf = HBaseConfiguration.create();
065    RegionInfo hri = RegionInfoBuilder.newBuilder(TABLENAME).build();
066    mockRegion = mock(HRegion.class);
067    doReturn(hri).when(mockRegion).getRegionInfo();
068    doReturn(true).when(mockRegion).isAvailable();
069    stores = new ArrayList<>();
070    doReturn(stores).when(mockRegion).getStores();
071  }
072
073  @Test
074  public void testForceSplitRegionWithReference() throws IOException {
075    TableDescriptor td = TableDescriptorBuilder.newBuilder(TABLENAME).setMaxFileSize(1024L).build();
076    doReturn(td).when(mockRegion).getTableDescriptor();
077    // Add a store above the requisite size. Should split.
078    HStore mockStore = mock(HStore.class);
079    doReturn(2000L).when(mockStore).getSize();
080    // Act as if there's a reference file or some other reason it can't split.
081    // This should prevent splitting even though it's big enough.
082    doReturn(false).when(mockStore).canSplit();
083    stores.add(mockStore);
084
085    conf.set(HConstants.HBASE_REGION_SPLIT_POLICY_KEY,
086      ConstantSizeRegionSplitPolicy.class.getName());
087    ConstantSizeRegionSplitPolicy policy =
088      (ConstantSizeRegionSplitPolicy) RegionSplitPolicy.create(mockRegion, conf);
089    assertFalse(policy.shouldSplit());
090
091    conf.set(HConstants.HBASE_REGION_SPLIT_POLICY_KEY,
092      IncreasingToUpperBoundRegionSplitPolicy.class.getName());
093    policy = (IncreasingToUpperBoundRegionSplitPolicy) RegionSplitPolicy.create(mockRegion, conf);
094    assertFalse(policy.shouldSplit());
095  }
096
097  @Test
098  public void testIncreasingToUpperBoundRegionSplitPolicy() throws IOException {
099    // Configure IncreasingToUpperBoundRegionSplitPolicy as our split policy
100    conf.set(HConstants.HBASE_REGION_SPLIT_POLICY_KEY,
101      IncreasingToUpperBoundRegionSplitPolicy.class.getName());
102    // Now make it so the mock region has a RegionServerService that will
103    // return 'online regions'.
104    RegionServerServices rss = mock(RegionServerServices.class);
105    final List<HRegion> regions = new ArrayList<>();
106    doReturn(regions).when(rss).getRegions(TABLENAME);
107    when(mockRegion.getRegionServerServices()).thenReturn(rss);
108    // Set max size for this 'table'.
109    long maxSplitSize = 1024L;
110    // Set flush size to 1/8. IncreasingToUpperBoundRegionSplitPolicy
111    // grows by the cube of the number of regions times flushsize each time.
112    long flushSize = maxSplitSize / 8;
113    conf.setLong(HConstants.HREGION_MEMSTORE_FLUSH_SIZE, flushSize);
114    TableDescriptor td = TableDescriptorBuilder.newBuilder(TABLENAME).setMaxFileSize(maxSplitSize)
115      .setMemStoreFlushSize(flushSize).build();
116    doReturn(td).when(mockRegion).getTableDescriptor();
117    // If RegionServerService with no regions in it -- 'online regions' == 0 --
118    // then IncreasingToUpperBoundRegionSplitPolicy should act like a
119    // ConstantSizePolicy
120    IncreasingToUpperBoundRegionSplitPolicy policy =
121      (IncreasingToUpperBoundRegionSplitPolicy) RegionSplitPolicy.create(mockRegion, conf);
122    doConstantSizePolicyTests(policy);
123
124    // Add a store in excess of split size. Because there are "no regions"
125    // on this server -- rss.getOnlineRegions is 0 -- then we should split
126    // like a constantsizeregionsplitpolicy would
127    HStore mockStore = mock(HStore.class);
128    doReturn(2000L).when(mockStore).getSize();
129    doReturn(true).when(mockStore).canSplit();
130    stores.add(mockStore);
131    // It should split
132    assertTrue(policy.shouldSplit());
133
134    // Now test that we increase our split size as online regions for a table
135    // grows. With one region, split size should be flushsize.
136    regions.add(mockRegion);
137    doReturn(flushSize).when(mockStore).getSize();
138    // Should not split since store is flush size.
139    assertFalse(policy.shouldSplit());
140    // Set size of store to be > 2*flush size and we should split
141    doReturn(flushSize * 2 + 1).when(mockStore).getSize();
142    assertTrue(policy.shouldSplit());
143    // Add another region to the 'online regions' on this server and we should
144    // now be no longer be splittable since split size has gone up.
145    regions.add(mockRegion);
146    assertFalse(policy.shouldSplit());
147    // make sure its just over; verify it'll split
148    doReturn((long) (maxSplitSize * 1.25 + 1)).when(mockStore).getSize();
149    assertTrue(policy.shouldSplit());
150
151    // Finally assert that even if loads of regions, we'll split at max size
152    assertWithinJitter(maxSplitSize, policy.getSizeToCheck(1000));
153    // Assert same is true if count of regions is zero.
154    assertWithinJitter(maxSplitSize, policy.getSizeToCheck(0));
155  }
156
157  @Test
158  public void testSteppingSplitPolicyWithRegionReplication() throws IOException {
159    conf.set(HConstants.HBASE_REGION_SPLIT_POLICY_KEY, SteppingSplitPolicy.class.getName());
160
161    RegionServerServices rss = mock(RegionServerServices.class);
162    doReturn(rss).when(mockRegion).getRegionServerServices();
163
164    long maxFileSize = HConstants.DEFAULT_MAX_FILE_SIZE;
165    TableDescriptor td =
166      TableDescriptorBuilder.newBuilder(TABLENAME).setMaxFileSize(maxFileSize).build();
167    doReturn(td).when(mockRegion).getTableDescriptor();
168    assertEquals(td.getMaxFileSize(), maxFileSize);
169
170    List<HStore> storefiles = new ArrayList<>();
171    HStore mockStore = mock(HStore.class);
172    long flushSize = conf.getLong(HConstants.HREGION_MEMSTORE_FLUSH_SIZE,
173      TableDescriptorBuilder.DEFAULT_MEMSTORE_FLUSH_SIZE);
174    long exceedSize = flushSize * 2 + 1;
175    doReturn(exceedSize).when(mockStore).getSize();
176    doReturn(true).when(mockStore).canSplit();
177    storefiles.add(mockStore);
178    doReturn(storefiles).when(mockRegion).getStores();
179
180    List<HRegion> regions = new ArrayList<>();
181    HRegion r1 = mock(HRegion.class);
182    RegionInfo regionInfo1 = mock(RegionInfo.class);
183    doReturn(regionInfo1).when(r1).getRegionInfo();
184    doReturn(RegionInfo.DEFAULT_REPLICA_ID).when(regionInfo1).getReplicaId();
185    HRegion r2 = mock(HRegion.class);
186    RegionInfo regionInfo2 = mock(RegionInfo.class);
187    doReturn(regionInfo2).when(r2).getRegionInfo();
188    doReturn(1).when(regionInfo2).getReplicaId();
189    regions.add(r1);
190    regions.add(r2);
191    doReturn(regions).when(rss).getRegions(td.getTableName());
192
193    SteppingSplitPolicy policy = (SteppingSplitPolicy) RegionSplitPolicy.create(mockRegion, conf);
194    assertTrue(policy.shouldSplit());
195  }
196
197  @Test
198  public void testIsExceedSize() throws IOException {
199    // Configure SteppingAllStoresSizeSplitPolicy as our split policy
200    conf.set(HConstants.HBASE_REGION_SPLIT_POLICY_KEY,
201      ConstantSizeRegionSplitPolicy.class.getName());
202    // Now make it so the mock region has a RegionServerService that will
203    // return 'online regions'.
204    RegionServerServices rss = mock(RegionServerServices.class);
205    final List<HRegion> regions = new ArrayList<>();
206    doReturn(regions).when(rss).getRegions(TABLENAME);
207    when(mockRegion.getRegionServerServices()).thenReturn(rss);
208
209    TableDescriptor td = TableDescriptorBuilder.newBuilder(TABLENAME).build();
210    doReturn(td).when(mockRegion).getTableDescriptor();
211    ConstantSizeRegionSplitPolicy policy =
212      (ConstantSizeRegionSplitPolicy) RegionSplitPolicy.create(mockRegion, conf);
213    regions.add(mockRegion);
214
215    HStore mockStore1 = mock(HStore.class);
216    doReturn(100L).when(mockStore1).getSize();
217    HStore mockStore2 = mock(HStore.class);
218    doReturn(924L).when(mockStore2).getSize();
219    HStore mockStore3 = mock(HStore.class);
220    doReturn(925L).when(mockStore3).getSize();
221
222    // test sum of store's size not greater than sizeToCheck
223    stores.add(mockStore1);
224    stores.add(mockStore2);
225    assertFalse(policy.isExceedSize(1024));
226    stores.clear();
227
228    // test sum of store's size greater than sizeToCheck
229    stores.add(mockStore1);
230    stores.add(mockStore3);
231    assertTrue(policy.isExceedSize(1024));
232  }
233
234  @Test
235  public void testBusyRegionSplitPolicy() throws Exception {
236    doReturn(TableDescriptorBuilder.newBuilder(TABLENAME).build()).when(mockRegion)
237      .getTableDescriptor();
238    conf.set(HConstants.HBASE_REGION_SPLIT_POLICY_KEY, BusyRegionSplitPolicy.class.getName());
239    conf.setLong("hbase.busy.policy.minAge", 1000000L);
240    conf.setFloat("hbase.busy.policy.blockedRequests", 0.1f);
241
242    RegionServerServices rss = mock(RegionServerServices.class);
243    final List<HRegion> regions = new ArrayList<>();
244    doReturn(regions).when(rss).getRegions(TABLENAME);
245    when(mockRegion.getRegionServerServices()).thenReturn(rss);
246    when(mockRegion.getBlockedRequestsCount()).thenReturn(0L);
247    when(mockRegion.getWriteRequestsCount()).thenReturn(0L);
248
249    BusyRegionSplitPolicy policy =
250      (BusyRegionSplitPolicy) RegionSplitPolicy.create(mockRegion, conf);
251
252    when(mockRegion.getBlockedRequestsCount()).thenReturn(10L);
253    when(mockRegion.getWriteRequestsCount()).thenReturn(10L);
254    // Not enough time since region came online
255    assertFalse(policy.shouldSplit());
256
257    // Reset min age for split to zero
258    conf.setLong("hbase.busy.policy.minAge", 0L);
259    // Aggregate over 500 ms periods
260    conf.setLong("hbase.busy.policy.aggWindow", 500L);
261    policy = (BusyRegionSplitPolicy) RegionSplitPolicy.create(mockRegion, conf);
262    long start = EnvironmentEdgeManager.currentTime();
263    when(mockRegion.getBlockedRequestsCount()).thenReturn(10L);
264    when(mockRegion.getWriteRequestsCount()).thenReturn(20L);
265    Thread.sleep(300);
266    assertFalse(policy.shouldSplit());
267    when(mockRegion.getBlockedRequestsCount()).thenReturn(12L);
268    when(mockRegion.getWriteRequestsCount()).thenReturn(30L);
269    Thread.sleep(2);
270    // Enough blocked requests since last time, but aggregate blocked request
271    // rate over last 500 ms is still low, because major portion of the window is constituted
272    // by the previous zero blocked request period which lasted at least 300 ms off last 500 ms.
273    if (EnvironmentEdgeManager.currentTime() - start < 500) {
274      assertFalse(policy.shouldSplit());
275    }
276    when(mockRegion.getBlockedRequestsCount()).thenReturn(14L);
277    when(mockRegion.getWriteRequestsCount()).thenReturn(40L);
278    Thread.sleep(200);
279    assertTrue(policy.shouldSplit());
280  }
281
282  private void assertWithinJitter(long maxSplitSize, long sizeToCheck) {
283    assertTrue("Size greater than lower bound of jitter",
284      (long) (maxSplitSize * 0.75) <= sizeToCheck);
285    assertTrue("Size less than upper bound of jitter", (long) (maxSplitSize * 1.25) >= sizeToCheck);
286  }
287
288  @Test
289  public void testCreateDefault() throws IOException {
290    conf.setLong(HConstants.HREGION_MAX_FILESIZE, 1234L);
291    TableDescriptor td = TableDescriptorBuilder.newBuilder(TABLENAME).build();
292    doReturn(td).when(mockRegion).getTableDescriptor();
293    // Using a default HTD, should pick up the file size from
294    // configuration.
295    ConstantSizeRegionSplitPolicy policy =
296      (ConstantSizeRegionSplitPolicy) RegionSplitPolicy.create(mockRegion, conf);
297    assertWithinJitter(1234L, policy.getDesiredMaxFileSize());
298
299    // If specified in HTD, should use that
300    td = TableDescriptorBuilder.newBuilder(TABLENAME).setMaxFileSize(9999L).build();
301    doReturn(td).when(mockRegion).getTableDescriptor();
302    policy = (ConstantSizeRegionSplitPolicy) RegionSplitPolicy.create(mockRegion, conf);
303    assertWithinJitter(9999L, policy.getDesiredMaxFileSize());
304  }
305
306  /**
307   * Test setting up a customized split policy
308   */
309  @Test
310  public void testCustomPolicy() throws IOException {
311    TableDescriptor td = TableDescriptorBuilder.newBuilder(TABLENAME)
312      .setRegionSplitPolicyClassName(KeyPrefixRegionSplitPolicy.class.getName())
313      .setValue(KeyPrefixRegionSplitPolicy.PREFIX_LENGTH_KEY, "2").build();
314
315    doReturn(td).when(mockRegion).getTableDescriptor();
316
317    HStore mockStore = mock(HStore.class);
318    doReturn(2000L).when(mockStore).getSize();
319    doReturn(true).when(mockStore).canSplit();
320    doReturn(Optional.of(Bytes.toBytes("abcd"))).when(mockStore).getSplitPoint();
321    stores.add(mockStore);
322
323    KeyPrefixRegionSplitPolicy policy =
324      (KeyPrefixRegionSplitPolicy) RegionSplitPolicy.create(mockRegion, conf);
325
326    assertEquals("ab", Bytes.toString(policy.getSplitPoint()));
327  }
328
329  @Test
330  public void testConstantSizePolicy() throws IOException {
331    TableDescriptor td = TableDescriptorBuilder.newBuilder(TABLENAME).setMaxFileSize(1024L).build();
332    doReturn(td).when(mockRegion).getTableDescriptor();
333    ConstantSizeRegionSplitPolicy policy =
334      (ConstantSizeRegionSplitPolicy) RegionSplitPolicy.create(mockRegion, conf);
335    doConstantSizePolicyTests(policy);
336  }
337
338  /**
339   * Run through tests for a ConstantSizeRegionSplitPolicy
340   */
341  private void doConstantSizePolicyTests(final ConstantSizeRegionSplitPolicy policy) {
342    // For no stores, should not split
343    assertFalse(policy.shouldSplit());
344
345    // Add a store above the requisite size. Should split.
346    HStore mockStore = mock(HStore.class);
347    doReturn(2000L).when(mockStore).getSize();
348    doReturn(true).when(mockStore).canSplit();
349    stores.add(mockStore);
350
351    assertTrue(policy.shouldSplit());
352
353    // Act as if there's a reference file or some other reason it can't split.
354    // This should prevent splitting even though it's big enough.
355    doReturn(false).when(mockStore).canSplit();
356    assertFalse(policy.shouldSplit());
357
358    // Reset splittability after above
359    doReturn(true).when(mockStore).canSplit();
360
361    // Set to a small size, should not split
362    doReturn(100L).when(mockStore).getSize();
363    assertFalse(policy.shouldSplit());
364
365    // Clear families we added above
366    stores.clear();
367  }
368
369  @Test
370  public void testGetSplitPoint() throws IOException {
371    TableDescriptor td = TableDescriptorBuilder.newBuilder(TABLENAME).build();
372    doReturn(td).when(mockRegion).getTableDescriptor();
373
374    ConstantSizeRegionSplitPolicy policy =
375      (ConstantSizeRegionSplitPolicy) RegionSplitPolicy.create(mockRegion, conf);
376
377    // For no stores, should not split
378    assertFalse(policy.shouldSplit());
379    assertNull(policy.getSplitPoint());
380
381    // Add a store above the requisite size. Should split.
382    HStore mockStore = mock(HStore.class);
383    doReturn(2000L).when(mockStore).getSize();
384    doReturn(true).when(mockStore).canSplit();
385    doReturn(Optional.of(Bytes.toBytes("store 1 split"))).when(mockStore).getSplitPoint();
386    stores.add(mockStore);
387
388    assertEquals("store 1 split", Bytes.toString(policy.getSplitPoint()));
389
390    // Add a bigger store. The split point should come from that one
391    HStore mockStore2 = mock(HStore.class);
392    doReturn(4000L).when(mockStore2).getSize();
393    doReturn(true).when(mockStore2).canSplit();
394    doReturn(Optional.of(Bytes.toBytes("store 2 split"))).when(mockStore2).getSplitPoint();
395    stores.add(mockStore2);
396
397    assertEquals("store 2 split", Bytes.toString(policy.getSplitPoint()));
398  }
399
400  @Test
401  public void testDelimitedKeyPrefixRegionSplitPolicy() throws IOException {
402    TableDescriptor td = TableDescriptorBuilder.newBuilder(TABLENAME)
403      .setRegionSplitPolicyClassName(DelimitedKeyPrefixRegionSplitPolicy.class.getName())
404      .setValue(DelimitedKeyPrefixRegionSplitPolicy.DELIMITER_KEY, ",").build();
405
406    doReturn(td).when(mockRegion).getTableDescriptor();
407    doReturn(stores).when(mockRegion).getStores();
408
409    HStore mockStore = mock(HStore.class);
410    doReturn(2000L).when(mockStore).getSize();
411    doReturn(true).when(mockStore).canSplit();
412    doReturn(Optional.of(Bytes.toBytes("ab,cd"))).when(mockStore).getSplitPoint();
413    stores.add(mockStore);
414
415    DelimitedKeyPrefixRegionSplitPolicy policy =
416      (DelimitedKeyPrefixRegionSplitPolicy) RegionSplitPolicy.create(mockRegion, conf);
417
418    assertEquals("ab", Bytes.toString(policy.getSplitPoint()));
419
420    doReturn(Optional.of(Bytes.toBytes("ijk"))).when(mockStore).getSplitPoint();
421    assertEquals("ijk", Bytes.toString(policy.getSplitPoint()));
422  }
423
424  @Test
425  public void testConstantSizePolicyWithJitter() throws IOException {
426    conf.set(HConstants.HBASE_REGION_SPLIT_POLICY_KEY,
427      ConstantSizeRegionSplitPolicy.class.getName());
428    TableDescriptor td =
429      TableDescriptorBuilder.newBuilder(TABLENAME).setMaxFileSize(Long.MAX_VALUE).build();
430    doReturn(td).when(mockRegion).getTableDescriptor();
431    boolean positiveJitter = false;
432    ConstantSizeRegionSplitPolicy policy = null;
433    while (!positiveJitter) {
434      policy = (ConstantSizeRegionSplitPolicy) RegionSplitPolicy.create(mockRegion, conf);
435      positiveJitter = policy.positiveJitterRate();
436    }
437    // add a store
438    HStore mockStore = mock(HStore.class);
439    doReturn(2000L).when(mockStore).getSize();
440    doReturn(true).when(mockStore).canSplit();
441    stores.add(mockStore);
442    // Jitter shouldn't cause overflow when HTableDescriptor.MAX_FILESIZE set to Long.MAX_VALUE
443    assertFalse(policy.shouldSplit());
444  }
445}