Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add background job to clear unreferenced state groups #18154

Open
wants to merge 45 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
e6a3c46
Add background job to clear unreferenced state groups
devonh Feb 12, 2025
f9670ff
Add changelog entry
devonh Feb 12, 2025
9afe80b
Merge branch 'develop' into devon/unreferenced-bg
devonh Feb 12, 2025
20efdd2
Add test for unreferenced state group cleanup
devonh Feb 12, 2025
9c50123
Remove comments
devonh Feb 12, 2025
28679b6
Fix linter errors
devonh Feb 12, 2025
e606f42
Merge branch 'develop' into devon/unreferenced-bg
devonh Feb 12, 2025
977d83b
Order state_groups
devonh Feb 12, 2025
a487bcb
Update synapse/storage/controllers/purge_events.py
devonh Feb 13, 2025
69d72c2
Update synapse/storage/controllers/purge_events.py
devonh Feb 13, 2025
d8bfac4
Update synapse/storage/controllers/purge_events.py
devonh Feb 13, 2025
0955b7b
Update synapse/storage/controllers/purge_events.py
devonh Feb 13, 2025
ce87ba6
Change mark as pending deletion to do nothing on conflict
devonh Feb 13, 2025
3900791
Fix linter errors
devonh Feb 13, 2025
fe1df20
Move state group deletion job to background updates
devonh Feb 13, 2025
cc9e33b
Fix linter error
devonh Feb 13, 2025
801ca86
Pull over all the db calls since that's what it wants...
devonh Feb 13, 2025
ccb2158
Try OVERRIDING SYSTEM VALUE
devonh Feb 14, 2025
ca7ed76
Move OVERRIDING SYSTEM VALUE
devonh Feb 14, 2025
f45dcb1
Update synapse/storage/databases/state/bg_updates.py
devonh Feb 18, 2025
5e05af2
Update synapse/storage/databases/state/bg_updates.py
devonh Feb 18, 2025
7d1ce8d
Review comments & cleanup
devonh Feb 18, 2025
3c50f71
No string interpolation for sql
devonh Feb 18, 2025
7f611e0
Move background task to current schema version
devonh Feb 18, 2025
21dc067
Comment ignoring table port
devonh Feb 18, 2025
09a817f
Deduplicate find_unreferenced_groups
devonh Feb 18, 2025
042af6e
Don't reuse variables
devonh Feb 18, 2025
4cae2e5
Switch to not use single transaction
devonh Feb 18, 2025
ae367b2
Try casting
devonh Feb 18, 2025
977a8d8
Readd duplication
devonh Feb 19, 2025
d8f920b
Put it back in place
devonh Feb 19, 2025
6582fed
Put it back in place
devonh Feb 19, 2025
ecb8ed5
Fix linter error
devonh Feb 19, 2025
8eae7dd
Use multiple db pools
devonh Feb 19, 2025
8ef4a23
Remove duplication again
devonh Feb 19, 2025
dfa55a9
Lift logic to purge events controller
devonh Feb 19, 2025
02c2c87
Add IGNORED_BACKGROUND_UPDATES to port_db
devonh Feb 19, 2025
6851eaa
Fix error
devonh Feb 19, 2025
d1ca8c7
Update comment on ignoring state_groups_pending_deletion
devonh Feb 19, 2025
0f7c874
Try different sql
devonh Feb 19, 2025
35f15e7
Fixes
devonh Feb 19, 2025
89ec2a3
Update synapse/_scripts/synapse_port_db.py
devonh Feb 24, 2025
f5e59f2
Fix port_db syntax
devonh Feb 24, 2025
92d459d
Remove unnecessary code
devonh Feb 24, 2025
5f5f090
Only clear state groups up to max from first iteration
devonh Feb 24, 2025
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/18154.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add background job to clear unreferenced state groups.
63 changes: 63 additions & 0 deletions synapse/storage/controllers/purge_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@
class PurgeEventsStorageController:
"""High level interface for purging rooms and event history."""

CLEAR_UNREFERENCED_STATE_GROUPS_PERIOD_MS = 60 * 1000

def __init__(self, hs: "HomeServer", stores: Databases):
self.stores = stores

Expand All @@ -44,6 +46,12 @@ def __init__(self, hs: "HomeServer", stores: Databases):
self._delete_state_groups_loop, 60 * 1000
)

self._last_checked_state_group = 0
if hs.config.worker.run_background_tasks:
self._clear_unreferenced_state_loop_call = hs.get_clock().looping_call(
self._clear_unreferenced_state_groups_loop, self.CLEAR_UNREFERENCED_STATE_GROUPS_PERIOD_MS
)

async def purge_room(self, room_id: str) -> None:
"""Deletes all record of a room"""

Expand Down Expand Up @@ -203,3 +211,58 @@ async def _delete_state_groups(
room_id,
groups_to_sequences,
)

@wrap_as_background_process("_clear_unreferenced_state_groups_loop")
async def _clear_unreferenced_state_groups_loop(self) -> None:
"""Background task that deletes any state groups that may be pending
deletion."""

