messages: Use "\x07" as topic for DMs and group DMs.

This commit updates code to use "\x07" as value for
"subject" field of Message objects for DMs and group
DMs, so that we have a unique value for DMs and group
DMs which cannot be used for channel messages.

This helps in avoiding having an empty string value as
topic for DMs, which is also used for "general chat"
channel messages, as large number of DMs in the realm
resulted in PostgreSQL query planner thinking that there
are too many "general chat" messages and thus generated
bad query plans for operations like fetching
"general chat" messages in a stream or moving messages
to and from "general chat" topic.

This change as done for ArchivedMessage and
ScheduledMessage objects as well.

Note that the clients still get "subject" value as
an empty string "".

This commit also adds tests for checking that "\x07"
cannot be used as topic for channel messages.

Fixes #34360.
This commit is contained in:
Sahil Batra
2025-05-22 12:35:28 +05:30
committed by Tim Abbott
parent 5a45779634
commit b655bd14ea
17 changed files with 136 additions and 17 deletions

View File

@@ -58,6 +58,21 @@ class AbstractMessage(models.Model):
db_default=MessageType.NORMAL,
)
# Direct messages do not have topics in the API. Originally, all
# DMs had a topic of "" in the database. When we started using ""
# as the topic for "general chat", this caused problems with the
# PostgreSQL query planner. Because a large portion of all
# messages are DMs, PostgreSQL could do very inefficient table
# scans to fetch messages in general chat, ignoring the topic
# index, because it assumed most messages were in general chat.
#
# To avoid that query planner statistics problem, we use an
# unprintable character, which isn't permitted in actual topics,
# as the topic for DMs in the database. The "BEL" character is an
# arbitrary choice, but feels suitable given DMs trigger a
# notification sound.
DM_TOPIC = "\x07"
# The message's topic.
#
# Early versions of Zulip called this concept a "subject", as in an email
@@ -105,6 +120,9 @@ class AbstractMessage(models.Model):
@override
def __str__(self) -> str:
if not self.is_channel_message:
return f"{self.recipient.label()} / / {self.sender!r}"
return f"{self.recipient.label()} / {self.subject} / {self.sender!r}"

View File

@@ -202,6 +202,9 @@ class ScheduledMessage(models.Model):
@override
def __str__(self) -> str:
if self.recipient.type != Recipient.STREAM:
return f"{self.recipient.label()} {self.sender!r} {self.scheduled_timestamp}"
return f"{self.recipient.label()} {self.subject} {self.sender!r} {self.scheduled_timestamp}"
def topic_name(self) -> str:
@@ -217,8 +220,8 @@ class ScheduledMessage(models.Model):
recipient, recipient_type_str = get_recipient_ids(self.recipient, self.sender.id)
if recipient_type_str == "private":
# The topic for direct messages should always be an empty string.
assert self.topic_name() == ""
# The topic for direct messages should always be "\x07".
assert self.topic_name() == Message.DM_TOPIC
return APIScheduledDirectMessageDict(
scheduled_message_id=self.id,