Skip to content

Commit

Permalink
Partition balance analysis tool for more than two zones.
Browse files Browse the repository at this point in the history
- Started a directory for all of our tools (src/java/voldemort/tools)

- PartitionBalance :
  - broke this util out of ClusterInstance to make it more self-contained
  - this changed the declaration of a lot of the methods that invoked analyzeBalance and analyzeBalanceVerbose

- ClusterInstanceTest  :  Added a bunch of tests to confirm that partition balance can be constructed (or not) for combinations of 2 & 3 zones
  • Loading branch information
jayjwylie committed Jun 20, 2013
1 parent ded4506 commit 34e7a4b
Show file tree
Hide file tree
Showing 10 changed files with 1,003 additions and 363 deletions.
7 changes: 4 additions & 3 deletions src/java/voldemort/client/rebalance/RebalanceCLI.java
Expand Up @@ -33,7 +33,7 @@
import voldemort.utils.ClusterInstance;
import voldemort.utils.CmdUtils;
import voldemort.utils.Entropy;
import voldemort.utils.Pair;
import voldemort.utils.PartitionBalance;
import voldemort.utils.RebalanceClusterUtils;
import voldemort.xml.ClusterMapper;
import voldemort.xml.StoreDefinitionsMapper;
Expand Down Expand Up @@ -294,8 +294,9 @@ public static void main(String[] args) throws Exception {
}

if(options.has("analyze")) {
Pair<Double, String> analysis = new ClusterInstance(currentCluster, storeDefs).analyzeBalanceVerbose();
System.out.println(analysis.getSecond());
PartitionBalance partitionBalance = new ClusterInstance(currentCluster,
storeDefs).getPartitionBalance();
System.out.println(partitionBalance);
return;
}

Expand Down
117 changes: 117 additions & 0 deletions src/java/voldemort/tools/PartitionAnalysisCLI.java
@@ -0,0 +1,117 @@
/*
* Copyright 2012-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.io.File;
import java.io.IOException;
import java.util.List;
import java.util.Set;

import joptsimple.OptionException;
import joptsimple.OptionParser;
import joptsimple.OptionSet;

import org.apache.log4j.Logger;

import voldemort.cluster.Cluster;
import voldemort.store.StoreDefinition;
import voldemort.utils.ClusterInstance;
import voldemort.utils.CmdUtils;
import voldemort.utils.PartitionBalance;
import voldemort.utils.Utils;
import voldemort.xml.ClusterMapper;
import voldemort.xml.StoreDefinitionsMapper;

import com.google.common.base.Joiner;

public class PartitionAnalysisCLI {

private final static Logger logger = Logger.getLogger(PartitionAnalysisCLI.class);

private static OptionParser parser;

private static void setupParser() {
parser = new OptionParser();
parser.accepts("help", "Print usage information");
parser.accepts("cluster", "Path to cluster xml")
.withRequiredArg()
.describedAs("cluster.xml");
parser.accepts("stores", "Path to store definition xml")
.withRequiredArg()
.describedAs("stores.xml");
}

private static void printUsage() {
StringBuilder help = new StringBuilder();
help.append("PartitionAnalysisCLI\n");
help.append(" Analyzes the partition layout of a cluster based on the storage "
+ "definitions for that cluster. Determines how well balanced the "
+ "partition layout is.\n");
help.append("Options:\n");
help.append(" Required:\n");
help.append(" --cluster <clusterXML>\n");
help.append(" --stores <storesXML>\n");

try {
parser.printHelpOn(System.out);
} catch(IOException e) {
e.printStackTrace();
}
System.out.print(help.toString());
}

private static void printUsageAndDie(String errMessage) {
printUsage();
Utils.croak("\n" + errMessage);
}

private static OptionSet getValidOptions(String[] args) {
OptionSet options = null;
try {
options = parser.parse(args);
} catch(OptionException oe) {
printUsageAndDie("Exception when parsing arguments : " + oe.getMessage());
}

if(options.has("help")) {
printUsage();
System.exit(0);
}

Set<String> missing = CmdUtils.missing(options, "cluster", "stores");
if(missing.size() > 0) {
printUsageAndDie("Missing required arguments: " + Joiner.on(", ").join(missing));
}

return options;
}

public static void main(String[] args) throws Exception {
setupParser();
OptionSet options = getValidOptions(args);

String clusterXML = (String) options.valueOf("cluster");
String storesXML = (String) options.valueOf("stores");

Cluster currentCluster = new ClusterMapper().readCluster(new File(clusterXML));
List<StoreDefinition> storeDefs = new StoreDefinitionsMapper().readStoreList(new File(storesXML));

PartitionBalance partitionBalance = new ClusterInstance(currentCluster, storeDefs).getPartitionBalance();
System.out.println(partitionBalance);
}

}
208 changes: 2 additions & 206 deletions src/java/voldemort/utils/ClusterInstance.java
Expand Up @@ -16,19 +16,11 @@

package voldemort.utils;

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

import voldemort.cluster.Cluster;
import voldemort.routing.StoreRoutingPlan;
import voldemort.store.StoreDefinition;

import com.google.common.collect.Maps;

// TODO: Add ClusterInstanceTest unit test for these helper methods.

/**
* This class wraps up a Cluster object and a List<StoreDefinition>. The methods
Expand Down Expand Up @@ -57,58 +49,6 @@ public List<StoreDefinition> getStoreDefs() {
return storeDefs;
}

/**
* Wrapper that just returns the max/min ratio metric and throws away the
* verbose string.
*/
public double analyzeBalance() {
Pair<Double, String> analysis = analyzeBalanceVerbose();
return analysis.getFirst();
}

