-
Notifications
You must be signed in to change notification settings - Fork 414
Cache _get_e2e_cross_signing_signatures_for_devices
#18899
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
ce8b074
325632f
6ffd8b9
1ea7cf0
21c2c04
b4ff74c
3ec9f16
a096967
c6534e6
90387cc
e7ca69d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| Add an in-memory cache to `_get_e2e_cross_signing_signatures_for_devices` to reduce DB load. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -20,6 +20,7 @@ | |
| # | ||
| # | ||
| import abc | ||
| import json | ||
| from typing import ( | ||
| TYPE_CHECKING, | ||
| Any, | ||
|
|
@@ -354,15 +355,17 @@ async def get_e2e_device_keys_and_signatures( | |
| ) | ||
|
|
||
| for batch in batch_iter(signature_query, 50): | ||
| cross_sigs_result = await self.db_pool.runInteraction( | ||
| "get_e2e_cross_signing_signatures_for_devices", | ||
| self._get_e2e_cross_signing_signatures_for_devices_txn, | ||
| batch, | ||
| cross_sigs_result = ( | ||
| await self._get_e2e_cross_signing_signatures_for_devices(batch) | ||
| ) | ||
|
|
||
| # add each cross-signing signature to the correct device in the result dict. | ||
| for user_id, key_id, device_id, signature in cross_sigs_result: | ||
| for ( | ||
| user_id, | ||
| device_id, | ||
| ), signature_list in cross_sigs_result.items(): | ||
| target_device_result = result[user_id][device_id] | ||
|
|
||
| # We've only looked up cross-signatures for non-deleted devices with key | ||
| # data. | ||
| assert target_device_result is not None | ||
|
|
@@ -373,7 +376,9 @@ async def get_e2e_device_keys_and_signatures( | |
| signing_user_signatures = target_device_signatures.setdefault( | ||
| user_id, {} | ||
| ) | ||
| signing_user_signatures[key_id] = signature | ||
|
|
||
| for key_id, signature in signature_list: | ||
| signing_user_signatures[key_id] = signature | ||
|
|
||
| log_kv(result) | ||
| return result | ||
|
|
@@ -479,41 +484,83 @@ def get_e2e_device_keys_txn( | |
|
|
||
| return result | ||
|
|
||
| def _get_e2e_cross_signing_signatures_for_devices_txn( | ||
| self, txn: LoggingTransaction, device_query: Iterable[Tuple[str, str]] | ||
| ) -> List[Tuple[str, str, str, str]]: | ||
| """Get cross-signing signatures for a given list of devices | ||
| @cached() | ||
| def _get_e2e_cross_signing_signatures_for_device( | ||
| self, | ||
| user_id_and_device_id: Tuple[str, str], | ||
| ) -> Sequence[Tuple[str, str]]: | ||
| """ | ||
| The single-item version of `_get_e2e_cross_signing_signatures_for_devices`. | ||
| See @cachedList for why a separate method is needed. | ||
| """ | ||
| raise NotImplementedError() | ||
|
|
||
| @cachedList( | ||
| cached_method_name="_get_e2e_cross_signing_signatures_for_device", | ||
| list_name="device_query", | ||
| ) | ||
| async def _get_e2e_cross_signing_signatures_for_devices( | ||
| self, device_query: Iterable[Tuple[str, str]] | ||
| ) -> Mapping[Tuple[str, str], Sequence[Tuple[str, str]]]: | ||
| """Get cross-signing signatures for a given list of user IDs and devices. | ||
|
|
||
| Args: | ||
| An iterable containing tuples of (user ID, device ID). | ||
|
|
||
| Returns: | ||
| A mapping of results. The keys are the original (user_id, device_id) | ||
| tuple, while the value is the matching list of tuples of | ||
| (key_id, signature). The value will be an empty list if no | ||
| signatures exist for the device. | ||
|
|
||
| Returns signatures made by the owners of the devices. | ||
| Given this method is annotated with `@cachedList`, the return dict's | ||
| keys match the tuples within `device_query`, so that cache entries can | ||
| be computed from the corresponding values. | ||
|
|
||
| Returns: a list of results; each entry in the list is a tuple of | ||
| (user_id, key_id, target_device_id, signature). | ||
| As results are cached, the return type is immutable. | ||
| """ | ||
| signature_query_clauses = [] | ||
| signature_query_params = [] | ||
|
|
||
| for user_id, device_id in device_query: | ||
| signature_query_clauses.append( | ||
| "target_user_id = ? AND target_device_id = ? AND user_id = ?" | ||
| def _get_e2e_cross_signing_signatures_for_devices_txn( | ||
| txn: LoggingTransaction, device_query: Iterable[Tuple[str, str]] | ||
| ) -> Mapping[Tuple[str, str], Sequence[Tuple[str, str]]]: | ||
| where_clause_sql, where_clause_params = make_tuple_in_list_sql_clause( | ||
| self.database_engine, | ||
| columns=("target_user_id", "target_device_id", "user_id"), | ||
| iterable=[ | ||
| (user_id, device_id, user_id) for user_id, device_id in device_query | ||
| ], | ||
| ) | ||
| signature_query_params.extend([user_id, device_id, user_id]) | ||
|
|
||
| signature_sql = """ | ||
| SELECT user_id, key_id, target_device_id, signature | ||
| FROM e2e_cross_signing_signatures WHERE %s | ||
| """ % (" OR ".join("(" + q + ")" for q in signature_query_clauses)) | ||
|
|
||
| txn.execute(signature_sql, signature_query_params) | ||
| return cast( | ||
| List[ | ||
| Tuple[ | ||
| str, | ||
| str, | ||
| str, | ||
| str, | ||
| ] | ||
| ], | ||
| txn.fetchall(), | ||
|
|
||
| signature_sql = f""" | ||
| SELECT user_id, key_id, target_device_id, signature | ||
| FROM e2e_cross_signing_signatures WHERE {where_clause_sql} | ||
| """ | ||
|
|
||
| txn.execute(signature_sql, where_clause_params) | ||
|
|
||
| devices_and_signatures: Dict[Tuple[str, str], List[Tuple[str, str]]] = {} | ||
|
|
||
| # `@cachedList` requires we return one key for every item in `device_query`. | ||
| # Pre-populate `devices_and_signatures` with each key so that none are missing. | ||
| # | ||
| # If any are missing, they will be cached as `None`, which is not | ||
| # what callers expected. | ||
| for user_id, device_id in device_query: | ||
| devices_and_signatures.setdefault((user_id, device_id), []) | ||
|
|
||
| # Populate the return dictionary with each found key_id and signature. | ||
| for user_id, key_id, target_device_id, signature in txn.fetchall(): | ||
| signature_tuple = (key_id, signature) | ||
| devices_and_signatures[(user_id, target_device_id)].append( | ||
| signature_tuple | ||
| ) | ||
|
|
||
| return devices_and_signatures | ||
|
|
||
| return await self.db_pool.runInteraction( | ||
| "_get_e2e_cross_signing_signatures_for_devices_txn", | ||
| _get_e2e_cross_signing_signatures_for_devices_txn, | ||
| device_query, | ||
| ) | ||
|
|
||
| async def get_e2e_one_time_keys( | ||
|
|
@@ -1772,26 +1819,71 @@ async def store_e2e_cross_signing_signatures( | |
| user_id: the user who made the signatures | ||
| signatures: signatures to add | ||
| """ | ||
| await self.db_pool.simple_insert_many( | ||
| "e2e_cross_signing_signatures", | ||
| keys=( | ||
| "user_id", | ||
| "key_id", | ||
| "target_user_id", | ||
| "target_device_id", | ||
| "signature", | ||
| ), | ||
| values=[ | ||
| ( | ||
| user_id, | ||
| item.signing_key_id, | ||
| item.target_user_id, | ||
| item.target_device_id, | ||
| item.signature, | ||
| ) | ||
|
|
||
| def _store_e2e_cross_signing_signatures( | ||
| txn: LoggingTransaction, | ||
| signatures: "Iterable[SignatureListItem]", | ||
| ) -> None: | ||
| self.db_pool.simple_insert_many_txn( | ||
| txn, | ||
| "e2e_cross_signing_signatures", | ||
| keys=( | ||
| "user_id", | ||
| "key_id", | ||
| "target_user_id", | ||
| "target_device_id", | ||
| "signature", | ||
| ), | ||
| values=[ | ||
| ( | ||
| user_id, | ||
| item.signing_key_id, | ||
| item.target_user_id, | ||
| item.target_device_id, | ||
| item.signature, | ||
| ) | ||
| for item in signatures | ||
| ], | ||
| ) | ||
|
|
||
| to_invalidate = [ | ||
| # Each entry is a tuple of arguments to | ||
| # `_get_e2e_cross_signing_signatures_for_device`, which | ||
| # itself takes a tuple. Hence the double-tuple. | ||
| ((user_id, item.target_device_id),) | ||
| for item in signatures | ||
| ], | ||
| desc="add_e2e_signing_key", | ||
| ] | ||
|
|
||
| if to_invalidate: | ||
| # Invalidate the local cache of this worker. | ||
| for cache_key in to_invalidate: | ||
| txn.call_after( | ||
| self._get_e2e_cross_signing_signatures_for_device.invalidate, | ||
| cache_key, | ||
| ) | ||
|
|
||
| # Stream cache invalidate keys over replication. | ||
| # | ||
| # We can only send a primitive per function argument across | ||
| # replication. | ||
| # | ||
| # Encode the array of strings as a JSON string, and we'll unpack | ||
| # it on the other side. | ||
| to_send = [ | ||
| (json.dumps([user_id, item.target_device_id]),) | ||
| for item in signatures | ||
|
Comment on lines
+1873
to
+1874
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. not possible to json.dumps the entries of
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can; it looks like this: to_send = [
(json.dumps(item[0]),)
for item in to_invalidate
]which seemed less clear as to what we're actually sending than what's there now. |
||
| ] | ||
|
|
||
| self._send_invalidation_to_replication_bulk( | ||
| txn, | ||
| cache_name=self._get_e2e_cross_signing_signatures_for_device.__name__, | ||
| key_tuples=to_send, | ||
| ) | ||
|
|
||
| await self.db_pool.runInteraction( | ||
| "add_e2e_signing_key", | ||
| _store_e2e_cross_signing_signatures, | ||
| signatures, | ||
| ) | ||
|
|
||
|
|
||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.