Skip to content
This repository has been archived by the owner on Apr 26, 2024. It is now read-only.

Optimise room creation event lookups part 2 #13224

1 change: 1 addition & 0 deletions changelog.d/13224.misc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Further reduce queries used sending events when creating new rooms. Contributed by Nick @ Beeper (@fizzadar).
45 changes: 37 additions & 8 deletions synapse/handlers/room.py
Original file line number Diff line number Diff line change
Expand Up @@ -889,7 +889,11 @@ async def create_room(
# override any attempt to set room versions via the creation_content
creation_content["room_version"] = room_version.identifier

last_stream_id = await self._send_events_for_new_room(
(
last_stream_id,
last_sent_event_id,
depth,
) = await self._send_events_for_new_room(
requester,
room_id,
preset_config=preset_config,
Expand All @@ -905,7 +909,7 @@ async def create_room(
if "name" in config:
name = config["name"]
(
_,
name_event,
last_stream_id,
) = await self.event_creation_handler.create_and_send_nonmember_event(
requester,
Expand All @@ -917,12 +921,16 @@ async def create_room(
"content": {"name": name},
},
ratelimit=False,
prev_event_ids=[last_sent_event_id],
depth=depth,
)
last_sent_event_id = name_event.event_id
depth += 1

if "topic" in config:
topic = config["topic"]
(
_,
topic_event,
last_stream_id,
) = await self.event_creation_handler.create_and_send_nonmember_event(
requester,
Expand All @@ -934,7 +942,11 @@ async def create_room(
"content": {"topic": topic},
},
ratelimit=False,
prev_event_ids=[last_sent_event_id],
depth=depth,
)
last_sent_event_id = topic_event.event_id
depth += 1

# we avoid dropping the lock between invites, as otherwise joins can
# start coming in and making the createRoom slow.
Expand All @@ -949,7 +961,7 @@ async def create_room(

for invitee in invite_list:
(
_,
member_event_id,
last_stream_id,
) = await self.room_member_handler.update_membership_locked(
requester,
Expand All @@ -959,7 +971,11 @@ async def create_room(
ratelimit=False,
content=content,
new_room=True,
prev_event_ids=[last_sent_event_id],
depth=depth,
)
last_sent_event_id = member_event_id
depth += 1
Fizzadar marked this conversation as resolved.
Show resolved Hide resolved

for invite_3pid in invite_3pid_list:
id_server = invite_3pid["id_server"]
Expand All @@ -968,7 +984,10 @@ async def create_room(
medium = invite_3pid["medium"]
# Note that do_3pid_invite can raise a ShadowBanError, but this was
# handled above by emptying invite_3pid_list.
last_stream_id = await self.hs.get_room_member_handler().do_3pid_invite(
(
member_event_id,
last_stream_id,
) = await self.hs.get_room_member_handler().do_3pid_invite(
room_id,
requester.user,
medium,
Expand All @@ -977,7 +996,11 @@ async def create_room(
requester,
txn_id=None,
id_access_token=id_access_token,
prev_event_ids=[last_sent_event_id],
depth=depth,
)
last_sent_event_id = member_event_id
depth += 1

result = {"room_id": room_id}

Expand Down Expand Up @@ -1005,20 +1028,22 @@ async def _send_events_for_new_room(
power_level_content_override: Optional[JsonDict] = None,
creator_join_profile: Optional[JsonDict] = None,
ratelimit: bool = True,
) -> int:
) -> Tuple[int, str, int]:
"""Sends the initial events into a new room.

`power_level_content_override` doesn't apply when initial state has
power level state event content.

Returns:
The stream_id of the last event persisted.
A tuple containing the stream ID, event ID and depth of the last
event sent to the room.
"""

creator_id = creator.user.to_string()

event_keys = {"room_id": room_id, "sender": creator_id, "state_key": ""}

depth = 1
last_sent_event_id: Optional[str] = None

def create(etype: str, content: JsonDict, **kwargs: Any) -> JsonDict:
Expand All @@ -1031,6 +1056,7 @@ def create(etype: str, content: JsonDict, **kwargs: Any) -> JsonDict:

async def send(etype: str, content: JsonDict, **kwargs: Any) -> int:
nonlocal last_sent_event_id
nonlocal depth

event = create(etype, content, **kwargs)
logger.debug("Sending %s in new room", etype)
Expand All @@ -1047,9 +1073,11 @@ async def send(etype: str, content: JsonDict, **kwargs: Any) -> int:
# Note: we don't pass state_event_ids here because this triggers
# an additional query per event to look them up from the events table.
prev_event_ids=[last_sent_event_id] if last_sent_event_id else [],
depth=depth,
)

last_sent_event_id = sent_event.event_id
depth += 1

return last_stream_id

Expand All @@ -1075,6 +1103,7 @@ async def send(etype: str, content: JsonDict, **kwargs: Any) -> int:
content=creator_join_profile,
new_room=True,
prev_event_ids=[last_sent_event_id],
depth=depth,
)
last_sent_event_id = member_event_id

Expand Down Expand Up @@ -1168,7 +1197,7 @@ async def send(etype: str, content: JsonDict, **kwargs: Any) -> int:
content={"algorithm": RoomEncryptionAlgorithms.DEFAULT},
)

return last_sent_stream_id
return last_sent_stream_id, last_sent_event_id, depth

def _generate_room_id(self) -> str:
"""Generates a random room ID.
Expand Down
43 changes: 36 additions & 7 deletions synapse/handlers/room_member.py
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,7 @@ async def _local_membership_update(
allow_no_prev_events: bool = False,
prev_event_ids: Optional[List[str]] = None,
state_event_ids: Optional[List[str]] = None,
depth: Optional[int] = None,
txn_id: Optional[str] = None,
ratelimit: bool = True,
content: Optional[dict] = None,
Expand Down Expand Up @@ -315,6 +316,9 @@ async def _local_membership_update(
prev_events are set so we need to set them ourself via this argument.
This should normally be left as None, which will cause the auth_event_ids
to be calculated based on the room state at the prev_events.
depth: Override the depth used to order the event in the DAG.
Should normally be set to None, which will cause the depth to be calculated
based on the prev_events.

txn_id:
ratelimit:
Expand Down Expand Up @@ -370,6 +374,7 @@ async def _local_membership_update(
allow_no_prev_events=allow_no_prev_events,
prev_event_ids=prev_event_ids,
state_event_ids=state_event_ids,
depth=depth,
require_consent=require_consent,
outlier=outlier,
historical=historical,
Expand Down Expand Up @@ -466,6 +471,7 @@ async def update_membership(
allow_no_prev_events: bool = False,
prev_event_ids: Optional[List[str]] = None,
state_event_ids: Optional[List[str]] = None,
depth: Optional[int] = None,
) -> Tuple[str, int]:
"""Update a user's membership in a room.

Expand Down Expand Up @@ -501,6 +507,9 @@ async def update_membership(
prev_events are set so we need to set them ourself via this argument.
This should normally be left as None, which will cause the auth_event_ids
to be calculated based on the room state at the prev_events.
depth: Override the depth used to order the event in the DAG.
Should normally be set to None, which will cause the depth to be calculated
based on the prev_events.

Returns:
A tuple of the new event ID and stream ID.
Expand Down Expand Up @@ -540,6 +549,7 @@ async def update_membership(
allow_no_prev_events=allow_no_prev_events,
prev_event_ids=prev_event_ids,
state_event_ids=state_event_ids,
depth=depth,
)

return result
Expand All @@ -562,6 +572,7 @@ async def update_membership_locked(
allow_no_prev_events: bool = False,
prev_event_ids: Optional[List[str]] = None,
state_event_ids: Optional[List[str]] = None,
depth: Optional[int] = None,
) -> Tuple[str, int]:
"""Helper for update_membership.

Expand Down Expand Up @@ -599,6 +610,9 @@ async def update_membership_locked(
prev_events are set so we need to set them ourself via this argument.
This should normally be left as None, which will cause the auth_event_ids
to be calculated based on the room state at the prev_events.
depth: Override the depth used to order the event in the DAG.
Should normally be set to None, which will cause the depth to be calculated
based on the prev_events.

Returns:
A tuple of the new event ID and stream ID.
Expand Down Expand Up @@ -732,6 +746,7 @@ async def update_membership_locked(
allow_no_prev_events=allow_no_prev_events,
prev_event_ids=prev_event_ids,
state_event_ids=state_event_ids,
depth=depth,
content=content,
require_consent=require_consent,
outlier=outlier,
Expand Down Expand Up @@ -967,6 +982,7 @@ async def update_membership_locked(
ratelimit=ratelimit,
prev_event_ids=latest_event_ids,
state_event_ids=state_event_ids,
depth=depth,
content=content,
require_consent=require_consent,
outlier=outlier,
Expand Down Expand Up @@ -1322,7 +1338,9 @@ async def do_3pid_invite(
requester: Requester,
txn_id: Optional[str],
id_access_token: Optional[str] = None,
) -> int:
prev_event_ids: Optional[List[str]] = None,
depth: Optional[int] = None,
) -> Tuple[str, int]:
"""Invite a 3PID to a room.

Args:
Expand All @@ -1335,9 +1353,13 @@ async def do_3pid_invite(
txn_id: The transaction ID this is part of, or None if this is not
part of a transaction.
id_access_token: The optional identity server access token.
depth: Override the depth used to order the event in the DAG.
prev_event_ids: The event IDs to use as the prev events
Should normally be set to None, which will cause the depth to be calculated
based on the prev_events.

Returns:
The new stream ID.
Tuple of event ID and stream ordering position

Raises:
ShadowBanError if the requester has been shadow-banned.
Expand Down Expand Up @@ -1383,7 +1405,7 @@ async def do_3pid_invite(
# We don't check the invite against the spamchecker(s) here (through
# user_may_invite) because we'll do it further down the line anyway (in
# update_membership_locked).
_, stream_id = await self.update_membership(
event_id, stream_id = await self.update_membership(
Comment on lines -1386 to +1408
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahh: the mistake was that we didn't have an event to return in this case? Sorry for missing this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nw at all, thanks for the review!

requester, UserID.from_string(invitee), room_id, "invite", txn_id=txn_id
)
else:
Expand All @@ -1402,7 +1424,7 @@ async def do_3pid_invite(
additional_fields=spam_check[1],
)

stream_id = await self._make_and_store_3pid_invite(
event, stream_id = await self._make_and_store_3pid_invite(
requester,
id_server,
medium,
Expand All @@ -1411,9 +1433,12 @@ async def do_3pid_invite(
inviter,
txn_id=txn_id,
id_access_token=id_access_token,
prev_event_ids=prev_event_ids,
depth=depth,
)
event_id = event.event_id

return stream_id
return event_id, stream_id

async def _make_and_store_3pid_invite(
self,
Expand All @@ -1425,7 +1450,9 @@ async def _make_and_store_3pid_invite(
user: UserID,
txn_id: Optional[str],
id_access_token: Optional[str] = None,
) -> int:
prev_event_ids: Optional[List[str]] = None,
depth: Optional[int] = None,
) -> Tuple[EventBase, int]:
room_state = await self._storage_controllers.state.get_current_state(
room_id,
StateFilter.from_types(
Expand Down Expand Up @@ -1518,8 +1545,10 @@ async def _make_and_store_3pid_invite(
},
ratelimit=False,
txn_id=txn_id,
prev_event_ids=prev_event_ids,
depth=depth,
)
return stream_id
return event, stream_id

async def _is_host_in_room(self, current_state_ids: StateMap[str]) -> bool:
# Have we just created the room, and is this about to be the very
Expand Down
8 changes: 4 additions & 4 deletions tests/rest/client/test_rooms.py
Original file line number Diff line number Diff line change
Expand Up @@ -709,7 +709,7 @@ def test_post_room_no_keys(self) -> None:
self.assertEqual(200, channel.code, channel.result)
self.assertTrue("room_id" in channel.json_body)
assert channel.resource_usage is not None
self.assertEqual(37, channel.resource_usage.db_txn_count)
self.assertEqual(32, channel.resource_usage.db_txn_count)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice!


def test_post_room_initial_state(self) -> None:
# POST with initial_state config key, expect new room id
Expand All @@ -722,7 +722,7 @@ def test_post_room_initial_state(self) -> None:
self.assertEqual(200, channel.code, channel.result)
self.assertTrue("room_id" in channel.json_body)
assert channel.resource_usage is not None
self.assertEqual(41, channel.resource_usage.db_txn_count)
self.assertEqual(35, channel.resource_usage.db_txn_count)

def test_post_room_visibility_key(self) -> None:
# POST with visibility config key, expect new room id
Expand Down Expand Up @@ -3283,7 +3283,7 @@ def test_threepid_invite_spamcheck_deprecated(self) -> None:
# Mock a few functions to prevent the test from failing due to failing to talk to
# a remote IS. We keep the mock for make_and_store_3pid_invite around so we
# can check its call_count later on during the test.
make_invite_mock = Mock(return_value=make_awaitable(0))
make_invite_mock = Mock(return_value=make_awaitable((0, {})))
self.hs.get_room_member_handler()._make_and_store_3pid_invite = make_invite_mock
self.hs.get_identity_handler().lookup_3pid = Mock(
return_value=make_awaitable(None),
Expand Down Expand Up @@ -3344,7 +3344,7 @@ def test_threepid_invite_spamcheck(self) -> None:
# Mock a few functions to prevent the test from failing due to failing to talk to
# a remote IS. We keep the mock for make_and_store_3pid_invite around so we
# can check its call_count later on during the test.
make_invite_mock = Mock(return_value=make_awaitable(0))
make_invite_mock = Mock(return_value=make_awaitable((0, {})))
self.hs.get_room_member_handler()._make_and_store_3pid_invite = make_invite_mock
self.hs.get_identity_handler().lookup_3pid = Mock(
return_value=make_awaitable(None),
Expand Down