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.util;
019
020import static org.junit.jupiter.api.Assertions.assertEquals;
021import static org.junit.jupiter.api.Assertions.assertTrue;
022
023import java.util.ArrayList;
024import java.util.Collection;
025import java.util.Comparator;
026import java.util.List;
027import java.util.SortedSet;
028import java.util.UUID;
029import org.apache.hadoop.hbase.HBaseTestingUtil;
030import org.apache.hadoop.hbase.testclassification.MiscTests;
031import org.apache.hadoop.hbase.testclassification.SmallTests;
032import org.junit.jupiter.api.Tag;
033import org.junit.jupiter.api.Test;
034import org.slf4j.Logger;
035import org.slf4j.LoggerFactory;
036
037import org.apache.hbase.thirdparty.com.google.common.collect.ComparisonChain;
038import org.apache.hbase.thirdparty.com.google.common.collect.Multimap;
039
040@Tag(MiscTests.TAG)
041@Tag(SmallTests.TAG)
042public class TestRegionSplitCalculator {
043
044  private static final Logger LOG = LoggerFactory.getLogger(TestRegionSplitCalculator.class);
045  public static final HBaseTestingUtil TEST_UTIL = new HBaseTestingUtil();
046
047  /**
048   * This is range uses a user specified start and end keys. It also has an extra tiebreaker so that
049   * different ranges with the same start/end key pair count as different regions.
050   */
051  static class SimpleRange implements KeyRange {
052    byte[] start, end;
053    UUID tiebreaker;
054
055    SimpleRange(byte[] start, byte[] end) {
056      this.start = start;
057      this.end = end;
058      this.tiebreaker = TEST_UTIL.getRandomUUID();
059    }
060
061    @Override
062    public byte[] getStartKey() {
063      return start;
064    }
065
066    @Override
067    public byte[] getEndKey() {
068      return end;
069    }
070
071    @Override
072    public String toString() {
073      return "[" + Bytes.toString(start) + ", " + Bytes.toString(end) + "]";
074    }
075  }
076
077  Comparator<SimpleRange> cmp = new Comparator<SimpleRange>() {
078    @Override
079    public int compare(SimpleRange sr1, SimpleRange sr2) {
080      ComparisonChain cc = ComparisonChain.start();
081      cc = cc.compare(sr1.getStartKey(), sr2.getStartKey(), Bytes.BYTES_COMPARATOR);
082      cc = cc.compare(sr1.getEndKey(), sr2.getEndKey(), RegionSplitCalculator.BYTES_COMPARATOR);
083      cc = cc.compare(sr1.tiebreaker, sr2.tiebreaker);
084      return cc.result();
085    }
086  };
087
088  /**
089   * Check the "depth" (number of regions included at a split) of a generated split calculation
090   */
091  void checkDepths(SortedSet<byte[]> splits, Multimap<byte[], SimpleRange> regions,
092    Integer... depths) {
093    assertEquals(splits.size(), depths.length);
094    int i = 0;
095    for (byte[] k : splits) {
096      Collection<SimpleRange> rs = regions.get(k);
097      int sz = rs == null ? 0 : rs.size();
098      assertEquals((int) depths[i], sz);
099      i++;
100    }
101  }
102
103  /**
104   * This dumps data in a visually reasonable way for visual debugging. It has the basic iteration
105   * structure.
106   */
107  String dump(SortedSet<byte[]> splits, Multimap<byte[], SimpleRange> regions) {
108    // we display this way because the last end key should be displayed as well.
109    StringBuilder sb = new StringBuilder();
110    for (byte[] k : splits) {
111      sb.append(Bytes.toString(k)).append(":\t");
112      for (SimpleRange r : regions.get(k)) {
113        sb.append(r.toString()).append("\t");
114      }
115      sb.append("\n");
116    }
117    String s = sb.toString();
118    LOG.info("\n" + s);
119    return s;
120  }
121
122  @Test
123  public void testSplitCalculator() {
124    SimpleRange a = new SimpleRange(Bytes.toBytes("A"), Bytes.toBytes("B"));
125    SimpleRange b = new SimpleRange(Bytes.toBytes("B"), Bytes.toBytes("C"));
126    SimpleRange c = new SimpleRange(Bytes.toBytes("C"), Bytes.toBytes("D"));
127    RegionSplitCalculator<SimpleRange> sc = new RegionSplitCalculator<>(cmp);
128    sc.add(a);
129    sc.add(b);
130    sc.add(c);
131
132    Multimap<byte[], SimpleRange> regions = sc.calcCoverage();
133    LOG.info("Standard");
134    String res = dump(sc.getSplits(), regions);
135    checkDepths(sc.getSplits(), regions, 1, 1, 1, 0);
136    assertEquals("A:\t[A, B]\t\n" + "B:\t[B, C]\t\n" + "C:\t[C, D]\t\nD:\t\n", res);
137  }
138
139  @Test
140  public void testSplitCalculatorNoEdge() {
141    RegionSplitCalculator<SimpleRange> sc = new RegionSplitCalculator<>(cmp);
142
143    Multimap<byte[], SimpleRange> regions = sc.calcCoverage();
144    LOG.info("Empty");
145    String res = dump(sc.getSplits(), regions);
146    checkDepths(sc.getSplits(), regions);
147    assertEquals("", res);
148  }
149
150  @Test
151  public void testSplitCalculatorSingleEdge() {
152    SimpleRange a = new SimpleRange(Bytes.toBytes("A"), Bytes.toBytes("B"));
153    RegionSplitCalculator<SimpleRange> sc = new RegionSplitCalculator<>(cmp);
154    sc.add(a);
155
156    Multimap<byte[], SimpleRange> regions = sc.calcCoverage();
157    LOG.info("Single edge");
158    String res = dump(sc.getSplits(), regions);
159    checkDepths(sc.getSplits(), regions, 1, 0);
160    assertEquals("A:\t[A, B]\t\n" + "B:\t\n", res);
161  }
162
163  @Test
164  public void testSplitCalculatorDegenerateEdge() {
165    SimpleRange a = new SimpleRange(Bytes.toBytes("A"), Bytes.toBytes("A"));
166    RegionSplitCalculator<SimpleRange> sc = new RegionSplitCalculator<>(cmp);
167    sc.add(a);
168
169    Multimap<byte[], SimpleRange> regions = sc.calcCoverage();
170    LOG.info("Single empty edge");
171    String res = dump(sc.getSplits(), regions);
172    checkDepths(sc.getSplits(), regions, 1);
173    assertEquals("A:\t[A, A]\t\n", res);
174  }
175
176  @Test
177  public void testSplitCalculatorCoverSplit() {
178    SimpleRange a = new SimpleRange(Bytes.toBytes("A"), Bytes.toBytes("B"));
179    SimpleRange b = new SimpleRange(Bytes.toBytes("B"), Bytes.toBytes("C"));
180    SimpleRange c = new SimpleRange(Bytes.toBytes("A"), Bytes.toBytes("C"));
181    RegionSplitCalculator<SimpleRange> sc = new RegionSplitCalculator<>(cmp);
182    sc.add(a);
183    sc.add(b);
184    sc.add(c);
185
186    Multimap<byte[], SimpleRange> regions = sc.calcCoverage();
187    LOG.info("AC covers AB, BC");
188    String res = dump(sc.getSplits(), regions);
189    checkDepths(sc.getSplits(), regions, 2, 2, 0);
190    assertEquals("A:\t[A, B]\t[A, C]\t\n" + "B:\t[A, C]\t[B, C]\t\n" + "C:\t\n", res);
191  }
192
193  @Test
194  public void testSplitCalculatorOverEndpoint() {
195    SimpleRange a = new SimpleRange(Bytes.toBytes("A"), Bytes.toBytes("B"));
196    SimpleRange b = new SimpleRange(Bytes.toBytes("B"), Bytes.toBytes("C"));
197    SimpleRange c = new SimpleRange(Bytes.toBytes("B"), Bytes.toBytes("D"));
198    RegionSplitCalculator<SimpleRange> sc = new RegionSplitCalculator<>(cmp);
199    sc.add(a);
200    sc.add(b);
201    sc.add(c);
202
203    Multimap<byte[], SimpleRange> regions = sc.calcCoverage();
204    LOG.info("AB, BD covers BC");
205    String res = dump(sc.getSplits(), regions);
206    checkDepths(sc.getSplits(), regions, 1, 2, 1, 0);
207    assertEquals("A:\t[A, B]\t\n" + "B:\t[B, C]\t[B, D]\t\n" + "C:\t[B, D]\t\n" + "D:\t\n", res);
208  }
209
210  @Test
211  public void testSplitCalculatorHoles() {
212    SimpleRange a = new SimpleRange(Bytes.toBytes("A"), Bytes.toBytes("B"));
213    SimpleRange b = new SimpleRange(Bytes.toBytes("B"), Bytes.toBytes("C"));
214    SimpleRange c = new SimpleRange(Bytes.toBytes("E"), Bytes.toBytes("F"));
215    RegionSplitCalculator<SimpleRange> sc = new RegionSplitCalculator<>(cmp);
216    sc.add(a);
217    sc.add(b);
218    sc.add(c);
219
220    Multimap<byte[], SimpleRange> regions = sc.calcCoverage();
221    LOG.info("Hole between C and E");
222    String res = dump(sc.getSplits(), regions);
223    checkDepths(sc.getSplits(), regions, 1, 1, 0, 1, 0);
224    assertEquals("A:\t[A, B]\t\n" + "B:\t[B, C]\t\n" + "C:\t\n" + "E:\t[E, F]\t\n" + "F:\t\n", res);
225  }
226
227  @Test
228  public void testSplitCalculatorOverreach() {
229    SimpleRange a = new SimpleRange(Bytes.toBytes("A"), Bytes.toBytes("C"));
230    SimpleRange b = new SimpleRange(Bytes.toBytes("B"), Bytes.toBytes("D"));
231    RegionSplitCalculator<SimpleRange> sc = new RegionSplitCalculator<>(cmp);
232    sc.add(a);
233    sc.add(b);
234
235    Multimap<byte[], SimpleRange> regions = sc.calcCoverage();
236    LOG.info("AC and BD overlap but share no start/end keys");
237    String res = dump(sc.getSplits(), regions);
238    checkDepths(sc.getSplits(), regions, 1, 2, 1, 0);
239    assertEquals("A:\t[A, C]\t\n" + "B:\t[A, C]\t[B, D]\t\n" + "C:\t[B, D]\t\n" + "D:\t\n", res);
240  }
241
242  @Test
243  public void testSplitCalculatorFloor() {
244    SimpleRange a = new SimpleRange(Bytes.toBytes("A"), Bytes.toBytes("C"));
245    SimpleRange b = new SimpleRange(Bytes.toBytes("A"), Bytes.toBytes("B"));
246    RegionSplitCalculator<SimpleRange> sc = new RegionSplitCalculator<>(cmp);
247    sc.add(a);
248    sc.add(b);
249
250    Multimap<byte[], SimpleRange> regions = sc.calcCoverage();
251    LOG.info("AC and AB overlap in the beginning");
252    String res = dump(sc.getSplits(), regions);
253    checkDepths(sc.getSplits(), regions, 2, 1, 0);
254    assertEquals("A:\t[A, B]\t[A, C]\t\n" + "B:\t[A, C]\t\n" + "C:\t\n", res);
255  }
256
257  @Test
258  public void testSplitCalculatorCeil() {
259    SimpleRange a = new SimpleRange(Bytes.toBytes("A"), Bytes.toBytes("C"));
260    SimpleRange b = new SimpleRange(Bytes.toBytes("B"), Bytes.toBytes("C"));
261    RegionSplitCalculator<SimpleRange> sc = new RegionSplitCalculator<>(cmp);
262    sc.add(a);
263    sc.add(b);
264
265    Multimap<byte[], SimpleRange> regions = sc.calcCoverage();
266    LOG.info("AC and BC overlap in the end");
267    String res = dump(sc.getSplits(), regions);
268    checkDepths(sc.getSplits(), regions, 1, 2, 0);
269    assertEquals("A:\t[A, C]\t\n" + "B:\t[A, C]\t[B, C]\t\n" + "C:\t\n", res);
270  }
271
272  @Test
273  public void testSplitCalculatorEq() {
274    SimpleRange a = new SimpleRange(Bytes.toBytes("A"), Bytes.toBytes("C"));
275    SimpleRange b = new SimpleRange(Bytes.toBytes("A"), Bytes.toBytes("C"));
276
277    LOG.info(a.tiebreaker + " - " + b.tiebreaker);
278    RegionSplitCalculator<SimpleRange> sc = new RegionSplitCalculator<>(cmp);
279    sc.add(a);
280    sc.add(b);
281
282    Multimap<byte[], SimpleRange> regions = sc.calcCoverage();
283    LOG.info("AC and AC overlap completely");
284    String res = dump(sc.getSplits(), regions);
285    checkDepths(sc.getSplits(), regions, 2, 0);
286    assertEquals("A:\t[A, C]\t[A, C]\t\n" + "C:\t\n", res);
287  }
288
289  @Test
290  public void testSplitCalculatorBackwards() {
291    SimpleRange a = new SimpleRange(Bytes.toBytes("C"), Bytes.toBytes("A"));
292    RegionSplitCalculator<SimpleRange> sc = new RegionSplitCalculator<>(cmp);
293    sc.add(a);
294
295    Multimap<byte[], SimpleRange> regions = sc.calcCoverage();
296    LOG.info("CA is backwards");
297    String res = dump(sc.getSplits(), regions);
298    checkDepths(sc.getSplits(), regions); // expect nothing
299    assertEquals("", res);
300  }
301
302  @Test
303  public void testComplex() {
304    RegionSplitCalculator<SimpleRange> sc = new RegionSplitCalculator<>(cmp);
305    sc.add(new SimpleRange(Bytes.toBytes("A"), Bytes.toBytes("Am")));
306    sc.add(new SimpleRange(Bytes.toBytes("A"), Bytes.toBytes("C")));
307    sc.add(new SimpleRange(Bytes.toBytes("Am"), Bytes.toBytes("C")));
308    sc.add(new SimpleRange(Bytes.toBytes("D"), Bytes.toBytes("E")));
309    sc.add(new SimpleRange(Bytes.toBytes("F"), Bytes.toBytes("G")));
310    sc.add(new SimpleRange(Bytes.toBytes("B"), Bytes.toBytes("E")));
311    sc.add(new SimpleRange(Bytes.toBytes("H"), Bytes.toBytes("I")));
312    sc.add(new SimpleRange(Bytes.toBytes("A"), Bytes.toBytes("B")));
313
314    Multimap<byte[], SimpleRange> regions = sc.calcCoverage();
315    LOG.info("Something fairly complex");
316    String res = dump(sc.getSplits(), regions);
317    checkDepths(sc.getSplits(), regions, 3, 3, 3, 1, 2, 0, 1, 0, 1, 0);
318    assertEquals("A:\t[A, Am]\t[A, B]\t[A, C]\t\n" + "Am:\t[A, B]\t[A, C]\t[Am, C]\t\n"
319      + "B:\t[A, C]\t[Am, C]\t[B, E]\t\n" + "C:\t[B, E]\t\n" + "D:\t[B, E]\t[D, E]\t\n" + "E:\t\n"
320      + "F:\t[F, G]\t\n" + "G:\t\n" + "H:\t[H, I]\t\n" + "I:\t\n", res);
321  }
322
323  @Test
324  public void testBeginEndMarker() {
325    RegionSplitCalculator<SimpleRange> sc = new RegionSplitCalculator<>(cmp);
326    sc.add(new SimpleRange(Bytes.toBytes(""), Bytes.toBytes("A")));
327    sc.add(new SimpleRange(Bytes.toBytes("A"), Bytes.toBytes("B")));
328    sc.add(new SimpleRange(Bytes.toBytes("B"), Bytes.toBytes("")));
329
330    Multimap<byte[], SimpleRange> regions = sc.calcCoverage();
331    LOG.info("Special cases -- empty");
332    String res = dump(sc.getSplits(), regions);
333    checkDepths(sc.getSplits(), regions, 1, 1, 1, 0);
334    assertEquals(":\t[, A]\t\n" + "A:\t[A, B]\t\n" + "B:\t[B, ]\t\n" + "null:\t\n", res);
335  }
336
337  @Test
338  public void testBigRanges() {
339    SimpleRange ai = new SimpleRange(Bytes.toBytes("A"), Bytes.toBytes("I"));
340    SimpleRange ae = new SimpleRange(Bytes.toBytes("A"), Bytes.toBytes("E"));
341    SimpleRange ac = new SimpleRange(Bytes.toBytes("A"), Bytes.toBytes("C"));
342
343    Collection<SimpleRange> bigOverlap = new ArrayList<>(8);
344    bigOverlap.add(new SimpleRange(Bytes.toBytes("A"), Bytes.toBytes("E")));
345    bigOverlap.add(new SimpleRange(Bytes.toBytes("A"), Bytes.toBytes("C")));
346    bigOverlap.add(new SimpleRange(Bytes.toBytes("A"), Bytes.toBytes("B")));
347    bigOverlap.add(new SimpleRange(Bytes.toBytes("B"), Bytes.toBytes("C")));
348    bigOverlap.add(new SimpleRange(Bytes.toBytes("E"), Bytes.toBytes("H")));
349    bigOverlap.add(ai);
350    bigOverlap.add(ae);
351    bigOverlap.add(ac);
352
353    // Expect 1 range to be returned: ai
354    List<SimpleRange> bigRanges = RegionSplitCalculator.findBigRanges(bigOverlap, 1);
355    assertEquals(1, bigRanges.size());
356    assertEquals(ai, bigRanges.get(0));
357
358    // Expect 3 ranges to be returned: ai, ae and ac
359    bigRanges = RegionSplitCalculator.findBigRanges(bigOverlap, 3);
360    assertEquals(3, bigRanges.size());
361    assertEquals(ai, bigRanges.get(0));
362
363    SimpleRange r1 = bigRanges.get(1);
364    SimpleRange r2 = bigRanges.get(2);
365    assertEquals("A", Bytes.toString(r1.start));
366    assertEquals("A", Bytes.toString(r2.start));
367    String r1e = Bytes.toString(r1.end);
368    String r2e = Bytes.toString(r2.end);
369    assertTrue((r1e.equals("C") && r2e.equals("E")) || (r1e.equals("E") && r2e.equals("C")));
370  }
371
372}