|
25 | 25 | trace,
|
26 | 26 | whitelisted_homeserver,
|
27 | 27 | )
|
28 |
| -from synapse.metrics.background_process_metrics import run_as_background_process |
| 28 | +from synapse.metrics.background_process_metrics import wrap_as_background_process |
29 | 29 | from synapse.storage._base import SQLBaseStore, db_to_json, make_in_list_sql_clause
|
30 | 30 | from synapse.storage.database import (
|
31 | 31 | DatabasePool,
|
|
48 | 48 |
|
49 | 49 |
|
50 | 50 | class DeviceWorkerStore(SQLBaseStore):
|
| 51 | + def __init__(self, database: DatabasePool, db_conn, hs): |
| 52 | + super().__init__(database, db_conn, hs) |
| 53 | + |
| 54 | + if hs.config.run_background_tasks: |
| 55 | + self._clock.looping_call( |
| 56 | + self._prune_old_outbound_device_pokes, 60 * 60 * 1000 |
| 57 | + ) |
| 58 | + |
51 | 59 | async def get_device(self, user_id: str, device_id: str) -> Dict[str, Any]:
|
52 | 60 | """Retrieve a device. Only returns devices that are not marked as
|
53 | 61 | hidden.
|
@@ -772,6 +780,98 @@ async def remove_dehydrated_device(self, user_id: str, device_id: str) -> bool:
|
772 | 780 | )
|
773 | 781 | return count >= 1
|
774 | 782 |
|
| 783 | + @wrap_as_background_process("prune_old_outbound_device_pokes") |
| 784 | + async def _prune_old_outbound_device_pokes( |
| 785 | + self, prune_age: int = 24 * 60 * 60 * 1000 |
| 786 | + ) -> None: |
| 787 | + """Delete old entries out of the device_lists_outbound_pokes to ensure |
| 788 | + that we don't fill up due to dead servers. |
| 789 | +
|
| 790 | + Normally, we try to send device updates as a delta since a previous known point: |
| 791 | + this is done by setting the prev_id in the m.device_list_update EDU. However, |
| 792 | + for that to work, we have to have a complete record of each change to |
| 793 | + each device, which can add up to quite a lot of data. |
| 794 | +
|
| 795 | + An alternative mechanism is that, if the remote server sees that it has missed |
| 796 | + an entry in the stream_id sequence for a given user, it will request a full |
| 797 | + list of that user's devices. Hence, we can reduce the amount of data we have to |
| 798 | + store (and transmit in some future transaction), by clearing almost everything |
| 799 | + for a given destination out of the database, and having the remote server |
| 800 | + resync. |
| 801 | +
|
| 802 | + All we need to do is make sure we keep at least one row for each |
| 803 | + (user, destination) pair, to remind us to send a m.device_list_update EDU for |
| 804 | + that user when the destination comes back. It doesn't matter which device |
| 805 | + we keep. |
| 806 | + """ |
| 807 | + yesterday = self._clock.time_msec() - prune_age |
| 808 | + |
| 809 | + def _prune_txn(txn): |
| 810 | + # look for (user, destination) pairs which have an update older than |
| 811 | + # the cutoff. |
| 812 | + # |
| 813 | + # For each pair, we also need to know the most recent stream_id, and |
| 814 | + # an arbitrary device_id at that stream_id. |
| 815 | + select_sql = """ |
| 816 | + SELECT |
| 817 | + dlop1.destination, |
| 818 | + dlop1.user_id, |
| 819 | + MAX(dlop1.stream_id) AS stream_id, |
| 820 | + (SELECT MIN(dlop2.device_id) AS device_id FROM |
| 821 | + device_lists_outbound_pokes dlop2 |
| 822 | + WHERE dlop2.destination = dlop1.destination AND |
| 823 | + dlop2.user_id=dlop1.user_id AND |
| 824 | + dlop2.stream_id=MAX(dlop1.stream_id) |
| 825 | + ) |
| 826 | + FROM device_lists_outbound_pokes dlop1 |
| 827 | + GROUP BY destination, user_id |
| 828 | + HAVING min(ts) < ? AND count(*) > 1 |
| 829 | + """ |
| 830 | + |
| 831 | + txn.execute(select_sql, (yesterday,)) |
| 832 | + rows = txn.fetchall() |
| 833 | + |
| 834 | + if not rows: |
| 835 | + return |
| 836 | + |
| 837 | + logger.info( |
| 838 | + "Pruning old outbound device list updates for %i users/destinations: %s", |
| 839 | + len(rows), |
| 840 | + shortstr((row[0], row[1]) for row in rows), |
| 841 | + ) |
| 842 | + |
| 843 | + # we want to keep the update with the highest stream_id for each user. |
| 844 | + # |
| 845 | + # there might be more than one update (with different device_ids) with the |
| 846 | + # same stream_id, so we also delete all but one rows with the max stream id. |
| 847 | + delete_sql = """ |
| 848 | + DELETE FROM device_lists_outbound_pokes |
| 849 | + WHERE destination = ? AND user_id = ? AND ( |
| 850 | + stream_id < ? OR |
| 851 | + (stream_id = ? AND device_id != ?) |
| 852 | + ) |
| 853 | + """ |
| 854 | + count = 0 |
| 855 | + for (destination, user_id, stream_id, device_id) in rows: |
| 856 | + txn.execute( |
| 857 | + delete_sql, (destination, user_id, stream_id, stream_id, device_id) |
| 858 | + ) |
| 859 | + count += txn.rowcount |
| 860 | + |
| 861 | + # Since we've deleted unsent deltas, we need to remove the entry |
| 862 | + # of last successful sent so that the prev_ids are correctly set. |
| 863 | + sql = """ |
| 864 | + DELETE FROM device_lists_outbound_last_success |
| 865 | + WHERE destination = ? AND user_id = ? |
| 866 | + """ |
| 867 | + txn.executemany(sql, ((row[0], row[1]) for row in rows)) |
| 868 | + |
| 869 | + logger.info("Pruned %d device list outbound pokes", count) |
| 870 | + |
| 871 | + await self.db_pool.runInteraction( |
| 872 | + "_prune_old_outbound_device_pokes", _prune_txn, |
| 873 | + ) |
| 874 | + |
775 | 875 |
|
776 | 876 | class DeviceBackgroundUpdateStore(SQLBaseStore):
|
777 | 877 | def __init__(self, database: DatabasePool, db_conn, hs):
|
@@ -908,8 +1008,6 @@ def __init__(self, database: DatabasePool, db_conn, hs):
|
908 | 1008 | name="device_id_exists", keylen=2, max_entries=10000
|
909 | 1009 | )
|
910 | 1010 |
|
911 |
| - self._clock.looping_call(self._prune_old_outbound_device_pokes, 60 * 60 * 1000) |
912 |
| - |
913 | 1011 | async def store_device(
|
914 | 1012 | self, user_id: str, device_id: str, initial_device_display_name: Optional[str]
|
915 | 1013 | ) -> bool:
|
@@ -1267,95 +1365,3 @@ def _add_device_outbound_poke_to_stream_txn(
|
1267 | 1365 | for device_id in device_ids
|
1268 | 1366 | ],
|
1269 | 1367 | )
|
1270 |
| - |
1271 |
| - def _prune_old_outbound_device_pokes(self, prune_age: int = 24 * 60 * 60 * 1000): |
1272 |
| - """Delete old entries out of the device_lists_outbound_pokes to ensure |
1273 |
| - that we don't fill up due to dead servers. |
1274 |
| -
|
1275 |
| - Normally, we try to send device updates as a delta since a previous known point: |
1276 |
| - this is done by setting the prev_id in the m.device_list_update EDU. However, |
1277 |
| - for that to work, we have to have a complete record of each change to |
1278 |
| - each device, which can add up to quite a lot of data. |
1279 |
| -
|
1280 |
| - An alternative mechanism is that, if the remote server sees that it has missed |
1281 |
| - an entry in the stream_id sequence for a given user, it will request a full |
1282 |
| - list of that user's devices. Hence, we can reduce the amount of data we have to |
1283 |
| - store (and transmit in some future transaction), by clearing almost everything |
1284 |
| - for a given destination out of the database, and having the remote server |
1285 |
| - resync. |
1286 |
| -
|
1287 |
| - All we need to do is make sure we keep at least one row for each |
1288 |
| - (user, destination) pair, to remind us to send a m.device_list_update EDU for |
1289 |
| - that user when the destination comes back. It doesn't matter which device |
1290 |
| - we keep. |
1291 |
| - """ |
1292 |
| - yesterday = self._clock.time_msec() - prune_age |
1293 |
| - |
1294 |
| - def _prune_txn(txn): |
1295 |
| - # look for (user, destination) pairs which have an update older than |
1296 |
| - # the cutoff. |
1297 |
| - # |
1298 |
| - # For each pair, we also need to know the most recent stream_id, and |
1299 |
| - # an arbitrary device_id at that stream_id. |
1300 |
| - select_sql = """ |
1301 |
| - SELECT |
1302 |
| - dlop1.destination, |
1303 |
| - dlop1.user_id, |
1304 |
| - MAX(dlop1.stream_id) AS stream_id, |
1305 |
| - (SELECT MIN(dlop2.device_id) AS device_id FROM |
1306 |
| - device_lists_outbound_pokes dlop2 |
1307 |
| - WHERE dlop2.destination = dlop1.destination AND |
1308 |
| - dlop2.user_id=dlop1.user_id AND |
1309 |
| - dlop2.stream_id=MAX(dlop1.stream_id) |
1310 |
| - ) |
1311 |
| - FROM device_lists_outbound_pokes dlop1 |
1312 |
| - GROUP BY destination, user_id |
1313 |
| - HAVING min(ts) < ? AND count(*) > 1 |
1314 |
| - """ |
1315 |
| - |
1316 |
| - txn.execute(select_sql, (yesterday,)) |
1317 |
| - rows = txn.fetchall() |
1318 |
| - |
1319 |
| - if not rows: |
1320 |
| - return |
1321 |
| - |
1322 |
| - logger.info( |
1323 |
| - "Pruning old outbound device list updates for %i users/destinations: %s", |
1324 |
| - len(rows), |
1325 |
| - shortstr((row[0], row[1]) for row in rows), |
1326 |
| - ) |
1327 |
| - |
1328 |
| - # we want to keep the update with the highest stream_id for each user. |
1329 |
| - # |
1330 |
| - # there might be more than one update (with different device_ids) with the |
1331 |
| - # same stream_id, so we also delete all but one rows with the max stream id. |
1332 |
| - delete_sql = """ |
1333 |
| - DELETE FROM device_lists_outbound_pokes |
1334 |
| - WHERE destination = ? AND user_id = ? AND ( |
1335 |
| - stream_id < ? OR |
1336 |
| - (stream_id = ? AND device_id != ?) |
1337 |
| - ) |
1338 |
| - """ |
1339 |
| - count = 0 |
1340 |
| - for (destination, user_id, stream_id, device_id) in rows: |
1341 |
| - txn.execute( |
1342 |
| - delete_sql, (destination, user_id, stream_id, stream_id, device_id) |
1343 |
| - ) |
1344 |
| - count += txn.rowcount |
1345 |
| - |
1346 |
| - # Since we've deleted unsent deltas, we need to remove the entry |
1347 |
| - # of last successful sent so that the prev_ids are correctly set. |
1348 |
| - sql = """ |
1349 |
| - DELETE FROM device_lists_outbound_last_success |
1350 |
| - WHERE destination = ? AND user_id = ? |
1351 |
| - """ |
1352 |
| - txn.executemany(sql, ((row[0], row[1]) for row in rows)) |
1353 |
| - |
1354 |
| - logger.info("Pruned %d device list outbound pokes", count) |
1355 |
| - |
1356 |
| - return run_as_background_process( |
1357 |
| - "prune_old_outbound_device_pokes", |
1358 |
| - self.db_pool.runInteraction, |
1359 |
| - "_prune_old_outbound_device_pokes", |
1360 |
| - _prune_txn, |
1361 |
| - ) |
|
0 commit comments