From 7f43561e185e141b9287a9a1a6467ea054b70040 Mon Sep 17 00:00:00 2001 From: Brendan Abolivier Date: Mon, 3 Oct 2022 16:55:59 +0100 Subject: [PATCH 1/9] Add documentation for caching in a module --- docs/modules/writing_a_module.md | 51 +++++++++++++++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-) diff --git a/docs/modules/writing_a_module.md b/docs/modules/writing_a_module.md index e6303b739e1a..535d958675d6 100644 --- a/docs/modules/writing_a_module.md +++ b/docs/modules/writing_a_module.md @@ -82,4 +82,53 @@ the callback name as the argument name and the function as its value. A `register_[...]_callbacks` method exists for each category. Callbacks for each category can be found on their respective page of the -[Synapse documentation website](https://matrix-org.github.io/synapse). \ No newline at end of file +[Synapse documentation website](https://matrix-org.github.io/synapse). + +## Caching + +Modules can leverage Synapse's caching tools to manage their own cached functions. This +canbe helpful for modules that need to repeatedly request the same data from the database +or a remote service. + +Functions that need to be wrapped with a cache need to be decorated with a `@cached()` +decorator (which can be imported from `synapse.module_api`) and registered with the +[`ModuleApi.register_cached_function`](https://github.com/matrix-org/synapse/blob/1cc2ca81badb9c5161d219dfc9a273a338adedd2/synapse/module_api/__init__.py#L839-L850) +API when initialising the module. If the module needs to invalidate an entry in a cache, +it needs to use the [`ModuleApi.invalidate_cache`](https://github.com/matrix-org/synapse/blob/1cc2ca81badb9c5161d219dfc9a273a338adedd2/synapse/module_api/__init__.py#L855-L872) +API, with the function to invalidate the cache of and the key(s) of the entry to +invalidate. + +Below is an example of a simple module using a cached function: + +```python +from typing import Any +from synapse.module_api import cached, ModuleApi + +class MyModule: + def __init__(self, config: Any, api: ModuleApi): + self.api = api + + # Register the cached function so Synapse knows how to correctly invalidate + # entries for it. + self.api.register_cached_function(self.get_user_from_id) + + @cached() + async def get_user_from_id(self, user_id: str) -> str: + """A function with a cache.""" + return await self.get_user(user_id) + + async def do_something_with_users(self) -> None: + """Calls the cached function and then invalidates an entry in its cache.""" + + user_id = "@alice:example.com" + + # Get the user. Since get_user_from_id is wrapped with a cache, the return value + # for this user_id will be cached. + user = await self.get_user_from_id(user_id) + + # Do something with user... + + # Let's say something has changed with our user, and the entry we have for them in + # the cache is out of date, so we want to invalidate it. + await self.api.invalidate_cache(self.get_user_from_id, (user_id,)) +``` \ No newline at end of file From eefa2833d5b33822cca415693eb8a981f9d4fbd5 Mon Sep 17 00:00:00 2001 From: Brendan Abolivier Date: Mon, 3 Oct 2022 16:57:45 +0100 Subject: [PATCH 2/9] Changelog --- changelog.d/14026.doc | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/14026.doc diff --git a/changelog.d/14026.doc b/changelog.d/14026.doc new file mode 100644 index 000000000000..28fc5568ea0b --- /dev/null +++ b/changelog.d/14026.doc @@ -0,0 +1 @@ +Document how to use caches in a module. From 7e82c720ba21f96c993296395ebfc2feeb48b5ea Mon Sep 17 00:00:00 2001 From: Brendan Abolivier Date: Mon, 3 Oct 2022 17:11:23 +0100 Subject: [PATCH 3/9] Formatting --- docs/modules/writing_a_module.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/modules/writing_a_module.md b/docs/modules/writing_a_module.md index 535d958675d6..b3e282e3c51a 100644 --- a/docs/modules/writing_a_module.md +++ b/docs/modules/writing_a_module.md @@ -126,7 +126,7 @@ class MyModule: # for this user_id will be cached. user = await self.get_user_from_id(user_id) - # Do something with user... + # Do something with `user`... # Let's say something has changed with our user, and the entry we have for them in # the cache is out of date, so we want to invalidate it. From 6a20635b8a18054042603432c70a50822d631592 Mon Sep 17 00:00:00 2001 From: Brendan Abolivier Date: Mon, 3 Oct 2022 17:13:39 +0100 Subject: [PATCH 4/9] Wrap lines at a length that mdbook is happier with --- docs/modules/writing_a_module.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/modules/writing_a_module.md b/docs/modules/writing_a_module.md index b3e282e3c51a..52232c56bb5a 100644 --- a/docs/modules/writing_a_module.md +++ b/docs/modules/writing_a_module.md @@ -122,13 +122,13 @@ class MyModule: user_id = "@alice:example.com" - # Get the user. Since get_user_from_id is wrapped with a cache, the return value - # for this user_id will be cached. + # Get the user. Since get_user_from_id is wrapped with a cache, the return + # value for this user_id will be cached. user = await self.get_user_from_id(user_id) # Do something with `user`... - # Let's say something has changed with our user, and the entry we have for them in - # the cache is out of date, so we want to invalidate it. + # Let's say something has changed with our user, and the entry we have for + # them in the cache is out of date, so we want to invalidate it. await self.api.invalidate_cache(self.get_user_from_id, (user_id,)) ``` \ No newline at end of file From 9c55e7cc797957bafff6c51f653ec85d3b89bf33 Mon Sep 17 00:00:00 2001 From: David Robertson Date: Thu, 16 Feb 2023 15:42:31 +0000 Subject: [PATCH 5/9] Typo fix Co-authored-by: Erik Johnston --- docs/modules/writing_a_module.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/modules/writing_a_module.md b/docs/modules/writing_a_module.md index 52232c56bb5a..1492708c7118 100644 --- a/docs/modules/writing_a_module.md +++ b/docs/modules/writing_a_module.md @@ -87,7 +87,7 @@ Callbacks for each category can be found on their respective page of the ## Caching Modules can leverage Synapse's caching tools to manage their own cached functions. This -canbe helpful for modules that need to repeatedly request the same data from the database +can be helpful for modules that need to repeatedly request the same data from the database or a remote service. Functions that need to be wrapped with a cache need to be decorated with a `@cached()` From 43e5f232d2a4dfc264f47f73f4f06c3675da1505 Mon Sep 17 00:00:00 2001 From: David Robertson Date: Thu, 16 Feb 2023 15:56:21 +0000 Subject: [PATCH 6/9] Link to recent version of the API In the longer term I'd like to see us generate markdown with Sphinx. --- docs/modules/writing_a_module.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/modules/writing_a_module.md b/docs/modules/writing_a_module.md index 1492708c7118..494cd6edec25 100644 --- a/docs/modules/writing_a_module.md +++ b/docs/modules/writing_a_module.md @@ -92,9 +92,9 @@ or a remote service. Functions that need to be wrapped with a cache need to be decorated with a `@cached()` decorator (which can be imported from `synapse.module_api`) and registered with the -[`ModuleApi.register_cached_function`](https://github.com/matrix-org/synapse/blob/1cc2ca81badb9c5161d219dfc9a273a338adedd2/synapse/module_api/__init__.py#L839-L850) +[`ModuleApi.register_cached_function`](https://github.com/matrix-org/synapse/blob/release-v1.77/synapse/module_api/__init__.py#L888) API when initialising the module. If the module needs to invalidate an entry in a cache, -it needs to use the [`ModuleApi.invalidate_cache`](https://github.com/matrix-org/synapse/blob/1cc2ca81badb9c5161d219dfc9a273a338adedd2/synapse/module_api/__init__.py#L855-L872) +it needs to use the [`ModuleApi.invalidate_cache`](https://github.com/matrix-org/synapse/blob/release-v1.77/synapse/module_api/__init__.py#L904) API, with the function to invalidate the cache of and the key(s) of the entry to invalidate. From 7dc8ebf7abdea10e4d8b1900a804002038484ad9 Mon Sep 17 00:00:00 2001 From: David Robertson Date: Thu, 16 Feb 2023 15:56:42 +0000 Subject: [PATCH 7/9] Refer to public `cached` decorator --- docs/modules/writing_a_module.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/modules/writing_a_module.md b/docs/modules/writing_a_module.md index 494cd6edec25..7a8f2e02285a 100644 --- a/docs/modules/writing_a_module.md +++ b/docs/modules/writing_a_module.md @@ -131,4 +131,6 @@ class MyModule: # Let's say something has changed with our user, and the entry we have for # them in the cache is out of date, so we want to invalidate it. await self.api.invalidate_cache(self.get_user_from_id, (user_id,)) -``` \ No newline at end of file +``` + +See the [`cached` docstring](https://github.com/matrix-org/synapse/blob/release-v1.77/synapse/module_api/__init__.py#L190) for more details. \ No newline at end of file From 05f66c8d4f14f6f2f95e897b35b3d0cdf0dd884a Mon Sep 17 00:00:00 2001 From: David Robertson Date: Thu, 16 Feb 2023 15:56:51 +0000 Subject: [PATCH 8/9] Mark caching as being added in 1.74 Some of the underlying infrastructure was added in 1.69, but the public-facing `cached` decorator was only added in 1.74. It is the latter that I think we should be advertising. --- docs/modules/writing_a_module.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/modules/writing_a_module.md b/docs/modules/writing_a_module.md index 7a8f2e02285a..e5e6f41bf8d9 100644 --- a/docs/modules/writing_a_module.md +++ b/docs/modules/writing_a_module.md @@ -86,6 +86,8 @@ Callbacks for each category can be found on their respective page of the ## Caching +_Added in Synapse 1.74.0._ + Modules can leverage Synapse's caching tools to manage their own cached functions. This can be helpful for modules that need to repeatedly request the same data from the database or a remote service. From 57f9f782fa5ac13d06fef1c0f813e3dc43934092 Mon Sep 17 00:00:00 2001 From: David Robertson Date: Tue, 28 Feb 2023 13:36:21 +0000 Subject: [PATCH 9/9] Update docs/modules/writing_a_module.md Co-authored-by: Patrick Cloke --- docs/modules/writing_a_module.md | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/docs/modules/writing_a_module.md b/docs/modules/writing_a_module.md index 0def198aa0fe..b99f64b9d8e8 100644 --- a/docs/modules/writing_a_module.md +++ b/docs/modules/writing_a_module.md @@ -115,24 +115,27 @@ class MyModule: self.api.register_cached_function(self.get_user_from_id) @cached() - async def get_user_from_id(self, user_id: str) -> str: + async def get_department_for_user(self, user_id: str) -> str: """A function with a cache.""" - return await self.get_user(user_id) + # Request a department from an external service. + return await self.http_client.get_json( + "https://int.example.com/users", {"user_id": user_id) + )["department"] async def do_something_with_users(self) -> None: """Calls the cached function and then invalidates an entry in its cache.""" user_id = "@alice:example.com" - # Get the user. Since get_user_from_id is wrapped with a cache, the return - # value for this user_id will be cached. - user = await self.get_user_from_id(user_id) + # Get the user. Since get_department_for_user is wrapped with a cache, + # the return value for this user_id will be cached. + department = await self.get_department_for_user(user_id) - # Do something with `user`... + # Do something with `department`... # Let's say something has changed with our user, and the entry we have for # them in the cache is out of date, so we want to invalidate it. - await self.api.invalidate_cache(self.get_user_from_id, (user_id,)) + await self.api.invalidate_cache(self.get_department_for_user, (user_id,)) ``` See the [`cached` docstring](https://github.com/matrix-org/synapse/blob/release-v1.77/synapse/module_api/__init__.py#L190) for more details.