diff --git a/synapse/storage/background_updates.py b/synapse/storage/background_updates.py
index 37f2d6c644..b1e5208c76 100644
--- a/synapse/storage/background_updates.py
+++ b/synapse/storage/background_updates.py
@@ -535,6 +535,7 @@ class BackgroundUpdater:
where_clause: Optional[str] = None,
unique: bool = False,
psql_only: bool = False,
+ replaces_index: Optional[str] = None,
) -> None:
"""Helper for store classes to do a background index addition
@@ -554,6 +555,8 @@ class BackgroundUpdater:
unique: true to make a UNIQUE index
psql_only: true to only create this index on psql databases (useful
for virtual sqlite tables)
+ replaces_index: The name of an index that this index replaces.
+ The named index will be dropped upon completion of the new index.
"""
def create_index_psql(conn: Connection) -> None:
@@ -585,6 +588,12 @@ class BackgroundUpdater:
}
logger.debug("[SQL] %s", sql)
c.execute(sql)
+
+ if replaces_index is not None:
+ # We drop the old index as the new index has now been created.
+ sql = f"DROP INDEX IF EXISTS {replaces_index}"
+ logger.debug("[SQL] %s", sql)
+ c.execute(sql)
finally:
conn.set_session(autocommit=False) # type: ignore
@@ -613,6 +622,12 @@ class BackgroundUpdater:
logger.debug("[SQL] %s", sql)
c.execute(sql)
+ if replaces_index is not None:
+ # We drop the old index as the new index has now been created.
+ sql = f"DROP INDEX IF EXISTS {replaces_index}"
+ logger.debug("[SQL] %s", sql)
+ c.execute(sql)
+
if isinstance(self.db_pool.engine, engines.PostgresEngine):
runner: Optional[Callable[[Connection], None]] = create_index_psql
elif psql_only:
diff --git a/synapse/storage/databases/state/bg_updates.py b/synapse/storage/databases/state/bg_updates.py
index 5de70f31d2..fa9eadaca7 100644
--- a/synapse/storage/databases/state/bg_updates.py
+++ b/synapse/storage/databases/state/bg_updates.py
@@ -195,6 +195,7 @@ class StateBackgroundUpdateStore(StateGroupBackgroundUpdateStore):
STATE_GROUP_DEDUPLICATION_UPDATE_NAME = "state_group_state_deduplication"
STATE_GROUP_INDEX_UPDATE_NAME = "state_group_state_type_index"
STATE_GROUPS_ROOM_INDEX_UPDATE_NAME = "state_groups_room_id_idx"
+ STATE_GROUP_EDGES_UNIQUE_INDEX_UPDATE_NAME = "state_group_edges_unique_idx"
def __init__(
self,
@@ -217,6 +218,21 @@ class StateBackgroundUpdateStore(StateGroupBackgroundUpdateStore):
columns=["room_id"],
)
+ # `state_group_edges` can cause severe performance issues if duplicate
+ # rows are introduced, which can accidentally be done by well-meaning
+ # server admins when trying to restore a database dump, etc.
+ # See https://github.com/matrix-org/synapse/issues/11779.
+ # Introduce a unique index to guard against that.
+ self.db_pool.updates.register_background_index_update(
+ self.STATE_GROUP_EDGES_UNIQUE_INDEX_UPDATE_NAME,
+ index_name="state_group_edges_unique_idx",
+ table="state_group_edges",
+ columns=["state_group", "prev_state_group"],
+ unique=True,
+ # The old index was on (state_group) and was not unique.
+ replaces_index="state_group_edges_idx",
+ )
+
async def _background_deduplicate_state(
self, progress: dict, batch_size: int
) -> int:
diff --git a/synapse/storage/schema/state/delta/70/08_state_group_edges_unique.sql b/synapse/storage/schema/state/delta/70/08_state_group_edges_unique.sql
new file mode 100644
index 0000000000..b8c0ee0fa0
--- /dev/null
+++ b/synapse/storage/schema/state/delta/70/08_state_group_edges_unique.sql
@@ -0,0 +1,17 @@
+/* Copyright 2022 The Matrix.org Foundation C.I.C
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+INSERT INTO background_updates (ordering, update_name, progress_json) VALUES
+ (7008, 'state_group_edges_unique_idx', '{}');
|