Package voldemort.tools

Source Code of voldemort.tools.PartitionBalance$ZoneBalanceStats

/*
* Copyright 2013 LinkedIn, Inc
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/

package voldemort.tools;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;

import voldemort.cluster.Cluster;
import voldemort.cluster.Node;
import voldemort.routing.StoreRoutingPlan;
import voldemort.store.StoreDefinition;
import voldemort.utils.Pair;
import voldemort.utils.PartitionBalanceUtils;
import voldemort.utils.StoreDefinitionUtils;
import voldemort.utils.Utils;

import com.google.common.collect.Maps;

public class PartitionBalance {

    /**
     * Multiplier in utility method to weight the balance of "IOPS" (get QPS &
     * pseudo-master put QPS) relative to "CAPACITY".
     */
    private final static int UTILITY_MULTIPLIER_IOPS = 1;
    /**
     * Multiplier in utility method to weight the balance of "CAPACITY" (put QPS
     * and therefore amount of data stored) relative to "IOPS".
     *
     * Currently, we bias towards balancing capacity over iops.
     */
    private final static int UTILITY_MULTIPLIER_CAPACITY = 2;

    private final Cluster cluster;

    private final double primaryMaxMin;
    private final double zonePrimaryMaxMin;
    private final double naryMaxMin;
    private final String verbose;

    private final Map<Integer, Integer> primaryAggNodeIdToPartitionCount;
    private final Map<Integer, Integer> aggNodeIdToZonePrimaryCount;
    private final Map<Integer, Integer> allAggNodeIdToPartitionCount;
    private final Map<Integer, Integer> zoneIdToPartitionStoreCount;

    public PartitionBalance(Cluster cluster, List<StoreDefinition> storeDefs) {
        this.cluster = cluster;

        StringBuilder builder = new StringBuilder();
        builder.append(PartitionBalanceUtils.verboseClusterDump(cluster));

        HashMap<StoreDefinition, Integer> uniqueStores = StoreDefinitionUtils.getUniqueStoreDefinitionsWithCounts(storeDefs);
        Set<Integer> nodeIds = cluster.getNodeIds();
        Set<Integer> zoneIds = cluster.getZoneIds();

        builder.append("PARTITION DUMP\n");
        this.primaryAggNodeIdToPartitionCount = Maps.newHashMap();
        for(Integer nodeId: nodeIds) {
            primaryAggNodeIdToPartitionCount.put(nodeId, 0);
        }

        this.aggNodeIdToZonePrimaryCount = Maps.newHashMap();
        for(Integer nodeId: nodeIds) {
            aggNodeIdToZonePrimaryCount.put(nodeId, 0);
        }

        this.allAggNodeIdToPartitionCount = Maps.newHashMap();
        for(Integer nodeId: nodeIds) {
            allAggNodeIdToPartitionCount.put(nodeId, 0);
        }

        this.zoneIdToPartitionStoreCount = Maps.newHashMap();
        for(Integer zoneId: zoneIds) {
            zoneIdToPartitionStoreCount.put(zoneId, 0);
        }

        for(StoreDefinition storeDefinition: uniqueStores.keySet()) {
            StoreRoutingPlan storeRoutingPlan = new StoreRoutingPlan(cluster, storeDefinition);

            // High level information about the store def exemplar
            builder.append(Utils.NEWLINE)
                   .append("Store exemplar: " + storeDefinition.getName())
                   .append(Utils.NEWLINE)
                   .append("\tReplication factor: " + storeDefinition.getReplicationFactor())
                   .append(Utils.NEWLINE)
                   .append("\tRouting strategy: " + storeDefinition.getRoutingStrategyType())
                   .append(Utils.NEWLINE)
                   .append("\tThere are " + uniqueStores.get(storeDefinition)
                           + " other similar stores.")
                   .append(Utils.NEWLINE)
                   .append(Utils.NEWLINE);

            // Detailed dump of partitions on nodes
            builder.append(dumpZoneNAryDetails(storeRoutingPlan));
            builder.append(Utils.NEWLINE);

            // Per-node counts of various partition types (primary,
            // zone-primary, and n-ary)
            Map<Integer, Integer> nodeIdToPrimaryCount = getNodeIdToPrimaryCount(cluster);
            Map<Integer, Integer> nodeIdToZonePrimaryCount = getNodeIdToZonePrimaryCount(cluster,
                                                                                         storeRoutingPlan);
            Map<Integer, Integer> nodeIdToNaryCount = getNodeIdToNaryCount(cluster,
                                                                           storeRoutingPlan);

            builder.append("\tSummary of NAry counts:").append(Utils.NEWLINE);
            for(Integer nodeId: nodeIds) {
                builder.append("\tNode ID: " + nodeId + " : " + nodeIdToNaryCount.get(nodeId)
                               + "\n");
                primaryAggNodeIdToPartitionCount.put(nodeId,
                                                     primaryAggNodeIdToPartitionCount.get(nodeId)
                                                             + (nodeIdToPrimaryCount.get(nodeId) * uniqueStores.get(storeDefinition)));
                aggNodeIdToZonePrimaryCount.put(nodeId, aggNodeIdToZonePrimaryCount.get(nodeId)
                                                        + nodeIdToZonePrimaryCount.get(nodeId)
                                                        * uniqueStores.get(storeDefinition));
                allAggNodeIdToPartitionCount.put(nodeId,
                                                 allAggNodeIdToPartitionCount.get(nodeId)
                                                         + (nodeIdToNaryCount.get(nodeId) * uniqueStores.get(storeDefinition)));

                // Count partition-stores per-zone
                int zoneId = cluster.getNodeById(nodeId).getZoneId();
                zoneIdToPartitionStoreCount.put(zoneId,
                                                zoneIdToPartitionStoreCount.get(zoneId)
                                                        + (nodeIdToNaryCount.get(nodeId) * uniqueStores.get(storeDefinition)));
            }
        }

        builder.append(Utils.NEWLINE).append(Utils.NEWLINE);

        builder.append("PARTITION-STORES PER-ZONE:").append(Utils.NEWLINE);
        for(Integer zoneId: zoneIds) {
            builder.append("\tZone ID: " + zoneId + " : " + zoneIdToPartitionStoreCount.get(zoneId))
                   .append(Utils.NEWLINE);
        }

        builder.append(Utils.NEWLINE).append(Utils.NEWLINE);

        Pair<Double, String> summary = summarizeBalance(primaryAggNodeIdToPartitionCount,
                                                        "AGGREGATE PRIMARY-PARTITION COUNT (across all stores)");
        builder.append(summary.getSecond());
        this.primaryMaxMin = summary.getFirst();

        summary = summarizeBalance(aggNodeIdToZonePrimaryCount,
                                   "AGGREGATE ZONEPRIMARY-PARTITION COUNT (across all stores)");
        builder.append(summary.getSecond());
        this.zonePrimaryMaxMin = summary.getFirst();

        summary = summarizeBalance(allAggNodeIdToPartitionCount,
                                   "AGGREGATE NARY-PARTITION COUNT (across all stores)");
        builder.append(summary.getSecond());
        this.naryMaxMin = summary.getFirst();
        builder.append("Utility value " + getUtility()).append(Utils.NEWLINE);
        this.verbose = builder.toString();
    }

    /**
     * Go through all nodes and determine how many partition Ids each node
     * hosts.
     *
     * @param cluster
     * @return map of nodeId to number of primary partitions hosted on node.
     */
    private Map<Integer, Integer> getNodeIdToPrimaryCount(Cluster cluster) {
        Map<Integer, Integer> nodeIdToPrimaryCount = Maps.newHashMap();
        for(Node node: cluster.getNodes()) {
            nodeIdToPrimaryCount.put(node.getId(), node.getPartitionIds().size());
        }

        return nodeIdToPrimaryCount;
    }

    /**
     * Go through all partition IDs and determine which node is "first" in the
     * replicating node list for every zone. This determines the number of
     * "zone primaries" each node hosts.
     *
     * @return map of nodeId to number of zone-primaries hosted on node.
     */
    private Map<Integer, Integer> getNodeIdToZonePrimaryCount(Cluster cluster,
                                                              StoreRoutingPlan storeRoutingPlan) {
        Map<Integer, Integer> nodeIdToZonePrimaryCount = Maps.newHashMap();
        for(Integer nodeId: cluster.getNodeIds()) {
            nodeIdToZonePrimaryCount.put(nodeId,
                                         storeRoutingPlan.getZonePrimaryPartitionIds(nodeId).size());
        }

        return nodeIdToZonePrimaryCount;
    }

    /**
     * Go through all node IDs and determine which node
     *
     * @param cluster
     * @param storeRoutingPlan
     * @return
     */
    private Map<Integer, Integer> getNodeIdToNaryCount(Cluster cluster,
                                                       StoreRoutingPlan storeRoutingPlan) {
        Map<Integer, Integer> nodeIdToNaryCount = Maps.newHashMap();

        for(int nodeId: cluster.getNodeIds()) {
            nodeIdToNaryCount.put(nodeId, storeRoutingPlan.getZoneNAryPartitionIds(nodeId).size());
        }

        return nodeIdToNaryCount;
    }

    /**
     * Dumps the partition IDs per node in terms of zone n-ary type.
     *
     * @param cluster
     * @param storeRoutingPlan
     * @return pretty printed string of detailed zone n-ary type.
     */
    private String dumpZoneNAryDetails(StoreRoutingPlan storeRoutingPlan) {
        StringBuilder sb = new StringBuilder();

        sb.append("\tDetailed Dump (Zone N-Aries):").append(Utils.NEWLINE);
        for(Node node: storeRoutingPlan.getCluster().getNodes()) {
            int zoneId = node.getZoneId();
            int nodeId = node.getId();
            sb.append("\tNode ID: " + nodeId + " in zone " + zoneId).append(Utils.NEWLINE);
            List<Integer> naries = storeRoutingPlan.getZoneNAryPartitionIds(nodeId);
            Map<Integer, List<Integer>> zoneNaryTypeToPartitionIds = new HashMap<Integer, List<Integer>>();
            for(int nary: naries) {
                int zoneReplicaType = storeRoutingPlan.getZoneNaryForNodesPartition(zoneId,
                                                                                    nodeId,
                                                                                    nary);
                if(!zoneNaryTypeToPartitionIds.containsKey(zoneReplicaType)) {
                    zoneNaryTypeToPartitionIds.put(zoneReplicaType, new ArrayList<Integer>());
                }
                zoneNaryTypeToPartitionIds.get(zoneReplicaType).add(nary);
            }

            for(int replicaType: new TreeSet<Integer>(zoneNaryTypeToPartitionIds.keySet())) {
                sb.append("\t\t" + replicaType + " : ");
                sb.append(zoneNaryTypeToPartitionIds.get(replicaType).toString());
                sb.append(Utils.NEWLINE);
            }
        }

        return sb.toString();
    }

    public double getPrimaryMaxMin() {
        return primaryMaxMin;
    }

    public int getPrimaryPartitionCount(int nodeId) {
        return primaryAggNodeIdToPartitionCount.get(nodeId);
    }

    public double getZonePrimaryMaxMin() {
        return zonePrimaryMaxMin;
    }

    public int getZonePrimaryPartitionCount(int nodeId) {
        return aggNodeIdToZonePrimaryCount.get(nodeId);
    }

    public double getNaryMaxMin() {
        return naryMaxMin;
    }

    public int getNaryPartitionCount(int nodeId) {
        return allAggNodeIdToPartitionCount.get(nodeId);
    }

    /**
     * Return utility of current partition balance. The utility value ought to
     * be minimized.
     *
     * In the future, could offer different utility functions (and select
     * between them via enumeration value passed into method or constant
     * provided to constructor). The exact utility function is a combination of
     * this method and of ZoneBalanceStats.getUtility.
     *
     * The current utility function is biased towards balancing IOPS (zone
     * primary balance) over capacity (Nary partition balance). Such bias
     * affects repartitioning algorithms that consider multiple options before
     * selecting "the best" option (e.g., greedy repartitioning algorithms for a
     * repartitioning and deciding among multiple repartitioning attempts).
     *
     * The current utility function can hit local minima if there are two or
     * more nodes having the min value for a zone and two or more nodes having
     * the max value for a zone. Such a situation means that pair-wise swaps may
     * not be sufficient to improve utility. Could consider swapping more than
     * two partitions at a time. Could also consider a utility method which has
     * "low bits" of the number of nodes that have either min or max value per
     * zone. If these low bits are greater than 2, then a partition swap which
     * keeps high bits same, but reduces low bits should be kept.
     *
     * @return A measure of utility that ought to be minimized.
     */
    public double getUtility() {

        return (UTILITY_MULTIPLIER_IOPS * getZonePrimaryMaxMin())
               + (UTILITY_MULTIPLIER_CAPACITY * getNaryMaxMin());
    }

    @Override
    public String toString() {
        return verbose;
    }

    private class ZoneBalanceStats {

        int minVal;
        int maxVal;
        int partitionCount;
        int nodeCount;

        ZoneBalanceStats() {
            minVal = Integer.MAX_VALUE;
            maxVal = Integer.MIN_VALUE;
            partitionCount = 0;
            nodeCount = 0;
        }

        public void addPartitions(int partitions) {
            if(partitions > maxVal)
                maxVal = partitions;
            if(partitions < minVal)
                minVal = partitions;

            partitionCount += partitions;
            nodeCount++;
        }

        private double getAvg() {
            if(nodeCount > 0) {
                return partitionCount / nodeCount;
            }
            return 0;
        }

        private double getMaxAvgRatio() {
            if(getAvg() == 0) {
                return maxVal;
            }
            return maxVal * 1.0 / getAvg();
        }

        private double getMaxMinRatio() {
            if(minVal == 0) {
                return maxVal;
            }
            return maxVal * 1.0 / minVal;
        }

        /**
         * Determins utility metric to be minimized.
         *
         * @return utility metric that ought to be minimized.
         */
        public double getUtility() {
            return getMaxMinRatio();
        }

        public String dumpStats() {
            StringBuilder builder = new StringBuilder();

            builder.append("\tMin: " + minVal + "\n");
            builder.append("\tAvg: " + getAvg() + "\n");
            builder.append("\tMax: " + maxVal + "\n");
            builder.append("\t\tMax/Avg: " + getMaxAvgRatio() + "\n");
            builder.append("\t\tMax/Min: " + getMaxMinRatio() + "\n");
            return builder.toString();
        }

    }

    /**
     * Summarizes balance for the given nodeId to PartitionCount.
     *
     * @param nodeIdToPartitionCount
     * @param title for use in pretty string
     * @return Pair: getFirst() is utility value to be minimized, getSecond() is
     *         pretty summary string of balance
     */
    private Pair<Double, String>
            summarizeBalance(final Map<Integer, Integer> nodeIdToPartitionCount, String title) {
        StringBuilder builder = new StringBuilder();
        builder.append("\n" + title + "\n");

        Map<Integer, ZoneBalanceStats> zoneToBalanceStats = new HashMap<Integer, ZoneBalanceStats>();
        for(Integer zoneId: cluster.getZoneIds()) {
            zoneToBalanceStats.put(zoneId, new ZoneBalanceStats());
        }

        for(Node node: cluster.getNodes()) {
            int curCount = nodeIdToPartitionCount.get(node.getId());
            builder.append("\tNode ID: " + node.getId() + " : " + curCount + " (" + node.getHost()
                           + ")\n");
            zoneToBalanceStats.get(node.getZoneId()).addPartitions(curCount);
        }

        // double utilityToBeMinimized = Double.MIN_VALUE;
        double utilityToBeMinimized = 0;
        for(Integer zoneId: cluster.getZoneIds()) {
            builder.append("Zone " + zoneId + "\n");
            builder.append(zoneToBalanceStats.get(zoneId).dumpStats());
            utilityToBeMinimized += zoneToBalanceStats.get(zoneId).getUtility();
            /*-
             * Another utility function to consider
            if(zoneToBalanceStats.get(zoneId).getMaxMinRatio() > utilityToBeMinimized) {
                utilityToBeMinimized = zoneToBalanceStats.get(zoneId).getUtility();
            }
             */
        }

        return Pair.create(utilityToBeMinimized, builder.toString());
    }
}
TOP

Related Classes of voldemort.tools.PartitionBalance$ZoneBalanceStats

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.