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.balancer;
019
020import java.util.Map;
021import java.util.concurrent.atomic.AtomicLong;
022import org.agrona.collections.Hashing;
023import org.agrona.collections.Int2IntCounterMap;
024import org.apache.yetus.audience.InterfaceAudience;
025
026/**
027 * A cost function for region replicas. We give a high cost for hosting replicas of the same region
028 * in the same server, host or rack. We do not prevent the case though, since if numReplicas >
029 * numRegionServers, we still want to keep the replica open.
030 */
031@InterfaceAudience.Private
032abstract class RegionReplicaGroupingCostFunction extends CostFunction {
033  protected long maxCost = 0;
034  protected long[] costsPerGroup; // group is either server, host or rack
035
036  @Override
037  final void prepare(BalancerClusterState cluster) {
038    super.prepare(cluster);
039    if (!isNeeded()) {
040      return;
041    }
042    loadCosts();
043  }
044
045  protected abstract void loadCosts();
046
047  protected final long getMaxCost(BalancerClusterState cluster) {
048    // max cost is the case where every region replica is hosted together regardless of host
049    Int2IntCounterMap colocatedReplicaCounts =
050      new Int2IntCounterMap(cluster.numRegions, Hashing.DEFAULT_LOAD_FACTOR, 0);
051    for (int i = 0; i < cluster.regionIndexToPrimaryIndex.length; i++) {
052      colocatedReplicaCounts.getAndIncrement(cluster.regionIndexToPrimaryIndex[i]);
053    }
054    // compute numReplicas from the sorted array
055    return costPerGroup(colocatedReplicaCounts);
056  }
057
058  @Override
059  boolean isNeeded() {
060    return cluster.hasRegionReplicas;
061  }
062
063  @Override
064  protected double cost() {
065    if (maxCost <= 0) {
066      return 0;
067    }
068
069    long totalCost = 0;
070    for (int i = 0; i < costsPerGroup.length; i++) {
071      totalCost += costsPerGroup[i];
072    }
073    return scale(0, maxCost, totalCost);
074  }
075
076  @Override
077  public final void updateWeight(Map<Class<? extends CandidateGenerator>, Double> weights) {
078    weights.merge(RegionReplicaRackCandidateGenerator.class, cost(), Double::sum);
079  }
080
081  /**
082   * For each primary region, it computes the total number of replicas in the array (numReplicas)
083   * and returns a sum of numReplicas-1 squared. For example, if the server hosts regions a, b, c,
084   * d, e, f where a and b are same replicas, and c,d,e are same replicas, it returns (2-1) * (2-1)
085   * + (3-1) * (3-1) + (1-1) * (1-1).
086   * @param colocatedReplicaCounts a sorted array of primary regions ids for the regions hosted
087   * @return a sum of numReplicas-1 squared for each primary region in the group.
088   */
089  protected final long costPerGroup(Int2IntCounterMap colocatedReplicaCounts) {
090    final AtomicLong cost = new AtomicLong(0);
091    // colocatedReplicaCounts is a sorted array of primary ids of regions. Replicas of regions
092    // sharing the same primary will have consecutive numbers in the array.
093    colocatedReplicaCounts.forEach((primary, count) -> {
094      if (count > 1) { // means consecutive primaries, indicating co-location
095        cost.getAndAdd((count - 1) * (count - 1));
096      }
097    });
098    return cost.longValue();
099  }
100}