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.util.ArrayList; 021import java.util.List; 022import java.util.concurrent.ConcurrentSkipListMap; 023import org.apache.hadoop.hbase.TableName; 024import org.apache.hadoop.hbase.client.RegionInfo; 025import org.apache.hadoop.hbase.client.TableState; 026import org.apache.hadoop.hbase.master.RegionState; 027import org.apache.hadoop.hbase.master.TableStateManager; 028import org.apache.yetus.audience.InterfaceAudience; 029import org.slf4j.Logger; 030import org.slf4j.LoggerFactory; 031 032/** 033 * Tracks regions that are currently in transition (RIT) - those not yet in their terminal state. 034 */ 035@InterfaceAudience.Private 036public class RegionInTransitionTracker { 037 private static final Logger LOG = LoggerFactory.getLogger(RegionInTransitionTracker.class); 038 039 private final List<RegionState.State> DISABLE_TABLE_REGION_STATE = 040 List.of(RegionState.State.OFFLINE, RegionState.State.CLOSED); 041 042 private final List<RegionState.State> ENABLE_TABLE_REGION_STATE = List.of(RegionState.State.OPEN); 043 044 private final ConcurrentSkipListMap<RegionInfo, RegionStateNode> regionInTransition = 045 new ConcurrentSkipListMap<>(RegionInfo.COMPARATOR); 046 047 private TableStateManager tableStateManager; 048 049 public boolean isRegionInTransition(final RegionInfo regionInfo) { 050 return regionInTransition.containsKey(regionInfo); 051 } 052 053 /** 054 * Handles a region whose hosting RegionServer has crashed. When a RegionServer fails, all regions 055 * it was hosting are automatically added to the RIT list since they need to be reassigned to 056 * other servers. 057 */ 058 public void regionCrashed(RegionStateNode regionStateNode) { 059 if (regionStateNode.getRegionInfo().getReplicaId() != RegionInfo.DEFAULT_REPLICA_ID) { 060 return; 061 } 062 063 if (addRegionInTransition(regionStateNode)) { 064 LOG.debug("{} added to RIT list because hosting region server is crashed ", 065 regionStateNode.getRegionInfo().getEncodedName()); 066 } 067 } 068 069 /** 070 * Processes a region state change and updates the RIT tracking accordingly. This is the core 071 * method that determines whether a region should be added to or removed from the RIT list based 072 * on its current state and the table's enabled/disabled status. This method should be called 073 * whenever a region state changes get stored to hbase:meta Note: Only default replicas (replica 074 * ID 0) are tracked. Read replicas are ignored. 075 * @param regionStateNode the region state node with the current state information 076 */ 077 public void handleRegionStateNodeOperation(RegionStateNode regionStateNode) { 078 // only consider default replica for availability 079 if (regionStateNode.getRegionInfo().getReplicaId() != RegionInfo.DEFAULT_REPLICA_ID) { 080 return; 081 } 082 083 RegionState.State currentState = regionStateNode.getState(); 084 boolean tableEnabled = isTableEnabled(regionStateNode.getTable()); 085 List<RegionState.State> terminalStates = 086 tableEnabled ? ENABLE_TABLE_REGION_STATE : DISABLE_TABLE_REGION_STATE; 087 088 // if region is merged or split it should not be in RIT list 089 if ( 090 currentState == RegionState.State.SPLIT || currentState == RegionState.State.MERGED 091 || regionStateNode.getRegionInfo().isSplit() 092 ) { 093 if (removeRegionInTransition(regionStateNode.getRegionInfo())) { 094 LOG.debug("Removed {} from RIT list as it is split or merged", 095 regionStateNode.getRegionInfo().getEncodedName()); 096 } 097 } else if (!terminalStates.contains(currentState)) { 098 if (addRegionInTransition(regionStateNode)) { 099 LOG.debug("{} added to RIT list because it is in-between state, region state : {} ", 100 regionStateNode.getRegionInfo().getEncodedName(), currentState); 101 } 102 } else { 103 if (removeRegionInTransition(regionStateNode.getRegionInfo())) { 104 LOG.debug("Removed {} from RIT list as reached to terminal state {}", 105 regionStateNode.getRegionInfo().getEncodedName(), currentState); 106 } 107 } 108 } 109 110 private boolean isTableEnabled(TableName tableName) { 111 if (tableStateManager != null) { 112 return tableStateManager.isTableState(tableName, TableState.State.ENABLED, 113 TableState.State.ENABLING); 114 } 115 // AssignmentManager calls setTableStateManager once hbase:meta is confirmed online, if it is 116 // still null it means confirmation is still pending. One should not access TableStateManger 117 // till the time. 118 assert TableName.isMetaTableName(tableName); 119 return true; 120 } 121 122 /** 123 * Handles the deletion of a region by removing it from RIT tracking. This is called when a region 124 * is permanently removed from the cluster, typically after a successful merge operation where the 125 * parent regions are cleaned up. During table deletion, table should be already disabled and all 126 * the region are already OFFLINE 127 * @param regionInfo the region being deleted 128 */ 129 public void handleRegionDelete(RegionInfo regionInfo) { 130 removeRegionInTransition(regionInfo); 131 } 132 133 private boolean addRegionInTransition(final RegionStateNode regionStateNode) { 134 return regionInTransition.putIfAbsent(regionStateNode.getRegionInfo(), regionStateNode) == null; 135 } 136 137 private boolean removeRegionInTransition(final RegionInfo regionInfo) { 138 return regionInTransition.remove(regionInfo) != null; 139 } 140 141 public void stop() { 142 regionInTransition.clear(); 143 } 144 145 public boolean hasRegionsInTransition() { 146 return !regionInTransition.isEmpty(); 147 } 148 149 public List<RegionStateNode> getRegionsInTransition() { 150 return new ArrayList<>(regionInTransition.values()); 151 } 152 153 public void setTableStateManager(TableStateManager tableStateManager) { 154 this.tableStateManager = tableStateManager; 155 } 156}