Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/20151.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix a bug where a device list change for a user with a large number of devices would consume far more `device_lists` stream IDs than necessary, causing replication to fall behind.
2 changes: 1 addition & 1 deletion synapse/storage/databases/main/devices.py
Original file line number Diff line number Diff line change
Expand Up @@ -2085,7 +2085,7 @@ def add_device_changes_txn(
batch_device_ids: StrCollection,
) -> int:
stream_ids = self._device_list_id_gen.get_next_mult_txn(
txn, len(device_ids)
txn, len(batch_device_ids)
)

self._add_device_change_to_stream_txn(
Expand Down
32 changes: 32 additions & 0 deletions tests/storage/test_devices.py
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,38 @@ def test_update_unknown_device(self) -> None:
)
self.assertEqual(404, exc.value.code)

def test_add_device_change_to_streams_allocates_one_id_per_device(self) -> None:
"""Adding more devices than fit in a single batch should still only consume
one stream ID per device.
"""
user_id = "@user_id:test"
# Enough devices to span more than one `batch_iter` batch.
device_ids = [f"device_id{i}" for i in range(1500)]

self.get_success(
self.store.add_device_change_to_streams(
user_id=user_id,
device_ids=device_ids,
room_ids=["!some:room"],
)
)

stream_ids = self.get_success(
self.store.db_pool.simple_select_onecol(
table="device_lists_stream",
keyvalues={"user_id": user_id},
retcol="stream_id",
)
)

self.assertEqual(len(stream_ids), len(device_ids))
# The allocated IDs should be contiguous: a gap means IDs were allocated
# and then thrown away.
self.assertEqual(
max(stream_ids) - min(stream_ids) + 1,
len(device_ids),
)

@patch("synapse.storage.databases.main.devices.PRUNE_DEVICE_LISTS_BATCH_SIZE", 5)
def test_prune_old_device_lists_changes_in_room(self) -> None:
"""Test that old entries in the `device_lists_changes_in_room` table are pruned properly."""
Expand Down
Loading