# Look for state groups that can be cleaned up.
search_limit = 100
next_set = await self.stores.main.get_state_groups(
self._last_checked_state_group + 1, search_limit
)
if len(next_set) < search_limit:
self._last_checked_state_group = 0
else:
self._last_checked_state_group = list(next_set)[-1]

if len(next_set) == 0:
return

referenced = await self.stores.main.get_referenced_state_groups(next_set)
next_set -= referenced

if len(next_set) == 0:
return

referenced = await self.stores.state.get_next_state_groups(next_set)
next_set -= set(referenced.values())

if len(next_set) == 0:
return

# Find all state groups that can be deleted if the original set is deleted.
# This set includes the original set, as well as any state groups that would
# become unreferenced upon deleting the original set.
to_delete = await self._find_unreferenced_groups(next_set)

if len(to_delete) == 0:
return

pending_deletions = await self.stores.state_deletion.get_pending_deletions(
to_delete
)
to_delete -= pending_deletions.keys()

if len(to_delete) == 0:
return

# TODO: state_groups_pending_deletion table is never cleaned up after deletion!
# TODO: should we be cleaning up any state_group_edges that are dangling after
# the deletion as well?

# Mark the state groups for deletion by the deletion background task.
await self.stores.state_deletion.mark_state_groups_as_pending_deletion(
to_delete
)
39 changes: 39 additions & 0 deletions synapse/storage/databases/main/state.py
Original file line number Diff line number Diff line change
Expand Up @@ -675,6 +675,45 @@ async def get_referenced_state_groups(

return {row[0] for row in rows}

async def get_state_groups(
self,
initial_state_group: int,
limit: int,
) -> Set[int]:
"""Get a list of stored state groups

Args:
initial_state_group: get state groups starting with this one.
limit: the maximum number of state groups to return.

Returns:
The set of stored state groups following the initial_state_group.
"""
return await self.db_pool.runInteraction(
"get_state_groups",
self._get_state_groups_txn,
initial_state_group,
limit,
)

def _get_state_groups_txn(
self,
txn: LoggingTransaction,
initial_state_group: int,
limit: int,
) -> Set[int]:
sql = f"""
SELECT id from state_groups
WHERE id > {initial_state_group}
LIMIT {limit}
"""

txn.execute(sql)

rows = txn.fetchall()

return {row[0] for row in rows}

async def update_state_for_partial_state_event(
self,
event: EventBase,
Expand Down
85 changes: 85 additions & 0 deletions tests/storage/test_purge.py
Original file line number Diff line number Diff line change
Expand Up @@ -270,3 +270,88 @@ def test_purge_unreferenced_state_group(self) -> None:
)
)
self.assertEqual(len(state_groups), 1)

def test_clear_unreferenced_state_groups(self) -> None:
"""Test that any unreferenced state groups are automatically cleaned up.
"""

self.helper.send(self.room_id, body="test1")
state1 = self.helper.send_state(
self.room_id, "org.matrix.test", body={"number": 2}
)
state2 = self.helper.send_state(
self.room_id, "org.matrix.test", body={"number": 3}
)
self.helper.send(self.room_id, body="test4")
last = self.helper.send(self.room_id, body="test5")

# Create an unreferenced state group that has a prev group of one of the
# to-be-purged events.
prev_group = self.get_success(
self.store._get_state_group_for_event(state1["event_id"])
)
unreferenced_state_group = self.get_success(
self.state_store.store_state_group(
event_id=last["event_id"],
room_id=self.room_id,
prev_group=prev_group,
delta_ids={("org.matrix.test", ""): state2["event_id"]},
current_state_ids=None,
)
)

another_unreferenced_state_group = self.get_success(
self.state_store.store_state_group(
event_id=last["event_id"],
room_id=self.room_id,
prev_group=unreferenced_state_group,
delta_ids={("org.matrix.test", ""): state2["event_id"]},
current_state_ids=None,
)
)

# Advance so that the background job to clear unreferenced state groups runs
self.reactor.advance(
1 + self._storage_controllers.purge_events.CLEAR_UNREFERENCED_STATE_GROUPS_PERIOD_MS / 1000
)

# Advance so that the background jobs to delete the state groups runs
self.reactor.advance(
1 + self.state_deletion_store.DELAY_BEFORE_DELETION_MS / 1000
)

# We expect that the unreferenced state group has been deleted.
row = self.get_success(
self.state_store.db_pool.simple_select_one_onecol(
table="state_groups",
keyvalues={"id": unreferenced_state_group},
retcol="id",
allow_none=True,
desc="test_purge_unreferenced_state_group",
)
)
self.assertIsNone(row)

# We expect that the other unreferenced state group has also been deleted.
row = self.get_success(
self.state_store.db_pool.simple_select_one_onecol(
table="state_groups",
keyvalues={"id": another_unreferenced_state_group},
retcol="id",
allow_none=True,
desc="test_purge_unreferenced_state_group",
)
)
self.assertIsNone(row)

# We expect there to now only be one state group for the room, which is
# the state group of the last event (as the only outlier).
state_groups = self.get_success(
self.state_store.db_pool.simple_select_onecol(
table="state_groups",
keyvalues={"room_id": self.room_id},
retcol="id",
desc="test_purge_unreferenced_state_group",
)
)
self.assertEqual(len(state_groups), 8)
Loading