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

Commit 7cb8b4b

Browse files
authored
Allow configuration of Synapse's cache without using synctl or environment variables (#6391)
1 parent a8580c5 commit 7cb8b4b

32 files changed

+620
-146
lines changed

changelog.d/6391.feature

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Synapse's cache factor can now be configured in `homeserver.yaml` by the `caches.global_factor` setting. Additionally, `caches.per_cache_factors` controls the cache factors for individual caches.

docs/sample_config.yaml

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -603,6 +603,45 @@ acme:
603603

604604

605605

606+
## Caching ##
607+
608+
# Caching can be configured through the following options.
609+
#
610+
# A cache 'factor' is a multiplier that can be applied to each of
611+
# Synapse's caches in order to increase or decrease the maximum
612+
# number of entries that can be stored.
613+
614+
# The number of events to cache in memory. Not affected by
615+
# caches.global_factor.
616+
#
617+
#event_cache_size: 10K
618+
619+
caches:
620+
# Controls the global cache factor, which is the default cache factor
621+
# for all caches if a specific factor for that cache is not otherwise
622+
# set.
623+
#
624+
# This can also be set by the "SYNAPSE_CACHE_FACTOR" environment
625+
# variable. Setting by environment variable takes priority over
626+
# setting through the config file.
627+
#
628+
# Defaults to 0.5, which will half the size of all caches.
629+
#
630+
#global_factor: 1.0
631+
632+
# A dictionary of cache name to cache factor for that individual
633+
# cache. Overrides the global cache factor for a given cache.
634+
#
635+
# These can also be set through environment variables comprised
636+
# of "SYNAPSE_CACHE_FACTOR_" + the name of the cache in capital
637+
# letters and underscores. Setting by environment variable
638+
# takes priority over setting through the config file.
639+
# Ex. SYNAPSE_CACHE_FACTOR_GET_USERS_WHO_SHARE_ROOM_WITH_USER=2.0
640+
#
641+
per_cache_factors:
642+
#get_users_who_share_room_with_user: 2.0
643+
644+
606645
## Database ##
607646

608647
# The 'database' setting defines the database that synapse uses to store all of
@@ -646,10 +685,6 @@ database:
646685
args:
647686
database: DATADIR/homeserver.db
648687

649-
# Number of events to cache in memory.
650-
#
651-
#event_cache_size: 10K
652-
653688

654689
## Logging ##
655690

synapse/api/auth.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
from synapse.api.room_versions import KNOWN_ROOM_VERSIONS
3838
from synapse.events import EventBase
3939
from synapse.types import StateMap, UserID
40-
from synapse.util.caches import CACHE_SIZE_FACTOR, register_cache
40+
from synapse.util.caches import register_cache
4141
from synapse.util.caches.lrucache import LruCache
4242
from synapse.util.metrics import Measure
4343

@@ -73,7 +73,7 @@ def __init__(self, hs):
7373
self.store = hs.get_datastore()
7474
self.state = hs.get_state_handler()
7575

76-
self.token_cache = LruCache(CACHE_SIZE_FACTOR * 10000)
76+
self.token_cache = LruCache(10000)
7777
register_cache("cache", "token_cache", self.token_cache)
7878

7979
self._auth_blocking = AuthBlocking(self.hs)

synapse/app/homeserver.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,6 @@
6969
from synapse.storage import DataStore
7070
from synapse.storage.engines import IncorrectDatabaseSetup
7171
from synapse.storage.prepare_database import UpgradeDatabaseException
72-
from synapse.util.caches import CACHE_SIZE_FACTOR
7372
from synapse.util.httpresourcetree import create_resource_tree
7473
from synapse.util.manhole import manhole
7574
from synapse.util.module_loader import load_module
@@ -516,8 +515,8 @@ def phone_stats_home(hs, stats, stats_process=_stats_process):
516515

517516
daily_sent_messages = yield hs.get_datastore().count_daily_sent_messages()
518517
stats["daily_sent_messages"] = daily_sent_messages
519-
stats["cache_factor"] = CACHE_SIZE_FACTOR
520-
stats["event_cache_size"] = hs.config.event_cache_size
518+
stats["cache_factor"] = hs.config.caches.global_factor
519+
stats["event_cache_size"] = hs.config.caches.event_cache_size
521520

522521
#
523522
# Performance statistics

