mirror of
https://github.com/zulip/zulip.git
synced 2026-07-15 21:03:26 +08:00
Avoid sorting in UserPresence query.
We were using `.order_by('user_profile_id', '-timestamp') in our
UserPresence query in get_status_dicts_for_query.
We don't need a full sort to produce the dictionary of statuses.
In fact the whole operation in Python is still O(N):
- divvy rows up to be per-user in an O(N) pass
- find max row for the 'aggregated' entry in an O(n) pass
per user
The one minor annoyance of this fix is that datetime_to_timestamp
is lossy, so if you naively call to_presence_dict before finding
the "max" row, you get test flakes if rows are created during the
same second. I decided to avoid calling to_presence_dict so there
are fewer moving parts, but there's still the ugly step of having
to remove the "dt" field from the final results.
This commit is contained in:
parent
19e134698e
commit
c19b3aec0c
@ -1617,7 +1617,7 @@ class UserPresence(models.Model):
|
||||
|
||||
@staticmethod
|
||||
def get_status_dict_by_user(user_profile):
|
||||
# type: (UserProfile) -> DefaultDict[Any, Dict[Any, Any]]
|
||||
# type: (UserProfile) -> Dict[Text, Dict[Any, Any]]
|
||||
query = UserPresence.objects.filter(user_profile=user_profile).values(
|
||||
'client__name',
|
||||
'status',
|
||||
@ -1643,7 +1643,7 @@ class UserPresence(models.Model):
|
||||
|
||||
@staticmethod
|
||||
def get_status_dict_by_realm(realm_id):
|
||||
# type: (int) -> DefaultDict[Any, Dict[Any, Any]]
|
||||
# type: (int) -> Dict[Text, Dict[Any, Any]]
|
||||
query = UserPresence.objects.filter(
|
||||
user_profile__realm_id=realm_id,
|
||||
user_profile__is_active=True,
|
||||
@ -1672,26 +1672,53 @@ class UserPresence(models.Model):
|
||||
|
||||
@staticmethod
|
||||
def get_status_dicts_for_query(query, mobile_user_ids):
|
||||
# type: (QuerySet, List[int]) -> DefaultDict[Any, Dict[Any, Any]]
|
||||
user_statuses = defaultdict(dict) # type: DefaultDict[Any, Dict[Any, Any]]
|
||||
# Order of query is important to get a latest status as aggregated status.
|
||||
for row in query.order_by("user_profile__id", "-timestamp"):
|
||||
info = UserPresence.to_presence_dict(
|
||||
row['client__name'],
|
||||
row['status'],
|
||||
row['timestamp'],
|
||||
push_enabled=row['user_profile__enable_offline_push_notifications'],
|
||||
has_push_devices=row['user_profile__id'] in mobile_user_ids,
|
||||
is_mirror_dummy=row['user_profile__is_mirror_dummy'],
|
||||
# type: (QuerySet, List[int]) -> Dict[Text, Dict[Any, Any]]
|
||||
|
||||
info_row_dct = defaultdict(list) # type: DefaultDict[Text, List[Dict[str, Any]]]
|
||||
for row in query:
|
||||
email = row['user_profile__email']
|
||||
client_name = row['client__name']
|
||||
status = UserPresence.status_to_string(row['status'])
|
||||
dt = row['timestamp']
|
||||
timestamp = datetime_to_timestamp(dt)
|
||||
push_enabled = row['user_profile__enable_offline_push_notifications']
|
||||
has_push_devices = row['user_profile__id'] in mobile_user_ids
|
||||
pushable = (push_enabled and has_push_devices)
|
||||
|
||||
info = dict(
|
||||
client=client_name,
|
||||
status=status,
|
||||
dt=dt,
|
||||
timestamp=timestamp,
|
||||
pushable=pushable,
|
||||
)
|
||||
if not user_statuses.get(row['user_profile__email']):
|
||||
# Applying the latest status as aggregated status for user.
|
||||
user_statuses[row['user_profile__email']]['aggregated'] = {
|
||||
'status': info['status'],
|
||||
'timestamp': info['timestamp'],
|
||||
'client': info['client']
|
||||
}
|
||||
user_statuses[row['user_profile__email']][row['client__name']] = info
|
||||
|
||||
info_row_dct[email].append(info)
|
||||
|
||||
user_statuses = dict() # type: Dict[str, Dict[str, Any]]
|
||||
|
||||
for email, info_rows in info_row_dct.items():
|
||||
# Note that datetime values have sub-second granularity, which is
|
||||
# mostly important for avoiding test flakes, but it's also technically
|
||||
# more precise for real users.
|
||||
by_time = lambda row: row['dt']
|
||||
most_recent_info = max(info_rows, key=by_time)
|
||||
|
||||
# We don't send datetime values to the client.
|
||||
for r in info_rows:
|
||||
del r['dt']
|
||||
|
||||
client_dict = {info['client']: info for info in info_rows}
|
||||
user_statuses[email] = client_dict
|
||||
|
||||
# The word "aggegrated" here is possibly misleading.
|
||||
# It's really just the most recent client's info.
|
||||
user_statuses[email]['aggregated'] = dict(
|
||||
client=most_recent_info['client'],
|
||||
status=most_recent_info['status'],
|
||||
timestamp=most_recent_info['timestamp'],
|
||||
)
|
||||
|
||||
return user_statuses
|
||||
|
||||
@staticmethod
|
||||
|
||||
Loading…
Reference in New Issue
Block a user