Skip to content
This repository was archived by the owner on Apr 26, 2024. It is now read-only.

Commit a7acb1f

Browse files
MadLittleModsH-Shay
authored andcommitted
Optimize filter_events_for_client for faster /messages - v2 (#14527)
Fix #14108
1 parent 01facbe commit a7acb1f

File tree

2 files changed

+80
-20
lines changed

2 files changed

+80
-20
lines changed

changelog.d/14527.misc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Speed-up `/messages` with `filter_events_for_client` optimizations.

synapse/storage/databases/state/bg_updates.py

Lines changed: 79 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -93,13 +93,6 @@ def _get_state_groups_from_groups_txn(
9393

9494
results: Dict[int, MutableStateMap[str]] = {group: {} for group in groups}
9595

96-
where_clause, where_args = state_filter.make_sql_filter_clause()
97-
98-
# Unless the filter clause is empty, we're going to append it after an
99-
# existing where clause
100-
if where_clause:
101-
where_clause = " AND (%s)" % (where_clause,)
102-
10396
if isinstance(self.database_engine, PostgresEngine):
10497
# Temporarily disable sequential scans in this transaction. This is
10598
# a temporary hack until we can add the right indices in
@@ -110,38 +103,104 @@ def _get_state_groups_from_groups_txn(
110103
# against `state_groups_state` to fetch the latest state.
111104
# It assumes that previous state groups are always numerically
112105
# lesser.
113-
# The PARTITION is used to get the event_id in the greatest state
114-
# group for the given type, state_key.
115106
# This may return multiple rows per (type, state_key), but last_value
116107
# should be the same.
117108
sql = """
118-
WITH RECURSIVE state(state_group) AS (
109+
WITH RECURSIVE sgs(state_group) AS (
119110
VALUES(?::bigint)
120111
UNION ALL
121-
SELECT prev_state_group FROM state_group_edges e, state s
112+
SELECT prev_state_group FROM state_group_edges e, sgs s
122113
WHERE s.state_group = e.state_group
123114
)
124-
SELECT DISTINCT ON (type, state_key)
125-
type, state_key, event_id
126-
FROM state_groups_state
127-
WHERE state_group IN (
128-
SELECT state_group FROM state
129-
) %s
130-
ORDER BY type, state_key, state_group DESC
115+
%s
131116
"""
132117

118+
overall_select_query_args: List[Union[int, str]] = []
119+
120+
# This is an optimization to create a select clause per-condition. This
121+
# makes the query planner a lot smarter on what rows should pull out in the
122+
# first place and we end up with something that takes 10x less time to get a
123+
# result.
124+
use_condition_optimization = (
125+
not state_filter.include_others and not state_filter.is_full()
126+
)
127+
state_filter_condition_combos: List[Tuple[str, Optional[str]]] = []
128+
# We don't need to caclculate this list if we're not using the condition
129+
# optimization
130+
if use_condition_optimization:
131+
for etype, state_keys in state_filter.types.items():
132+
if state_keys is None:
133+
state_filter_condition_combos.append((etype, None))
134+
else:
135+
for state_key in state_keys:
136+
state_filter_condition_combos.append((etype, state_key))
137+
# And here is the optimization itself. We don't want to do the optimization
138+
# if there are too many individual conditions. 10 is an arbitrary number
139+
# with no testing behind it but we do know that we specifically made this
140+
# optimization for when we grab the necessary state out for
141+
# `filter_events_for_client` which just uses 2 conditions
142+
# (`EventTypes.RoomHistoryVisibility` and `EventTypes.Member`).
143+
if use_condition_optimization and len(state_filter_condition_combos) < 10:
144+
select_clause_list: List[str] = []
145+
for etype, skey in state_filter_condition_combos:
146+
if skey is None:
147+
where_clause = "(type = ?)"
148+
overall_select_query_args.extend([etype])
149+
else:
150+
where_clause = "(type = ? AND state_key = ?)"
151+
overall_select_query_args.extend([etype, skey])
152+
153+
select_clause_list.append(
154+
f"""
155+
(
156+
SELECT DISTINCT ON (type, state_key)
157+
type, state_key, event_id
158+
FROM state_groups_state
159+
INNER JOIN sgs USING (state_group)
160+
WHERE {where_clause}
161+
ORDER BY type, state_key, state_group DESC
162+
)
163+
"""
164+
)
165+
166+
overall_select_clause = " UNION ".join(select_clause_list)
167+
else:
168+
where_clause, where_args = state_filter.make_sql_filter_clause()
169+
# Unless the filter clause is empty, we're going to append it after an
170+
# existing where clause
171+
if where_clause:
172+
where_clause = " AND (%s)" % (where_clause,)
173+
174+
overall_select_query_args.extend(where_args)
175+
176+
overall_select_clause = f"""
177+
SELECT DISTINCT ON (type, state_key)
178+
type, state_key, event_id
179+
FROM state_groups_state
180+
WHERE state_group IN (
181+
SELECT state_group FROM sgs
182+
) {where_clause}
183+
ORDER BY type, state_key, state_group DESC
184+
"""
185+
133186
for group in groups:
134187
args: List[Union[int, str]] = [group]
135-
args.extend(where_args)
188+
args.extend(overall_select_query_args)
136189

137-
txn.execute(sql % (where_clause,), args)
190+
txn.execute(sql % (overall_select_clause,), args)
138191
for row in txn:
139192
typ, state_key, event_id = row
140193
key = (intern_string(typ), intern_string(state_key))
141194
results[group][key] = event_id
142195
else:
143196
max_entries_returned = state_filter.max_entries_returned()
144197

198+
where_clause, where_args = state_filter.make_sql_filter_clause()
199+
# Unless the filter clause is empty, we're going to append it after an
200+
# existing where clause
201+
if where_clause:
202+
where_clause = " AND (%s)" % (where_clause,)
203+
145204
# We don't use WITH RECURSIVE on sqlite3 as there are distributions
146205
# that ship with an sqlite3 version that doesn't support it (e.g. wheezy)
147206
for group in groups:

0 commit comments

Comments
 (0)