synapse/config/cache.py

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
# -*- coding: utf-8 -*-
2+
# Copyright 2019 Matrix.org Foundation C.I.C.
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+
16+
import os
17+
from typing import Callable, Dict
18+
19+
from ._base import Config, ConfigError
20+
21+
# The prefix for all cache factor-related environment variables
22+
_CACHES = {}
23+
_CACHE_PREFIX = "SYNAPSE_CACHE_FACTOR"
24+
_DEFAULT_FACTOR_SIZE = 0.5
25+
_DEFAULT_EVENT_CACHE_SIZE = "10K"
26+
27+
28+
class CacheProperties(object):
29+
def __init__(self):
30+
# The default factor size for all caches
31+
self.default_factor_size = float(
32+
os.environ.get(_CACHE_PREFIX, _DEFAULT_FACTOR_SIZE)
33+
)
34+
self.resize_all_caches_func = None
35+
36+
37+
properties = CacheProperties()
38+
39+
40+
def add_resizable_cache(cache_name: str, cache_resize_callback: Callable):
41+
"""Register a cache that's size can dynamically change
42+
43+
Args:
44+
cache_name: A reference to the cache
45+
cache_resize_callback: A callback function that will be ran whenever
46+
the cache needs to be resized
47+
"""
48+
_CACHES[cache_name.lower()] = cache_resize_callback
49+
50+
# Ensure all loaded caches are sized appropriately
51+
#
52+
# This method should only run once the config has been read,
53+
# as it uses values read from it
54+
if properties.resize_all_caches_func:
55+
properties.resize_all_caches_func()
56+
57+
58+
class CacheConfig(Config):
59+
section = "caches"
60+
_environ = os.environ
61+
62+
@staticmethod
63+
def reset():
64+
"""Resets the caches to their defaults. Used for tests."""
65+
properties.default_factor_size = float(
66+
os.environ.get(_CACHE_PREFIX, _DEFAULT_FACTOR_SIZE)
67+
)
68+
properties.resize_all_caches_func = None
69+
_CACHES.clear()
70+
71+
def generate_config_section(self, **kwargs):
72+
return """\
73+
## Caching ##
74+
75+
# Caching can be configured through the following options.
76+
#
77+
# A cache 'factor' is a multiplier that can be applied to each of
78+
# Synapse's caches in order to increase or decrease the maximum
79+
# number of entries that can be stored.
80+
81+
# The number of events to cache in memory. Not affected by
82+
# caches.global_factor.
83+
#
84+
#event_cache_size: 10K
85+
86+
caches:
87+
# Controls the global cache factor, which is the default cache factor
88+
# for all caches if a specific factor for that cache is not otherwise
89+
# set.
90+
#
91+
# This can also be set by the "SYNAPSE_CACHE_FACTOR" environment
92+
# variable. Setting by environment variable takes priority over
93+
# setting through the config file.
94+
#
95+
# Defaults to 0.5, which will half the size of all caches.
96+
#
97+
#global_factor: 1.0
98+
99+
# A dictionary of cache name to cache factor for that individual
100+
# cache. Overrides the global cache factor for a given cache.
101+
#
102+
# These can also be set through environment variables comprised
103+
# of "SYNAPSE_CACHE_FACTOR_" + the name of the cache in capital
104+
# letters and underscores. Setting by environment variable
105+
# takes priority over setting through the config file.
106+
# Ex. SYNAPSE_CACHE_FACTOR_GET_USERS_WHO_SHARE_ROOM_WITH_USER=2.0
107+
#
108+
per_cache_factors:
109+
#get_users_who_share_room_with_user: 2.0
110+
"""
111+
112+
def read_config(self, config, **kwargs):
113+
self.event_cache_size = self.parse_size(
114+
config.get("event_cache_size", _DEFAULT_EVENT_CACHE_SIZE)
115+
)
116+
self.cache_factors = {} # type: Dict[str, float]
117+
118+
cache_config = config.get("caches") or {}
119+
self.global_factor = cache_config.get(
120+
"global_factor", properties.default_factor_size
121+
)
122+
if not isinstance(self.global_factor, (int, float)):
123+
raise ConfigError("caches.global_factor must be a number.")
124+
125+
# Set the global one so that it's reflected in new caches
126+
properties.default_factor_size = self.global_factor
127+
128+
# Load cache factors from the config
129+
individual_factors = cache_config.get("per_cache_factors") or {}
130+
if not isinstance(individual_factors, dict):
131+
raise ConfigError("caches.per_cache_factors must be a dictionary")
132+
133+
# Override factors from environment if necessary
134+
individual_factors.update(
135+
{
136+
key[len(_CACHE_PREFIX) + 1 :].lower(): float(val)
137+
for key, val in self._environ.items()
138+
if key.startswith(_CACHE_PREFIX + "_")
139+
}
140+
)
141+
142+
for cache, factor in individual_factors.items():
143+
if not isinstance(factor, (int, float)):
144+
raise ConfigError(
145+
"caches.per_cache_factors.%s must be a number" % (cache.lower(),)
146+
)
147+
self.cache_factors[cache.lower()] = factor
148+
149+
# Resize all caches (if necessary) with the new factors we've loaded
150+
self.resize_all_caches()
151+
152+
# Store this function so that it can be called from other classes without
153+
# needing an instance of Config
154+
properties.resize_all_caches_func = self.resize_all_caches
155+
156+
def resize_all_caches(self):
157+
"""Ensure all cache sizes are up to date
158+
159+
For each cache, run the mapped callback function with either
160+
a specific cache factor or the default, global one.
161+
"""
162+
for cache_name, callback in _CACHES.items():
163+
new_factor = self.cache_factors.get(cache_name, self.global_factor)
164+
callback(new_factor)

