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.util.ArrayList; 022import java.util.Arrays; 023import java.util.Collection; 024import java.util.Collections; 025import java.util.List; 026import java.util.stream.Stream; 027import org.apache.hadoop.conf.Configuration; 028import org.apache.hadoop.fs.FileSystem; 029import org.apache.hadoop.fs.Path; 030import org.apache.hadoop.hbase.MetaMutationAnnotation; 031import org.apache.hadoop.hbase.ServerName; 032import org.apache.hadoop.hbase.TableName; 033import org.apache.hadoop.hbase.UnknownRegionException; 034import org.apache.hadoop.hbase.client.ColumnFamilyDescriptor; 035import org.apache.hadoop.hbase.client.DoNotRetryRegionException; 036import org.apache.hadoop.hbase.client.MasterSwitchType; 037import org.apache.hadoop.hbase.client.Mutation; 038import org.apache.hadoop.hbase.client.RegionInfo; 039import org.apache.hadoop.hbase.client.RegionInfoBuilder; 040import org.apache.hadoop.hbase.client.TableDescriptor; 041import org.apache.hadoop.hbase.exceptions.MergeRegionException; 042import org.apache.hadoop.hbase.io.hfile.CacheConfig; 043import org.apache.hadoop.hbase.master.CatalogJanitor; 044import org.apache.hadoop.hbase.master.MasterCoprocessorHost; 045import org.apache.hadoop.hbase.master.MasterFileSystem; 046import org.apache.hadoop.hbase.master.RegionState; 047import org.apache.hadoop.hbase.master.RegionState.State; 048import org.apache.hadoop.hbase.master.normalizer.NormalizationPlan; 049import org.apache.hadoop.hbase.master.procedure.AbstractStateMachineTableProcedure; 050import org.apache.hadoop.hbase.master.procedure.MasterProcedureEnv; 051import org.apache.hadoop.hbase.master.procedure.MasterProcedureUtil; 052import org.apache.hadoop.hbase.procedure2.ProcedureMetrics; 053import org.apache.hadoop.hbase.procedure2.ProcedureStateSerializer; 054import org.apache.hadoop.hbase.quotas.QuotaExceededException; 055import org.apache.hadoop.hbase.regionserver.HRegionFileSystem; 056import org.apache.hadoop.hbase.regionserver.HStoreFile; 057import org.apache.hadoop.hbase.regionserver.StoreFileInfo; 058import org.apache.hadoop.hbase.util.Bytes; 059import org.apache.hadoop.hbase.util.FSUtils; 060import org.apache.hadoop.hbase.wal.WALSplitUtil; 061import org.apache.yetus.audience.InterfaceAudience; 062import org.slf4j.Logger; 063import org.slf4j.LoggerFactory; 064 065import org.apache.hbase.thirdparty.com.google.common.annotations.VisibleForTesting; 066 067import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil; 068import org.apache.hadoop.hbase.shaded.protobuf.generated.AdminProtos.GetRegionInfoResponse; 069import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProcedureProtos; 070import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProcedureProtos.MergeTableRegionsState; 071 072/** 073 * The procedure to Merge regions in a table. This procedure takes an exclusive table 074 * lock since it is working over multiple regions. It holds the lock for the life of the procedure. 075 * Throws exception on construction if determines context hostile to merge (cluster going down or 076 * master is shutting down or table is disabled). 077 */ 078@InterfaceAudience.Private 079public class MergeTableRegionsProcedure 080 extends AbstractStateMachineTableProcedure<MergeTableRegionsState> { 081 private static final Logger LOG = LoggerFactory.getLogger(MergeTableRegionsProcedure.class); 082 private ServerName regionLocation; 083 084 /** 085 * Two or more regions to merge, the 'merge parents'. 086 */ 087 private RegionInfo[] regionsToMerge; 088 089 /** 090 * The resulting merged region. 091 */ 092 private RegionInfo mergedRegion; 093 094 private boolean force; 095 096 public MergeTableRegionsProcedure() { 097 // Required by the Procedure framework to create the procedure on replay 098 } 099 100 public MergeTableRegionsProcedure(final MasterProcedureEnv env, 101 final RegionInfo[] regionsToMerge, final boolean force) 102 throws IOException { 103 super(env); 104 // Check parent regions. Make sure valid before starting work. 105 // This check calls the super method #checkOnline also. 106 checkRegionsToMerge(env, regionsToMerge, force); 107 // Sort the regions going into the merge. 108 Arrays.sort(regionsToMerge); 109 this.regionsToMerge = regionsToMerge; 110 this.mergedRegion = createMergedRegionInfo(regionsToMerge); 111 // Preflight depends on mergedRegion being set (at least). 112 preflightChecks(env, true); 113 this.force = force; 114 } 115 116 /** 117 * @throws MergeRegionException If unable to merge regions for whatever reasons. 118 */ 119 private static void checkRegionsToMerge(MasterProcedureEnv env, final RegionInfo[] regions, 120 final boolean force) throws MergeRegionException { 121 long count = Arrays.stream(regions).distinct().count(); 122 if (regions.length != count) { 123 throw new MergeRegionException("Duplicate regions specified; cannot merge a region to " + 124 "itself. Passed in " + regions.length + " but only " + count + " unique."); 125 } 126 if (count < 2) { 127 throw new MergeRegionException("Need two Regions at least to run a Merge"); 128 } 129 RegionInfo previous = null; 130 for (RegionInfo ri: regions) { 131 if (previous != null) { 132 if (!previous.getTable().equals(ri.getTable())) { 133 String msg = "Can't merge regions from different tables: " + previous + ", " + ri; 134 LOG.warn(msg); 135 throw new MergeRegionException(msg); 136 } 137 if (!force && !ri.isAdjacent(previous) && !ri.isOverlap(previous)) { 138 String msg = "Unable to merge non-adjacent or non-overlapping regions " + 139 previous.getShortNameToLog() + ", " + ri.getShortNameToLog() + " when force=false"; 140 LOG.warn(msg); 141 throw new MergeRegionException(msg); 142 } 143 } 144 145 if (ri.getReplicaId() != RegionInfo.DEFAULT_REPLICA_ID) { 146 throw new MergeRegionException("Can't merge non-default replicas; " + ri); 147 } 148 try { 149 checkOnline(env, ri); 150 } catch (DoNotRetryRegionException dnrre) { 151 throw new MergeRegionException(dnrre); 152 } 153 154 previous = ri; 155 } 156 } 157 158 /** 159 * Create merged region info by looking at passed in <code>regionsToMerge</code> 160 * to figure what extremes for start and end keys to use; merged region needs 161 * to have an extent sufficient to cover all regions-to-merge. 162 */ 163 private static RegionInfo createMergedRegionInfo(final RegionInfo[] regionsToMerge) { 164 byte [] lowestStartKey = null; 165 byte [] highestEndKey = null; 166 // Region Id is a timestamp. Merged region's id can't be less than that of 167 // merging regions else will insert at wrong location in hbase:meta (See HBASE-710). 168 long highestRegionId = -1; 169 for (RegionInfo ri: regionsToMerge) { 170 if (lowestStartKey == null) { 171 lowestStartKey = ri.getStartKey(); 172 } else if (Bytes.compareTo(ri.getStartKey(), lowestStartKey) < 0) { 173 lowestStartKey = ri.getStartKey(); 174 } 175 if (highestEndKey == null) { 176 highestEndKey = ri.getEndKey(); 177 } else if (ri.isLast() || Bytes.compareTo(ri.getEndKey(), highestEndKey) > 0) { 178 highestEndKey = ri.getEndKey(); 179 } 180 highestRegionId = ri.getRegionId() > highestRegionId? ri.getRegionId(): highestRegionId; 181 } 182 // Merged region is sorted between two merging regions in META 183 return RegionInfoBuilder.newBuilder(regionsToMerge[0].getTable()). 184 setStartKey(lowestStartKey). 185 setEndKey(highestEndKey). 186 setSplit(false). 187 setRegionId(highestRegionId + 1/*Add one so new merged region is highest*/). 188 build(); 189 } 190 191 @Override 192 protected Flow executeFromState(final MasterProcedureEnv env, 193 MergeTableRegionsState state) { 194 LOG.trace("{} execute state={}", this, state); 195 try { 196 switch (state) { 197 case MERGE_TABLE_REGIONS_PREPARE: 198 if (!prepareMergeRegion(env)) { 199 assert isFailed() : "Merge region should have an exception here"; 200 return Flow.NO_MORE_STATE; 201 } 202 setNextState(MergeTableRegionsState.MERGE_TABLE_REGIONS_PRE_MERGE_OPERATION); 203 break; 204 case MERGE_TABLE_REGIONS_PRE_MERGE_OPERATION: 205 preMergeRegions(env); 206 setNextState(MergeTableRegionsState.MERGE_TABLE_REGIONS_CLOSE_REGIONS); 207 break; 208 case MERGE_TABLE_REGIONS_CLOSE_REGIONS: 209 addChildProcedure(createUnassignProcedures(env)); 210 setNextState(MergeTableRegionsState.MERGE_TABLE_REGIONS_CHECK_CLOSED_REGIONS); 211 break; 212 case MERGE_TABLE_REGIONS_CHECK_CLOSED_REGIONS: 213 checkClosedRegions(env); 214 setNextState(MergeTableRegionsState.MERGE_TABLE_REGIONS_CREATE_MERGED_REGION); 215 break; 216 case MERGE_TABLE_REGIONS_CREATE_MERGED_REGION: 217 removeNonDefaultReplicas(env); 218 createMergedRegion(env); 219 setNextState(MergeTableRegionsState.MERGE_TABLE_REGIONS_WRITE_MAX_SEQUENCE_ID_FILE); 220 break; 221 case MERGE_TABLE_REGIONS_WRITE_MAX_SEQUENCE_ID_FILE: 222 writeMaxSequenceIdFile(env); 223 setNextState(MergeTableRegionsState.MERGE_TABLE_REGIONS_PRE_MERGE_COMMIT_OPERATION); 224 break; 225 case MERGE_TABLE_REGIONS_PRE_MERGE_COMMIT_OPERATION: 226 preMergeRegionsCommit(env); 227 setNextState(MergeTableRegionsState.MERGE_TABLE_REGIONS_UPDATE_META); 228 break; 229 case MERGE_TABLE_REGIONS_UPDATE_META: 230 updateMetaForMergedRegions(env); 231 setNextState(MergeTableRegionsState.MERGE_TABLE_REGIONS_POST_MERGE_COMMIT_OPERATION); 232 break; 233 case MERGE_TABLE_REGIONS_POST_MERGE_COMMIT_OPERATION: 234 postMergeRegionsCommit(env); 235 setNextState(MergeTableRegionsState.MERGE_TABLE_REGIONS_OPEN_MERGED_REGION); 236 break; 237 case MERGE_TABLE_REGIONS_OPEN_MERGED_REGION: 238 addChildProcedure(createAssignProcedures(env)); 239 setNextState(MergeTableRegionsState.MERGE_TABLE_REGIONS_POST_OPERATION); 240 break; 241 case MERGE_TABLE_REGIONS_POST_OPERATION: 242 postCompletedMergeRegions(env); 243 return Flow.NO_MORE_STATE; 244 default: 245 throw new UnsupportedOperationException(this + " unhandled state=" + state); 246 } 247 } catch (IOException e) { 248 String msg = "Error trying to merge " + RegionInfo.getShortNameToLog(regionsToMerge) + 249 " in " + getTableName() + " (in state=" + state + ")"; 250 if (!isRollbackSupported(state)) { 251 // We reach a state that cannot be rolled back. We just need to keep retrying. 252 LOG.warn(msg, e); 253 } else { 254 LOG.error(msg, e); 255 setFailure("master-merge-regions", e); 256 } 257 } 258 return Flow.HAS_MORE_STATE; 259 } 260 261 /** 262 * To rollback {@link MergeTableRegionsProcedure}, two AssignProcedures are asynchronously 263 * submitted for each region to be merged (rollback doesn't wait on the completion of the 264 * AssignProcedures) . This can be improved by changing rollback() to support sub-procedures. 265 * See HBASE-19851 for details. 266 */ 267 @Override 268 protected void rollbackState(final MasterProcedureEnv env, final MergeTableRegionsState state) 269 throws IOException { 270 LOG.trace("{} rollback state={}", this, state); 271 272 try { 273 switch (state) { 274 case MERGE_TABLE_REGIONS_POST_OPERATION: 275 case MERGE_TABLE_REGIONS_OPEN_MERGED_REGION: 276 case MERGE_TABLE_REGIONS_POST_MERGE_COMMIT_OPERATION: 277 case MERGE_TABLE_REGIONS_UPDATE_META: 278 String msg = this + " We are in the " + state + " state." + 279 " It is complicated to rollback the merge operation that region server is working on." + 280 " Rollback is not supported and we should let the merge operation to complete"; 281 LOG.warn(msg); 282 // PONR 283 throw new UnsupportedOperationException(this + " unhandled state=" + state); 284 case MERGE_TABLE_REGIONS_PRE_MERGE_COMMIT_OPERATION: 285 break; 286 case MERGE_TABLE_REGIONS_CREATE_MERGED_REGION: 287 case MERGE_TABLE_REGIONS_WRITE_MAX_SEQUENCE_ID_FILE: 288 cleanupMergedRegion(env); 289 break; 290 case MERGE_TABLE_REGIONS_CHECK_CLOSED_REGIONS: 291 break; 292 case MERGE_TABLE_REGIONS_CLOSE_REGIONS: 293 rollbackCloseRegionsForMerge(env); 294 break; 295 case MERGE_TABLE_REGIONS_PRE_MERGE_OPERATION: 296 postRollBackMergeRegions(env); 297 break; 298 case MERGE_TABLE_REGIONS_PREPARE: 299 break; 300 default: 301 throw new UnsupportedOperationException(this + " unhandled state=" + state); 302 } 303 } catch (Exception e) { 304 // This will be retried. Unless there is a bug in the code, 305 // this should be just a "temporary error" (e.g. network down) 306 LOG.warn("Failed rollback attempt step " + state + " for merging the regions " 307 + RegionInfo.getShortNameToLog(regionsToMerge) + " in table " + getTableName(), e); 308 throw e; 309 } 310 } 311 312 /* 313 * Check whether we are in the state that can be rolled back 314 */ 315 @Override 316 protected boolean isRollbackSupported(final MergeTableRegionsState state) { 317 switch (state) { 318 case MERGE_TABLE_REGIONS_POST_OPERATION: 319 case MERGE_TABLE_REGIONS_OPEN_MERGED_REGION: 320 case MERGE_TABLE_REGIONS_POST_MERGE_COMMIT_OPERATION: 321 case MERGE_TABLE_REGIONS_UPDATE_META: 322 // It is not safe to rollback in these states. 323 return false; 324 default: 325 break; 326 } 327 return true; 328 } 329 330 private void removeNonDefaultReplicas(MasterProcedureEnv env) throws IOException { 331 AssignmentManagerUtil.removeNonDefaultReplicas(env, Stream.of(regionsToMerge), 332 getRegionReplication(env)); 333 } 334 335 private void checkClosedRegions(MasterProcedureEnv env) throws IOException { 336 // Theoretically this should not happen any more after we use TRSP, but anyway 337 // let's add a check here 338 for (RegionInfo region : regionsToMerge) { 339 AssignmentManagerUtil.checkClosedRegion(env, region); 340 } 341 } 342 343 @Override 344 protected MergeTableRegionsState getState(final int stateId) { 345 return MergeTableRegionsState.forNumber(stateId); 346 } 347 348 @Override 349 protected int getStateId(final MergeTableRegionsState state) { 350 return state.getNumber(); 351 } 352 353 @Override 354 protected MergeTableRegionsState getInitialState() { 355 return MergeTableRegionsState.MERGE_TABLE_REGIONS_PREPARE; 356 } 357 358 @Override 359 protected void serializeStateData(ProcedureStateSerializer serializer) 360 throws IOException { 361 super.serializeStateData(serializer); 362 363 final MasterProcedureProtos.MergeTableRegionsStateData.Builder mergeTableRegionsMsg = 364 MasterProcedureProtos.MergeTableRegionsStateData.newBuilder() 365 .setUserInfo(MasterProcedureUtil.toProtoUserInfo(getUser())) 366 .setMergedRegionInfo(ProtobufUtil.toRegionInfo(mergedRegion)) 367 .setForcible(force); 368 for (RegionInfo ri: regionsToMerge) { 369 mergeTableRegionsMsg.addRegionInfo(ProtobufUtil.toRegionInfo(ri)); 370 } 371 serializer.serialize(mergeTableRegionsMsg.build()); 372 } 373 374 @Override 375 protected void deserializeStateData(ProcedureStateSerializer serializer) 376 throws IOException { 377 super.deserializeStateData(serializer); 378 379 final MasterProcedureProtos.MergeTableRegionsStateData mergeTableRegionsMsg = 380 serializer.deserialize(MasterProcedureProtos.MergeTableRegionsStateData.class); 381 setUser(MasterProcedureUtil.toUserInfo(mergeTableRegionsMsg.getUserInfo())); 382 383 assert(mergeTableRegionsMsg.getRegionInfoCount() == 2); 384 regionsToMerge = new RegionInfo[mergeTableRegionsMsg.getRegionInfoCount()]; 385 for (int i = 0; i < regionsToMerge.length; i++) { 386 regionsToMerge[i] = ProtobufUtil.toRegionInfo(mergeTableRegionsMsg.getRegionInfo(i)); 387 } 388 389 mergedRegion = ProtobufUtil.toRegionInfo(mergeTableRegionsMsg.getMergedRegionInfo()); 390 } 391 392 @Override 393 public void toStringClassDetails(StringBuilder sb) { 394 sb.append(getClass().getSimpleName()); 395 sb.append(" table="); 396 sb.append(getTableName()); 397 sb.append(", regions="); 398 sb.append(RegionInfo.getShortNameToLog(regionsToMerge)); 399 sb.append(", force="); 400 sb.append(force); 401 } 402 403 @Override 404 protected LockState acquireLock(final MasterProcedureEnv env) { 405 RegionInfo[] lockRegions = Arrays.copyOf(regionsToMerge, regionsToMerge.length + 1); 406 lockRegions[lockRegions.length - 1] = mergedRegion; 407 408 if (env.getProcedureScheduler().waitRegions(this, getTableName(), lockRegions)) { 409 try { 410 LOG.debug(LockState.LOCK_EVENT_WAIT + " " + env.getProcedureScheduler().dumpLocks()); 411 } catch (IOException e) { 412 // Ignore, just for logging 413 } 414 return LockState.LOCK_EVENT_WAIT; 415 } 416 return LockState.LOCK_ACQUIRED; 417 } 418 419 @Override 420 protected void releaseLock(final MasterProcedureEnv env) { 421 RegionInfo[] lockRegions = Arrays.copyOf(regionsToMerge, regionsToMerge.length + 1); 422 lockRegions[lockRegions.length - 1] = mergedRegion; 423 424 env.getProcedureScheduler().wakeRegions(this, getTableName(), lockRegions); 425 } 426 427 @Override 428 protected boolean holdLock(MasterProcedureEnv env) { 429 return true; 430 } 431 432 @Override 433 public TableName getTableName() { 434 return mergedRegion.getTable(); 435 } 436 437 @Override 438 public TableOperationType getTableOperationType() { 439 return TableOperationType.REGION_MERGE; 440 } 441 442 @Override 443 protected ProcedureMetrics getProcedureMetrics(MasterProcedureEnv env) { 444 return env.getAssignmentManager().getAssignmentManagerMetrics().getMergeProcMetrics(); 445 } 446 447 /** 448 * Prepare merge and do some check 449 */ 450 private boolean prepareMergeRegion(final MasterProcedureEnv env) throws IOException { 451 // Fail if we are taking snapshot for the given table 452 TableName tn = regionsToMerge[0].getTable(); 453 if (env.getMasterServices().getSnapshotManager().isTakingSnapshot(tn)) { 454 throw new MergeRegionException("Skip merging regions " + 455 RegionInfo.getShortNameToLog(regionsToMerge) + ", because we are snapshotting " + tn); 456 } 457 if (!env.getMasterServices().isSplitOrMergeEnabled(MasterSwitchType.MERGE)) { 458 String regionsStr = Arrays.deepToString(this.regionsToMerge); 459 LOG.warn("Merge switch is off! skip merge of " + regionsStr); 460 super.setFailure(getClass().getSimpleName(), 461 new IOException("Merge of " + regionsStr + " failed because merge switch is off")); 462 return false; 463 } 464 465 if (!env.getMasterServices().getTableDescriptors().get(getTableName()).isMergeEnabled()) { 466 String regionsStr = Arrays.deepToString(regionsToMerge); 467 LOG.warn("Merge is disabled for the table! Skipping merge of {}", regionsStr); 468 super.setFailure(getClass().getSimpleName(), new IOException( 469 "Merge of " + regionsStr + " failed as region merge is disabled for the table")); 470 return false; 471 } 472 473 CatalogJanitor catalogJanitor = env.getMasterServices().getCatalogJanitor(); 474 RegionStates regionStates = env.getAssignmentManager().getRegionStates(); 475 for (RegionInfo ri: this.regionsToMerge) { 476 if (!catalogJanitor.cleanMergeQualifier(ri)) { 477 String msg = "Skip merging " + RegionInfo.getShortNameToLog(regionsToMerge) + 478 ", because parent " + RegionInfo.getShortNameToLog(ri) + " has a merge qualifier"; 479 LOG.warn(msg); 480 throw new MergeRegionException(msg); 481 } 482 RegionState state = regionStates.getRegionState(ri.getEncodedName()); 483 if (state == null) { 484 throw new UnknownRegionException("No state for " + RegionInfo.getShortNameToLog(ri)); 485 } 486 if (!state.isOpened()) { 487 throw new MergeRegionException("Unable to merge regions that are not online: " + ri); 488 } 489 // Ask the remote regionserver if regions are mergeable. If we get an IOE, report it 490 // along with the failure, so we can see why regions are not mergeable at this time. 491 try { 492 if (!isMergeable(env, state)) { 493 return false; 494 } 495 } catch (IOException e) { 496 IOException ioe = new IOException(RegionInfo.getShortNameToLog(ri) + " NOT mergeable", e); 497 super.setFailure(getClass().getSimpleName(), ioe); 498 return false; 499 } 500 } 501 502 // Update region states to Merging 503 setRegionStateToMerging(env); 504 return true; 505 } 506 507 private boolean isMergeable(final MasterProcedureEnv env, final RegionState rs) 508 throws IOException { 509 GetRegionInfoResponse response = 510 AssignmentManagerUtil.getRegionInfoResponse(env, rs.getServerName(), rs.getRegion()); 511 return response.hasMergeable() && response.getMergeable(); 512 } 513 514 /** 515 * Pre merge region action 516 * @param env MasterProcedureEnv 517 **/ 518 private void preMergeRegions(final MasterProcedureEnv env) throws IOException { 519 final MasterCoprocessorHost cpHost = env.getMasterCoprocessorHost(); 520 if (cpHost != null) { 521 cpHost.preMergeRegionsAction(regionsToMerge, getUser()); 522 } 523 // TODO: Clean up split and merge. Currently all over the place. 524 try { 525 env.getMasterServices().getMasterQuotaManager().onRegionMerged(this.mergedRegion); 526 } catch (QuotaExceededException e) { 527 env.getMasterServices().getRegionNormalizer().planSkipped(this.mergedRegion, 528 NormalizationPlan.PlanType.MERGE); 529 throw e; 530 } 531 } 532 533 /** 534 * Action after rollback a merge table regions action. 535 */ 536 private void postRollBackMergeRegions(final MasterProcedureEnv env) throws IOException { 537 final MasterCoprocessorHost cpHost = env.getMasterCoprocessorHost(); 538 if (cpHost != null) { 539 cpHost.postRollBackMergeRegionsAction(regionsToMerge, getUser()); 540 } 541 } 542 543 /** 544 * Set the region states to MERGING state 545 */ 546 private void setRegionStateToMerging(final MasterProcedureEnv env) { 547 // Set State.MERGING to regions to be merged 548 RegionStates regionStates = env.getAssignmentManager().getRegionStates(); 549 for (RegionInfo ri: this.regionsToMerge) { 550 regionStates.getRegionStateNode(ri).setState(State.MERGING); 551 } 552 } 553 554 /** 555 * Create merged region. 556 * The way the merge works is that we make a 'merges' temporary 557 * directory in the FIRST parent region to merge (Do not change this without 558 * also changing the rollback where we look in this FIRST region for the 559 * merge dir). We then collect here references to all the store files in all 560 * the parent regions including those of the FIRST parent region into a 561 * subdirectory, named for the resultant merged region. We then call 562 * commitMergeRegion. It finds this subdirectory of storefile references 563 * and moves them under the new merge region (creating the region layout 564 * as side effect). After assign of the new merge region, we will run a 565 * compaction. This will undo the references but the reference files remain 566 * in place until the archiver runs (which it does on a period as a chore 567 * in the RegionServer that hosts the merge region -- see 568 * CompactedHFilesDischarger). Once the archiver has moved aside the 569 * no-longer used references, the merge region no longer has references. 570 * The catalog janitor will notice when it runs next and it will remove 571 * the old parent regions. 572 */ 573 private void createMergedRegion(final MasterProcedureEnv env) throws IOException { 574 final MasterFileSystem mfs = env.getMasterServices().getMasterFileSystem(); 575 final Path tabledir = FSUtils.getTableDir(mfs.getRootDir(), regionsToMerge[0].getTable()); 576 final FileSystem fs = mfs.getFileSystem(); 577 HRegionFileSystem mergeRegionFs = null; 578 for (RegionInfo ri: this.regionsToMerge) { 579 HRegionFileSystem regionFs = HRegionFileSystem.openRegionFromFileSystem( 580 env.getMasterConfiguration(), fs, tabledir, ri, false); 581 if (mergeRegionFs == null) { 582 mergeRegionFs = regionFs; 583 mergeRegionFs.createMergesDir(); 584 } 585 mergeStoreFiles(env, regionFs, mergeRegionFs.getMergesDir()); 586 } 587 assert mergeRegionFs != null; 588 mergeRegionFs.commitMergedRegion(mergedRegion); 589 590 // Prepare to create merged regions 591 env.getAssignmentManager().getRegionStates(). 592 getOrCreateRegionStateNode(mergedRegion).setState(State.MERGING_NEW); 593 } 594 595 /** 596 * Create reference file(s) to parent region hfiles in the <code>mergeDir</code> 597 * @param regionFs merge parent region file system 598 * @param mergeDir the temp directory in which we are accumulating references. 599 */ 600 private void mergeStoreFiles(final MasterProcedureEnv env, final HRegionFileSystem regionFs, 601 final Path mergeDir) throws IOException { 602 final MasterFileSystem mfs = env.getMasterServices().getMasterFileSystem(); 603 final Configuration conf = env.getMasterConfiguration(); 604 final TableDescriptor htd = env.getMasterServices().getTableDescriptors().get(getTableName()); 605 for (ColumnFamilyDescriptor hcd : htd.getColumnFamilies()) { 606 String family = hcd.getNameAsString(); 607 final Collection<StoreFileInfo> storeFiles = regionFs.getStoreFiles(family); 608 if (storeFiles != null && storeFiles.size() > 0) { 609 for (StoreFileInfo storeFileInfo : storeFiles) { 610 // Create reference file(s) to parent region file here in mergedDir. 611 // As this procedure is running on master, use CacheConfig.DISABLED means 612 // don't cache any block. 613 regionFs.mergeStoreFile(mergedRegion, family, new HStoreFile(mfs.getFileSystem(), 614 storeFileInfo, conf, CacheConfig.DISABLED, hcd.getBloomFilterType(), true), 615 mergeDir); 616 } 617 } 618 } 619 } 620 621 /** 622 * Clean up a merged region on rollback after failure. 623 */ 624 private void cleanupMergedRegion(final MasterProcedureEnv env) throws IOException { 625 final MasterFileSystem mfs = env.getMasterServices().getMasterFileSystem(); 626 TableName tn = this.regionsToMerge[0].getTable(); 627 final Path tabledir = FSUtils.getTableDir(mfs.getRootDir(), tn); 628 final FileSystem fs = mfs.getFileSystem(); 629 // See createMergedRegion above where we specify the merge dir as being in the 630 // FIRST merge parent region. 631 HRegionFileSystem regionFs = HRegionFileSystem.openRegionFromFileSystem( 632 env.getMasterConfiguration(), fs, tabledir, regionsToMerge[0], false); 633 regionFs.cleanupMergedRegion(mergedRegion); 634 } 635 636 /** 637 * Rollback close regions 638 **/ 639 private void rollbackCloseRegionsForMerge(MasterProcedureEnv env) throws IOException { 640 AssignmentManagerUtil.reopenRegionsForRollback(env, Arrays.asList(regionsToMerge), 641 getRegionReplication(env), getServerName(env)); 642 } 643 644 private TransitRegionStateProcedure[] createUnassignProcedures(MasterProcedureEnv env) 645 throws IOException { 646 return AssignmentManagerUtil.createUnassignProceduresForSplitOrMerge(env, 647 Stream.of(regionsToMerge), getRegionReplication(env)); 648 } 649 650 private TransitRegionStateProcedure[] createAssignProcedures(MasterProcedureEnv env) 651 throws IOException { 652 return AssignmentManagerUtil.createAssignProceduresForOpeningNewRegions(env, 653 Collections.singletonList(mergedRegion), getRegionReplication(env), getServerName(env)); 654 } 655 656 private int getRegionReplication(final MasterProcedureEnv env) throws IOException { 657 return env.getMasterServices().getTableDescriptors().get(getTableName()). 658 getRegionReplication(); 659 } 660 661 /** 662 * Post merge region action 663 * @param env MasterProcedureEnv 664 **/ 665 private void preMergeRegionsCommit(final MasterProcedureEnv env) throws IOException { 666 final MasterCoprocessorHost cpHost = env.getMasterCoprocessorHost(); 667 if (cpHost != null) { 668 @MetaMutationAnnotation 669 final List<Mutation> metaEntries = new ArrayList<>(); 670 cpHost.preMergeRegionsCommit(regionsToMerge, metaEntries, getUser()); 671 try { 672 for (Mutation p : metaEntries) { 673 RegionInfo.parseRegionName(p.getRow()); 674 } 675 } catch (IOException e) { 676 LOG.error("Row key of mutation from coprocessor is not parsable as region name. " 677 + "Mutations from coprocessor should only be for hbase:meta table.", e); 678 throw e; 679 } 680 } 681 } 682 683 /** 684 * Add merged region to META and delete original regions. 685 */ 686 private void updateMetaForMergedRegions(final MasterProcedureEnv env) throws IOException { 687 env.getAssignmentManager().markRegionAsMerged(mergedRegion, getServerName(env), 688 this.regionsToMerge); 689 } 690 691 /** 692 * Post merge region action 693 * @param env MasterProcedureEnv 694 **/ 695 private void postMergeRegionsCommit(final MasterProcedureEnv env) throws IOException { 696 final MasterCoprocessorHost cpHost = env.getMasterCoprocessorHost(); 697 if (cpHost != null) { 698 cpHost.postMergeRegionsCommit(regionsToMerge, mergedRegion, getUser()); 699 } 700 } 701 702 /** 703 * Post merge region action 704 * @param env MasterProcedureEnv 705 **/ 706 private void postCompletedMergeRegions(final MasterProcedureEnv env) throws IOException { 707 final MasterCoprocessorHost cpHost = env.getMasterCoprocessorHost(); 708 if (cpHost != null) { 709 cpHost.postCompletedMergeRegionsAction(regionsToMerge, mergedRegion, getUser()); 710 } 711 } 712 713 /** 714 * The procedure could be restarted from a different machine. If the variable is null, we need to 715 * retrieve it. 716 * @param env MasterProcedureEnv 717 * @return serverName 718 */ 719 private ServerName getServerName(final MasterProcedureEnv env) { 720 if (regionLocation == null) { 721 regionLocation = env.getAssignmentManager().getRegionStates(). 722 getRegionServerOfRegion(regionsToMerge[0]); 723 // May still be null here but return null and let caller deal. 724 // Means we lost the in-memory-only location. We are in recovery 725 // or so. The caller should be able to deal w/ a null ServerName. 726 // Let them go to the Balancer to find one to use instead. 727 } 728 return regionLocation; 729 } 730 731 private void writeMaxSequenceIdFile(MasterProcedureEnv env) throws IOException { 732 MasterFileSystem fs = env.getMasterFileSystem(); 733 long maxSequenceId = -1L; 734 for (RegionInfo region : regionsToMerge) { 735 maxSequenceId = 736 Math.max(maxSequenceId, WALSplitUtil.getMaxRegionSequenceId(env.getMasterConfiguration(), 737 region, fs::getFileSystem, fs::getWALFileSystem)); 738 } 739 if (maxSequenceId > 0) { 740 WALSplitUtil.writeRegionSequenceIdFile(fs.getWALFileSystem(), 741 getWALRegionDir(env, mergedRegion), maxSequenceId); 742 } 743 } 744 745 /** 746 * @return The merged region. Maybe be null if called to early or we failed. 747 */ 748 @VisibleForTesting 749 RegionInfo getMergedRegion() { 750 return this.mergedRegion; 751 } 752 753 @Override 754 protected boolean abort(MasterProcedureEnv env) { 755 // Abort means rollback. We can't rollback all steps. HBASE-18018 added abort to all 756 // Procedures. Here is a Procedure that has a PONR and cannot be aborted once it enters this 757 // range of steps; what do we do for these should an operator want to cancel them? HBASE-20022. 758 return isRollbackSupported(getCurrentState()) && super.abort(env); 759 } 760}