Skip to content

Commit b3ba501

Browse files
Properly purge state groups tables when purging a room (#18024)
Currently purging a complex room can lead to a lot of orphaned rows left behind in the state groups tables. It seems it is because we are loosing track of state groups sometimes. This change uses the `room_id` indexed column of `state_groups` table to decide what to delete instead of doing an indirection through `event_to_state_groups`. Related to #3364. ### Pull Request Checklist * [x] Pull request is based on the develop branch * [x] Pull request includes a [changelog file](https://element-hq.github.io/synapse/latest/development/contributing_guide.html#changelog). * [x] [Code style](https://element-hq.github.io/synapse/latest/code_style.html) is correct (run the [linters](https://element-hq.github.io/synapse/latest/development/contributing_guide.html#run-the-linters)) --------- Co-authored-by: Erik Johnston <[email protected]>
1 parent 6306de8 commit b3ba501

File tree

5 files changed

+34
-69
lines changed

5 files changed

+34
-69
lines changed

changelog.d/18024.bugfix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Properly purge state groups tables when purging a room with the admin API.

synapse/storage/controllers/purge_events.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,8 @@ async def purge_room(self, room_id: str) -> None:
4242
"""Deletes all record of a room"""
4343

4444
with nested_logging_context(room_id):
45-
state_groups_to_delete = await self.stores.main.purge_room(room_id)
46-
await self.stores.state.purge_room_state(room_id, state_groups_to_delete)
45+
await self.stores.main.purge_room(room_id)
46+
await self.stores.state.purge_room_state(room_id)
4747

4848
async def purge_history(
4949
self, room_id: str, token: str, delete_local_events: bool

synapse/storage/databases/main/purge_events.py

Lines changed: 9 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
#
2121

2222
import logging
23-
from typing import Any, List, Set, Tuple, cast
23+
from typing import Any, Set, Tuple, cast
2424

2525
from synapse.api.errors import SynapseError
2626
from synapse.storage.database import LoggingTransaction
@@ -332,7 +332,7 @@ def _purge_history_txn(
332332

333333
return referenced_state_groups
334334

335-
async def purge_room(self, room_id: str) -> List[int]:
335+
async def purge_room(self, room_id: str) -> None:
336336
"""Deletes all record of a room
337337
338338
Args:
@@ -348,26 +348,23 @@ async def purge_room(self, room_id: str) -> List[int]:
348348
# purge any of those rows which were added during the first.
349349

350350
logger.info("[purge] Starting initial main purge of [1/2]")
351-
state_groups_to_delete = await self.db_pool.runInteraction(
351+
await self.db_pool.runInteraction(
352352
"purge_room",
353353
self._purge_room_txn,
354354
room_id=room_id,
355355
isolation_level=IsolationLevel.READ_COMMITTED,
356356
)
357357

358358
logger.info("[purge] Starting secondary main purge of [2/2]")
359-
state_groups_to_delete.extend(
360-
await self.db_pool.runInteraction(
361-
"purge_room",
362-
self._purge_room_txn,
363-
room_id=room_id,
364-
),
359+
await self.db_pool.runInteraction(
360+
"purge_room",
361+
self._purge_room_txn,
362+
room_id=room_id,
365363
)
366-
logger.info("[purge] Done with main purge")
367364

368-
return state_groups_to_delete
365+
logger.info("[purge] Done with main purge")
369366

370-
def _purge_room_txn(self, txn: LoggingTransaction, room_id: str) -> List[int]:
367+
def _purge_room_txn(self, txn: LoggingTransaction, room_id: str) -> None:
371368
# This collides with event persistence so we cannot write new events and metadata into
372369
# a room while deleting it or this transaction will fail.
373370
if isinstance(self.database_engine, PostgresEngine):
@@ -381,19 +378,6 @@ def _purge_room_txn(self, txn: LoggingTransaction, room_id: str) -> List[int]:
381378
# take a while!
382379
txn.execute("SET LOCAL statement_timeout = 0")
383380

384-
# First, fetch all the state groups that should be deleted, before
385-
# we delete that information.
386-
txn.execute(
387-
"""
388-
SELECT DISTINCT state_group FROM events
389-
INNER JOIN event_to_state_groups USING(event_id)
390-
WHERE events.room_id = ?
391-
""",
392-
(room_id,),
393-
)
394-
395-
state_groups = [row[0] for row in txn]
396-
397381
# Get all the auth chains that are referenced by events that are to be
398382
# deleted.
399383
txn.execute(
@@ -513,5 +497,3 @@ def _purge_room_txn(self, txn: LoggingTransaction, room_id: str) -> List[int]:
513497
# periodically anyway (https://github.com/matrix-org/synapse/issues/5888)
514498

515499
self._invalidate_caches_for_room_and_stream(txn, room_id)
516-
517-
return state_groups

synapse/storage/databases/state/store.py

Lines changed: 21 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -840,60 +840,42 @@ async def get_previous_state_groups(
840840

841841
return dict(rows)
842842

843-
async def purge_room_state(
844-
self, room_id: str, state_groups_to_delete: Collection[int]
845-
) -> None:
846-
"""Deletes all record of a room from state tables
847-
848-
Args:
849-
room_id:
850-
state_groups_to_delete: State groups to delete
851-
"""
852-
853-
logger.info("[purge] Starting state purge")
854-
await self.db_pool.runInteraction(
843+
async def purge_room_state(self, room_id: str) -> None:
844+
return await self.db_pool.runInteraction(
855845
"purge_room_state",
856846
self._purge_room_state_txn,
857847
room_id,
858-
state_groups_to_delete,
859848
)
860-
logger.info("[purge] Done with state purge")
861849

862850
def _purge_room_state_txn(
863851
self,
864852
txn: LoggingTransaction,
865853
room_id: str,
866-
state_groups_to_delete: Collection[int],
867854
) -> None:
868-
# first we have to delete the state groups states
869-
logger.info("[purge] removing %s from state_groups_state", room_id)
870-
871-
self.db_pool.simple_delete_many_txn(
872-
txn,
873-
table="state_groups_state",
874-
column="state_group",
875-
values=state_groups_to_delete,
876-
keyvalues={},
877-
)
878-
879-
# ... and the state group edges
855+
# Delete all edges that reference a state group linked to room_id
880856
logger.info("[purge] removing %s from state_group_edges", room_id)
857+
txn.execute(
858+
"""
859+
DELETE FROM state_group_edges AS sge WHERE sge.state_group IN (
860+
SELECT id FROM state_groups AS sg WHERE sg.room_id = ?
861+
)""",
862+
(room_id,),
863+
)
881864

882-
self.db_pool.simple_delete_many_txn(
883-
txn,
884-
table="state_group_edges",
885-
column="state_group",
886-
values=state_groups_to_delete,
887-
keyvalues={},
865+
# state_groups_state table has a room_id column but no index on it, unlike state_groups,
866+
# so we delete them by matching the room_id through the state_groups table.
867+
logger.info("[purge] removing %s from state_groups_state", room_id)
868+
txn.execute(
869+
"""
870+
DELETE FROM state_groups_state AS sgs WHERE sgs.state_group IN (
871+
SELECT id FROM state_groups AS sg WHERE sg.room_id = ?
872+
)""",
873+
(room_id,),
888874
)
889875

890-
# ... and the state groups
891876
logger.info("[purge] removing %s from state_groups", room_id)
892-
893-
self.db_pool.simple_delete_many_txn(
877+
self.db_pool.simple_delete_txn(
894878
txn,
895879
table="state_groups",
896-
column="id",
897-
values=state_groups_to_delete,
898-
keyvalues={},
880+
keyvalues={"room_id": room_id},
899881
)

tests/rest/admin/test_room.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3050,7 +3050,7 @@ def _block_room(self, room_id: str) -> None:
30503050
"pusher_throttle",
30513051
"room_account_data",
30523052
"room_tags",
3053-
# "state_groups", # Current impl leaves orphaned state groups around.
3053+
"state_groups",
30543054
"state_groups_state",
30553055
"federation_inbound_events_staging",
30563056
]

0 commit comments

Comments
 (0)