-
Notifications
You must be signed in to change notification settings - Fork 3.2k
Python EventHubs load balancing #6901
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
3a32907
39b1b86
17f5153
04ef548
9be1741
1b5753c
b4b77f9
1787fdd
c2d0155
1074385
386baf0
1afbf0c
c126bea
40c7f03
cf22c7c
c7440b2
fa804f4
470cf7e
e5f3b50
8343876
66c5b31
bcd851a
d740bb0
aad6978
a55dc13
a339985
9102713
278592c
665f28c
0060f9d
7b4273a
bdf97c8
02a4daf
a9446de
8dfdec9
2aace82
36ba0a3
7f95d9e
f5870af
f30d143
893bee0
4b41fa5
fef0551
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
- Loading branch information
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,8 +6,8 @@ | |
| import time | ||
| import random | ||
| import math | ||
| import collections | ||
| from collections import Counter | ||
| from typing import List | ||
| from collections import Counter, defaultdict | ||
| from azure.eventhub.aio import EventHubClient | ||
| from .partition_manager import PartitionManager | ||
|
|
||
|
|
@@ -26,7 +26,7 @@ def __init__( | |
| self, eventhub_client: EventHubClient, consumer_group_name: str, owner_id: str, | ||
| partition_manager: PartitionManager, ownership_timeout: int | ||
| ): | ||
| self.cached_parition_ids = [] | ||
| self.cached_parition_ids = [] # type: List[str] | ||
| self.eventhub_client = eventhub_client | ||
| self.eventhub_name = eventhub_client.eh_name | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there a specific reason that we make the eventhub_name an attribute on the instance, but that we also continue to get
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. eventhub_name, consumer_group_name are two important attributes for an ownership. They usually appear together. They don't change anywhere in an ownership manager. Putting them together has better readability, I think.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let me rephrase, why are you both assigning the name to an attribute and accessing the name from
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's a mistake. Changed all to self.eventhub_name |
||
| self.consumer_group_name = consumer_group_name | ||
|
|
@@ -45,7 +45,7 @@ async def claim_ownership(self): | |
| """ | ||
| if not self.cached_parition_ids: | ||
| await self._retrieve_partition_ids() | ||
| to_claim = await self._balance_ownership() | ||
| to_claim = await self._balance_ownership(self.cached_parition_ids) | ||
| claimed_list = await self.partition_manager.claim_ownership(to_claim) if to_claim else None | ||
| return claimed_list | ||
|
|
||
|
|
@@ -56,7 +56,7 @@ async def _retrieve_partition_ids(self): | |
| """ | ||
| self.cached_parition_ids = await self.eventhub_client.get_partition_ids() | ||
|
|
||
| async def _balance_ownership(self): | ||
| async def _balance_ownership(self, all_partition_ids): | ||
| """Balances and claims ownership of partitions for this EventProcessor. | ||
| The balancing algorithm is: | ||
| 1. Find partitions with inactive ownership and partitions that haven never been claimed before | ||
|
|
@@ -87,19 +87,19 @@ async def _balance_ownership(self): | |
| ) | ||
| now = time.time() | ||
| ownership_dict = dict((x["partition_id"], x) for x in ownership_list) # put the list to dict for fast lookup | ||
| not_owned_partition_ids = [pid for pid in self.cached_parition_ids if pid not in ownership_dict] | ||
| not_owned_partition_ids = [pid for pid in all_partition_ids if pid not in ownership_dict] | ||
| timed_out_partition_ids = [ownership["partition_id"] for ownership in ownership_list | ||
| if ownership["last_modified_time"] + self.ownership_timeout < now] | ||
| claimable_partition_ids = not_owned_partition_ids + timed_out_partition_ids | ||
| active_ownership = [ownership for ownership in ownership_list | ||
| if ownership["last_modified_time"] + self.ownership_timeout >= now] | ||
| active_ownership_by_owner = collections.defaultdict(list) | ||
| active_ownership_by_owner = defaultdict(list) | ||
| for ownership in active_ownership: | ||
| active_ownership_by_owner[ownership["owner_id"]].append(ownership) | ||
| active_ownership_self = active_ownership_by_owner[self.owner_id] | ||
|
|
||
| # calculate expected count per owner | ||
| all_partition_count = len(self.cached_parition_ids) | ||
| all_partition_count = len(all_partition_ids) | ||
| owners_count = len(active_ownership_by_owner) + \ | ||
| (0 if self.owner_id in active_ownership_by_owner else 1) | ||
| expected_count_per_owner = all_partition_count // owners_count | ||
|
|
@@ -109,20 +109,21 @@ async def _balance_ownership(self): | |
| to_claim = active_ownership_self | ||
| if len(active_ownership_self) > most_count_allowed_per_owner: # needs to abandon a partition | ||
| to_claim.pop() # abandon one partition if owned too many | ||
| # TODO: Release a ownership immediately so other EventProcessors won't need to wait it to timeout | ||
| elif len(active_ownership_self) < expected_count_per_owner: # Either claims an inactive partition, or steals from other owners | ||
| # TODO: Release an ownership immediately so other EventProcessors won't need to wait it to timeout | ||
| elif len(active_ownership_self) < expected_count_per_owner: | ||
| # Either claims an inactive partition, or steals from other owners | ||
| if claimable_partition_ids: # claim an inactive partition if there is | ||
| random_partition_id = random.choice(claimable_partition_ids) | ||
| random_chosen_to_claim = ownership_dict.get(random_partition_id, | ||
| {"partition_id": random_partition_id, | ||
| "eventhub_name": self.eventhub_client.eh_name, | ||
| "consumer_group_name": self.consumer_group_name, | ||
| "owner_level": 0}) # TODO: consider removing owner_level | ||
| "consumer_group_name": self.consumer_group_name | ||
| }) | ||
| random_chosen_to_claim["owner_id"] = self.owner_id | ||
| to_claim.append(random_chosen_to_claim) | ||
| else: # steal from another owner that has the most count | ||
| active_ownership_count_group_by_owner = Counter( | ||
| (x, len(y)) for x, y in active_ownership_by_owner.items()) | ||
| dict((x, len(y)) for x, y in active_ownership_by_owner.items())) | ||
| most_frequent_owner_id = active_ownership_count_group_by_owner.most_common(1)[0][0] | ||
| # randomly choose a partition to steal from the most_frequent_owner | ||
| to_steal_partition = random.choice(active_ownership_by_owner[most_frequent_owner_id]) | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is the user supposed to use the CheckpointManager directly? Creating new instances of it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, user will use checkpoint_manager.update_checkpoint(), but they don't create a CheckpointManager