synapse/config/database.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -68,10 +68,6 @@
6868
name: sqlite3
6969
args:
7070
database: %(database_path)s
71-
72-
# Number of events to cache in memory.
73-
#
74-
#event_cache_size: 10K
7571
"""
7672

7773

@@ -116,8 +112,6 @@ def __init__(self, *args, **kwargs):
116112
self.databases = []
117113

118114
def read_config(self, config, **kwargs):
119-
self.event_cache_size = self.parse_size(config.get("event_cache_size", "10K"))
120-
121115
# We *experimentally* support specifying multiple databases via the
122116
# `databases` key. This is a map from a label to database config in the
123117
# same format as the `database` config option, plus an extra

synapse/config/homeserver.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from ._base import RootConfig
1818
from .api import ApiConfig
1919
from .appservice import AppServiceConfig
20+
from .cache import CacheConfig
2021
from .captcha import CaptchaConfig
2122
from .cas import CasConfig
2223
from .consent_config import ConsentConfig
@@ -55,6 +56,7 @@ class HomeServerConfig(RootConfig):
5556
config_classes = [
5657
ServerConfig,
5758
TlsConfig,
59+
CacheConfig,
5860
DatabaseConfig,
5961
LoggingConfig,
6062
RatelimitConfig,

synapse/http/client.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,6 @@
4949
from synapse.logging.context import make_deferred_yieldable
5050
from synapse.logging.opentracing import set_tag, start_active_span, tags
5151
from synapse.util.async_helpers import timeout_deferred
52-
from synapse.util.caches import CACHE_SIZE_FACTOR
5352

5453
logger = logging.getLogger(__name__)
5554

@@ -241,7 +240,10 @@ def __getattr__(_self, attr):
241240
# tends to do so in batches, so we need to allow the pool to keep
242241
# lots of idle connections around.
243242
pool = HTTPConnectionPool(self.reactor)
244-
pool.maxPersistentPerHost = max((100 * CACHE_SIZE_FACTOR, 5))
243+
# XXX: The justification for using the cache factor here is that larger instances
244+
# will need both more cache and more connections.
245+
# Still, this should probably be a separate dial
246+
pool.maxPersistentPerHost = max((100 * hs.config.caches.global_factor, 5))
245247
pool.cachedConnectionTimeout = 2 * 60
246248

247249
self.agent = ProxyAgent(

synapse/metrics/_exposition.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@
3333

3434
from twisted.web.resource import Resource
3535

36+
from synapse.util import caches
37+
3638
try:
3739
from prometheus_client.samples import Sample
3840
except ImportError:
@@ -103,13 +105,15 @@ def nameify_sample(sample):
103105

104106

105107
def generate_latest(registry, emit_help=False):
106-
output = []
107108

108-
for metric in registry.collect():
109+
# Trigger the cache metrics to be rescraped, which updates the common
110+
# metrics but do not produce metrics themselves
111+
for collector in caches.collectors_by_name.values():
112+
collector.collect()
109113

110-
if metric.name.startswith("__unused"):
111-
continue
114+
output = []
112115

116+
for metric in registry.collect():
113117
if not metric.samples:
114118
# No samples, don't bother.
115119
continue

synapse/push/bulk_push_rule_evaluator.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
"cache",
5252
"push_rules_delta_state_cache_metric",
5353
cache=[], # Meaningless size, as this isn't a cache that stores values
54+
resizable=False,
5455
)
5556

5657

@@ -67,7 +68,8 @@ def __init__(self, hs):
6768
self.room_push_rule_cache_metrics = register_cache(
6869
"cache",
6970
"room_push_rule_cache",
70-
cache=[], # Meaningless size, as this isn't a cache that stores values
71+
cache=[], # Meaningless size, as this isn't a cache that stores values,
72+
resizable=False,
7173
)
7274

7375
@defer.inlineCallbacks

0 commit comments

Comments
 (0)