You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
This repository was archived by the owner on Apr 26, 2024. It is now read-only.
Repository navigation
This repository was archived by the owner on Apr 26, 2024. It is now read-only.
state_groups (& state_groups_state & state_group_edges) are not fully purged alongside the rooms #12821
While investigating my database size, I found that state_groups_state and state_groups hold many stale entries that reference old rooms that have long since been removed from rooms and all related events tables.
It appears these are not purged alongside other tables, particularly via the DELETE _synapse/admin/v1/rooms/{room_id} API (with purge: true)
changed the title [-]`state_groups` (& `_state`) does not appear to be purged alongside the rooms[/-][+]`state_groups` (& `state_groups_state`) does not appear to be purged alongside the rooms[/+]on May 20, 2022
Context: state_groups_state appears to be my biggest table in the database at this point, and I was really questioning why this is the case;
Related question: Can I just delete these rows (that reference rooms that my homeserver isnt a part of, and have been purged long ago) with no effect? Am I supposed to?
After clearing the tables with non-referenced room names, it seems to have cleared up 4GB of disk space (from 44GB to 40GB, that specific table 12GB -> 8GB)
It appears that deleting and purging a room can create an orphaned state group when a DELETE _synapse/admin/v1/rooms/{room_id} request arrives for a room where the dag is in a forked condition. This was discovered when Sean and I were debugging why some tests in the DeleteRoomTestCase class were failing on PR I had opened to batch up some of the events in room creation. All of the failing tests were due to an entry for the room being left in the table state_groups_state, which was causing the is_purged assertion in the test to fail. The following scenario illustrates this, and I used actual results from the test purge_room_and_block, setting breakpoints and pulling events/dumping tables to get examples.
Consider the following: a room is created. Three events are sent into the
room, one after another, each one referencing the previous one as it's previous event:
graph TD;
A[m.room.create] --> B[m.room.member]
B --> C[m.room.power_levels]
Loading
After this, several events are created and then batched up to be persisted. In the current scenario, these are
a join_rules event and a history_visibility event. These both point to the power_levels event as their
previous event. The room creation code has finished, and now the dag looks like this:
graph TD;
A[m.room.create] --> B[m.room.member]
B --> C[m.room.power_levels]
C -->D[m.room.join_rules]
C -->E[m.room_history_visibility]
Loading
At this point, the state_groups_state table looks like this:
Each event has a separate state group, which makes sense as each event is a state event. Next the delete room code path begins. As part of the shutdown room code, a leave event is created, with two previous events, the current forward
extremities. The dag now looks like this:
graph TD;
A[m.room.create] --> B[m.room.member]
B --> C[m.room.power_levels]
C -->D[m.room.join_rules]
C -->E[m.room_history_visibility]
E -->F[m.room.member]
D -->F[m.room.member]
You can see that now the same history visibility group event is recorded in two different state groups.
Events have two state groups, one before the event and one after the event. When state events are in a linear chain, an event's "before" state group is the same as the previous event's "after" state group, but when the dag is forked, this is no longer the case. In the table above, state group 6 is the room history visibility event's "after" state group, and state group 7 is the leave event's "before" state group. Since the event_to_state_groups table only stores the "after" state groups, when the _purge_room_txn code fetches the state groups to be deleted, it leaves behind the second history_visibility state group.
The code in question, synapse/storage/database/main/purge_events lines 354-365,
# First, fetch all the state groups that should be deleted, before
# we delete that information.
txn.execute(
"""
SELECT DISTINCT state_group FROM events
INNER JOIN event_to_state_groups USING(event_id)
WHERE events.room_id = ?
""",
(room_id,),
)
state_groups = [row[0] for row in txn]
results in state_groups resolving to [2, 3, 4, 5, 6, 8]. State group 7 is not selected to be deleted. This list is eventually returned and passed to purge_room_state on line 37 of synapse/storage/controllers/purge_events.py, but since state group 7 is not in the list, it is not deleted and thus becomes an orphan.
After clearing the tables with non-referenced room names, it seems to have cleared up 4GB of disk space (from 44GB to 40GB, that specific table 12GB -> 8GB)
Any opinions on whether this is safe and not corrupting the database even more?
I think this is basically a duplicate of #3364 for the record.
Any opinions on whether this is safe and not corrupting the database even more?
provided you're sure that all other references to the room have been purged from the database, yes it should be safe to remove the unreferenced rows from state_groups and state_groups_state.
@richvdh the query you provide in that issue for checking if you are affected or not doesn't give me a count in the thousands (it's around 500).
I can see state groups for rooms which have been deleted with the admin api however, and one of them even had 530,636 rows in the state_groups_state table. (I compressed it without realizing it wasn't even meant to be there at first and it went down to 425,268)
provided you're sure that all other references to the room have been purged from the database, yes it should be safe to remove the unreferenced rows from state_groups and state_groups_state.
How would I determine if all references have been purged?
Asking because there are many tables with a column named room_id:
@richvdh Thanks for the answer. Just did so, reduced the database from 75 to below 35 GB.
Follow up question: When deleting a room, not just state_groups and state_groups_state keep entries, but current_state_delta_stream and device_lists_changes_in_room too. Is it safe to delete those too?
So, two months later, still with retention enabled, the DB grew again to 76 GB. The room !YTvKGNlinIzlkMTVRl:matrix.org (Element Web/Desktop) had 184082350 (!) rows in state_groups_state, which seems an awful lot. Same goes for !xBRJadUtxNCiIKdwZT:matrix.org (Cybersecurity-General, 17632406 rows) and !HiMrQwoxwdnWgrTVly:matrix.org (FOSDEM, 13416609 rows). Are those rooms really THAT active/big?
In order to shrink my DB once gain, I did the following:
Removed those rooms, e.g.:
curl 'http://localhost:8008/_synapse/admin/v1/rooms/!YTvKGNlinIzlkMTVRl:matrix.org' -X DELETE -H 'Accept: application/json' -H 'Referer: http://localhost:8080/' -H 'authorization: Bearer $TOKEN' --data '{ "purge": true, "message": "Sorry - kicking you out to clean up the database" }'
Shut down Synapse and cleaned up the database like this:
DELETE FROM
state_groups_state
WHERE
room_id IN (
SELECT
DISTINCT(state_groups.room_id) AS room_id_gone
FROM
state_groups
LEFT JOIN events USING(room_id)
WHERE
events.room_id IS NULL
GROUP BY
room_id_gone
);
I joined using state_groups instead of state_groups_state for performance reasons before cleaning it up as well:
DELETE FROM
state_groups
WHERE
room_id IN (
SELECT
DISTINCT(state_groups.room_id) AS room_id_gone
FROM
state_groups
LEFT JOIN events USING(room_id)
WHERE
events.room_id IS NULL
GROUP BY
room_id_gone
);
Reclaimed the space on the file system:
REINDEX (VERBOSE) DATABASE synapse; VACUUM FULL VERBOSE;
As a result of this, my DB shrunk to 16 GB (on the file system).
Restarted Synapse
Open question: Some other tables also return small to big numbers, an I am wondering if it is save to delete the found entries?
synapse=# SELECT FROM device_lists_changes_in_room WHERE room_id IN (SELECT DISTINCT(device_lists_changes_in_room.room_id) AS room_id_gone FROM device_lists_changes_in_room LEFT JOIN events USING(room_id) WHERE events.room_id IS NULL GROUP BY room_id_gone);
--
(1230238 rows)
The other tables being:
current_state_delta_stream: 3405301 rows
e2e_room_keys: 71
event_reports: 1 row
receipts_graph: 2742 rows
receipts_linearized: 2742 rows
room_account_data: 4 rows
room_retention: 4 rows
room_tags_revisions: 61 rows
Reacted by Jonathan de Jong, Kokokokoka, Tonkku, marvinwankersteen, boontifex, Jivan Pal, yingziwu, sarah4d2 and aaronraimist
@rettichschnidi
Are there any problems since your cleanup? We probably have the same problem and would also like to get rid of the deleted rooms. I'm just curious if there were any other problems after that?
Are there any problems since your cleanup? We probably have the same problem and would also like to get rid of the deleted rooms. I'm just curious if there were any other problems after that?
I have not experienced any myself and also have not gotten any complains yet. Will update here if this changes.
Are there any problems since your cleanup? We probably have the same problem and would also like to get rid of the deleted rooms. I'm just curious if there were any other problems after that?
I have not experienced any myself and also have not gotten any complains yet. Will update here if this changes.
Thank you for testing this. We did that too and so far so good. We even went from about 115 GB down to 13 GB.
changed the title [-]`state_groups` (& `state_groups_state`) does not appear to be purged alongside the rooms[/-][+]`state_groups` (& `state_groups_state`) are not fully purged alongside the rooms[/+]on Dec 16, 2022
changed the title [-]`state_groups` (& `state_groups_state`) are not fully purged alongside the rooms[/-][+]`state_groups` (& `state_groups_state` & `state_group_edges`) are not fully purged alongside the rooms[/+]on Dec 16, 2022
There are also references of room_id in table current_state_delta_stream which held the reference stream_id to table cache_invalidation_stream_by_instance.
cache_invalidation_stream_by_instance has a column cache_func. Any ideas how this is been used and whats the correct way?
DELETEFROM
cache_invalidation_stream_by_instance as inv
INNER JOIN current_state_delta_stream as stream ONinv.stream_id=stream.stream_idWHERE room_id IN (
SELECT
DISTINCT(state_groups.room_id) AS room_id_gone
FROM
state_groups
LEFT JOIN events USING(room_id)
WHEREevents.room_id IS NULLGROUP BY
room_id_gone
)
AND cache_func ='have_seen_event';
There are also references of room_id in table current_state_delta_stream which held the reference stream_id to table cache_invalidation_stream_by_instance.
I am far from convinced those columns are referring to the same "stream", though I haven't actually checked. Both of those tables can be periodically cleaned out anyway; see #5888 and #13456.
Activity
[-]`state_groups` (& `_state`) does not appear to be purged alongside the rooms[/-][+]`state_groups` (& `state_groups_state`) does not appear to be purged alongside the rooms[/+]ShadowJonathan commentedon May 20, 2022
Context:
state_groups_stateappears to be my biggest table in the database at this point, and I was really questioning why this is the case;Related question: Can I just delete these rows (that reference rooms that my homeserver isnt a part of, and have been purged long ago) with no effect? Am I supposed to?
reivilibre commentedon May 20, 2022
Andrew noticed that there's a note about this in the code...
synapse/tests/rest/admin/test_room.py
Line 2490 in 02d7085
ShadowJonathan commentedon May 20, 2022
After clearing the tables with non-referenced room names, it seems to have cleared up 4GB of disk space (from 44GB to 40GB, that specific table 12GB -> 8GB)
clokep commentedon May 20, 2022
We do seem to delete it now though:
synapse/synapse/storage/databases/state/store.py
Lines 667 to 676 in 19d79b6
I wonder if the test would now pass if you check if those are gone?
(Maybe we need a background update or something to clean-up?)
richvdh commentedon May 23, 2022
possibly related to #3364 (comment)
squahtx commentedon May 30, 2022
I re-ran the tests with the following line uncommented:
synapse/tests/rest/admin/test_room.py
Line 2490 in 02d7085
and a bunch of them failed with
twisted.trial.unittest.FailTest: 1 != 0 : Rows not purged in state_groups:So it looks like we're still missing something.
H-Shay commentedon Aug 12, 2022
It appears that deleting and purging a room can create an orphaned state group when a
DELETE _synapse/admin/v1/rooms/{room_id}request arrives for a room where the dag is in a forked condition. This was discovered when Sean and I were debugging why some tests in theDeleteRoomTestCaseclass were failing on PR I had opened to batch up some of the events in room creation. All of the failing tests were due to an entry for the room being left in the tablestate_groups_state, which was causing theis_purgedassertion in the test to fail. The following scenario illustrates this, and I used actual results from the testpurge_room_and_block, setting breakpoints and pulling events/dumping tables to get examples.Consider the following: a room is created. Three events are sent into the
room, one after another, each one referencing the previous one as it's previous event:
graph TD; A[m.room.create] --> B[m.room.member] B --> C[m.room.power_levels]After this, several events are created and then batched up to be persisted. In the current scenario, these are
a
join_rulesevent and ahistory_visibilityevent. These both point to thepower_levelsevent as theirprevious event. The room creation code has finished, and now the dag looks like this:
graph TD; A[m.room.create] --> B[m.room.member] B --> C[m.room.power_levels] C -->D[m.room.join_rules] C -->E[m.room_history_visibility]At this point, the state_groups_state table looks like this:
Each event has a separate state group, which makes sense as each event is a state event. Next the delete room code path begins. As part of the shutdown room code, a leave event is created, with two previous events, the current forward
extremities. The dag now looks like this:
graph TD; A[m.room.create] --> B[m.room.member] B --> C[m.room.power_levels] C -->D[m.room.join_rules] C -->E[m.room_history_visibility] E -->F[m.room.member] D -->F[m.room.member]And the state_groups_state table looks like this:
You can see that now the same history visibility group event is recorded in two different state groups.
Events have two state groups, one before the event and one after the event. When state events are in a linear chain, an event's "before" state group is the same as the previous event's "after" state group, but when the dag is forked, this is no longer the case. In the table above, state group 6 is the room history visibility event's "after" state group, and state group 7 is the leave event's "before" state group. Since the
event_to_state_groupstable only stores the "after" state groups, when the_purge_room_txncode fetches the state groups to be deleted, it leaves behind the second history_visibility state group.The code in question, synapse/storage/database/main/purge_events lines 354-365,
results in
state_groupsresolving to[2, 3, 4, 5, 6, 8]. State group 7 is not selected to be deleted. This list is eventually returned and passed topurge_room_stateon line 37 ofsynapse/storage/controllers/purge_events.py, but since state group 7 is not in the list, it is not deleted and thus becomes an orphan.rettichschnidi commentedon Aug 18, 2022
Any opinions on whether this is safe and not corrupting the database even more?
richvdh commentedon Aug 18, 2022
I think this is basically a duplicate of #3364 for the record.
provided you're sure that all other references to the room have been purged from the database, yes it should be safe to remove the unreferenced rows from
state_groupsandstate_groups_state.tonkku107 commentedon Aug 19, 2022
@richvdh the query you provide in that issue for checking if you are affected or not doesn't give me a count in the thousands (it's around 500).
I can see state groups for rooms which have been deleted with the admin api however, and one of them even had 530,636 rows in the
state_groups_statetable. (I compressed it without realizing it wasn't even meant to be there at first and it went down to 425,268)rettichschnidi commentedon Aug 30, 2022
How would I determine if all references have been purged?
Asking because there are many tables with a column named room_id:
Would I need to check every single table?
richvdh commentedon Aug 30, 2022
@rettichschnidi check the
eventstable. If there's nothing there for your room, you can remove the room fromstate_groupsandstate_groups_state.rettichschnidi commentedon Aug 30, 2022
@richvdh Thanks for the answer. Just did so, reduced the database from 75 to below 35 GB.
Follow up question: When deleting a room, not just
state_groupsandstate_groups_statekeep entries, butcurrent_state_delta_streamanddevice_lists_changes_in_roomtoo. Is it safe to delete those too?rettichschnidi commentedon Oct 29, 2022
So, two months later, still with retention enabled, the DB grew again to 76 GB. The room
!YTvKGNlinIzlkMTVRl:matrix.org(Element Web/Desktop) had 184082350 (!) rows instate_groups_state, which seems an awful lot. Same goes for!xBRJadUtxNCiIKdwZT:matrix.org(Cybersecurity-General, 17632406 rows) and!HiMrQwoxwdnWgrTVly:matrix.org(FOSDEM, 13416609 rows). Are those rooms really THAT active/big?In order to shrink my DB once gain, I did the following:
I joined using
state_groupsinstead ofstate_groups_statefor performance reasons before cleaning it up as well:As a result of this, my DB shrunk to 16 GB (on the file system).
Open question: Some other tables also return small to big numbers, an I am wondering if it is save to delete the found entries?
The other tables being:
Dan-Sun commentedon Nov 1, 2022
@rettichschnidi
Are there any problems since your cleanup? We probably have the same problem and would also like to get rid of the deleted rooms. I'm just curious if there were any other problems after that?
rettichschnidi commentedon Nov 1, 2022
I have not experienced any myself and also have not gotten any complains yet. Will update here if this changes.
Dan-Sun commentedon Nov 4, 2022
Thank you for testing this. We did that too and so far so good. We even went from about 115 GB down to 13 GB.
marvinwankersteen commentedon Dec 11, 2022
Very nice @rettichschnidi!
This reduced the database size from 10 to 4 GB.
[-]`state_groups` (& `state_groups_state`) does not appear to be purged alongside the rooms[/-][+]`state_groups` (& `state_groups_state`) are not fully purged alongside the rooms[/+][-]`state_groups` (& `state_groups_state`) are not fully purged alongside the rooms[/-][+]`state_groups` (& `state_groups_state` & `state_group_edges`) are not fully purged alongside the rooms[/+]boontifex commentedon Feb 1, 2023
There are also references of
room_idin tablecurrent_state_delta_streamwhich held the referencestream_idto tablecache_invalidation_stream_by_instance.cache_invalidation_stream_by_instancehas a columncache_func. Any ideas how this is been used and whats the correct way?In addition to @rettichschnidi i made the following draft:
richvdh commentedon Feb 2, 2023
I am far from convinced those columns are referring to the same "stream", though I haven't actually checked. Both of those tables can be periodically cleaned out anyway; see #5888 and #13456.
boontifex commentedon Feb 7, 2023
@richvdh I didn't really find the comprehensive documentation how synapse stores data and why, but i've give some thoughts.
Events (joining, leaving, messages etc) are stored in the tables
eventsandevents_json.IMO every entry in the following tables, that doesn't have a related event_id in
eventsis orphaned and can be deleted. Or am I wrong?