Skip to content

Priority on loading for primary replica#4757

Merged
drcrallen merged 38 commits intoapache:masterfrom
metamx:prioritized-segment-loading
Sep 28, 2017
Merged

Priority on loading for primary replica#4757
drcrallen merged 38 commits intoapache:masterfrom
metamx:prioritized-segment-loading

Conversation

@xanec
Copy link
Copy Markdown
Contributor

@xanec xanec commented Sep 6, 2017

This PR seeks to address a previously-encountered bug that if a LoadRule has multiple tiers, prolonged loading on a (lower) tier seems to "block" another tier from loading even when the latter is available for loading. Preliminary investigation suggests that, depending on the BalancerStrategy and/or configurations (e.g., maxSegmentsInNodeLoadingQueue and replicationThrottleLimit), the circumstances may result in missed opportunity in loading, which could critical when prompt loading in bulk is required (e.g., recovery of historicals).

In the current implementation, the primary replica will be assigned to the tier that comes first in the tieredReplicants map when there is no replica in the cluster and this replica will not be throttled: https://github.com/druid-io/druid/blob/d43687d578bf3dea98b01d0899bcfbb2125d142e/server/src/main/java/io/druid/server/coordinator/rules/LoadRule.java#L130-L134

The main mechanism in the PR uses the priority parameter set on the server to prioritized which server is selected as the holder of the primary replica. The change in this PR simply identifies if the primary replica needs to be loaded (i.e., when totalReplicantsInCluster <= 0) and will prioritize appropriately (i.e., select among the candidate servers the one with the highest priority), instead of using the arbitrary ordering of the hash map.

Copy link
Copy Markdown
Contributor

@drcrallen drcrallen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see @leventov is also making changes, so I'm going to submit what I have so far before going on


