use reservoir sampling in pickSegmentToMove to avoid IndexOutOfBoundsException when a segment gets dropped mid-run

This commit is contained in:
Nelson Ray 2013-04-02 11:36:01 -07:00 committed by Gian Merlino
parent 3937a77665
commit 092fe70a35
3 changed files with 21 additions and 37 deletions

View File

@ -144,46 +144,31 @@ public class BalancerCostAnalyzer
} }
/** /**
* Sample from each server with probability proportional to the number of segments on that server. * The balancing application requires us to pick a proposal segment uniformly at random from the set of
* all servers. We use reservoir sampling to do this.
* *
* @param serverHolders A list of ServerHolders for a particular tier. * @param serverHolders A list of ServerHolders for a particular tier.
* @param numSegments
*
* @return A ServerHolder sampled with probability proportional to the
* number of segments on that server
*/
private ServerHolder sampleServer(final List<ServerHolder> serverHolders, final int numSegments)
{
final int num = rand.nextInt(numSegments);
int cumulativeSegments = 0;
int numToStopAt = 0;
while (cumulativeSegments <= num) {
cumulativeSegments += serverHolders.get(numToStopAt).getServer().getSegments().size();
numToStopAt++;
}
return serverHolders.get(numToStopAt - 1);
}
/**
* The balancing application requires us to pick a proposal segment.
*
* @param serverHolders A list of ServerHolders for a particular tier.
* @param numSegments The total number of segments on a particular tier.
* *
* @return A BalancerSegmentHolder sampled uniformly at random. * @return A BalancerSegmentHolder sampled uniformly at random.
*/ */
public BalancerSegmentHolder pickSegmentToMove(final List<ServerHolder> serverHolders, final int numSegments) public BalancerSegmentHolder pickSegmentToMove(final List<ServerHolder> serverHolders)
{ {
/** We want to sample from each server w.p. numSegmentsOnServer / totalSegments */ ServerHolder fromServerHolder = null;
ServerHolder fromServerHolder = sampleServer(serverHolders, numSegments); DataSegment proposalSegment = null;
int numSoFar = 0;
/** and actually pick that segment uniformly at random w.p. 1 / numSegmentsOnServer for (ServerHolder server : serverHolders) {
so that the probability of picking a segment is 1 / totalSegments. */ for (DataSegment segment : server.getServer().getSegments().values()) {
List<DataSegment> segments = Lists.newArrayList(fromServerHolder.getServer().getSegments().values()); int randNum = rand.nextInt(numSoFar + 1);
// w.p. 1 / (numSoFar + 1), swap out the server and segment
if (randNum == numSoFar) {
fromServerHolder = server;
proposalSegment = segment;
numSoFar++;
}
}
}
DataSegment proposalSegment = segments.get(rand.nextInt(segments.size()));
return new BalancerSegmentHolder(fromServerHolder.getServer(), proposalSegment); return new BalancerSegmentHolder(fromServerHolder.getServer(), proposalSegment);
} }
@ -214,7 +199,7 @@ public class BalancerCostAnalyzer
} }
/** /**
* For assigment, we want to move to the lowest cost server that isn't already serving the segment. * For assignment, we want to move to the lowest cost server that isn't already serving the segment.
* *
* @param proposalSegment A DataSegment that we are proposing to move. * @param proposalSegment A DataSegment that we are proposing to move.
* @param serverHolders An iterable of ServerHolders for a particular tier. * @param serverHolders An iterable of ServerHolders for a particular tier.

View File

@ -28,7 +28,6 @@ import com.metamx.druid.client.DruidServer;
import com.metamx.emitter.EmittingLogger; import com.metamx.emitter.EmittingLogger;
import org.joda.time.DateTime; import org.joda.time.DateTime;
import java.util.ArrayList;
import java.util.Comparator; import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -116,7 +115,7 @@ public class DruidMasterBalancer implements DruidMasterHelper
int iter = 0; int iter = 0;
while (iter < maxSegmentsToMove) { while (iter < maxSegmentsToMove) {
iter++; iter++;
final BalancerSegmentHolder segmentToMove = analyzer.pickSegmentToMove(serverHolderList, numSegments); final BalancerSegmentHolder segmentToMove = analyzer.pickSegmentToMove(serverHolderList);
final ServerHolder holder = analyzer.findNewSegmentHomeBalance(segmentToMove.getSegment(), serverHolderList); final ServerHolder holder = analyzer.findNewSegmentHomeBalance(segmentToMove.getSegment(), serverHolderList);
if (holder == null) { if (holder == null) {
continue; continue;

View File

@ -193,7 +193,7 @@ public class DruidMasterBalancerTest
.build(); .build();
params = new DruidMasterBalancerTester(master).run(params); params = new DruidMasterBalancerTester(master).run(params);
Assert.assertTrue(params.getMasterStats().getPerTierStats().get("movedCount").get("normal").get() == 3); Assert.assertTrue(params.getMasterStats().getPerTierStats().get("movedCount").get("normal").get() > 0);
} }
@Test @Test
@ -271,6 +271,6 @@ public class DruidMasterBalancerTest
.build(); .build();
params = new DruidMasterBalancerTester(master).run(params); params = new DruidMasterBalancerTester(master).run(params);
Assert.assertTrue(params.getMasterStats().getPerTierStats().get("movedCount").get("normal").get() == 4); Assert.assertTrue(params.getMasterStats().getPerTierStats().get("movedCount").get("normal").get() > 0);
} }
} }