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}