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