Merge "Various optimizations to RingBuilder.rebalance()"

This commit is contained in:
Jenkins 2014-01-10 12:23:37 +00:00 committed by Gerrit Code Review
commit 45b481b364

View File

@ -760,12 +760,15 @@ class RingBuilder(object):
key=lambda x: x['sort_key']) key=lambda x: x['sort_key'])
tier2devs = defaultdict(list) tier2devs = defaultdict(list)
tier2sort_key = defaultdict(list) tier2sort_key = defaultdict(tuple)
tier2dev_sort_key = defaultdict(list)
max_tier_depth = 0 max_tier_depth = 0
for dev in available_devs: for dev in available_devs:
for tier in tiers_for_dev(dev): dev['tiers'] = tiers_for_dev(dev)
for tier in dev['tiers']:
tier2devs[tier].append(dev) # <-- starts out sorted! tier2devs[tier].append(dev) # <-- starts out sorted!
tier2sort_key[tier].append(dev['sort_key']) tier2dev_sort_key[tier].append(dev['sort_key'])
tier2sort_key[tier] = dev['sort_key']
if len(tier) > max_tier_depth: if len(tier) > max_tier_depth:
max_tier_depth = len(tier) max_tier_depth = len(tier)
@ -778,10 +781,10 @@ class RingBuilder(object):
new_tiers_list = [] new_tiers_list = []
for tier in tiers_list: for tier in tiers_list:
child_tiers = list(tier2children_sets[tier]) child_tiers = list(tier2children_sets[tier])
child_tiers.sort(key=lambda t: tier2sort_key[t][-1]) child_tiers.sort(key=tier2sort_key.__getitem__)
tier2children[tier] = child_tiers tier2children[tier] = child_tiers
tier2children_sort_key[tier] = map( tier2children_sort_key[tier] = map(
lambda t: tier2sort_key[t][-1], child_tiers) tier2sort_key.__getitem__, child_tiers)
new_tiers_list.extend(child_tiers) new_tiers_list.extend(child_tiers)
tiers_list = new_tiers_list tiers_list = new_tiers_list
depth += 1 depth += 1
@ -794,7 +797,7 @@ class RingBuilder(object):
for replica in self._replicas_for_part(part): for replica in self._replicas_for_part(part):
if replica not in replace_replicas: if replica not in replace_replicas:
dev = self.devs[self._replica2part2dev[replica][part]] dev = self.devs[self._replica2part2dev[replica][part]]
for tier in tiers_for_dev(dev): for tier in dev['tiers']:
other_replicas[tier] += 1 other_replicas[tier] += 1
unique_tiers_by_tier_len[len(tier)].add(tier) unique_tiers_by_tier_len[len(tier)].add(tier)
@ -828,50 +831,45 @@ class RingBuilder(object):
# with the largest sort_key value). This lets us # with the largest sort_key value). This lets us
# short-circuit the search while still ensuring we get the # short-circuit the search while still ensuring we get the
# right tier. # right tier.
candidate_tiers = sorted(
tier2children[tier],
key=lambda tier: tier2devs[tier][-1]['sort_key'],
reverse=True)
candidates_with_replicas = \ candidates_with_replicas = \
unique_tiers_by_tier_len[len(tier) + 1] unique_tiers_by_tier_len[len(tier) + 1]
if len(candidate_tiers) > len(candidates_with_replicas): # Find a tier with the minimal replica count and the
# There exists at least one tier with 0 other # hungriest drive among all the tiers with the minimal
# replicas, so avoid calling the min() below, which is # replica count.
# expensive if you've got thousands of drives. if len(tier2children[tier]) > \
min_replica_count = 0 len(candidates_with_replicas):
# There exists at least one tier with 0 other replicas
tier = max((t for t in tier2children[tier]
if other_replicas[t] == 0),
key=tier2sort_key.__getitem__)
else: else:
min_replica_count = min(other_replicas[t] tier = max(tier2children[tier],
for t in candidate_tiers) key=lambda t: (-other_replicas[t],
# Find the first tier with the minimal replica count. tier2sort_key[t]))
# Since they're sorted, this will also have the hungriest
# drive among all the tiers with the minimal replica
# count.
for t in candidate_tiers:
if other_replicas[t] == min_replica_count:
tier = t
break
depth += 1 depth += 1
dev = tier2devs[tier][-1] dev = tier2devs[tier][-1]
dev['parts_wanted'] -= 1 dev['parts_wanted'] -= 1
dev['parts'] += 1 dev['parts'] += 1
old_sort_key = dev['sort_key'] old_sort_key = dev['sort_key']
new_sort_key = dev['sort_key'] = self._sort_key_for(dev) new_sort_key = dev['sort_key'] = self._sort_key_for(dev)
for tier in tiers_for_dev(dev): for tier in dev['tiers']:
other_replicas[tier] += 1 other_replicas[tier] += 1
unique_tiers_by_tier_len[len(tier)].add(tier) unique_tiers_by_tier_len[len(tier)].add(tier)
index = bisect.bisect_left(tier2sort_key[tier], index = bisect.bisect_left(tier2dev_sort_key[tier],
old_sort_key) old_sort_key)
tier2devs[tier].pop(index) tier2devs[tier].pop(index)
tier2sort_key[tier].pop(index) tier2dev_sort_key[tier].pop(index)
new_index = bisect.bisect_left(tier2sort_key[tier], new_index = bisect.bisect_left(tier2dev_sort_key[tier],
new_sort_key) new_sort_key)
tier2devs[tier].insert(new_index, dev) tier2devs[tier].insert(new_index, dev)
tier2sort_key[tier].insert(new_index, new_sort_key) tier2dev_sort_key[tier].insert(new_index, new_sort_key)
new_last_sort_key = tier2dev_sort_key[tier][-1]
tier2sort_key[tier] = new_last_sort_key
# Now jiggle tier2children values to keep them sorted # Now jiggle tier2children values to keep them sorted
new_last_sort_key = tier2sort_key[tier][-1]
parent_tier = tier[0:-1] parent_tier = tier[0:-1]
index = bisect.bisect_left( index = bisect.bisect_left(
tier2children_sort_key[parent_tier], tier2children_sort_key[parent_tier],
@ -891,19 +889,10 @@ class RingBuilder(object):
# Just to save memory and keep from accidental reuse. # Just to save memory and keep from accidental reuse.
for dev in self._iter_devs(): for dev in self._iter_devs():
del dev['sort_key'] del dev['sort_key']
dev.pop('tiers', None) # May be absent for devices w/o weight
def _sort_key_for(self, dev): def _sort_key_for(self, dev):
# The maximum value of self.parts is 2^32, which is 9 hex return (dev['parts_wanted'], random.randint(0, 0xFFFF), dev['id'])
# digits wide (0x100000000). Using a width of 16 here gives us
# plenty of breathing room; you'd need more than 2^28 replicas
# to overflow it.
# Since the sort key is a string and therefore an ascii sort applies,
# the maximum_parts_wanted + parts_wanted is used so negative
# parts_wanted end up sorted above positive parts_wanted.
return '%016x.%04x.%04x' % (
(self.parts * self.replicas) + dev['parts_wanted'],
random.randint(0, 0xFFFF),
dev['id'])
def _build_max_replicas_by_tier(self): def _build_max_replicas_by_tier(self):
""" """