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.master.assignment;
019
020import java.io.IOException;
021import java.io.InterruptedIOException;
022import java.util.ArrayList;
023import java.util.Arrays;
024import java.util.Collection;
025import java.util.Collections;
026import java.util.HashMap;
027import java.util.List;
028import java.util.Map;
029import java.util.concurrent.Callable;
030import java.util.concurrent.ExecutionException;
031import java.util.concurrent.ExecutorService;
032import java.util.concurrent.Executors;
033import java.util.concurrent.Future;
034import java.util.concurrent.TimeUnit;
035import java.util.stream.Stream;
036import org.apache.hadoop.conf.Configuration;
037import org.apache.hadoop.fs.FileSystem;
038import org.apache.hadoop.fs.Path;
039import org.apache.hadoop.hbase.DoNotRetryIOException;
040import org.apache.hadoop.hbase.HConstants;
041import org.apache.hadoop.hbase.ServerName;
042import org.apache.hadoop.hbase.TableName;
043import org.apache.hadoop.hbase.UnknownRegionException;
044import org.apache.hadoop.hbase.client.ColumnFamilyDescriptor;
045import org.apache.hadoop.hbase.client.MasterSwitchType;
046import org.apache.hadoop.hbase.client.Mutation;
047import org.apache.hadoop.hbase.client.RegionInfo;
048import org.apache.hadoop.hbase.client.RegionInfoBuilder;
049import org.apache.hadoop.hbase.client.TableDescriptor;
050import org.apache.hadoop.hbase.io.hfile.CacheConfig;
051import org.apache.hadoop.hbase.master.MasterCoprocessorHost;
052import org.apache.hadoop.hbase.master.MasterFileSystem;
053import org.apache.hadoop.hbase.master.RegionState.State;
054import org.apache.hadoop.hbase.master.normalizer.NormalizationPlan;
055import org.apache.hadoop.hbase.master.procedure.AbstractStateMachineRegionProcedure;
056import org.apache.hadoop.hbase.master.procedure.MasterProcedureEnv;
057import org.apache.hadoop.hbase.master.procedure.MasterProcedureUtil;
058import org.apache.hadoop.hbase.procedure2.ProcedureMetrics;
059import org.apache.hadoop.hbase.procedure2.ProcedureStateSerializer;
060import org.apache.hadoop.hbase.quotas.MasterQuotaManager;
061import org.apache.hadoop.hbase.quotas.QuotaExceededException;
062import org.apache.hadoop.hbase.regionserver.HRegionFileSystem;
063import org.apache.hadoop.hbase.regionserver.HStore;
064import org.apache.hadoop.hbase.regionserver.HStoreFile;
065import org.apache.hadoop.hbase.regionserver.RegionSplitPolicy;
066import org.apache.hadoop.hbase.regionserver.RegionSplitRestriction;
067import org.apache.hadoop.hbase.regionserver.StoreFileInfo;
068import org.apache.hadoop.hbase.regionserver.StoreUtils;
069import org.apache.hadoop.hbase.regionserver.storefiletracker.StoreFileTracker;
070import org.apache.hadoop.hbase.regionserver.storefiletracker.StoreFileTrackerFactory;
071import org.apache.hadoop.hbase.util.Bytes;
072import org.apache.hadoop.hbase.util.CommonFSUtils;
073import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
074import org.apache.hadoop.hbase.util.ModifyRegionUtils;
075import org.apache.hadoop.hbase.util.Pair;
076import org.apache.hadoop.hbase.util.Threads;
077import org.apache.hadoop.hbase.wal.WALSplitUtil;
078import org.apache.hadoop.util.ReflectionUtils;
079import org.apache.yetus.audience.InterfaceAudience;
080import org.slf4j.Logger;
081import org.slf4j.LoggerFactory;
082
083import org.apache.hbase.thirdparty.com.google.common.util.concurrent.ThreadFactoryBuilder;
084
085import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil;
086import org.apache.hadoop.hbase.shaded.protobuf.generated.AdminProtos.GetRegionInfoResponse;
087import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProcedureProtos;
088import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProcedureProtos.SplitTableRegionState;
089
090/**
091 * The procedure to split a region in a table. Takes lock on the parent region. It holds the lock
092 * for the life of the procedure.
093 * <p>
094 * Throws exception on construction if determines context hostile to spllt (cluster going down or
095 * master is shutting down or table is disabled).
096 * </p>
097 */
098@InterfaceAudience.Private
099public class SplitTableRegionProcedure
100  extends AbstractStateMachineRegionProcedure<SplitTableRegionState> {
101  private static final Logger LOG = LoggerFactory.getLogger(SplitTableRegionProcedure.class);
102  private RegionInfo daughterOneRI;
103  private RegionInfo daughterTwoRI;
104  private byte[] bestSplitRow;
105  private RegionSplitPolicy splitPolicy;
106  // exposed for unit testing
107  boolean checkTableModifyInProgress = true;
108
109  public SplitTableRegionProcedure() {
110    // Required by the Procedure framework to create the procedure on replay
111  }
112
113  public SplitTableRegionProcedure(final MasterProcedureEnv env, final RegionInfo regionToSplit,
114    final byte[] splitRow) throws IOException {
115    super(env, regionToSplit);
116    preflightChecks(env, true);
117    // When procedure goes to run in its prepare step, it also does these checkOnline checks. Here
118    // we fail-fast on construction. There it skips the split with just a warning.
119    checkOnline(env, regionToSplit);
120    this.bestSplitRow = splitRow;
121    TableDescriptor tableDescriptor =
122      env.getMasterServices().getTableDescriptors().get(getTableName());
123    Configuration conf = env.getMasterConfiguration();
124    if (hasBestSplitRow()) {
125      // Apply the split restriction for the table to the user-specified split point
126      RegionSplitRestriction splitRestriction =
127        RegionSplitRestriction.create(tableDescriptor, conf);
128      byte[] restrictedSplitRow = splitRestriction.getRestrictedSplitPoint(bestSplitRow);
129      if (!Bytes.equals(bestSplitRow, restrictedSplitRow)) {
130        LOG.warn(
131          "The specified split point {} violates the split restriction of the table. "
132            + "Using {} as a split point.",
133          Bytes.toStringBinary(bestSplitRow), Bytes.toStringBinary(restrictedSplitRow));
134        bestSplitRow = restrictedSplitRow;
135      }
136    }
137    checkSplittable(env, regionToSplit);
138    final TableName table = regionToSplit.getTable();
139    final long rid = getDaughterRegionIdTimestamp(regionToSplit);
140    this.daughterOneRI =
141      RegionInfoBuilder.newBuilder(table).setStartKey(regionToSplit.getStartKey())
142        .setEndKey(bestSplitRow).setSplit(false).setRegionId(rid).build();
143    this.daughterTwoRI = RegionInfoBuilder.newBuilder(table).setStartKey(bestSplitRow)
144      .setEndKey(regionToSplit.getEndKey()).setSplit(false).setRegionId(rid).build();
145    ModifyRegionUtils.checkForEncodedNameCollisions(Arrays.asList(daughterOneRI, daughterTwoRI),
146      env.getAssignmentManager().getRegionStates());
147
148    if (tableDescriptor.getRegionSplitPolicyClassName() != null) {
149      // Since we don't have region reference here, creating the split policy instance without it.
150      // This can be used to invoke methods which don't require Region reference. This instantiation
151      // of a class on Master-side though it only makes sense on the RegionServer-side is
152      // for Phoenix Local Indexing. Refer HBASE-12583 for more information.
153      Class<? extends RegionSplitPolicy> clazz =
154        RegionSplitPolicy.getSplitPolicyClass(tableDescriptor, conf);
155      this.splitPolicy = ReflectionUtils.newInstance(clazz, conf);
156    }
157  }
158
159  @Override
160  protected LockState acquireLock(final MasterProcedureEnv env) {
161    if (
162      env.getProcedureScheduler().waitRegions(this, getTableName(), getParentRegion(),
163        daughterOneRI, daughterTwoRI)
164    ) {
165      try {
166        LOG.debug(LockState.LOCK_EVENT_WAIT + " " + env.getProcedureScheduler().dumpLocks());
167      } catch (IOException e) {
168        // Ignore, just for logging
169      }
170      return LockState.LOCK_EVENT_WAIT;
171    }
172    return LockState.LOCK_ACQUIRED;
173  }
174
175  @Override
176  protected void releaseLock(final MasterProcedureEnv env) {
177    env.getProcedureScheduler().wakeRegions(this, getTableName(), getParentRegion(), daughterOneRI,
178      daughterTwoRI);
179  }
180
181  public RegionInfo getDaughterOneRI() {
182    return daughterOneRI;
183  }
184
185  public RegionInfo getDaughterTwoRI() {
186    return daughterTwoRI;
187  }
188
189  private boolean hasBestSplitRow() {
190    return bestSplitRow != null && bestSplitRow.length > 0;
191  }
192
193  /**
194   * Check whether the region is splittable
195   * @param env           MasterProcedureEnv
196   * @param regionToSplit parent Region to be split
197   */
198  private void checkSplittable(final MasterProcedureEnv env, final RegionInfo regionToSplit)
199    throws IOException {
200    // Ask the remote RS if this region is splittable.
201    // If we get an IOE, report it along w/ the failure so can see why we are not splittable at
202    // this time.
203    if (regionToSplit.getReplicaId() != RegionInfo.DEFAULT_REPLICA_ID) {
204      throw new IllegalArgumentException("Can't invoke split on non-default regions directly");
205    }
206    RegionStateNode node =
207      env.getAssignmentManager().getRegionStates().getRegionStateNode(getParentRegion());
208    IOException splittableCheckIOE = null;
209    boolean splittable = false;
210    if (node != null) {
211      try {
212        GetRegionInfoResponse response;
213        if (!hasBestSplitRow()) {
214          LOG.info(
215            "{} splitKey isn't explicitly specified, will try to find a best split key from RS {}",
216            node.getRegionInfo().getRegionNameAsString(), node.getRegionLocation());
217          response = AssignmentManagerUtil.getRegionInfoResponse(env, node.getRegionLocation(),
218            node.getRegionInfo(), true);
219          bestSplitRow =
220            response.hasBestSplitRow() ? response.getBestSplitRow().toByteArray() : null;
221        } else {
222          response = AssignmentManagerUtil.getRegionInfoResponse(env, node.getRegionLocation(),
223            node.getRegionInfo(), false);
224        }
225        splittable = response.hasSplittable() && response.getSplittable();
226        if (LOG.isDebugEnabled()) {
227          LOG.debug("Splittable=" + splittable + " " + node.toShortString());
228        }
229      } catch (IOException e) {
230        splittableCheckIOE = e;
231      }
232    }
233
234    if (!splittable) {
235      IOException e =
236        new DoNotRetryIOException(regionToSplit.getShortNameToLog() + " NOT splittable");
237      if (splittableCheckIOE != null) {
238        e.initCause(splittableCheckIOE);
239      }
240      throw e;
241    }
242
243    if (!hasBestSplitRow()) {
244      throw new DoNotRetryIOException("Region not splittable because bestSplitPoint = null, "
245        + "maybe table is too small for auto split. For force split, try specifying split row");
246    }
247
248    if (Bytes.equals(regionToSplit.getStartKey(), bestSplitRow)) {
249      throw new DoNotRetryIOException(
250        "Split row is equal to startkey: " + Bytes.toStringBinary(bestSplitRow));
251    }
252
253    if (!regionToSplit.containsRow(bestSplitRow)) {
254      throw new DoNotRetryIOException("Split row is not inside region key range splitKey:"
255        + Bytes.toStringBinary(bestSplitRow) + " region: " + regionToSplit);
256    }
257  }
258
259  /**
260   * Calculate daughter regionid to use.
261   * @param hri Parent {@link RegionInfo}
262   * @return Daughter region id (timestamp) to use.
263   */
264  private static long getDaughterRegionIdTimestamp(final RegionInfo hri) {
265    long rid = EnvironmentEdgeManager.currentTime();
266    // Regionid is timestamp. Can't be less than that of parent else will insert
267    // at wrong location in hbase:meta (See HBASE-710).
268    if (rid < hri.getRegionId()) {
269      LOG.warn("Clock skew; parent regions id is " + hri.getRegionId()
270        + " but current time here is " + rid);
271      rid = hri.getRegionId() + 1;
272    }
273    return rid;
274  }
275
276  private void removeNonDefaultReplicas(MasterProcedureEnv env) throws IOException {
277    AssignmentManagerUtil.removeNonDefaultReplicas(env, Stream.of(getParentRegion()),
278      getRegionReplication(env));
279  }
280
281  private void checkClosedRegions(MasterProcedureEnv env) throws IOException {
282    // theoretically this should not happen any more after we use TRSP, but anyway let's add a check
283    // here
284    AssignmentManagerUtil.checkClosedRegion(env, getParentRegion());
285  }
286
287  @Override
288  protected Flow executeFromState(MasterProcedureEnv env, SplitTableRegionState state)
289    throws InterruptedException {
290    LOG.trace("{} execute state={}", this, state);
291
292    try {
293      switch (state) {
294        case SPLIT_TABLE_REGION_PREPARE:
295          if (prepareSplitRegion(env)) {
296            setNextState(SplitTableRegionState.SPLIT_TABLE_REGION_PRE_OPERATION);
297            break;
298          } else {
299            return Flow.NO_MORE_STATE;
300          }
301        case SPLIT_TABLE_REGION_PRE_OPERATION:
302          preSplitRegion(env);
303          setNextState(SplitTableRegionState.SPLIT_TABLE_REGION_CLOSE_PARENT_REGION);
304          break;
305        case SPLIT_TABLE_REGION_CLOSE_PARENT_REGION:
306          addChildProcedure(createUnassignProcedures(env));
307          // createUnassignProcedures() can throw out IOException. If this happens,
308          // it wont reach state SPLIT_TABLE_REGIONS_CHECK_CLOSED_REGION and no parent regions
309          // is closed as all created UnassignProcedures are rolled back. If it rolls back with
310          // state SPLIT_TABLE_REGION_CLOSE_PARENT_REGION, no need to call openParentRegion(),
311          // otherwise, it will result in OpenRegionProcedure for an already open region.
312          setNextState(SplitTableRegionState.SPLIT_TABLE_REGIONS_CHECK_CLOSED_REGIONS);
313          break;
314        case SPLIT_TABLE_REGIONS_CHECK_CLOSED_REGIONS:
315          checkClosedRegions(env);
316          setNextState(SplitTableRegionState.SPLIT_TABLE_REGION_CREATE_DAUGHTER_REGIONS);
317          break;
318        case SPLIT_TABLE_REGION_CREATE_DAUGHTER_REGIONS:
319          removeNonDefaultReplicas(env);
320          createDaughterRegions(env);
321          setNextState(SplitTableRegionState.SPLIT_TABLE_REGION_WRITE_MAX_SEQUENCE_ID_FILE);
322          break;
323        case SPLIT_TABLE_REGION_WRITE_MAX_SEQUENCE_ID_FILE:
324          writeMaxSequenceIdFile(env);
325          setNextState(SplitTableRegionState.SPLIT_TABLE_REGION_PRE_OPERATION_BEFORE_META);
326          break;
327        case SPLIT_TABLE_REGION_PRE_OPERATION_BEFORE_META:
328          preSplitRegionBeforeMETA(env);
329          setNextState(SplitTableRegionState.SPLIT_TABLE_REGION_UPDATE_META);
330          break;
331        case SPLIT_TABLE_REGION_UPDATE_META:
332          updateMeta(env);
333          setNextState(SplitTableRegionState.SPLIT_TABLE_REGION_PRE_OPERATION_AFTER_META);
334          break;
335        case SPLIT_TABLE_REGION_PRE_OPERATION_AFTER_META:
336          preSplitRegionAfterMETA(env);
337          setNextState(SplitTableRegionState.SPLIT_TABLE_REGION_OPEN_CHILD_REGIONS);
338          break;
339        case SPLIT_TABLE_REGION_OPEN_CHILD_REGIONS:
340          addChildProcedure(createAssignProcedures(env));
341          setNextState(SplitTableRegionState.SPLIT_TABLE_REGION_POST_OPERATION);
342          break;
343        case SPLIT_TABLE_REGION_POST_OPERATION:
344          postSplitRegion(env);
345          return Flow.NO_MORE_STATE;
346        default:
347          throw new UnsupportedOperationException(this + " unhandled state=" + state);
348      }
349    } catch (IOException e) {
350      String msg = "Splitting " + getParentRegion().getEncodedName() + ", " + this;
351      if (!isRollbackSupported(state)) {
352        // We reach a state that cannot be rolled back. We just need to keep retrying.
353        LOG.warn(msg, e);
354      } else {
355        LOG.error(msg, e);
356        setFailure("master-split-regions", e);
357      }
358    }
359    // if split fails, need to call ((HRegion)parent).clearSplit() when it is a force split
360    return Flow.HAS_MORE_STATE;
361  }
362
363  /**
364   * To rollback {@link SplitTableRegionProcedure}, an AssignProcedure is asynchronously submitted
365   * for parent region to be split (rollback doesn't wait on the completion of the AssignProcedure)
366   * . This can be improved by changing rollback() to support sub-procedures. See HBASE-19851 for
367   * details.
368   */
369  @Override
370  protected void rollbackState(final MasterProcedureEnv env, final SplitTableRegionState state)
371    throws IOException, InterruptedException {
372    LOG.trace("{} rollback state={}", this, state);
373
374    try {
375      switch (state) {
376        case SPLIT_TABLE_REGION_POST_OPERATION:
377        case SPLIT_TABLE_REGION_OPEN_CHILD_REGIONS:
378        case SPLIT_TABLE_REGION_PRE_OPERATION_AFTER_META:
379        case SPLIT_TABLE_REGION_UPDATE_META:
380          // PONR
381          throw new UnsupportedOperationException(this + " unhandled state=" + state);
382        case SPLIT_TABLE_REGION_PRE_OPERATION_BEFORE_META:
383          break;
384        case SPLIT_TABLE_REGION_CREATE_DAUGHTER_REGIONS:
385        case SPLIT_TABLE_REGION_WRITE_MAX_SEQUENCE_ID_FILE:
386          deleteDaughterRegions(env);
387          break;
388        case SPLIT_TABLE_REGIONS_CHECK_CLOSED_REGIONS:
389          openParentRegion(env);
390          break;
391        case SPLIT_TABLE_REGION_CLOSE_PARENT_REGION:
392          // If it rolls back with state SPLIT_TABLE_REGION_CLOSE_PARENT_REGION, no need to call
393          // openParentRegion(), otherwise, it will result in OpenRegionProcedure for an
394          // already open region.
395          break;
396        case SPLIT_TABLE_REGION_PRE_OPERATION:
397          postRollBackSplitRegion(env);
398          break;
399        case SPLIT_TABLE_REGION_PREPARE:
400          rollbackPrepareSplit(env);
401          break;
402        default:
403          throw new UnsupportedOperationException(this + " unhandled state=" + state);
404      }
405    } catch (IOException e) {
406      // This will be retried. Unless there is a bug in the code,
407      // this should be just a "temporary error" (e.g. network down)
408      LOG.warn("pid=" + getProcId() + " failed rollback attempt step " + state
409        + " for splitting the region " + getParentRegion().getEncodedName() + " in table "
410        + getTableName(), e);
411      throw e;
412    }
413  }
414
415  /*
416   * Check whether we are in the state that can be rollback
417   */
418  @Override
419  protected boolean isRollbackSupported(final SplitTableRegionState state) {
420    switch (state) {
421      case SPLIT_TABLE_REGION_POST_OPERATION:
422      case SPLIT_TABLE_REGION_OPEN_CHILD_REGIONS:
423      case SPLIT_TABLE_REGION_PRE_OPERATION_AFTER_META:
424      case SPLIT_TABLE_REGION_UPDATE_META:
425        // It is not safe to rollback if we reach to these states.
426        return false;
427      default:
428        break;
429    }
430    return true;
431  }
432
433  @Override
434  protected SplitTableRegionState getState(final int stateId) {
435    return SplitTableRegionState.forNumber(stateId);
436  }
437
438  @Override
439  protected int getStateId(final SplitTableRegionState state) {
440    return state.getNumber();
441  }
442
443  @Override
444  protected SplitTableRegionState getInitialState() {
445    return SplitTableRegionState.SPLIT_TABLE_REGION_PREPARE;
446  }
447
448  @Override
449  protected void serializeStateData(ProcedureStateSerializer serializer) throws IOException {
450    super.serializeStateData(serializer);
451
452    final MasterProcedureProtos.SplitTableRegionStateData.Builder splitTableRegionMsg =
453      MasterProcedureProtos.SplitTableRegionStateData.newBuilder()
454        .setUserInfo(MasterProcedureUtil.toProtoUserInfo(getUser()))
455        .setParentRegionInfo(ProtobufUtil.toRegionInfo(getRegion()))
456        .addChildRegionInfo(ProtobufUtil.toRegionInfo(daughterOneRI))
457        .addChildRegionInfo(ProtobufUtil.toRegionInfo(daughterTwoRI));
458    serializer.serialize(splitTableRegionMsg.build());
459  }
460
461  @Override
462  protected void deserializeStateData(ProcedureStateSerializer serializer) throws IOException {
463    super.deserializeStateData(serializer);
464
465    final MasterProcedureProtos.SplitTableRegionStateData splitTableRegionsMsg =
466      serializer.deserialize(MasterProcedureProtos.SplitTableRegionStateData.class);
467    setUser(MasterProcedureUtil.toUserInfo(splitTableRegionsMsg.getUserInfo()));
468    setRegion(ProtobufUtil.toRegionInfo(splitTableRegionsMsg.getParentRegionInfo()));
469    assert (splitTableRegionsMsg.getChildRegionInfoCount() == 2);
470    daughterOneRI = ProtobufUtil.toRegionInfo(splitTableRegionsMsg.getChildRegionInfo(0));
471    daughterTwoRI = ProtobufUtil.toRegionInfo(splitTableRegionsMsg.getChildRegionInfo(1));
472  }
473
474  @Override
475  public void toStringClassDetails(StringBuilder sb) {
476    sb.append(getClass().getSimpleName());
477    sb.append(" table=");
478    sb.append(getTableName());
479    sb.append(", parent=");
480    sb.append(getParentRegion().getShortNameToLog());
481    sb.append(", daughterA=");
482    sb.append(daughterOneRI.getShortNameToLog());
483    sb.append(", daughterB=");
484    sb.append(daughterTwoRI.getShortNameToLog());
485  }
486
487  private RegionInfo getParentRegion() {
488    return getRegion();
489  }
490
491  @Override
492  public TableOperationType getTableOperationType() {
493    return TableOperationType.REGION_SPLIT;
494  }
495
496  @Override
497  protected ProcedureMetrics getProcedureMetrics(MasterProcedureEnv env) {
498    return env.getAssignmentManager().getAssignmentManagerMetrics().getSplitProcMetrics();
499  }
500
501  private byte[] getSplitRow() {
502    return daughterTwoRI.getStartKey();
503  }
504
505  private static final State[] EXPECTED_SPLIT_STATES = new State[] { State.OPEN, State.CLOSED };
506
507  /**
508   * Prepare to Split region.
509   * @param env MasterProcedureEnv
510   */
511  public boolean prepareSplitRegion(final MasterProcedureEnv env) throws IOException {
512    // Fail if we are taking snapshot for the given table
513    if (
514      env.getMasterServices().getSnapshotManager()
515        .isTableTakingAnySnapshot(getParentRegion().getTable())
516    ) {
517      setFailure(new IOException("Skip splitting region " + getParentRegion().getShortNameToLog()
518        + ", because we are taking snapshot for the table " + getParentRegion().getTable()));
519      return false;
520    }
521
522    /*
523     * Sometimes a ModifyTableProcedure has edited a table descriptor to change the number of region
524     * replicas for a table, but it has not yet opened/closed the new replicas. The
525     * ModifyTableProcedure assumes that nobody else will do the opening/closing of the new
526     * replicas, but a concurrent SplitTableRegionProcedure would violate that assumption.
527     */
528    if (checkTableModifyInProgress && isTableModificationInProgress(env)) {
529      setFailure(new IOException("Skip splitting region " + getParentRegion().getShortNameToLog()
530        + ", because there is an active procedure that is modifying the table "
531        + getParentRegion().getTable()));
532      return false;
533    }
534
535    // Check whether the region is splittable
536    RegionStateNode node =
537      env.getAssignmentManager().getRegionStates().getRegionStateNode(getParentRegion());
538
539    if (node == null) {
540      throw new UnknownRegionException(getParentRegion().getRegionNameAsString());
541    }
542
543    RegionInfo parentHRI = node.getRegionInfo();
544    if (parentHRI == null) {
545      LOG.info("Unsplittable; parent region is null; node={}", node);
546      return false;
547    }
548    // Lookup the parent HRI state from the AM, which has the latest updated info.
549    // Protect against the case where concurrent SPLIT requests came in and succeeded
550    // just before us.
551    if (node.isInState(State.SPLIT)) {
552      LOG.info("Split of " + parentHRI + " skipped; state is already SPLIT");
553      return false;
554    }
555    if (parentHRI.isSplit() || parentHRI.isOffline()) {
556      LOG.info("Split of " + parentHRI + " skipped because offline/split.");
557      return false;
558    }
559
560    // expected parent to be online or closed
561    if (!node.isInState(EXPECTED_SPLIT_STATES)) {
562      // We may have SPLIT already?
563      setFailure(
564        new IOException("Split " + parentHRI.getRegionNameAsString() + " FAILED because state="
565          + node.getState() + "; expected " + Arrays.toString(EXPECTED_SPLIT_STATES)));
566      return false;
567    }
568
569    // Mostly the below two checks are not used because we already check the switches before
570    // submitting the split procedure. Just for safety, we are checking the switch again here.
571    // Also, in case the switch was set to false after submission, this procedure can be rollbacked,
572    // thanks to this double check!
573    // case 1: check for cluster level switch
574    if (!env.getMasterServices().isSplitOrMergeEnabled(MasterSwitchType.SPLIT)) {
575      LOG.warn("pid=" + getProcId() + " split switch is off! skip split of " + parentHRI);
576      setFailure(new IOException(
577        "Split region " + parentHRI.getRegionNameAsString() + " failed due to split switch off"));
578      return false;
579    }
580    // case 2: check for table level switch
581    if (!env.getMasterServices().getTableDescriptors().get(getTableName()).isSplitEnabled()) {
582      LOG.warn("pid={}, split is disabled for the table! Skipping split of {}", getProcId(),
583        parentHRI);
584      setFailure(new IOException("Split region " + parentHRI.getRegionNameAsString()
585        + " failed as region split is disabled for the table"));
586      return false;
587    }
588
589    // set node state as SPLITTING
590    node.setState(State.SPLITTING);
591
592    // Since we have the lock and the master is coordinating the operation
593    // we are always able to split the region
594    return true;
595  }
596
597  /**
598   * Rollback prepare split region
599   * @param env MasterProcedureEnv
600   */
601  private void rollbackPrepareSplit(final MasterProcedureEnv env) {
602    RegionStateNode parentRegionStateNode =
603      env.getAssignmentManager().getRegionStates().getRegionStateNode(getParentRegion());
604    if (parentRegionStateNode.getState() == State.SPLITTING) {
605      parentRegionStateNode.setState(State.OPEN);
606    }
607  }
608
609  /**
610   * Action before splitting region in a table.
611   * @param env MasterProcedureEnv
612   */
613  private void preSplitRegion(final MasterProcedureEnv env)
614    throws IOException, InterruptedException {
615    final MasterCoprocessorHost cpHost = env.getMasterCoprocessorHost();
616    if (cpHost != null) {
617      cpHost.preSplitRegionAction(getTableName(), getSplitRow(), getUser());
618    }
619
620    // TODO: Clean up split and merge. Currently all over the place.
621    // Notify QuotaManager and RegionNormalizer
622    try {
623      MasterQuotaManager masterQuotaManager = env.getMasterServices().getMasterQuotaManager();
624      if (masterQuotaManager != null) {
625        masterQuotaManager.onRegionSplit(this.getParentRegion());
626      }
627    } catch (QuotaExceededException e) {
628      // TODO: why is this here? split requests can be submitted by actors other than the normalizer
629      env.getMasterServices().getRegionNormalizerManager()
630        .planSkipped(NormalizationPlan.PlanType.SPLIT);
631      throw e;
632    }
633  }
634
635  /**
636   * Action after rollback a split table region action.
637   * @param env MasterProcedureEnv
638   */
639  private void postRollBackSplitRegion(final MasterProcedureEnv env) throws IOException {
640    final MasterCoprocessorHost cpHost = env.getMasterCoprocessorHost();
641    if (cpHost != null) {
642      cpHost.postRollBackSplitRegionAction(getUser());
643    }
644  }
645
646  /**
647   * Rollback close parent region
648   */
649  private void openParentRegion(MasterProcedureEnv env) throws IOException {
650    AssignmentManagerUtil.reopenRegionsForRollback(env,
651      Collections.singletonList((getParentRegion())), getRegionReplication(env),
652      getParentRegionServerName(env));
653  }
654
655  /**
656   * Create daughter regions
657   */
658  public void createDaughterRegions(final MasterProcedureEnv env) throws IOException {
659    final MasterFileSystem mfs = env.getMasterServices().getMasterFileSystem();
660    final Path tabledir = CommonFSUtils.getTableDir(mfs.getRootDir(), getTableName());
661    final FileSystem fs = mfs.getFileSystem();
662    HRegionFileSystem regionFs = HRegionFileSystem.openRegionFromFileSystem(
663      env.getMasterConfiguration(), fs, tabledir, getParentRegion(), false);
664    regionFs.createSplitsDir(daughterOneRI, daughterTwoRI);
665    Pair<List<StoreFileInfo>, List<StoreFileInfo>> expectedReferences =
666      splitStoreFiles(env, regionFs);
667    final ExecutorService threadPool = Executors.newFixedThreadPool(2,
668      new ThreadFactoryBuilder().setNameFormat("RegionCommitter-pool-%d").setDaemon(true)
669        .setUncaughtExceptionHandler(Threads.LOGGING_EXCEPTION_HANDLER).build());
670    Future<Path> futureOne = threadPool.submit(new Callable<Path>() {
671      @Override
672      public Path call() throws IOException {
673        return regionFs.commitDaughterRegion(daughterOneRI, expectedReferences.getFirst(), env);
674      }
675    });
676    Future<Path> futureTwo = threadPool.submit(new Callable<Path>() {
677      @Override
678      public Path call() throws IOException {
679        return regionFs.commitDaughterRegion(daughterTwoRI, expectedReferences.getSecond(), env);
680      }
681    });
682    handleThreadPoolShutdown(threadPool, env.getMasterConfiguration());
683
684    try {
685      futureOne.get();
686      futureTwo.get();
687    } catch (InterruptedException e) {
688      throw (InterruptedIOException) new InterruptedIOException().initCause(e);
689    } catch (ExecutionException e) {
690      throw new IOException("Daughter region commit failed", e);
691    }
692  }
693
694  private void handleThreadPoolShutdown(ExecutorService threadPool, Configuration conf)
695    throws IOException {
696    threadPool.shutdown();
697    // Wait for all the tasks to finish.
698    // When splits ran on the RegionServer, how-long-to-wait-configuration was named
699    // fileSplitTimeout. If set, use its value.
700    long fileSplitTimeout = conf.getLong("hbase.master.fileSplitTimeout",
701      conf.getLong("hbase.regionserver.fileSplitTimeout", 600000));
702    try {
703      boolean stillRunning = !threadPool.awaitTermination(fileSplitTimeout, TimeUnit.MILLISECONDS);
704      if (stillRunning) {
705        threadPool.shutdownNow();
706        // wait for the thread to shutdown completely.
707        while (!threadPool.isTerminated()) {
708          Thread.sleep(50);
709        }
710        throw new IOException(
711          "Took too long to split the files and create the references, aborting split");
712      }
713    } catch (InterruptedException e) {
714      throw (InterruptedIOException) new InterruptedIOException().initCause(e);
715    }
716  }
717
718  private void deleteDaughterRegions(final MasterProcedureEnv env) throws IOException {
719    final MasterFileSystem mfs = env.getMasterServices().getMasterFileSystem();
720    final Path tabledir = CommonFSUtils.getTableDir(mfs.getRootDir(), getTableName());
721    HRegionFileSystem.deleteRegionFromFileSystem(env.getMasterConfiguration(), mfs.getFileSystem(),
722      tabledir, daughterOneRI);
723    HRegionFileSystem.deleteRegionFromFileSystem(env.getMasterConfiguration(), mfs.getFileSystem(),
724      tabledir, daughterTwoRI);
725  }
726
727  /**
728   * Create Split directory
729   * @param env MasterProcedureEnv
730   */
731  private Pair<List<StoreFileInfo>, List<StoreFileInfo>> splitStoreFiles(
732    final MasterProcedureEnv env, final HRegionFileSystem regionFs) throws IOException {
733    final Configuration conf = env.getMasterConfiguration();
734    TableDescriptor htd = env.getMasterServices().getTableDescriptors().get(getTableName());
735    // The following code sets up a thread pool executor with as many slots as
736    // there's files to split. It then fires up everything, waits for
737    // completion and finally checks for any exception
738    //
739    // Note: From HBASE-26187, splitStoreFiles now creates daughter region dirs straight under the
740    // table dir. In case of failure, the proc would go through this again, already existing
741    // region dirs and split files would just be ignored, new split files should get created.
742    int nbFiles = 0;
743    final Map<String, Collection<StoreFileInfo>> files =
744      new HashMap<String, Collection<StoreFileInfo>>(htd.getColumnFamilyCount());
745    for (ColumnFamilyDescriptor cfd : htd.getColumnFamilies()) {
746      String family = cfd.getNameAsString();
747      StoreFileTracker tracker =
748        StoreFileTrackerFactory.create(env.getMasterConfiguration(), htd, cfd, regionFs);
749      Collection<StoreFileInfo> sfis = tracker.load();
750      if (sfis == null) {
751        continue;
752      }
753      Collection<StoreFileInfo> filteredSfis = null;
754      for (StoreFileInfo sfi : sfis) {
755        // Filter. There is a lag cleaning up compacted reference files. They get cleared
756        // after a delay in case outstanding Scanners still have references. Because of this,
757        // the listing of the Store content may have straggler reference files. Skip these.
758        // It should be safe to skip references at this point because we checked above with
759        // the region if it thinks it is splittable and if we are here, it thinks it is
760        // splitable.
761        if (sfi.isReference()) {
762          LOG.info("Skipping split of " + sfi + "; presuming ready for archiving.");
763          continue;
764        }
765        if (filteredSfis == null) {
766          filteredSfis = new ArrayList<StoreFileInfo>(sfis.size());
767          files.put(family, filteredSfis);
768        }
769        filteredSfis.add(sfi);
770        nbFiles++;
771      }
772    }
773    if (nbFiles == 0) {
774      // no file needs to be splitted.
775      return new Pair<>(Collections.emptyList(), Collections.emptyList());
776    }
777    // Max #threads is the smaller of the number of storefiles or the default max determined above.
778    int maxThreads = Math.min(
779      conf.getInt(HConstants.REGION_SPLIT_THREADS_MAX,
780        conf.getInt(HStore.BLOCKING_STOREFILES_KEY, HStore.DEFAULT_BLOCKING_STOREFILE_COUNT)),
781      nbFiles);
782    LOG.info("pid=" + getProcId() + " splitting " + nbFiles + " storefiles, region="
783      + getParentRegion().getShortNameToLog() + ", threads=" + maxThreads);
784    final ExecutorService threadPool = Executors.newFixedThreadPool(maxThreads,
785      new ThreadFactoryBuilder().setNameFormat("StoreFileSplitter-pool-%d").setDaemon(true)
786        .setUncaughtExceptionHandler(Threads.LOGGING_EXCEPTION_HANDLER).build());
787    final List<Future<Pair<StoreFileInfo, StoreFileInfo>>> futures =
788      new ArrayList<Future<Pair<StoreFileInfo, StoreFileInfo>>>(nbFiles);
789
790    // Split each store file.
791    for (Map.Entry<String, Collection<StoreFileInfo>> e : files.entrySet()) {
792      byte[] familyName = Bytes.toBytes(e.getKey());
793      final ColumnFamilyDescriptor hcd = htd.getColumnFamily(familyName);
794      Collection<StoreFileInfo> storeFileInfos = e.getValue();
795      final Collection<StoreFileInfo> storeFiles = storeFileInfos;
796      if (storeFiles != null && storeFiles.size() > 0) {
797        final Configuration storeConfiguration =
798          StoreUtils.createStoreConfiguration(env.getMasterConfiguration(), htd, hcd);
799        for (StoreFileInfo storeFileInfo : storeFiles) {
800          // As this procedure is running on master, use CacheConfig.DISABLED means
801          // don't cache any block.
802          // We also need to pass through a suitable CompoundConfiguration as if this
803          // is running in a regionserver's Store context, or we might not be able
804          // to read the hfiles.
805          storeFileInfo.setConf(storeConfiguration);
806          StoreFileSplitter sfs = new StoreFileSplitter(regionFs, htd, hcd,
807            new HStoreFile(storeFileInfo, hcd.getBloomFilterType(), CacheConfig.DISABLED));
808          futures.add(threadPool.submit(sfs));
809        }
810      }
811    }
812    handleThreadPoolShutdown(threadPool, conf);
813    List<StoreFileInfo> daughterA = new ArrayList<>();
814    List<StoreFileInfo> daughterB = new ArrayList<>();
815    // Look for any exception
816    for (Future<Pair<StoreFileInfo, StoreFileInfo>> future : futures) {
817      try {
818        Pair<StoreFileInfo, StoreFileInfo> p = future.get();
819        if (p.getFirst() != null) {
820          daughterA.add(p.getFirst());
821        }
822        if (p.getSecond() != null) {
823          daughterB.add(p.getSecond());
824        }
825      } catch (InterruptedException e) {
826        throw (InterruptedIOException) new InterruptedIOException().initCause(e);
827      } catch (ExecutionException e) {
828        throw new IOException(e);
829      }
830    }
831
832    if (LOG.isDebugEnabled()) {
833      LOG.debug("pid=" + getProcId() + " split storefiles for region "
834        + getParentRegion().getShortNameToLog() + " Daughter A: " + daughterA
835        + " storefiles, Daughter B: " + daughterB + " storefiles.");
836    }
837    return new Pair<>(daughterA, daughterB);
838  }
839
840  private Pair<StoreFileInfo, StoreFileInfo> splitStoreFile(HRegionFileSystem regionFs,
841    TableDescriptor htd, ColumnFamilyDescriptor hcd, HStoreFile sf) throws IOException {
842    if (LOG.isDebugEnabled()) {
843      LOG.debug("pid=" + getProcId() + " splitting started for store file: " + sf.getPath()
844        + " for region: " + getParentRegion().getShortNameToLog());
845    }
846
847    final byte[] splitRow = getSplitRow();
848    final String familyName = hcd.getNameAsString();
849    StoreFileTracker daughterOneSft =
850      StoreFileTrackerFactory.create(regionFs.getFileSystem().getConf(), htd, hcd,
851        HRegionFileSystem.create(regionFs.getFileSystem().getConf(), regionFs.getFileSystem(),
852          regionFs.getTableDir(), daughterOneRI));
853    StoreFileTracker daughterTwoSft =
854      StoreFileTrackerFactory.create(regionFs.getFileSystem().getConf(), htd, hcd,
855        HRegionFileSystem.create(regionFs.getFileSystem().getConf(), regionFs.getFileSystem(),
856          regionFs.getTableDir(), daughterTwoRI));
857    final StoreFileInfo sfiFirst = regionFs.splitStoreFile(this.daughterOneRI, familyName, sf,
858      splitRow, false, splitPolicy, daughterOneSft);
859    final StoreFileInfo sfiSecond = regionFs.splitStoreFile(this.daughterTwoRI, familyName, sf,
860      splitRow, true, splitPolicy, daughterTwoSft);
861    if (LOG.isDebugEnabled()) {
862      LOG.debug("pid=" + getProcId() + " splitting complete for store file: " + sf.getPath()
863        + " for region: " + getParentRegion().getShortNameToLog());
864    }
865    return new Pair<StoreFileInfo, StoreFileInfo>(sfiFirst, sfiSecond);
866  }
867
868  /**
869   * Utility class used to do the file splitting / reference writing in parallel instead of
870   * sequentially.
871   */
872  private class StoreFileSplitter implements Callable<Pair<StoreFileInfo, StoreFileInfo>> {
873    private final HRegionFileSystem regionFs;
874    private final ColumnFamilyDescriptor hcd;
875    private final HStoreFile sf;
876    private final TableDescriptor htd;
877
878    /**
879     * Constructor that takes what it needs to split
880     * @param regionFs the file system
881     * @param hcd      Family that contains the store file
882     * @param sf       which file
883     */
884    public StoreFileSplitter(HRegionFileSystem regionFs, TableDescriptor htd,
885      ColumnFamilyDescriptor hcd, HStoreFile sf) {
886      this.regionFs = regionFs;
887      this.sf = sf;
888      this.hcd = hcd;
889      this.htd = htd;
890    }
891
892    @Override
893    public Pair<StoreFileInfo, StoreFileInfo> call() throws IOException {
894      return splitStoreFile(regionFs, htd, hcd, sf);
895    }
896  }
897
898  /**
899   * Post split region actions before the Point-of-No-Return step
900   * @param env MasterProcedureEnv
901   **/
902  private void preSplitRegionBeforeMETA(final MasterProcedureEnv env)
903    throws IOException, InterruptedException {
904    final List<Mutation> metaEntries = new ArrayList<Mutation>();
905    final MasterCoprocessorHost cpHost = env.getMasterCoprocessorHost();
906    if (cpHost != null) {
907      cpHost.preSplitBeforeMETAAction(getSplitRow(), metaEntries, getUser());
908      try {
909        for (Mutation p : metaEntries) {
910          RegionInfo.parseRegionName(p.getRow());
911        }
912      } catch (IOException e) {
913        LOG.error(
914          "pid={} row key of mutation from coprocessor not parsable as region name. "
915            + "Mutations from coprocessor should only be for {} table.",
916          getProcId(), TableName.META_TABLE_NAME);
917        throw e;
918      }
919    }
920  }
921
922  /**
923   * Add daughter regions to META
924   * @param env MasterProcedureEnv
925   */
926  private void updateMeta(final MasterProcedureEnv env) throws IOException {
927    env.getAssignmentManager().markRegionAsSplit(getParentRegion(), getParentRegionServerName(env),
928      daughterOneRI, daughterTwoRI);
929  }
930
931  /**
932   * Pre split region actions after the Point-of-No-Return step
933   * @param env MasterProcedureEnv
934   **/
935  private void preSplitRegionAfterMETA(final MasterProcedureEnv env)
936    throws IOException, InterruptedException {
937    final MasterCoprocessorHost cpHost = env.getMasterCoprocessorHost();
938    if (cpHost != null) {
939      cpHost.preSplitAfterMETAAction(getUser());
940    }
941  }
942
943  /**
944   * Post split region actions
945   * @param env MasterProcedureEnv
946   **/
947  private void postSplitRegion(final MasterProcedureEnv env) throws IOException {
948    final MasterCoprocessorHost cpHost = env.getMasterCoprocessorHost();
949    if (cpHost != null) {
950      cpHost.postCompletedSplitRegionAction(daughterOneRI, daughterTwoRI, getUser());
951    }
952  }
953
954  private ServerName getParentRegionServerName(final MasterProcedureEnv env) {
955    return env.getMasterServices().getAssignmentManager().getRegionStates()
956      .getRegionServerOfRegion(getParentRegion());
957  }
958
959  private TransitRegionStateProcedure[] createUnassignProcedures(MasterProcedureEnv env)
960    throws IOException {
961    return AssignmentManagerUtil.createUnassignProceduresForSplitOrMerge(env,
962      Stream.of(getParentRegion()), getRegionReplication(env));
963  }
964
965  private TransitRegionStateProcedure[] createAssignProcedures(MasterProcedureEnv env)
966    throws IOException {
967    List<RegionInfo> hris = new ArrayList<RegionInfo>(2);
968    hris.add(daughterOneRI);
969    hris.add(daughterTwoRI);
970    return AssignmentManagerUtil.createAssignProceduresForSplitDaughters(env, hris,
971      getRegionReplication(env), getParentRegionServerName(env));
972  }
973
974  private int getRegionReplication(final MasterProcedureEnv env) throws IOException {
975    final TableDescriptor htd = env.getMasterServices().getTableDescriptors().get(getTableName());
976    return htd.getRegionReplication();
977  }
978
979  private void writeMaxSequenceIdFile(MasterProcedureEnv env) throws IOException {
980    MasterFileSystem fs = env.getMasterFileSystem();
981    long maxSequenceId = WALSplitUtil.getMaxRegionSequenceId(env.getMasterConfiguration(),
982      getParentRegion(), fs::getFileSystem, fs::getWALFileSystem);
983    if (maxSequenceId > 0) {
984      WALSplitUtil.writeRegionSequenceIdFile(fs.getWALFileSystem(),
985        getWALRegionDir(env, daughterOneRI), maxSequenceId);
986      WALSplitUtil.writeRegionSequenceIdFile(fs.getWALFileSystem(),
987        getWALRegionDir(env, daughterTwoRI), maxSequenceId);
988    }
989  }
990
991  @Override
992  protected boolean abort(MasterProcedureEnv env) {
993    // Abort means rollback. We can't rollback all steps. HBASE-18018 added abort to all
994    // Procedures. Here is a Procedure that has a PONR and cannot be aborted wants it enters this
995    // range of steps; what do we do for these should an operator want to cancel them? HBASE-20022.
996    return isRollbackSupported(getCurrentState()) ? super.abort(env) : false;
997  }
998}