/**
*
* @param nodeIdToPartitionCount
* @param title
* @return
*/
public Pair<Double, String> summarizeBalance(final Map<Integer, Integer> nodeIdToPartitionCount,
String title) {
StringBuilder builder = new StringBuilder();
Set<Integer> nodeIds = cluster.getNodeIds();

builder.append("\n" + title + "\n");
int minVal = Integer.MAX_VALUE;
int maxVal = Integer.MIN_VALUE;
int aggCount = 0;
for(Integer nodeId: nodeIds) {
int curCount = nodeIdToPartitionCount.get(nodeId);
builder.append("\tNode ID: " + nodeId + " : " + curCount + " ("
+ cluster.getNodeById(nodeId).getHost() + ")\n");
aggCount += curCount;
if(curCount > maxVal)
maxVal = curCount;
if(curCount < minVal)
minVal = curCount;
}
int avgVal = aggCount / nodeIdToPartitionCount.size();
double maxAvgRatio = maxVal * 1.0 / avgVal;
if(avgVal == 0) {
maxAvgRatio = maxVal;
}
double maxMinRatio = maxVal * 1.0 / minVal;
if(minVal == 0) {
maxMinRatio = maxVal;
}
builder.append("\tMin: " + minVal + "\n");
builder.append("\tAvg: " + avgVal + "\n");
builder.append("\tMax: " + maxVal + "\n");
builder.append("\t\tMax/Avg: " + maxAvgRatio + "\n");
builder.append("\t\tMax/Min: " + maxMinRatio + "\n");

return Pair.create(maxMinRatio, builder.toString());
}

