users: Refactor and optimize max_message_id_for_user by removing a join.

This algorithm existed in multiple places, with different queries.
Since we only access properties in the UserMessage table, we
standardize on the much simpler and faster Index Only Scan, rather
than a merge join.
This commit is contained in:
Alex Vandiver 2023-08-30 21:40:24 +00:00 committed by Tim Abbott
parent 0c88cfca63
commit 631868a05b
3 changed files with 26 additions and 27 deletions

View File

@ -54,17 +54,20 @@ from zerver.lib.topic import TOPIC_NAME
from zerver.lib.user_groups import user_groups_in_realm_serialized
from zerver.lib.user_status import get_user_status_dict
from zerver.lib.user_topics import get_topic_mutes, get_user_topics
from zerver.lib.users import get_cross_realm_dicts, get_raw_user_data, is_administrator_role
from zerver.lib.users import (
get_cross_realm_dicts,
get_raw_user_data,
is_administrator_role,
max_message_id_for_user,
)
from zerver.models import (
MAX_TOPIC_NAME_LENGTH,
Client,
CustomProfileField,
Draft,
Message,
Realm,
RealmUserDefault,
Stream,
UserMessage,
UserProfile,
UserStatus,
UserTopic,
@ -182,17 +185,7 @@ def fetch_initial_state_data(
# `max_message_id` is primarily used for generating `local_id`
# values that are higher than this. We likely can eventually
# remove this parameter from the API.
user_messages = None
if user_profile is not None:
user_messages = (
UserMessage.objects.filter(user_profile=user_profile)
.order_by("-message_id")
.values("message_id")[:1]
)
if user_messages:
state["max_message_id"] = user_messages[0]["message_id"]
else:
state["max_message_id"] = -1
state["max_message_id"] = max_message_id_for_user(user_profile)
if want("drafts"):
if user_profile is None:
@ -1232,13 +1225,7 @@ def apply_event(
message_ids = [event["message_id"]]
else:
message_ids = event["message_ids"] # nocoverage
max_message = (
Message.objects.filter(usermessage__user_profile=user_profile).order_by("-id").first()
)
if max_message:
state["max_message_id"] = max_message.id
else:
state["max_message_id"] = -1
state["max_message_id"] = max_message_id_for_user(user_profile)
if "raw_unread_msgs" in state:
for remove_id in message_ids:

View File

@ -26,6 +26,7 @@ from zerver.models import (
CustomProfileFieldValue,
Realm,
Service,
UserMessage,
UserProfile,
get_realm_user_dicts,
get_user,
@ -622,3 +623,18 @@ def get_users_with_access_to_real_email(user_profile: UserProfile) -> List[int]:
user_profile.email_address_visibility,
)
]
def max_message_id_for_user(user_profile: Optional[UserProfile]) -> int:
if user_profile is None:
return -1
max_message = (
UserMessage.objects.filter(user_profile=user_profile)
.order_by("-message_id")
.only("message_id")
.first()
)
if max_message:
return max_message.message_id
else:
return -1

View File

@ -67,6 +67,7 @@ from zerver.lib.users import (
check_valid_interface_type,
get_api_key,
get_raw_user_data,
max_message_id_for_user,
validate_user_custom_profile_data,
)
from zerver.lib.utils import generate_api_key
@ -88,7 +89,6 @@ from zerver.models import (
DomainNotAllowedForRealmError,
EmailContainsPlusError,
InvalidFakeEmailDomainError,
Message,
Service,
Stream,
UserProfile,
@ -743,11 +743,7 @@ def get_profile_backend(request: HttpRequest, user_profile: UserProfile) -> Http
)
result: Dict[str, Any] = raw_user_data[user_profile.id]
result["max_message_id"] = -1
messages = Message.objects.filter(usermessage__user_profile=user_profile).order_by("-id")[:1]
if messages:
result["max_message_id"] = messages[0].id
result["max_message_id"] = max_message_id_for_user(user_profile)
return json_success(request, data=result)