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

Commit c8684e6

Browse files
authored
Reduce DB load of /sync when using presence (#12885)
While the query was fast, we were calling it *a lot*.
1 parent 5e17922 commit c8684e6

File tree

2 files changed

+49
-27
lines changed

2 files changed

+49
-27
lines changed

changelog.d/12885.misc

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Reduce database load of `/sync` when presence is enabled.

synapse/storage/databases/main/presence.py

+48-27
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15-
from typing import TYPE_CHECKING, Any, Dict, Iterable, List, Tuple, cast
15+
from typing import TYPE_CHECKING, Any, Dict, Iterable, List, Optional, Tuple, cast
1616

1717
from synapse.api.presence import PresenceState, UserPresenceState
1818
from synapse.replication.tcp.streams import PresenceStream
@@ -22,6 +22,7 @@
2222
LoggingDatabaseConnection,
2323
LoggingTransaction,
2424
)
25+
from synapse.storage.databases.main.cache import CacheInvalidationWorkerStore
2526
from synapse.storage.engines import PostgresEngine
2627
from synapse.storage.types import Connection
2728
from synapse.storage.util.id_generators import (
@@ -56,7 +57,7 @@ def __init__(
5657
)
5758

5859

59-
class PresenceStore(PresenceBackgroundUpdateStore):
60+
class PresenceStore(PresenceBackgroundUpdateStore, CacheInvalidationWorkerStore):
6061
def __init__(
6162
self,
6263
database: DatabasePool,
@@ -281,20 +282,30 @@ async def should_user_receive_full_presence_with_token(
281282
True if the user should have full presence sent to them, False otherwise.
282283
"""
283284

284-
def _should_user_receive_full_presence_with_token_txn(
285-
txn: LoggingTransaction,
286-
) -> bool:
287-
sql = """
288-
SELECT 1 FROM users_to_send_full_presence_to
289-
WHERE user_id = ?
290-
AND presence_stream_id >= ?
291-
"""
292-
txn.execute(sql, (user_id, from_token))
293-
return bool(txn.fetchone())
285+
token = await self._get_full_presence_stream_token_for_user(user_id)
286+
if token is None:
287+
return False
294288

295-
return await self.db_pool.runInteraction(
296-
"should_user_receive_full_presence_with_token",
297-
_should_user_receive_full_presence_with_token_txn,
289+
return from_token <= token
290+
291+
@cached()
292+
async def _get_full_presence_stream_token_for_user(
293+
self, user_id: str
294+
) -> Optional[int]:
295+
"""Get the presence token corresponding to the last full presence update
296+
for this user.
297+
298+
If the user presents a sync token with a presence stream token at least
299+
as old as the result, then we need to send them a full presence update.
300+
301+
If this user has never needed a full presence update, returns `None`.
302+
"""
303+
return await self.db_pool.simple_select_one_onecol(
304+
table="users_to_send_full_presence_to",
305+
keyvalues={"user_id": user_id},
306+
retcol="presence_stream_id",
307+
allow_none=True,
308+
desc="_get_full_presence_stream_token_for_user",
298309
)
299310

300311
async def add_users_to_send_full_presence_to(self, user_ids: Iterable[str]) -> None:
@@ -307,18 +318,28 @@ async def add_users_to_send_full_presence_to(self, user_ids: Iterable[str]) -> N
307318
# Add user entries to the table, updating the presence_stream_id column if the user already
308319
# exists in the table.
309320
presence_stream_id = self._presence_id_gen.get_current_token()
310-
await self.db_pool.simple_upsert_many(
311-
table="users_to_send_full_presence_to",
312-
key_names=("user_id",),
313-
key_values=[(user_id,) for user_id in user_ids],
314-
value_names=("presence_stream_id",),
315-
# We save the current presence stream ID token along with the user ID entry so
316-
# that when a user /sync's, even if they syncing multiple times across separate
317-
# devices at different times, each device will receive full presence once - when
318-
# the presence stream ID in their sync token is less than the one in the table
319-
# for their user ID.
320-
value_values=[(presence_stream_id,) for _ in user_ids],
321-
desc="add_users_to_send_full_presence_to",
321+
322+
def _add_users_to_send_full_presence_to(txn: LoggingTransaction) -> None:
323+
self.db_pool.simple_upsert_many_txn(
324+
txn,
325+
table="users_to_send_full_presence_to",
326+
key_names=("user_id",),
327+
key_values=[(user_id,) for user_id in user_ids],
328+
value_names=("presence_stream_id",),
329+
# We save the current presence stream ID token along with the user ID entry so
330+
# that when a user /sync's, even if they syncing multiple times across separate
331+
# devices at different times, each device will receive full presence once - when
332+
# the presence stream ID in their sync token is less than the one in the table
333+
# for their user ID.
334+
value_values=[(presence_stream_id,) for _ in user_ids],
335+
)
336+
for user_id in user_ids:
337+
self._invalidate_cache_and_stream(
338+
txn, self._get_full_presence_stream_token_for_user, (user_id,)
339+
)
340+
341+
return await self.db_pool.runInteraction(
342+
"add_users_to_send_full_presence_to", _add_users_to_send_full_presence_to
322343
)
323344

324345
async def get_presence_for_all_users(

0 commit comments

Comments
 (0)