/**
* Outputs an analysis of how balanced the cluster is given the store
* definitions. The metric max/min ratio is used to describe balance. The
Expand All @@ -121,152 +61,8 @@ public Pair<Double, String> summarizeBalance(final Map<Integer, Integer> nodeIdT
* pair is a string that can be printed to dump all the gory details
* of the analysis.
*/
public Pair<Double, String> analyzeBalanceVerbose() {
StringBuilder builder = new StringBuilder();
builder.append(ClusterUtils.verboseClusterDump(cluster));

HashMap<StoreDefinition, Integer> uniqueStores = KeyDistributionGenerator.getUniqueStoreDefinitionsWithCounts(storeDefs);
List<ByteArray> keys = KeyDistributionGenerator.generateKeys(KeyDistributionGenerator.DEFAULT_NUM_KEYS);
Set<Integer> nodeIds = cluster.getNodeIds();
Set<Integer> zoneIds = cluster.getZoneIds();

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

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

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

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

builder.append("\n");
builder.append("Store exemplar: " + storeDefinition.getName() + "\n");
builder.append("\tReplication factor: " + storeDefinition.getReplicationFactor() + "\n");
builder.append("\tRouting strategy: " + storeDefinition.getRoutingStrategyType() + "\n");
builder.append("\tThere are " + uniqueStores.get(storeDefinition)
+ " other similar stores.\n");

// Map of node Id to Sets of pairs. Pairs of Integers are of
// <replica_type, partition_id>
Map<Integer, Set<Pair<Integer, Integer>>> nodeIdToAllPartitions = RebalanceUtils.getNodeIdToAllPartitions(cluster,
storeDefinition,
true);
Map<Integer, Integer> primaryNodeIdToPartitionCount = Maps.newHashMap();
Map<Integer, Integer> nodeIdToZonePrimaryCount = Maps.newHashMap();
Map<Integer, Integer> allNodeIdToPartitionCount = Maps.newHashMap();

// Print out all partitions, by replica type, per node
builder.append("\n");
builder.append("\tDetailed Dump:\n");
for(Integer nodeId: nodeIds) {
builder.append("\tNode ID: " + nodeId + "in zone "
+ cluster.getNodeById(nodeId).getZoneId() + "\n");
primaryNodeIdToPartitionCount.put(nodeId, 0);
nodeIdToZonePrimaryCount.put(nodeId, 0);
allNodeIdToPartitionCount.put(nodeId, 0);
Set<Pair<Integer, Integer>> partitionPairs = nodeIdToAllPartitions.get(nodeId);

int replicaType = 0;
while(partitionPairs.size() > 0) {
List<Pair<Integer, Integer>> replicaPairs = new ArrayList<Pair<Integer, Integer>>();
for(Pair<Integer, Integer> pair: partitionPairs) {
if(pair.getFirst() == replicaType) {
replicaPairs.add(pair);
}
}
List<Integer> partitions = new ArrayList<Integer>();
for(Pair<Integer, Integer> pair: replicaPairs) {
partitionPairs.remove(pair);
partitions.add(pair.getSecond());
}
java.util.Collections.sort(partitions);

builder.append("\t\t" + replicaType);
for(int zoneId: zoneIds) {
builder.append(" : z" + zoneId + " : ");
List<Integer> zonePartitions = new ArrayList<Integer>();
for(int partitionId: partitions) {
if(cluster.getPartitionIdsInZone(zoneId).contains(partitionId)) {
zonePartitions.add(partitionId);
}
}
builder.append(zonePartitions.toString());

}
builder.append("\n");
if(replicaType == 0) {
primaryNodeIdToPartitionCount.put(nodeId,
primaryNodeIdToPartitionCount.get(nodeId)
+ partitions.size());
}

allNodeIdToPartitionCount.put(nodeId, allNodeIdToPartitionCount.get(nodeId)
+ partitions.size());
replicaType++;
}
}

// 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.
for(int partitionId = 0; partitionId < cluster.getNumberOfPartitions(); partitionId++) {
for(int zoneId: zoneIds) {
for(int nodeId: storeInstance.getReplicationNodeList(partitionId)) {
if(cluster.getNodeById(nodeId).getZoneId() == zoneId) {
nodeIdToZonePrimaryCount.put(nodeId,
nodeIdToZonePrimaryCount.get(nodeId) + 1);
break;
}
}
}
}

builder.append("\n");
builder.append("\tSummary Dump:\n");
for(Integer nodeId: nodeIds) {
builder.append("\tNode ID: " + nodeId + " : "
+ allNodeIdToPartitionCount.get(nodeId) + "\n");
primaryAggNodeIdToPartitionCount.put(nodeId,
primaryAggNodeIdToPartitionCount.get(nodeId)
+ (primaryNodeIdToPartitionCount.get(nodeId) * uniqueStores.get(storeDefinition)));
aggNodeIdToZonePrimaryCount.put(nodeId, aggNodeIdToZonePrimaryCount.get(nodeId)
+ nodeIdToZonePrimaryCount.get(nodeId)
* uniqueStores.get(storeDefinition));
allAggNodeIdToPartitionCount.put(nodeId,
allAggNodeIdToPartitionCount.get(nodeId)
+ (allNodeIdToPartitionCount.get(nodeId) * uniqueStores.get(storeDefinition)));
}
}

builder.append("\n");
builder.append("STD DEV ANALYSIS\n");
builder.append("\n");
builder.append(KeyDistributionGenerator.printOverallDistribution(cluster, storeDefs, keys));
builder.append("\n");
builder.append("\n");

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

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

summary = summarizeBalance(allAggNodeIdToPartitionCount,
"AGGREGATE NARY-PARTITION COUNT (across all stores)");
builder.append(summary.getSecond());
public PartitionBalance getPartitionBalance() {

return new Pair<Double, String>(summary.getFirst(), builder.toString());
return new PartitionBalance(cluster, storeDefs);
}
}
5 changes: 5 additions & 0 deletions src/java/voldemort/utils/ClusterUtils.java
Expand Up @@ -281,6 +281,11 @@ public static String getHotPartitionsDueToContiguity(final Cluster cluster,
for(Integer zoneId: cluster.getZoneIds()) {
List<Integer> partitionIds = new ArrayList<Integer>(cluster.getPartitionIdsInZone(zoneId));

// Skip zones without any partition IDs.
if(partitionIds.size() == 0) {
continue;
}

int lastPartitionId = partitionIds.get(0);
int initPartitionId = lastPartitionId;

Expand Down

0 comments on commit 34e7a4b

Please sign in to comment.