final Optional<ServerHolder> primaryHolderToLoad;
if (totalReplicantsInCluster <= 0) {
log.trace("No replicants for %s", segment.getIdentifier());
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggest just debug level

final Optional<ServerHolder> primaryHolderToLoad;
if (totalReplicantsInCluster <= 0) {
log.trace("No replicants for %s", segment.getIdentifier());
primaryHolderToLoad = getPrimaryHolder(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how about getPriorityHolder instead?

return stats;
}
} else {
primaryHolderToLoad = Optional.empty();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can this logic flip around? I find the tiny cases easier to read if they are first.

for example change to

if (totalReplicantsInCluster > 0) {
  primaryHolderToLoad = Optional.empty();
} else {
.....
}

serverHolderPredicate
);

if (primaryHolderToLoad.isPresent()) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this feels strange to not use a functional workflow here, but it is not clear a functional flow would be easier to read.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On the contrary, I removed use of Optional and mapping, because IMO it only adds obscurity. "Too functional" for Java

++totalReplicantsInCluster;
} else {
log.trace("No primary holder found for %s", segment.getIdentifier());
return stats;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is hidden in a weird spot. Is this the same behavior as previously?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also suggest moving this up to the top of the if statement and flipping the boolean for ease of readability.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, I'll move it up.

And yeah, I think I have mistakenly skipped the "drop" part; I will add that in.


return candidates
.stream()
.max((s1, s2) -> Ints.compare(s1.getServer().getPriority(), s2.getServer().getPriority()));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can this get resolved without materializing the whole candidates array?

{
final List<ServerHolder> candidates = Lists.newLinkedList();

for (final Map.Entry<String, Integer> entry : tieredReplicants.entrySet()) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This workflow could probably be a lot cleaner if rewritten functionally

continue;
}

final ServerHolder candidate = strategy.findNewSegmentHomeReplicator(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I'm reading this correctly, this is doing a LOT more here compared to what it was previously

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it should not be that bad. If the findNewSegmentHomeReplicator method call is of concern, then this change will make N - 1 more calls where N is the number of tiers. If we have about 2 tiers per rule, this will make one more call.

@@ -57,12 +59,58 @@ public CoordinatorStats run(DruidCoordinator coordinator, DruidCoordinatorRuntim

final Map<String, Integer> loadStatus = Maps.newHashMap();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@xanec could you please extract some functionality into dedicated methods from this run() method. It's too long.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, do you mind if I do some significant significant refactoring?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't mind


final Map<String, Integer> tieredReplicants = getTieredReplicants();
for (final String tier : tieredReplicants.keySet()) {
stats.addToTieredStat(ASSIGNED_COUNT, tier, 0);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why does this need initialized now?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this needs initialized. All the components underneath lazily add entries as needed

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is for the expected behavior in once of the tests: https://github.com/druid-io/druid/blob/d43687d578bf3dea98b01d0899bcfbb2125d142e/server/src/test/java/io/druid/server/coordinator/rules/LoadRuleTest.java#L631

Without the initialization, stat3 would not have the hot tier and throw NPE. Note that previously, a 0 will be added into the statistics when no assignment is done:
https://github.com/druid-io/druid/blob/d43687d578bf3dea98b01d0899bcfbb2125d142e/server/src/main/java/io/druid/server/coordinator/rules/LoadRule.java#L102

Personally, I also feel that no initialization is required but I am not sure if receiving 0 is part of the expected usage.

@xanec
Copy link
Copy Markdown
Contributor Author

xanec commented Sep 8, 2017

@leventov @drcrallen I have implemented the previously requested changes and did some refactoring. Could you kindly review the code again? Thanks.

}

final MinMaxPriorityQueue<ServerHolder> serverQueue = params.getDruidCluster().getHistoricalsByTier(tier);
private static Predicate<ServerHolder> createPredicate(final DruidCoordinatorRuntimeParams params)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this method be given a more speaking name? E. g. "createLoadQueueSizeLimitingPredicate()"

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

final String tier,
final DruidCluster druidCluster,
final Predicate<ServerHolder> firstPredicate,
final Predicate<ServerHolder>... otherPredicates
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested for this method just to accept one predicate, and callers call and() on some predicates, if needed

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

);
return numAssigned;
}
holders.remove(holder);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why added this?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is to prevent the holder from serving more than one replica of the same segment. During the current run, I believe the assignment will not be immediately reflected in the ServerHolder. Have I mistaken on the expected behavior?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you are correct

log.makeAlert("No holders found for tier[%s]", tier).emit();
numDropped = 0;
} else {
final int numToDrop = entry.getIntValue() - targetReplicants.getOrDefault(tier, 0);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested to extract entry.getIntValue() as "currentReplicants" for readability

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

for (final Object2IntMap.Entry<String> entry : targetReplicants.object2IntEntrySet()) {
final String tier = entry.getKey();
// if there are replicants loading in cluster
if (druidCluster.hasTier(tier) && entry.getIntValue() > currentReplicants.getOrDefault(tier, 0)) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why if this it true in any tier, we exit the method? maybe still drop segments in other tiers?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I also do not understand the complete rational behind this decision but the test cases do enforce such a behavior. May be it is to prevent some form of "thrashing" whereby segments get loaded and dropped excessively?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@fjy are you able to comment here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@leventov @fjy So should I do any modification for this?


private static int dropForTier(
final int numToDrop,
final MinMaxPriorityQueue<ServerHolder> holders,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe "tierHolders" or "holdersInTier"

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

{
int numDropped = 0;

final List<ServerHolder> droppedHolders = new LinkedList<>();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no point to use LinkedList here, please use ArrayList

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, but won't a LinkedList will be more suitable here since the size is unknown and we don't need random access? Using a LinkedList will also prevent the problem of array resizing.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, if created as ArrayList<>(1) it is strictly always more memory efficient than LinkedList, and amortized cost of adding an element is smaller. LinkedList is almost never a good data structure as is (only intrusive linked lists are useful in some forms, sometimes). Exceptions, when LinkedList is useful itself, are so rare that I've never seen them in my practice, and there are no such in the Druid codebase.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍


final List<ServerHolder> droppedHolders = new LinkedList<>();
while (numDropped < numToDrop) {
final ServerHolder holder = holders.pollLast();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why need to remove holders, and then add back? Couldn't the same logic be expressed merely with iteration?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is because we are using pollLast, an iterator will not give us the same order (i.e., in descending available size).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I inspected all usages of MinMaxPriorityQueue<ServerHolder> in the coordinator code, and it seems to me that poll() or peek() or pollFirst() or peekFirst() is never called on those min-max queues. There are only iterated "directly", elements added to them, and here, min-max is "iterated" in reverse order, via calling pollLast() and then adding elements back.

It means that either

  • Min-max queue is not needed, it could be a simple PriorityQueue in the reverse order from what is used now to create min-max queues.
  • Or, if some particular order is expected when those queues are iterated, usage of min-max queues is a mistake, because it doesn't guarantee any particular iteration order. In this case, it should be replaced with TreeSet.

Also @fjy could you please comment here

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@leventov @fjy From its use in this case, it seems the order is required. Should I modify the usages of MinMaxPriorityQueue to TreeSet then?

if (serverQueue == null) {
log.makeAlert("Tier[%s] has no servers! Check your cluster configuration!", tier).emit();
@SafeVarargs
private static List<ServerHolder> getHolderList(
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe "getFilteredHolders"

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

stats.addToTieredStat(ASSIGNED_COUNT, tier, numAssigned);

// tier with primary replica
final int targetReplicantsInTier = targetReplicants.removeInt(tier);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe add @Nullable String primaryTier parameter to assignReplicas(), to avoid this error-prone remove tier - add tier code.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

final DruidCoordinatorRuntimeParams params,
final DataSegment segment,
final CoordinatorStats stats,
@Nullable final String primaryTier
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also maybe call it "tierToSkip", to make the intention more explicit.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@drcrallen drcrallen requested a review from himanshug September 19, 2017 19:13
{
MinMaxPriorityQueue<ServerHolder> servers = historicals.get(tier);
return (servers == null) || servers.isEmpty();
return (servers != null) && !servers.isEmpty();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This logic flipped?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is it covered in unit tests?

* Iterates through each tier and find the respective segment homes; with the found segment homes, selects the one
* with the highest priority to be the holder for the primary replica.
*
* @param params
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove empty javadoc stubs

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

} else {
// cache the result for later use.
strategyCache.put(tier, candidate);
if (
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't break line after (

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

if (
topCandidate == null ||
candidate.getServer().getPriority() > topCandidate.getServer().getPriority()
) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't break line before )

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

}

return stats;
/***
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

**

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

return stats;
/***
*
* @param params
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove empty stubs

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

if (leftToLoad > 0) {
return stats;
// This enforces that loading is completed before we attempt to drop stuffs as a safety measure
for (final Object2IntMap.Entry<String> entry : targetReplicants.object2IntEntrySet()) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please extract this block as a method with boolean result

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@@ -293,12 +293,8 @@ private void drop(

// Make sure we have enough loaded replicants in the correct tiers in the cluster before doing anything
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With method extracted, this comment line doesn't make much sense to me

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@leventov
Copy link
Copy Markdown
Member

@xanec could you please remove "Enforce Indentation with Checkstyle" commit from the history?

@xanec xanec force-pushed the prioritized-segment-loading branch from 0d181a0 to d7b7c55 Compare September 27, 2017 17:12
@leventov
Copy link
Copy Markdown
Member

@drcrallen do you have more comments here?

DruidCoordinatorRuntimeParams params,
String tier,
MinMaxPriorityQueue<ServerHolder> servers,
NavigableSet<ServerHolder> servers,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Optional) Does this need to be anything other than Collection<ServerHolder>?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For some of the uses of the NavigableSet, it seems that the ordering and uniqueness is implicit in the logic (e.g., in loops). From what I can see, while very very unlikely, changes in these two qualities may alter the behavior. Hence, I have changed the variable to SortedSet instead as a safeguard against future changes to DruidCluster.getSortedHistoricalByTier.

For the other uses, I have changed it to Iterable.

Map<String, VersionedIntervalTimeline<String, DataSegment>> timelines = Maps.newHashMap();

for (MinMaxPriorityQueue<ServerHolder> serverHolders : cluster.getSortedHistoricalsByTier()) {
for (NavigableSet<ServerHolder> serverHolders : cluster.getSortedHistoricalsByTier()) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Optional) Does this need to be anything other than Collection<ServerHolder>?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refer to #4757 (comment)

// cleanup before it finished polling the metadata storage for available segments for the first time.
if (!availableSegments.isEmpty()) {
for (MinMaxPriorityQueue<ServerHolder> serverHolders : cluster.getSortedHistoricalsByTier()) {
for (NavigableSet<ServerHolder> serverHolders : cluster.getSortedHistoricalsByTier()) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Optional) Does this need to be anything other than Collection<ServerHolder>?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refer to #4757 (comment)


log.info("Load Queues:");
for (MinMaxPriorityQueue<ServerHolder> serverHolders : cluster.getSortedHistoricalsByTier()) {
for (NavigableSet<ServerHolder> serverHolders : cluster.getSortedHistoricalsByTier()) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Optional) Does this need to be anything other than Collection<ServerHolder>?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refer to #4757 (comment)

.getTotalReplicants(segment.getIdentifier(), tier);
final int loadedReplicantsInTier = params.getSegmentReplicantLookup()
.getLoadedReplicants(segment.getIdentifier(), tier);
// performs
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

??

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops

tier,
targetReplicants.getOrDefault(tier, 0),
// note: adding 1 to currentReplicantsInTier to account for the one assigned as primary replica
currentReplicants.getOrDefault(tier, 0) + 1,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

currentReplicants.getOrDefault(tier, 0) should always be 0 here right? can this just be a hard coded 1? Actually, I suggest changing the logic here a bit and make int numAssigned = 1; immediately after your assignPrimary call, then make this statement numAssigned += .... with numAssigned passed as a parameter, maybe with a code comment that it will always be 1. IMHO makes what is going on easier to follow.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

);
return numAssigned;
}
holders.remove(holder);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you are correct

final DruidCoordinatorRuntimeParams params,
final DataSegment segment,
final DruidCoordinatorRuntimeParams params
final CoordinatorStats stats
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is deceiving, it is a final object reference, but the contents are modified. Suggest adding a method comment to such effect.

final int targetReplicantsInTier,
final int currentReplicantsInTier,
final DruidCoordinatorRuntimeParams params,
final List<ServerHolder> holders,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is modified in the method call, suggest calling that out in the method docs

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have moved the retrieval of ServerHolders into the method to maintain "immutability" of parameters.

final DataSegment segment
)
{
int numDropped = 0;
Copy link
Copy Markdown
Contributor

@drcrallen drcrallen Sep 28, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Optional) This can be replaced with java 8 awesomeness

    return StreamSupport
        .stream(Spliterators.spliteratorUnknownSize(holdersInTier.descendingIterator(), Spliterator.ORDERED), false)
        .limit(numToDrop)
        .filter(sh -> sh.isServingSegment(segment))
        .mapToInt(sh -> {
          sh.getPeon().dropSegment(segment, null);
          return 1;
        })
        .sum();

Wait, actually this doesn't work but passes tests, which is not good.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

    return StreamSupport
        .stream(Spliterators.spliteratorUnknownSize(holdersInTier.descendingIterator(), Spliterator.ORDERED), false)
        .filter(sh -> sh.isServingSegment(segment))
        .limit(numToDrop)
        .mapToInt(sh -> {
          sh.getPeon().dropSegment(segment, null);
          return 1;
        })
        .sum();

is the correct one I think, but highlights a blind spot in the testing.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I am going to skip using monad-chaining on this one because we still need to work in the log.warn() into it and it does not seem quite worth it anymore 🤷‍♂️

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've included additional auxiliary fixture to fail the first implementation.

Copy link
Copy Markdown
Contributor

@drcrallen drcrallen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool, thanks! waiting for more test cases from @xanec . Please feel free to merge once the test cases are in.

@drcrallen drcrallen merged commit 26fd2b3 into apache:master Sep 28, 2017
@drcrallen drcrallen deleted the prioritized-segment-loading branch September 28, 2017 20:02
leventov pushed a commit to metamx/druid that referenced this pull request Oct 4, 2017
* Priority on loading for primary replica

* Simplicity fixes

* Fix on skipping drop for quick return.

* change to debug logging for no replicants.

* Fix on filter logic

* swapping if-else

* Fix on wrong "hasTier" logic

* Refactoring of LoadRule

* Rename createPredicate to createLoadQueueSizeLimitingPredicate

* Rename getHolderList to getFilteredHolders

* remove varargs

* extract out currentReplicantsInTier

* rename holders to holdersInTier

* don't do temporary removal of tier.

* rename primaryTier to tierToSkip

* change LinkedList to ArrayList

* Change MinMaxPriorityQueue in DruidCluster to TreeSet.

* Adding some comments.

* Modify log messages in light of predicates.

* Add in-method comments

* Don't create new Object2IntOpenHashMap for each run() call.

* Cache result from strategy call in the primary assignment to be reused during the same run.

* Spelling mistake

* Cleaning up javadoc.

* refactor out loading in progress check.

* Removed redundant comment.

* Removed forbidden API

* Correct non-forbidden API.

* Precision in variable type for NavigableSet.

* Obsolete comment.

* Clarity in method call and moving retrieval of ServerHolder into method call.

* Comment on mutability of CoordinatoorStats.

* Added auxiliary fixture for dropping.
gianm pushed a commit to implydata/druid-public that referenced this pull request Oct 30, 2017
* Priority on loading for primary replica

* Simplicity fixes

* Fix on skipping drop for quick return.

* change to debug logging for no replicants.

* Fix on filter logic

* swapping if-else

* Fix on wrong "hasTier" logic

* Refactoring of LoadRule

* Rename createPredicate to createLoadQueueSizeLimitingPredicate

* Rename getHolderList to getFilteredHolders

* remove varargs

* extract out currentReplicantsInTier

* rename holders to holdersInTier

* don't do temporary removal of tier.

* rename primaryTier to tierToSkip

* change LinkedList to ArrayList

* Change MinMaxPriorityQueue in DruidCluster to TreeSet.

* Adding some comments.

* Modify log messages in light of predicates.

* Add in-method comments

* Don't create new Object2IntOpenHashMap for each run() call.

* Cache result from strategy call in the primary assignment to be reused during the same run.

* Spelling mistake

* Cleaning up javadoc.

* refactor out loading in progress check.

* Removed redundant comment.

* Removed forbidden API

* Correct non-forbidden API.

* Precision in variable type for NavigableSet.

* Obsolete comment.

* Clarity in method call and moving retrieval of ServerHolder into method call.

* Comment on mutability of CoordinatoorStats.

* Added auxiliary fixture for dropping.
gianm pushed a commit to implydata/druid-public that referenced this pull request Nov 14, 2017
* Priority on loading for primary replica

* Simplicity fixes

* Fix on skipping drop for quick return.

* change to debug logging for no replicants.

* Fix on filter logic

* swapping if-else

* Fix on wrong "hasTier" logic

* Refactoring of LoadRule

* Rename createPredicate to createLoadQueueSizeLimitingPredicate

* Rename getHolderList to getFilteredHolders

* remove varargs

* extract out currentReplicantsInTier

* rename holders to holdersInTier

* don't do temporary removal of tier.

* rename primaryTier to tierToSkip

* change LinkedList to ArrayList

* Change MinMaxPriorityQueue in DruidCluster to TreeSet.

* Adding some comments.

* Modify log messages in light of predicates.

* Add in-method comments

* Don't create new Object2IntOpenHashMap for each run() call.

* Cache result from strategy call in the primary assignment to be reused during the same run.

* Spelling mistake

* Cleaning up javadoc.

* refactor out loading in progress check.

* Removed redundant comment.

* Removed forbidden API

* Correct non-forbidden API.

* Precision in variable type for NavigableSet.

* Obsolete comment.

* Clarity in method call and moving retrieval of ServerHolder into method call.

* Comment on mutability of CoordinatoorStats.

* Added auxiliary fixture for dropping.
gianm pushed a commit to implydata/druid-public that referenced this pull request Dec 5, 2017
* Priority on loading for primary replica

* Simplicity fixes

* Fix on skipping drop for quick return.

* change to debug logging for no replicants.

* Fix on filter logic

* swapping if-else

* Fix on wrong "hasTier" logic

* Refactoring of LoadRule

* Rename createPredicate to createLoadQueueSizeLimitingPredicate

* Rename getHolderList to getFilteredHolders

* remove varargs

* extract out currentReplicantsInTier

* rename holders to holdersInTier

* don't do temporary removal of tier.

* rename primaryTier to tierToSkip

* change LinkedList to ArrayList

* Change MinMaxPriorityQueue in DruidCluster to TreeSet.

* Adding some comments.

* Modify log messages in light of predicates.

* Add in-method comments

* Don't create new Object2IntOpenHashMap for each run() call.

* Cache result from strategy call in the primary assignment to be reused during the same run.

* Spelling mistake

* Cleaning up javadoc.

* refactor out loading in progress check.

* Removed redundant comment.

* Removed forbidden API

* Correct non-forbidden API.

* Precision in variable type for NavigableSet.

* Obsolete comment.

* Clarity in method call and moving retrieval of ServerHolder into method call.

* Comment on mutability of CoordinatoorStats.

* Added auxiliary fixture for dropping.
@jon-wei jon-wei added this to the 0.12.0 milestone Jan 5, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants