oracle.kv.impl.admin.topo.TopologyDiff Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of oracle-nosql-server Show documentation
Show all versions of oracle-nosql-server Show documentation
NoSQL Database Server - supplies build and runtime support for the server (store) side of the Oracle NoSQL Database.
/*-
* Copyright (C) 2011, 2018 Oracle and/or its affiliates. All rights reserved.
*
* This file was distributed by Oracle as part of a version of Oracle NoSQL
* Database made available at:
*
* http://www.oracle.com/technetwork/database/database-technologies/nosqldb/downloads/index.html
*
* Please see the LICENSE file included in the top-level directory of the
* appropriate version of Oracle NoSQL Database for a copy of the license and
* additional information.
*/
package oracle.kv.impl.admin.topo;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.concurrent.atomic.AtomicInteger;
import oracle.kv.impl.admin.param.Parameters;
import oracle.kv.impl.admin.param.RepNodeParams;
import oracle.kv.impl.topo.ArbNode;
import oracle.kv.impl.topo.ArbNodeId;
import oracle.kv.impl.topo.Datacenter;
import oracle.kv.impl.topo.DatacenterId;
import oracle.kv.impl.topo.DatacenterType;
import oracle.kv.impl.topo.Partition;
import oracle.kv.impl.topo.PartitionId;
import oracle.kv.impl.topo.RepGroupId;
import oracle.kv.impl.topo.RepNode;
import oracle.kv.impl.topo.RepNodeId;
import oracle.kv.impl.topo.StorageNodeId;
import oracle.kv.impl.topo.Topology;
import oracle.kv.impl.util.JsonUtils;
import org.codehaus.jackson.node.ArrayNode;
import org.codehaus.jackson.node.ObjectNode;
/**
* Compare and categorize the differences between two topologies. Used both as
* an initial step when constructing a deployment plan and for supplying an
* user-viewable preview of the work required to transform from one topology
* to another.
*/
public class TopologyDiff {
private static final String CURRENT = "current deployed topology";
public static final String NO_CHANGE = "No differences in topologies.";
private final Topology source;
private final String sourceName;
private final TopologyCandidate candidate;
private final Parameters params;
/*
* Brand new shards, listed in ordinal value. The RepGroupIds are from the
* source topology. That is, newShard.get(0) is the repGroupId for the
* first new shard, from the source topo. When the topology is deployed,
* the shards may not be created in precisely ordinal order, if there is
* some parallelism, and the actual repGroupId that is allocated may be
* different.
*/
private final List newShards;
/*
* A record of all changes to all shards, such as relocated or newly
* created RNS, or relocated partitions. At the time when the diff is
* generated, the repGroupId of the shard in the source and candidate
* topologies are obviously the same, but when the tasks for the topology
* are generated by the TopoTaskGenerator, the resulting shards might have
* ids that are different, because component id generation it controlled
* transparently by the Topology.
*/
private final Map changedShards;
private final Set newRNs;
private final Set newANs;
private final Set removedANs;
/* The RNs should be removed */
private final Set removedRNs;
/* Map of changes to zone types */
private final Map changedZones;
/* Map of changes to zone master affinity */
private final Map changedAffinityZones;
/* ===== Topology components that are now unused. ===== */
/*
* Note that in R2, contraction is not supported, and RNs and shards
* should not be removed.
*/
/* Shards that are in the source but not the candidate. */
private final Set removedShards;
/* ===== For reporting and validation ===== */
/*
* Shards that are sources for partition migration. Currently used for
* stats, will be used for optimizing task ordering in the future.
*/
private final Set pMigrationSources;
/*
* Shards that are destinations for partition migration. Currently used for
* stats, will be used for optimizing task ordering in the future. When
* displaying this, take care to account for the fact that the destination
* shard id will not truly have been created yet; the RepGroupId used as
* a destination id is a placeholder until the shard has truly been created
* in the topology.
*/
private final Set pMigrationDestinations;
private int numCreatedPartitions;
private int numPartitionMigrations;
private int numRelocatedRNs;
private int numRelocatedANs;
/** Storage directory assignments created by the topology building phase. */
private Map storageDirAssignments;
/** RN log directory assignments created by the topology building phase. */
private Map rnLogDirAssignments;
/**
* Evaluate the differences between the source and destination, or
* candidate.
*
* @param source the original topology
* @param sourceName the candidate name of the original topology. If null,
* the default name of CURRENT will be used.
* @param candidate the destination topology.
*/
public TopologyDiff(Topology source, String sourceName,
TopologyCandidate candidate, Parameters params) {
this(source, sourceName, candidate, params, true /*validate*/);
}
/**
* Evaluate the differences between the source and destination, or
* candidate. Optionally validate the transition.
*
* @param source the original topology
* @param sourceName the candidate name of the original topology. If null,
* the default name of CURRENT will be used.
* @param candidate the destination topology.
*/
public TopologyDiff(Topology source, String sourceName,
TopologyCandidate candidate, Parameters params,
boolean validate) {
this.source = source;
if (sourceName == null) {
this.sourceName = CURRENT;
} else {
this.sourceName = sourceName;
}
this.candidate = candidate;
this.params = params;
/*
* Sort the change set info by shard name, so the preview/display is
* nice, and so that the generated topology transformation tasks are
* deterministic and more intuitive to the user.
*/
changedShards = new TreeMap<>
(new Comparator() {
@Override
public
int compare(RepGroupId r1, RepGroupId r2) {
return r1.getGroupId() - r2.getGroupId();
}
});
numCreatedPartitions = 0;
newShards = new ArrayList<>();
removedShards = new HashSet<>();
changedZones = new HashMap<>();
changedAffinityZones = new HashMap<>();
newRNs = new HashSet<>();
removedRNs = new HashSet<>();
newANs = new HashSet<>();
removedANs = new HashSet<>();
pMigrationSources = new HashSet<>();
pMigrationDestinations = new HashSet<>();
doDiff(validate);
}
/*
* Methods to interrogate the diff. Should there be more, or are these
* unnecessary?
*/
public int getNumCreatedPartitions() {
return numCreatedPartitions;
}
/**
* Return the number of new shards to create.
*/
public int getNumNewShards() {
return newShards.size();
}
/**
* Return the number of to-be-removed shards
*/
public int getNumRemovedShards() {
return removedShards.size();
}
/**
* Return the number of new RNs to create.
*/
public int getNumNewRNs() {
return newRNs.size();
}
/**
* Return the number of to-be-removed RNs
*/
public int getNumRemovedRNs() {
return removedRNs.size();
}
/**
* Return the number of RNs to relocate.
*/
public int getNumRelocatedRNs() {
return numRelocatedRNs;
}
private int getNumNewANs() {
return newANs.size();
}
private int getNumRelocatedANs() {
return numRelocatedANs;
}
private int getNumRemovedANs() {
return removedANs.size();
}
/**
* Returns true if the specified zone type is changed.
*/
public boolean typeChanged(DatacenterId zoneId) {
return changedZones.containsKey(zoneId);
}
/**
* Returns true if the specified zone master affinity is changed.
*/
public boolean affinityChanged(DatacenterId zoneId) {
return changedAffinityZones.containsKey(zoneId);
}
/**
* Return a listing of the work to be done. Format is
* Summary:
* Change N zone types
* Create N shards
* Create N RNs
* Migrate N partitions
*
* change ZN to TYPE
* shard X
* Y new RNs: rg7-rn1 rg7-rn2 rg7-rn3
* Z partition migrations
*
* if verbose, a listing of all the partitions to migrated follows, sorted
* by source rep group, and then by partition id.
*/
public String display(boolean verbose) {
final StringBuilder sb = new StringBuilder();
/* Summary information. */
if (!changedZones.isEmpty()) {
sb.append("Change ").append(changedZones.size()).
append(" zone type").append(plural(changedZones.size())).
append("\n");
}
if (!changedAffinityZones.isEmpty()) {
sb.append("Change ").append(changedAffinityZones.size()).
append(" zone master ").
append(changedAffinityZones.size() == 0 ?
"affinity" :
"affinities").
append("\n");
}
int nShards = getNumNewShards();
if (nShards > 0) {
sb.append("Create ").append(nShards).
append(" shard").append(plural(nShards)).append("\n");
}
int nNewRNs = getNumNewRNs();
if (nNewRNs > 0) {
sb.append("Create ").append(nNewRNs).append(" RN").
append(plural(nNewRNs)).append("\n");
}
/* Show the number of to-be-removed RNs */
int nRemovedRNs = getNumRemovedRNs();
if (nRemovedRNs > 0) {
sb.append("Remove ").append(nRemovedRNs).append(" RN").
append(plural(nRemovedRNs)).append("\n");
}
/* Show the number of to-be-removed shards */
int nRemovedShards = getNumRemovedShards();
if (nRemovedShards > 0) {
sb.append("Remove ").append(nRemovedShards).append(" shard").
append(plural(nRemovedShards)).append("\n");
}
int moved = getNumRelocatedRNs();
if (moved > 0) {
sb.append("Relocate ").append(moved).append(" RN").
append(plural(moved)).append("\n");
}
if (numCreatedPartitions != 0) {
sb.append("Create ").append(numCreatedPartitions).
append(" partition").append(plural(numCreatedPartitions)).
append("\n");
}
if (numPartitionMigrations != 0) {
sb.append("Migrate ").append(numPartitionMigrations).
append(" partition").append(plural(numPartitionMigrations)).
append("\n");
}
int nNewANs = getNumNewANs();
if (nNewANs > 0) {
sb.append("Create ").append(nNewANs).append(" AN").
append(plural(nNewANs)).append("\n");
}
moved = getNumRelocatedANs();
if (moved > 0) {
sb.append("Relocate ").append(moved).append(" AN").
append(plural(moved)).append("\n");
}
moved = getNumRemovedANs();
if (moved > 0) {
sb.append("Remove ").append(moved).append(" AN").
append(plural(moved)).append("\n");
}
/* If some summary information has been generated, add a newline */
if (sb.length() > 0) {
sb.append("\n");
}
for (Entry e : changedZones.entrySet()) {
sb.append("change ").append(e.getKey()).
append(" to ").append(e.getValue()).append("\n");
}
for (Entry e : changedAffinityZones.entrySet()) {
sb.append("change ").append(e.getKey()).
append(" master affinity to ").
append(e.getValue()).append("\n");
}
final String indent = " ";
for (Map.Entry s : changedShards.entrySet()) {
sb.append("shard ").append(s.getKey());
if (removedShards.contains(s.getKey())) {
sb.append(" (to be removed)");
}
sb.append("\n");
ShardChange c = s.getValue();
int newCreates = c.newlyCreatedRNs.size();
if (newCreates > 0) {
sb.append(indent);
sb.append(newCreates).append(" new RN").
append(plural(newCreates)).append(": ");
for (RepNodeId rnId : c.newlyCreatedRNs) {
sb.append(rnId).append(" ");
}
sb.append("\n");
}
/* Display to-be-removed RNs against a shard */
int numRemovedRNsByShard = c.removedRNs.size();
if (numRemovedRNsByShard > 0) {
sb.append(indent);
sb.append(numRemovedRNsByShard).append(" to be removed RN")
.append(plural(numRemovedRNsByShard)).append(": ");
for (RepNodeId rnId : c.removedRNs) {
sb.append(rnId).append(" ");
}
sb.append("\n");
}
if (!c.getRelocatedRNs().isEmpty()) {
for (RelocatedRN rel : c.getRelocatedRNs()) {
sb.append(indent).append(rel).append("\n");
}
}
newCreates = c.newlyCreatedANs.size();
if (newCreates > 0) {
sb.append(indent);
sb.append(newCreates).append(" new AN").
append(plural(newCreates)).append(": ");
for (ArbNodeId arbId : c.newlyCreatedANs) {
sb.append(arbId).append(" ");
}
sb.append("\n");
}
if (!c.getRelocatedANs().isEmpty()) {
for (RelocatedAN rel : c.getRelocatedANs()) {
sb.append(indent).append(rel).append("\n");
}
}
final int nRemovedANs = c.removedANs.size();
if (nRemovedANs > 0) {
sb.append(indent);
sb.append(nRemovedANs).append(" remove AN").
append(plural(nRemovedANs)).append(": ");
for (ArbNodeId arbId : c.removedANs) {
sb.append(arbId).append(" ");
}
sb.append("\n");
}
if (c.newPartitionCount != 0) {
sb.append(indent).append(c.newPartitionCount).
append(" new partition").
append(plural(c.newPartitionCount)).append("\n");
}
int nMigrates = c.migrations.size();
if (nMigrates > 0) {
sb.append(indent).append(nMigrates).
append(" partition migration").append(plural(nMigrates)).
append("\n");
if (verbose) {
for (RelocatedPartition p :
sortBySrcAndPart(c.migrations)) {
sb.append(indent).append(p).append("\n");
}
}
}
}
if (sb.length() == 0) {
return NO_CHANGE;
}
return "Topology transformation from " + sourceName + " to " +
candidate.getName() + ":\n" + sb.toString();
}
public ObjectNode displayJson(boolean verbose) {
final ObjectNode top = JsonUtils.createObjectNode();
if (!changedZones.isEmpty()) {
top.put("changeZoneTypeTotal", changedZones.size());
}
if (!changedAffinityZones.isEmpty()) {
top.put("changeZoneAffinityTotal", changedAffinityZones.size());
}
int nShards = getNumNewShards();
if (nShards > 0) {
top.put("createShardTotal", nShards);
}
int nNewRNs = getNumNewRNs();
if (nNewRNs > 0) {
top.put("createRnTotal", nNewRNs);
}
/* Show the number of to-be-removed RNs */
int nRemovedRNs = getNumRemovedRNs();
if (nRemovedRNs > 0) {
top.put("removeRnTotal", nRemovedRNs);
}
/* Show the number of to-be-removed shards */
int nRemovedShards = getNumRemovedShards();
if (nRemovedShards > 0) {
top.put("removeShardTotal", nRemovedShards);
}
int moved = getNumRelocatedRNs();
if (moved > 0) {
top.put("relocateRnTotal", moved);
}
if (numCreatedPartitions != 0) {
top.put("createPartitionTotal", numCreatedPartitions);
}
if (numPartitionMigrations != 0) {
top.put("migratePartitionTotal", numPartitionMigrations);
}
int nNewANs = getNumNewANs();
if (nNewANs > 0) {
top.put("createAnTotal", nNewANs);
}
moved = getNumRelocatedANs();
if (moved > 0) {
top.put("relocateAnTotal", moved);
}
moved = getNumRemovedANs();
if (moved > 0) {
top.put("removeAnTotal", moved);
}
final ArrayNode changeZoneArray = top.putArray("changeZones");
for (Entry e : changedZones.entrySet()) {
final ObjectNode on = JsonUtils.createObjectNode();
on.put("change", e.getKey().toString());
on.put("to", e.getValue().toString());
changeZoneArray.add(on);
}
final ArrayNode changeZoneAffinityArray =
top.putArray("changeAffinityZones");
for (Entry e :
changedAffinityZones.entrySet()) {
final ObjectNode on = JsonUtils.createObjectNode();
on.put("change", e.getKey().toString());
on.put("to", e.getValue().toString());
changeZoneAffinityArray.add(on);
}
final ArrayNode shardArray = top.putArray("changeShards");
for (Map.Entry s : changedShards.entrySet()) {
final ObjectNode on = JsonUtils.createObjectNode();
on.put("shard", s.getKey().toString());
on.put("remove", false);
if (removedShards.contains(s.getKey())) {
on.put("remove", true);
}
final ArrayNode createRnArray = on.putArray("createRns");
ShardChange c = s.getValue();
int newCreates = c.newlyCreatedRNs.size();
if (newCreates > 0) {
for (RepNodeId rnId : c.newlyCreatedRNs) {
createRnArray.add(rnId.toString());
}
}
/* Display to-be-removed RNs against a shard */
final ArrayNode removeRnArray = on.putArray("removeRns");
int numRemovedRNsByShard = c.removedRNs.size();
if (numRemovedRNsByShard > 0) {
for (RepNodeId rnId : c.removedRNs) {
removeRnArray.add(rnId.toString());
}
}
final ArrayNode relocateRnArray = on.putArray("relocateRns");
if (!c.getRelocatedRNs().isEmpty()) {
for (RelocatedRN rel : c.getRelocatedRNs()) {
relocateRnArray.add(rel.toString());
}
}
final ArrayNode createAnArray = on.putArray("createAns");
newCreates = c.newlyCreatedANs.size();
if (newCreates > 0) {
for (ArbNodeId arbId : c.newlyCreatedANs) {
createAnArray.add(arbId.toString());
}
}
final ArrayNode relocateAnArray = on.putArray("relocateAns");
if (!c.getRelocatedANs().isEmpty()) {
for (RelocatedAN rel : c.getRelocatedANs()) {
relocateAnArray.add(rel.toString());
}
}
final ArrayNode removeAnArray = on.putArray("removeAns");
final int nRemovedANs = c.removedANs.size();
if (nRemovedANs > 0) {
for (ArbNodeId arbId : c.removedANs) {
removeAnArray.add(arbId.toString());
}
}
if (c.newPartitionCount != 0) {
on.put("newPartitionCount", c.newPartitionCount);
}
int nMigrates = c.migrations.size();
if (nMigrates > 0) {
on.put("migratePartitionCount", nMigrates);
if (verbose) {
final ArrayNode migratePartitionArray =
on.putArray("migratePartitions");
for (RelocatedPartition p :
sortBySrcAndPart(c.migrations)) {
migratePartitionArray.add(p.toString());
}
}
}
shardArray.add(on);
}
return top;
}
/**
* Return an "s" if the value is > 1, so the plurality is right in the
* display.
*/
private String plural(int val) {
return (val==1) ? " " : "s ";
}
/**
* Sort the partition migrations first by source shard, and then by
* partition id, for display.
*/
private List sortBySrcAndPart
(List migrations) {
List relPart = new ArrayList<>(migrations);
Collections.sort
(relPart,
new Comparator() {
@Override
public int compare(RelocatedPartition p1,
RelocatedPartition p2) {
int p1Src = p1.getSourceShard().getGroupId();
int p2Src = p2.getSourceShard().getGroupId();
int shardDiff = p1Src - p2Src;
if (shardDiff != 0) {
return shardDiff;
}
return p1.getPartitionId().getPartitionId() -
p2.getPartitionId().getPartitionId();
}});
return relPart;
}
/**
* Return information about shards that have changed. This includes shards
* that are newly created.
*/
public Map getChangedShards() {
return changedShards;
}
/**
* Return the different in the number of shards between the two topologies.
*/
public static int shardDelta(Topology topoA, Topology topoB) {
return topoA.getRepGroupMap().size() - topoB.getRepGroupMap().size();
}
/**
* The actual work of doing the diff.
* @throws InvalidTopologyException
*/
private void doDiff(boolean validate) {
/*
* Check that the source and target topologies obey all rules, and
* that source can be legitimately transformed into the target.
* Validation may be turned off if TopologyDiff is being used solely
* to display potential transitions.
*/
if (validate) {
Rules.validateTransition(source, candidate, params);
}
findChangedZones();
/*
* The topology was validated earlier to ensure that R2 assumptions are
* correct.
*
* Find the set of brand new RNs that are only in the target topology,
* and need to be created.
*/
Topology target = candidate.getTopology();
Set originalRNs = source.getRepNodeIds();
newRNs.addAll(target.getRepNodeIds());
newRNs.removeAll(originalRNs);
/* Find to-be-removed RNs */
removedRNs.addAll(originalRNs);
removedRNs.removeAll(target.getRepNodeIds());
/* The new created RNs won't be removed, so remove them from the set */
removedRNs.removeAll(newRNs);
/* Group the brand new RNs by shard. */
identifyNewRNs();
/* Group the to be deleted RNs by shard. */
identifyRemovedRNs();
/*
* Identify all the RNs that have been relocated, grouping them
* by shard.
*/
identifyRelocatedRNs(originalRNs);
Set originalANs = source.getArbNodeIds();
Set targetANs = target.getArbNodeIds();
newANs.addAll(targetANs);
newANs.removeAll(originalANs);
removedANs.addAll(originalANs);
removedANs.removeAll(targetANs);
identifyANsToShards();
identifyRelocatedANs(originalANs);
/*
* Find all the shards that did not exist before..
*/
findNewShards();
/* Find partitions that need to be moved or created. */
findPartitionChanges();
storageDirAssignments = candidate.getStorageDirAssignments(params);
rnLogDirAssignments = candidate.getRNLogDirAssignments(params);
}
/*
* Finds zones that have changed types. The zone ID and new type are added
* to the changeZones map.
*/
private void findChangedZones() {
for (Datacenter dc :
candidate.getTopology().getDatacenterMap().getAll()) {
final Datacenter oldDc = source.get(dc.getResourceId());
if (oldDc == null) {
continue;
}
if (!oldDc.getDatacenterType().equals(dc.getDatacenterType())) {
changedZones.put(dc.getResourceId(), dc.getDatacenterType());
}
if (oldDc.getMasterAffinity() != dc.getMasterAffinity()) {
changedAffinityZones.put(dc.getResourceId(),
dc.getMasterAffinity());
}
}
}
/**
* Find shards that have new RNs.
*/
private void identifyNewRNs() {
/* Identify all brand new RNs, grouping them by shard. */
for (RepNodeId rnId : newRNs) {
ShardChange change =
getShardChange(new RepGroupId(rnId.getGroupId()));
change.addNewRN(rnId);
}
}
/**
* Add information about which shards that have RNs to be removed.
*/
private void identifyRemovedRNs() {
for (RepNodeId rnId : removedRNs) {
ShardChange change =
getShardChange(new RepGroupId(rnId.getGroupId()));
change.addRemovedRN(rnId);
}
}
/**
* Add information about which shards have relocated RNs
*/
private void identifyRelocatedRNs(Set originalRNs) {
Topology target = candidate.getTopology();
for (RepNodeId existingRNId : originalRNs) {
StorageNodeId sourceSNId =
source.get(existingRNId).getStorageNodeId();
RepNode targetRN = target.get(existingRNId);
/*
* Removed RN does not existing in target topology, it is not the
* relocated RN
*/
if (targetRN == null) {
continue;
}
StorageNodeId targetSNId = targetRN.getStorageNodeId();
if (!sourceSNId.equals(targetSNId)) {
final DatacenterId sourceZoneId =
source.getDatacenter(sourceSNId).getResourceId();
final DatacenterId targetZoneId =
target.getDatacenter(targetSNId).getResourceId();
if (!sourceZoneId.equals(targetZoneId)) {
throw new UnsupportedOperationException(
"Cannot move " + existingRNId + " from zone " +
sourceZoneId + " to " + targetZoneId);
}
String sourceStorageDirPath = null;
String sourceRNLogDirPath = null;
RepNodeParams rnp = params.get(existingRNId);
if (rnp != null) {
sourceStorageDirPath = rnp.getStorageDirectoryPath();
sourceRNLogDirPath = rnp.getLogDirectoryPath();
}
final StorageDirectory targetStorageDir =
candidate.getStorageDir(existingRNId, params);
final LogDirectory targetRNLogDir =
candidate.getRNLogDir(existingRNId, params);
RelocatedRN rel = new RelocatedRN(existingRNId,
sourceSNId,
sourceStorageDirPath,
sourceRNLogDirPath,
targetSNId,
targetStorageDir,
targetRNLogDir,
params);
ShardChange change =
getShardChange(new RepGroupId(existingRNId.getGroupId()));
change.addRelocatedRN(rel);
numRelocatedRNs++;
}
}
}
/**
* Add information about which shards have relocated Arbiters
*/
private void identifyRelocatedANs(Set originalARBs) {
Topology target = candidate.getTopology();
for (ArbNodeId existingARBId : originalARBs) {
StorageNodeId sourceSNId =
source.get(existingARBId).getStorageNodeId();
ArbNode targetAN = target.get(existingARBId);
if (targetAN == null) {
/* AN is not in the target topo */
continue;
}
StorageNodeId targetSNId = targetAN.getStorageNodeId();
if (!sourceSNId.equals(targetSNId)) {
RelocatedAN rel = new RelocatedAN(existingARBId,
sourceSNId,
targetSNId);
ShardChange change =
getShardChange(new RepGroupId(existingARBId.getGroupId()));
change.addRelocatedANs(rel);
numRelocatedANs++;
}
}
}
/**
* Find shards and add/remove Arbiters
*/
private void identifyANsToShards() {
/* Identify all brand new ARBs, grouping them by shard. */
for (ArbNodeId arbId : newANs) {
ShardChange change =
getShardChange(new RepGroupId(arbId.getGroupId()));
change.addNewAN(arbId);
}
/* Identify all removed ANs, grouping them by shard. */
for (ArbNodeId arbId : removedANs) {
ShardChange change =
getShardChange(new RepGroupId(arbId.getGroupId()));
change.addRemovedAN(arbId);
}
}
/**
* Find all the shards which did not exist before.
*/
private void findNewShards() {
Topology target = candidate.getTopology();
Set originalShards = source.getRepGroupIds();
Set candidateShards = target.getRepGroupIds();
Set brandNew = new HashSet<>(candidateShards);
brandNew.removeAll(originalShards);
newShards.addAll(brandNew);
/*
* Sort the new shard list so it's ordered by RepGroupId. This will
* make the actual initial creation of partitions in the AddPartition
* task match the candidate topology. New partition creation is
* driven by a count of partitions on the ordinal shard rather than
* an actual match of partition ids and shard ids, because the
* shard id and partition id are generated internally by the topology,
* and can't be specified by externally when constructing those items.
*/
Collections.sort(newShards);
removedShards.addAll(originalShards);
removedShards.removeAll(candidateShards);
}
/**
* Assumes that the newShards list has been populated before this is
* called, as it assigns partitions to shards..
*/
private void findPartitionChanges() {
Topology target = candidate.getTopology();
if (source.getPartitionMap().isEmpty()) {
/* Brand new store, assign partitions to new shards. */
for (Partition p: target.getPartitionMap().getAll()) {
ShardChange change = getShardChange(p.getRepGroupId());
change.incNewPartionCount();
numCreatedPartitions++;
}
return;
}
/*
* Before looking at partition assignments, set aside RNs that already
* have the right number of partitions. Assume that all partitions
* are the same, and that we don't want to move partitions on or off
* a RN if it already houses the right number. This guards against
* inadvertent, unnecessary movement of partitions. In future releases,
* we may give partitions different weight, and there may be a reason
* to move a partitions even though the number of partitions on the
* source or destination are unchanged.
*/
/* Count the number of partitions per shard in the old topo */
Map sourceCount = new HashMap<>();
for (RepGroupId rgId: source.getRepGroupIds()) {
sourceCount.put(rgId, new AtomicInteger(0));
}
for (Partition p: source.getPartitionMap().getAll()) {
sourceCount.get(p.getRepGroupId()).incrementAndGet();
}
/* Count the number of partitions per shard in the new topo */
Map targetCount = new HashMap<>();
for (RepGroupId rgId: target.getRepGroupIds()) {
targetCount.put(rgId, new AtomicInteger(0));
}
for (Partition p: target.getPartitionMap().getAll()) {
targetCount.get(p.getRepGroupId()).incrementAndGet();
}
Set changed = new HashSet<>();
for (RepGroupId rgId: target.getRepGroupIds()) {
if (sourceCount.get(rgId) == null) {
changed.add(rgId);
} else if (sourceCount.get(rgId).get() !=
targetCount.get(rgId).get()) {
changed.add(rgId);
}
}
/*
* The to-be-removed shard existing in source topology and not existing
* in target topology
*/
for (RepGroupId rgId : source.getRepGroupIds()) {
if (targetCount.get(rgId) == null) {
changed.add(rgId);
}
}
for (Partition p: target.getPartitionMap().getAll()) {
PartitionId pId = p.getResourceId();
RepGroupId oldShard = source.get(pId).getRepGroupId();
RepGroupId newShard = target.get(pId).getRepGroupId();
/*
* If the shard assignments are different AND the old and new shard
* changed the numbers of partitions they house, do a migration.
*/
if ((!oldShard.equals(newShard)) &&
(changed.contains(oldShard) && changed.contains(newShard))){
ShardChange change = getShardChange(newShard);
change.add(new RelocatedPartition(pId, oldShard, newShard));
pMigrationSources.add(oldShard);
pMigrationDestinations.add(newShard);
numPartitionMigrations++;
}
}
}
/**
* Get the appropriate ShardChange instance, creating one of it does not
* already exist.
*/
public ShardChange getShardChange(RepGroupId id) {
ShardChange change = changedShards.get(id);
if (change == null) {
change = new ShardChange();
changedShards.put(id, change);
}
return change;
}
/**
* @return a list of newly created shards.
*/
public List getNewShards() {
return newShards;
}
/**
* @return a list of to be removed shards.
*/
public Set getRemovedShards() {
return removedShards;
}
/**
* Returns the storage directory assigned to the specified RN.
*/
public StorageDirectory getStorageDir(RepNodeId rnId) {
return storageDirAssignments.get(rnId);
}
/**
* Returns the RN log directory assigned to the specified RN.
*/
public LogDirectory getRNLogDir(RepNodeId rnId) {
return rnLogDirAssignments.get(rnId);
}
/**
* Records the changes that have to be made to a shard; which is to add or
* move RNs, and add partitions.
*/
public static class ShardChange {
/* RNs that need to be created */
private final Set newlyCreatedRNs;
/* RNs that need to be removed */
private final Set removedRNs;
/* RNs that are to be moved from one SN to another. */
private final Set relocatedRNs;
/* ANs that need to be created */
private final Set newlyCreatedANs;
/* ANs that are to be moved from one SN to another. */
private final Set relocatedANs;
private final Set removedANs;
/*
* The number of partitions that should be created on this shard.
* Applies only to brand new shards. Partitions that are migrated
* are recorded as a PartitionMigration.
*/
private int newPartitionCount;
/*
* Partitions that are to be migrated to this shard.
*/
private final List migrations;
ShardChange() {
/*
* Keep the the relocated and newly created RNs sorted by node id,
* so that the display of the diff is nicer. Also makes task
* construction and order more deterministic and intuitive to the
* user.
*/
relocatedRNs = new TreeSet<>
(new Comparator() {
@Override
public int compare(RelocatedRN o1, RelocatedRN o2) {
return o1.getRnId().getNodeNum() -
o2.getRnId().getNodeNum();
}
});
newlyCreatedRNs = new TreeSet<>
(new Comparator() {
@Override
public int compare(RepNodeId o1, RepNodeId o2) {
return o1.getNodeNum() - o2.getNodeNum();
}
});
removedRNs = new TreeSet<>
(new Comparator() {
@Override
public int compare(RepNodeId o1, RepNodeId o2) {
return o1.getNodeNum() - o2.getNodeNum();
}
});
relocatedANs = new TreeSet<>
(new Comparator() {
@Override
public int compare(RelocatedAN o1, RelocatedAN o2) {
return o1.getArbId().getNodeNum() -
o2.getArbId().getNodeNum();
}
});
newlyCreatedANs = new TreeSet<>
(new Comparator() {
@Override
public int compare(ArbNodeId o1, ArbNodeId o2) {
return o1.getNodeNum() - o2.getNodeNum();
}
});
removedANs = new TreeSet<>
(new Comparator() {
@Override
public int compare(ArbNodeId o1, ArbNodeId o2) {
return o1.getNodeNum() - o2.getNodeNum();
}
});
migrations = new ArrayList<>();
}
public List getMigrations() {
return migrations;
}
public void add(RelocatedPartition relocatedPartition) {
migrations.add(relocatedPartition);
}
void incNewPartionCount() {
newPartitionCount++;
}
public int getNumNewPartitions() {
return newPartitionCount;
}
public Set getNewRNs() {
return newlyCreatedRNs;
}
void addNewRN(RepNodeId rnId) {
newlyCreatedRNs.add(rnId);
}
void addRemovedRN(RepNodeId rnId) {
removedRNs.add(rnId);
}
public Set getRelocatedRNs() {
return relocatedRNs;
}
void addRelocatedRN(RelocatedRN rel) {
relocatedRNs.add(rel);
}
public Set getNewANs() {
return newlyCreatedANs;
}
void addNewAN(ArbNodeId arbId) {
newlyCreatedANs.add(arbId);
}
public Set getRemovedANs() {
return removedANs;
}
void addRemovedAN(ArbNodeId arbId) {
removedANs.add(arbId);
}
public Set getRelocatedANs() {
return relocatedANs;
}
void addRelocatedANs(RelocatedAN rel) {
relocatedANs.add(rel);
}
/**
* Make a string that contains the set of SNs that house this shard,
* purely for debugging/descriptive purposes.
*/
public String getSNSetDescription(Topology topo) {
StringBuilder sb = new StringBuilder();
for (RepNodeId rnId : newlyCreatedRNs) {
StorageNodeId snId = topo.get(rnId).getStorageNodeId();
sb.append(topo.get(snId).toString());
sb.append(" ");
}
return sb.toString();
}
}
/**
* Records info for RNs that have moved to a different SN.
*/
public static class RelocatedRN {
private final RepNodeId rnId;
private final StorageNodeId oldSNId;
private final StorageNodeId newSNId;
private final String oldStorageDirPath;
private final StorageDirectory newStorageDir;
private final String oldRNLogDirPath;
private final LogDirectory newRNLogDir;
private final String oldRootDir;
private final String newRootDir;
private RelocatedRN(RepNodeId rnId,
StorageNodeId oldSNId,
String oldStorageDirPath,
String oldRNLogDirPath,
StorageNodeId newSNId,
StorageDirectory newStorageDir,
LogDirectory newRNLogDir,
Parameters params) {
this.rnId = rnId;
this.oldSNId = oldSNId;
this.oldStorageDirPath = oldStorageDirPath;
this.oldRNLogDirPath = oldRNLogDirPath;
this.oldRootDir = params.get(oldSNId).getRootDirPath();
this.newSNId = newSNId;
this.newStorageDir = newStorageDir;
this.newRNLogDir = newRNLogDir;
this.newRootDir = params.get(newSNId).getRootDirPath();
}
public RepNodeId getRnId() {
return rnId;
}
public StorageNodeId getNewSNId() {
return newSNId;
}
public StorageNodeId getOldSNId() {
return oldSNId;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("Relocate ").append(rnId).append(" from ");
sb.append(oldSNId).append(" storagedir=");
if (oldStorageDirPath == null) {
sb.append(oldRootDir);
} else {
sb.append(oldStorageDirPath);
}
sb.append(" rnlogdir=");
if (oldRNLogDirPath == null) {
sb.append(oldRootDir);
} else {
sb.append(oldRNLogDirPath);
}
sb.append(" to ");
sb.append(newSNId).append(" storagedir=");
if (newStorageDir.getPath() == null) {
sb.append(newRootDir);
} else {
sb.append(newStorageDir.getPath());
}
sb.append(" rnlogdir=");
if (newRNLogDir.getPath() == null) {
sb.append(newRootDir);
} else {
sb.append(newRNLogDir.getPath());
}
return sb.toString();
}
}
/**
* Records info for ANs that have moved to a different SN.
*/
public static class RelocatedAN {
private final ArbNodeId arbId;
private final StorageNodeId oldSNId;
private final StorageNodeId newSNId;
RelocatedAN(ArbNodeId arbId,
StorageNodeId oldSNId,
StorageNodeId newSNId) {
this.arbId = arbId;
this.oldSNId = oldSNId;
this.newSNId = newSNId;
}
public ArbNodeId getArbId() {
return arbId;
}
public StorageNodeId getNewSNId() {
return newSNId;
}
public StorageNodeId getOldSNId() {
return oldSNId;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("Relocate ").append(arbId).append(" from ");
sb.append(oldSNId).append(" ");
sb.append(" to ");
sb.append(newSNId).append(" ");
return sb.toString();
}
}
/**
* Records info for partitions that have moved to a new shard.
*/
public static class RelocatedPartition {
private final PartitionId partitionId;
private final RepGroupId sourceShard;
private final RepGroupId targetShard;
public RelocatedPartition(PartitionId partitionId,
RepGroupId sourceShard,
RepGroupId targetShard) {
this.partitionId = partitionId;
this.sourceShard = sourceShard;
this.targetShard = targetShard;
}
public PartitionId getPartitionId() {
return partitionId;
}
public RepGroupId getSourceShard() {
return sourceShard;
}
@Override
public String toString() {
return "Migrate " + partitionId + " from " + sourceShard + " to " +
targetShard;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy