event_federation.py 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724
  1. # -*- coding: utf-8 -*-
  2. # Copyright 2014-2016 OpenMarket Ltd
  3. #
  4. # Licensed under the Apache License, Version 2.0 (the "License");
  5. # you may not use this file except in compliance with the License.
  6. # You may obtain a copy of the License at
  7. #
  8. # http://www.apache.org/licenses/LICENSE-2.0
  9. #
  10. # Unless required by applicable law or agreed to in writing, software
  11. # distributed under the License is distributed on an "AS IS" BASIS,
  12. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. # See the License for the specific language governing permissions and
  14. # limitations under the License.
  15. import itertools
  16. import logging
  17. from queue import Empty, PriorityQueue
  18. from typing import Dict, List, Optional, Set, Tuple
  19. from twisted.internet import defer
  20. from synapse.api.errors import StoreError
  21. from synapse.metrics.background_process_metrics import run_as_background_process
  22. from synapse.storage._base import SQLBaseStore, make_in_list_sql_clause
  23. from synapse.storage.data_stores.main.events_worker import EventsWorkerStore
  24. from synapse.storage.data_stores.main.signatures import SignatureWorkerStore
  25. from synapse.storage.database import Database
  26. from synapse.util.caches.descriptors import cached
  27. from synapse.util.iterutils import batch_iter
  28. logger = logging.getLogger(__name__)
  29. class EventFederationWorkerStore(EventsWorkerStore, SignatureWorkerStore, SQLBaseStore):
  30. def get_auth_chain(self, event_ids, include_given=False):
  31. """Get auth events for given event_ids. The events *must* be state events.
  32. Args:
  33. event_ids (list): state events
  34. include_given (bool): include the given events in result
  35. Returns:
  36. list of events
  37. """
  38. return self.get_auth_chain_ids(
  39. event_ids, include_given=include_given
  40. ).addCallback(self.get_events_as_list)
  41. def get_auth_chain_ids(
  42. self,
  43. event_ids: List[str],
  44. include_given: bool = False,
  45. ignore_events: Optional[Set[str]] = None,
  46. ):
  47. """Get auth events for given event_ids. The events *must* be state events.
  48. Args:
  49. event_ids: state events
  50. include_given: include the given events in result
  51. ignore_events: Set of events to exclude from the returned auth
  52. chain. This is useful if the caller will just discard the
  53. given events anyway, and saves us from figuring out their auth
  54. chains if not required.
  55. Returns:
  56. list of event_ids
  57. """
  58. return self.db.runInteraction(
  59. "get_auth_chain_ids",
  60. self._get_auth_chain_ids_txn,
  61. event_ids,
  62. include_given,
  63. ignore_events,
  64. )
  65. def _get_auth_chain_ids_txn(self, txn, event_ids, include_given, ignore_events):
  66. if ignore_events is None:
  67. ignore_events = set()
  68. if include_given:
  69. results = set(event_ids)
  70. else:
  71. results = set()
  72. base_sql = "SELECT auth_id FROM event_auth WHERE "
  73. front = set(event_ids)
  74. while front:
  75. new_front = set()
  76. for chunk in batch_iter(front, 100):
  77. clause, args = make_in_list_sql_clause(
  78. txn.database_engine, "event_id", chunk
  79. )
  80. txn.execute(base_sql + clause, args)
  81. new_front.update(r[0] for r in txn)
  82. new_front -= ignore_events
  83. new_front -= results
  84. front = new_front
  85. results.update(front)
  86. return list(results)
  87. def get_auth_chain_difference(self, state_sets: List[Set[str]]):
  88. """Given sets of state events figure out the auth chain difference (as
  89. per state res v2 algorithm).
  90. This equivalent to fetching the full auth chain for each set of state
  91. and returning the events that don't appear in each and every auth
  92. chain.
  93. Returns:
  94. Deferred[Set[str]]
  95. """
  96. return self.db.runInteraction(
  97. "get_auth_chain_difference",
  98. self._get_auth_chain_difference_txn,
  99. state_sets,
  100. )
  101. def _get_auth_chain_difference_txn(
  102. self, txn, state_sets: List[Set[str]]
  103. ) -> Set[str]:
  104. # Algorithm Description
  105. # ~~~~~~~~~~~~~~~~~~~~~
  106. #
  107. # The idea here is to basically walk the auth graph of each state set in
  108. # tandem, keeping track of which auth events are reachable by each state
  109. # set. If we reach an auth event we've already visited (via a different
  110. # state set) then we mark that auth event and all ancestors as reachable
  111. # by the state set. This requires that we keep track of the auth chains
  112. # in memory.
  113. #
  114. # Doing it in a such a way means that we can stop early if all auth
  115. # events we're currently walking are reachable by all state sets.
  116. #
  117. # *Note*: We can't stop walking an event's auth chain if it is reachable
  118. # by all state sets. This is because other auth chains we're walking
  119. # might be reachable only via the original auth chain. For example,
  120. # given the following auth chain:
  121. #
  122. # A -> C -> D -> E
  123. # / /
  124. # B -´---------´
  125. #
  126. # and state sets {A} and {B} then walking the auth chains of A and B
  127. # would immediately show that C is reachable by both. However, if we
  128. # stopped at C then we'd only reach E via the auth chain of B and so E
  129. # would errornously get included in the returned difference.
  130. #
  131. # The other thing that we do is limit the number of auth chains we walk
  132. # at once, due to practical limits (i.e. we can only query the database
  133. # with a limited set of parameters). We pick the auth chains we walk
  134. # each iteration based on their depth, in the hope that events with a
  135. # lower depth are likely reachable by those with higher depths.
  136. #
  137. # We could use any ordering that we believe would give a rough
  138. # topological ordering, e.g. origin server timestamp. If the ordering
  139. # chosen is not topological then the algorithm still produces the right
  140. # result, but perhaps a bit more inefficiently. This is why it is safe
  141. # to use "depth" here.
  142. initial_events = set(state_sets[0]).union(*state_sets[1:])
  143. # Dict from events in auth chains to which sets *cannot* reach them.
  144. # I.e. if the set is empty then all sets can reach the event.
  145. event_to_missing_sets = {
  146. event_id: {i for i, a in enumerate(state_sets) if event_id not in a}
  147. for event_id in initial_events
  148. }
  149. # The sorted list of events whose auth chains we should walk.
  150. search = [] # type: List[Tuple[int, str]]
  151. # We need to get the depth of the initial events for sorting purposes.
  152. sql = """
  153. SELECT depth, event_id FROM events
  154. WHERE %s
  155. """
  156. # the list can be huge, so let's avoid looking them all up in one massive
  157. # query.
  158. for batch in batch_iter(initial_events, 1000):
  159. clause, args = make_in_list_sql_clause(
  160. txn.database_engine, "event_id", batch
  161. )
  162. txn.execute(sql % (clause,), args)
  163. # I think building a temporary list with fetchall is more efficient than
  164. # just `search.extend(txn)`, but this is unconfirmed
  165. search.extend(txn.fetchall())
  166. # sort by depth
  167. search.sort()
  168. # Map from event to its auth events
  169. event_to_auth_events = {} # type: Dict[str, Set[str]]
  170. base_sql = """
  171. SELECT a.event_id, auth_id, depth
  172. FROM event_auth AS a
  173. INNER JOIN events AS e ON (e.event_id = a.auth_id)
  174. WHERE
  175. """
  176. while search:
  177. # Check whether all our current walks are reachable by all state
  178. # sets. If so we can bail.
  179. if all(not event_to_missing_sets[eid] for _, eid in search):
  180. break
  181. # Fetch the auth events and their depths of the N last events we're
  182. # currently walking
  183. search, chunk = search[:-100], search[-100:]
  184. clause, args = make_in_list_sql_clause(
  185. txn.database_engine, "a.event_id", [e_id for _, e_id in chunk]
  186. )
  187. txn.execute(base_sql + clause, args)
  188. for event_id, auth_event_id, auth_event_depth in txn:
  189. event_to_auth_events.setdefault(event_id, set()).add(auth_event_id)
  190. sets = event_to_missing_sets.get(auth_event_id)
  191. if sets is None:
  192. # First time we're seeing this event, so we add it to the
  193. # queue of things to fetch.
  194. search.append((auth_event_depth, auth_event_id))
  195. # Assume that this event is unreachable from any of the
  196. # state sets until proven otherwise
  197. sets = event_to_missing_sets[auth_event_id] = set(
  198. range(len(state_sets))
  199. )
  200. else:
  201. # We've previously seen this event, so look up its auth
  202. # events and recursively mark all ancestors as reachable
  203. # by the current event's state set.
  204. a_ids = event_to_auth_events.get(auth_event_id)
  205. while a_ids:
  206. new_aids = set()
  207. for a_id in a_ids:
  208. event_to_missing_sets[a_id].intersection_update(
  209. event_to_missing_sets[event_id]
  210. )
  211. b = event_to_auth_events.get(a_id)
  212. if b:
  213. new_aids.update(b)
  214. a_ids = new_aids
  215. # Mark that the auth event is reachable by the approriate sets.
  216. sets.intersection_update(event_to_missing_sets[event_id])
  217. search.sort()
  218. # Return all events where not all sets can reach them.
  219. return {eid for eid, n in event_to_missing_sets.items() if n}
  220. def get_oldest_events_in_room(self, room_id):
  221. return self.db.runInteraction(
  222. "get_oldest_events_in_room", self._get_oldest_events_in_room_txn, room_id
  223. )
  224. def get_oldest_events_with_depth_in_room(self, room_id):
  225. return self.db.runInteraction(
  226. "get_oldest_events_with_depth_in_room",
  227. self.get_oldest_events_with_depth_in_room_txn,
  228. room_id,
  229. )
  230. def get_oldest_events_with_depth_in_room_txn(self, txn, room_id):
  231. sql = (
  232. "SELECT b.event_id, MAX(e.depth) FROM events as e"
  233. " INNER JOIN event_edges as g"
  234. " ON g.event_id = e.event_id"
  235. " INNER JOIN event_backward_extremities as b"
  236. " ON g.prev_event_id = b.event_id"
  237. " WHERE b.room_id = ? AND g.is_state is ?"
  238. " GROUP BY b.event_id"
  239. )
  240. txn.execute(sql, (room_id, False))
  241. return dict(txn)
  242. @defer.inlineCallbacks
  243. def get_max_depth_of(self, event_ids):
  244. """Returns the max depth of a set of event IDs
  245. Args:
  246. event_ids (list[str])
  247. Returns
  248. Deferred[int]
  249. """
  250. rows = yield self.db.simple_select_many_batch(
  251. table="events",
  252. column="event_id",
  253. iterable=event_ids,
  254. retcols=("depth",),
  255. desc="get_max_depth_of",
  256. )
  257. if not rows:
  258. return 0
  259. else:
  260. return max(row["depth"] for row in rows)
  261. def _get_oldest_events_in_room_txn(self, txn, room_id):
  262. return self.db.simple_select_onecol_txn(
  263. txn,
  264. table="event_backward_extremities",
  265. keyvalues={"room_id": room_id},
  266. retcol="event_id",
  267. )
  268. def get_prev_events_for_room(self, room_id: str):
  269. """
  270. Gets a subset of the current forward extremities in the given room.
  271. Limits the result to 10 extremities, so that we can avoid creating
  272. events which refer to hundreds of prev_events.
  273. Args:
  274. room_id (str): room_id
  275. Returns:
  276. Deferred[List[str]]: the event ids of the forward extremites
  277. """
  278. return self.db.runInteraction(
  279. "get_prev_events_for_room", self._get_prev_events_for_room_txn, room_id
  280. )
  281. def _get_prev_events_for_room_txn(self, txn, room_id: str):
  282. # we just use the 10 newest events. Older events will become
  283. # prev_events of future events.
  284. sql = """
  285. SELECT e.event_id FROM event_forward_extremities AS f
  286. INNER JOIN events AS e USING (event_id)
  287. WHERE f.room_id = ?
  288. ORDER BY e.depth DESC
  289. LIMIT 10
  290. """
  291. txn.execute(sql, (room_id,))
  292. return [row[0] for row in txn]
  293. def get_rooms_with_many_extremities(self, min_count, limit, room_id_filter):
  294. """Get the top rooms with at least N extremities.
  295. Args:
  296. min_count (int): The minimum number of extremities
  297. limit (int): The maximum number of rooms to return.
  298. room_id_filter (iterable[str]): room_ids to exclude from the results
  299. Returns:
  300. Deferred[list]: At most `limit` room IDs that have at least
  301. `min_count` extremities, sorted by extremity count.
  302. """
  303. def _get_rooms_with_many_extremities_txn(txn):
  304. where_clause = "1=1"
  305. if room_id_filter:
  306. where_clause = "room_id NOT IN (%s)" % (
  307. ",".join("?" for _ in room_id_filter),
  308. )
  309. sql = """
  310. SELECT room_id FROM event_forward_extremities
  311. WHERE %s
  312. GROUP BY room_id
  313. HAVING count(*) > ?
  314. ORDER BY count(*) DESC
  315. LIMIT ?
  316. """ % (
  317. where_clause,
  318. )
  319. query_args = list(itertools.chain(room_id_filter, [min_count, limit]))
  320. txn.execute(sql, query_args)
  321. return [room_id for room_id, in txn]
  322. return self.db.runInteraction(
  323. "get_rooms_with_many_extremities", _get_rooms_with_many_extremities_txn
  324. )
  325. @cached(max_entries=5000, iterable=True)
  326. def get_latest_event_ids_in_room(self, room_id):
  327. return self.db.simple_select_onecol(
  328. table="event_forward_extremities",
  329. keyvalues={"room_id": room_id},
  330. retcol="event_id",
  331. desc="get_latest_event_ids_in_room",
  332. )
  333. def get_min_depth(self, room_id):
  334. """ For hte given room, get the minimum depth we have seen for it.
  335. """
  336. return self.db.runInteraction(
  337. "get_min_depth", self._get_min_depth_interaction, room_id
  338. )
  339. def _get_min_depth_interaction(self, txn, room_id):
  340. min_depth = self.db.simple_select_one_onecol_txn(
  341. txn,
  342. table="room_depth",
  343. keyvalues={"room_id": room_id},
  344. retcol="min_depth",
  345. allow_none=True,
  346. )
  347. return int(min_depth) if min_depth is not None else None
  348. def get_forward_extremeties_for_room(self, room_id, stream_ordering):
  349. """For a given room_id and stream_ordering, return the forward
  350. extremeties of the room at that point in "time".
  351. Throws a StoreError if we have since purged the index for
  352. stream_orderings from that point.
  353. Args:
  354. room_id (str):
  355. stream_ordering (int):
  356. Returns:
  357. deferred, which resolves to a list of event_ids
  358. """
  359. # We want to make the cache more effective, so we clamp to the last
  360. # change before the given ordering.
  361. last_change = self._events_stream_cache.get_max_pos_of_last_change(room_id)
  362. # We don't always have a full stream_to_exterm_id table, e.g. after
  363. # the upgrade that introduced it, so we make sure we never ask for a
  364. # stream_ordering from before a restart
  365. last_change = max(self._stream_order_on_start, last_change)
  366. # provided the last_change is recent enough, we now clamp the requested
  367. # stream_ordering to it.
  368. if last_change > self.stream_ordering_month_ago:
  369. stream_ordering = min(last_change, stream_ordering)
  370. return self._get_forward_extremeties_for_room(room_id, stream_ordering)
  371. @cached(max_entries=5000, num_args=2)
  372. def _get_forward_extremeties_for_room(self, room_id, stream_ordering):
  373. """For a given room_id and stream_ordering, return the forward
  374. extremeties of the room at that point in "time".
  375. Throws a StoreError if we have since purged the index for
  376. stream_orderings from that point.
  377. """
  378. if stream_ordering <= self.stream_ordering_month_ago:
  379. raise StoreError(400, "stream_ordering too old")
  380. sql = """
  381. SELECT event_id FROM stream_ordering_to_exterm
  382. INNER JOIN (
  383. SELECT room_id, MAX(stream_ordering) AS stream_ordering
  384. FROM stream_ordering_to_exterm
  385. WHERE stream_ordering <= ? GROUP BY room_id
  386. ) AS rms USING (room_id, stream_ordering)
  387. WHERE room_id = ?
  388. """
  389. def get_forward_extremeties_for_room_txn(txn):
  390. txn.execute(sql, (stream_ordering, room_id))
  391. return [event_id for event_id, in txn]
  392. return self.db.runInteraction(
  393. "get_forward_extremeties_for_room", get_forward_extremeties_for_room_txn
  394. )
  395. def get_backfill_events(self, room_id, event_list, limit):
  396. """Get a list of Events for a given topic that occurred before (and
  397. including) the events in event_list. Return a list of max size `limit`
  398. Args:
  399. txn
  400. room_id (str)
  401. event_list (list)
  402. limit (int)
  403. """
  404. return (
  405. self.db.runInteraction(
  406. "get_backfill_events",
  407. self._get_backfill_events,
  408. room_id,
  409. event_list,
  410. limit,
  411. )
  412. .addCallback(self.get_events_as_list)
  413. .addCallback(lambda l: sorted(l, key=lambda e: -e.depth))
  414. )
  415. def _get_backfill_events(self, txn, room_id, event_list, limit):
  416. logger.debug("_get_backfill_events: %s, %r, %s", room_id, event_list, limit)
  417. event_results = set()
  418. # We want to make sure that we do a breadth-first, "depth" ordered
  419. # search.
  420. query = (
  421. "SELECT depth, prev_event_id FROM event_edges"
  422. " INNER JOIN events"
  423. " ON prev_event_id = events.event_id"
  424. " WHERE event_edges.event_id = ?"
  425. " AND event_edges.is_state = ?"
  426. " LIMIT ?"
  427. )
  428. queue = PriorityQueue()
  429. for event_id in event_list:
  430. depth = self.db.simple_select_one_onecol_txn(
  431. txn,
  432. table="events",
  433. keyvalues={"event_id": event_id, "room_id": room_id},
  434. retcol="depth",
  435. allow_none=True,
  436. )
  437. if depth:
  438. queue.put((-depth, event_id))
  439. while not queue.empty() and len(event_results) < limit:
  440. try:
  441. _, event_id = queue.get_nowait()
  442. except Empty:
  443. break
  444. if event_id in event_results:
  445. continue
  446. event_results.add(event_id)
  447. txn.execute(query, (event_id, False, limit - len(event_results)))
  448. for row in txn:
  449. if row[1] not in event_results:
  450. queue.put((-row[0], row[1]))
  451. return event_results
  452. @defer.inlineCallbacks
  453. def get_missing_events(self, room_id, earliest_events, latest_events, limit):
  454. ids = yield self.db.runInteraction(
  455. "get_missing_events",
  456. self._get_missing_events,
  457. room_id,
  458. earliest_events,
  459. latest_events,
  460. limit,
  461. )
  462. events = yield self.get_events_as_list(ids)
  463. return events
  464. def _get_missing_events(self, txn, room_id, earliest_events, latest_events, limit):
  465. seen_events = set(earliest_events)
  466. front = set(latest_events) - seen_events
  467. event_results = []
  468. query = (
  469. "SELECT prev_event_id FROM event_edges "
  470. "WHERE room_id = ? AND event_id = ? AND is_state = ? "
  471. "LIMIT ?"
  472. )
  473. while front and len(event_results) < limit:
  474. new_front = set()
  475. for event_id in front:
  476. txn.execute(
  477. query, (room_id, event_id, False, limit - len(event_results))
  478. )
  479. new_results = {t[0] for t in txn} - seen_events
  480. new_front |= new_results
  481. seen_events |= new_results
  482. event_results.extend(new_results)
  483. front = new_front
  484. # we built the list working backwards from latest_events; we now need to
  485. # reverse it so that the events are approximately chronological.
  486. event_results.reverse()
  487. return event_results
  488. @defer.inlineCallbacks
  489. def get_successor_events(self, event_ids):
  490. """Fetch all events that have the given events as a prev event
  491. Args:
  492. event_ids (iterable[str])
  493. Returns:
  494. Deferred[list[str]]
  495. """
  496. rows = yield self.db.simple_select_many_batch(
  497. table="event_edges",
  498. column="prev_event_id",
  499. iterable=event_ids,
  500. retcols=("event_id",),
  501. desc="get_successor_events",
  502. )
  503. return [row["event_id"] for row in rows]
  504. class EventFederationStore(EventFederationWorkerStore):
  505. """ Responsible for storing and serving up the various graphs associated
  506. with an event. Including the main event graph and the auth chains for an
  507. event.
  508. Also has methods for getting the front (latest) and back (oldest) edges
  509. of the event graphs. These are used to generate the parents for new events
  510. and backfilling from another server respectively.
  511. """
  512. EVENT_AUTH_STATE_ONLY = "event_auth_state_only"
  513. def __init__(self, database: Database, db_conn, hs):
  514. super(EventFederationStore, self).__init__(database, db_conn, hs)
  515. self.db.updates.register_background_update_handler(
  516. self.EVENT_AUTH_STATE_ONLY, self._background_delete_non_state_event_auth
  517. )
  518. hs.get_clock().looping_call(
  519. self._delete_old_forward_extrem_cache, 60 * 60 * 1000
  520. )
  521. def _delete_old_forward_extrem_cache(self):
  522. def _delete_old_forward_extrem_cache_txn(txn):
  523. # Delete entries older than a month, while making sure we don't delete
  524. # the only entries for a room.
  525. sql = """
  526. DELETE FROM stream_ordering_to_exterm
  527. WHERE
  528. room_id IN (
  529. SELECT room_id
  530. FROM stream_ordering_to_exterm
  531. WHERE stream_ordering > ?
  532. ) AND stream_ordering < ?
  533. """
  534. txn.execute(
  535. sql, (self.stream_ordering_month_ago, self.stream_ordering_month_ago)
  536. )
  537. return run_as_background_process(
  538. "delete_old_forward_extrem_cache",
  539. self.db.runInteraction,
  540. "_delete_old_forward_extrem_cache",
  541. _delete_old_forward_extrem_cache_txn,
  542. )
  543. def clean_room_for_join(self, room_id):
  544. return self.db.runInteraction(
  545. "clean_room_for_join", self._clean_room_for_join_txn, room_id
  546. )
  547. def _clean_room_for_join_txn(self, txn, room_id):
  548. query = "DELETE FROM event_forward_extremities WHERE room_id = ?"
  549. txn.execute(query, (room_id,))
  550. txn.call_after(self.get_latest_event_ids_in_room.invalidate, (room_id,))
  551. @defer.inlineCallbacks
  552. def _background_delete_non_state_event_auth(self, progress, batch_size):
  553. def delete_event_auth(txn):
  554. target_min_stream_id = progress.get("target_min_stream_id_inclusive")
  555. max_stream_id = progress.get("max_stream_id_exclusive")
  556. if not target_min_stream_id or not max_stream_id:
  557. txn.execute("SELECT COALESCE(MIN(stream_ordering), 0) FROM events")
  558. rows = txn.fetchall()
  559. target_min_stream_id = rows[0][0]
  560. txn.execute("SELECT COALESCE(MAX(stream_ordering), 0) FROM events")
  561. rows = txn.fetchall()
  562. max_stream_id = rows[0][0]
  563. min_stream_id = max_stream_id - batch_size
  564. sql = """
  565. DELETE FROM event_auth
  566. WHERE event_id IN (
  567. SELECT event_id FROM events
  568. LEFT JOIN state_events USING (room_id, event_id)
  569. WHERE ? <= stream_ordering AND stream_ordering < ?
  570. AND state_key IS null
  571. )
  572. """
  573. txn.execute(sql, (min_stream_id, max_stream_id))
  574. new_progress = {
  575. "target_min_stream_id_inclusive": target_min_stream_id,
  576. "max_stream_id_exclusive": min_stream_id,
  577. }
  578. self.db.updates._background_update_progress_txn(
  579. txn, self.EVENT_AUTH_STATE_ONLY, new_progress
  580. )
  581. return min_stream_id >= target_min_stream_id
  582. result = yield self.db.runInteraction(
  583. self.EVENT_AUTH_STATE_ONLY, delete_event_auth
  584. )
  585. if not result:
  586. yield self.db.updates._end_background_update(self.EVENT_AUTH_STATE_ONLY)
  587. return batch_size