-
-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Add a five minute cache to get_destination_retry_timings #3933
Changes from 5 commits
79eded1
fdd1a62
5230bc1
19dc676
a1cd373
5b4028f
9c8cec5
8ea8878
4f3e3ac
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 a cache to get_destination_retry_timings |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,12 +16,17 @@ | |
import logging | ||
from collections import OrderedDict | ||
|
||
from six import iteritems, itervalues | ||
|
||
from synapse.metrics.background_process_metrics import run_as_background_process | ||
from synapse.util.caches import register_cache | ||
|
||
logger = logging.getLogger(__name__) | ||
|
||
|
||
SENTINEL = object() | ||
|
||
|
||
class ExpiringCache(object): | ||
def __init__(self, cache_name, clock, max_len=0, expiry_ms=0, | ||
reset_expiry_on_get=False, iterable=False): | ||
|
@@ -54,8 +59,6 @@ def __init__(self, cache_name, clock, max_len=0, expiry_ms=0, | |
|
||
self.iterable = iterable | ||
|
||
self._size_estimate = 0 | ||
|
||
self.metrics = register_cache("expiring", cache_name, self) | ||
|
||
if not self._expiry_ms: | ||
|
@@ -74,16 +77,11 @@ def __setitem__(self, key, value): | |
now = self._clock.time_msec() | ||
self._cache[key] = _CacheEntry(now, value) | ||
|
||
if self.iterable: | ||
self._size_estimate += len(value) | ||
|
||
# Evict if there are now too many items | ||
while self._max_len and len(self) > self._max_len: | ||
_key, value = self._cache.popitem(last=False) | ||
if self.iterable: | ||
removed_len = len(value.value) | ||
self.metrics.inc_evictions(removed_len) | ||
self._size_estimate -= removed_len | ||
self.metrics.inc_evictions(len(value.value)) | ||
else: | ||
self.metrics.inc_evictions() | ||
|
||
|
@@ -100,6 +98,18 @@ def __getitem__(self, key): | |
|
||
return entry.value | ||
|
||
def pop(self, key, default=None): | ||
value = self._cache.pop(key, SENTINEL) | ||
if value is SENTINEL: | ||
return default | ||
|
||
if self.iterable: | ||
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. I would not call this an eviction. To me, eviction means that we have removed a valid entry to make room for a new one. I'd consider this as equivalent to an update with a magic "doesn't exist" value. 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. I think the problem is that we're potentially conflating "eviction because of invalidation" (which this code may do) with "eviction because of some automated process" in the statistics. If something pops from the cache so it won't be cached anymore deliberately, I don't think that's useful to track in what this metric appears to do (assessing whether our cache is effective). 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. @hawkowl: so I think you're agreeing with me? If so I'll remove this bit of code and merge it. 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. Ah, for some reason I thought that this was consistent with what we did elsewhere, but it isn't. |
||
self.metrics.inc_evictions(len(value.value)) | ||
else: | ||
self.metrics.inc_evictions() | ||
|
||
return value | ||
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. I could have just done: def pop(self, key, *args, **kwargs):
"Identical functionality to `dict.pop(..)`"
return self._cache.pop(key, *args, **kwargs) But that feels quite opaque to me? 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. yeah sorry, I hadn't quite twigged that |
||
|
||
def __contains__(self, key): | ||
return key in self._cache | ||
|
||
|
@@ -127,14 +137,16 @@ def _prune_cache(self): | |
|
||
keys_to_delete = set() | ||
|
||
for key, cache_entry in self._cache.items(): | ||
for key, cache_entry in iteritems(self._cache): | ||
if now - cache_entry.time > self._expiry_ms: | ||
keys_to_delete.add(key) | ||
|
||
for k in keys_to_delete: | ||
value = self._cache.pop(k) | ||
if self.iterable: | ||
self._size_estimate -= len(value.value) | ||
self.metrics.inc_evictions(len(value.value)) | ||
else: | ||
self.metrics.inc_evictions() | ||
|
||
logger.debug( | ||
"[%s] _prune_cache before: %d, after len: %d", | ||
|
@@ -143,12 +155,14 @@ def _prune_cache(self): | |
|
||
def __len__(self): | ||
if self.iterable: | ||
return self._size_estimate | ||
return sum(len(entry.value) for entry in itervalues(self._cache)) | ||
else: | ||
return len(self._cache) | ||
|
||
|
||
class _CacheEntry(object): | ||
__slots__ = ["time", "value"] | ||
|
||
def __init__(self, time, value): | ||
self.time = time | ||
self.value = value |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is the same as
return self._cache.pop(key, default)
now.docstring wouldn't go amiss.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not, actually, as
self._cache.pop
won't raise ifdefault
isNone
. Thinking about it we can probably just doreturn self._cache.pop(key, **kwargs)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Argh, I've completely messed this up