Repository: zulip/zulip

Issue:
Improve perceived latency for sender when sending/editing a message in very large streams
When sending messages to a stream with 15k+ subscribers, it can take 300ms between when the first user receives the message and the last does.  It would be desirable for the sender to be "first in line" for this purpose, since the UX for the sender is not ideal while waiting for the message to send (especially with edits).

The logic for this system involves several `Set` data structures, starting with this part of `do_send_messages`:

```
        user_ids = send_request.active_user_ids | set(user_flags.keys())                             
                                                                                                     
        users = [                                                                                    
            dict(                                                                                    
                id=user_id,                                                                          
                flags=user_flags.get(user_id, []),                                                   
                always_push_notify=(user_id in send_request.push_notify_user_ids),                   
                stream_push_notify=(user_id in send_request.stream_push_user_ids),                   
                stream_email_notify=(user_id in send_request.stream_email_user_ids),                 
                wildcard_mention_notify=(user_id in send_request.wildcard_mention_user_ids),         
            )                                                                                        
            for user_id in user_ids                                                                  
        ]                                                                                            
                                                                                                     
        send_event(send_request.realm, event, users)                                                 
```

That then runs to `zerver/tornado/events_queue.py`, and in particular `get_client_info_for_message_event` (and `process_message_event`, which actually does the work).  I think if we do a bit of work to make sure the sender is first in the list sent from `do_send_messages` and then modify the Tornado code to use an OrderedDict or something (actually I think modern Python dicts all?) to avoid changing the iteration order, we can arrange it so that the order in the event sent from `do_send_messages` is also the processing order.

A fix should be testable by just adding `print(user_id)` type statements in `process_message_event` (with a bit of extra work to make sure `send_to_clients` doesn't break the ordering).

https://zulip.readthedocs.io/en/latest/subsystems/sending-messages.html has relevant background.


Discussion:
Hello @zulip/server-compose, @zulip/server-message-view members, this issue was labeled with the "area: compose", "area: message-editing" labels, so you may want to check it out!

<!-- areaLabelAddition -->
I have less idea about the backend. But this one will be interesting to try on as related to compose.
@zulipbot claim

I think this should be doable for you -- I've mentioned the code paths involved and I think it just takes some print-debugging and care with sort order.

Resolving patch:
diff --git a/zerver/lib/actions.py b/zerver/lib/actions.py
--- a/zerver/lib/actions.py
+++ b/zerver/lib/actions.py
@@ -1928,6 +1928,15 @@ def do_send_messages(
                about changing the next line.
         """
         user_ids = send_request.active_user_ids | set(user_flags.keys())
+        sender_id = send_request.message.sender_id
+
+        # We make sure the sender is listed first in the `users` list;
+        # this results in the sender receiving the message first if
+        # there are thousands of recipients, decreasing perceived latency.
+        if sender_id in user_ids:
+            user_list = [sender_id] + list(user_ids - {sender_id})
+        else:
+            user_list = list(user_ids)
 
         users = [
             dict(
@@ -1938,7 +1947,7 @@ def do_send_messages(
                 stream_email_notify=(user_id in send_request.stream_email_user_ids),
                 wildcard_mention_notify=(user_id in send_request.wildcard_mention_user_ids),
             )
-            for user_id in user_ids
+            for user_id in user_list
         ]
 
         if send_request.message.is_stream_message():


Test patch: