There are cases where probabilistic early expiry is not enough to
prevent cache stampedes, most notably during a cold start/restart where
the cache is not yet populated and during conditions where source I/O
wait times may exceed the possible range of early expiry offset.
To completely prevent cache stampedes from occurring, a mutex must be
used around settings (re)caching.
Early expiry is left in place as it should still be quite effective in
reducing cache lock contention.
Bug: T296771
Change-Id: I99676466ce1ad4f1622867158d2ed15ef9202ec2
Require a `CacheableSource` implementation to signal whether stale
settings may be served from the cache by implementing an
`allowsStaleLoad()` method. This will be useful for sources (e.g. etcd)
that expect some degree of unavailability and failover during
unavailability is desired.
In order to support stale results, `CachedSource` sets cache items with
an indefinite TTL but uses the computed expiry timestamp in the envelope
to consider whether the item is considered expired as opposed to a true
miss where the item is malformed or not present in the cache store. If
a `SettingsBuilderException` is thrown during the source's `load()`
method but the cache check was not a real miss, the stale cached results
are returned.
Note there is currently no pruning of cache items so it is advised that
`CacheableSource` implementations for which stale loads are allowed also
implement an immutable `getHashKey()` based on constructor arguments.
Bug: T296771
Change-Id: Ida0698a237dc0939230799700b6f54956f033d50
Removed use of Psr\SimpleCache\CacheInterface in favor of BagOStuff, as
the latter is a tried-and-true abstraction and the former offers no real
upfront benefits since the caching patterns of SettingsBuilder are quite
basic at this time.
The simplicity of cache interface use is largely in part to the minimal
probabilistic stampede protection implementation within CachedSource
which is left untouched by this change.
Bug: T294748
Change-Id: Ie59b37a8d5c7bf96225757fa9eb9d2c762476713
Move cache TTL from `CachedSource` into the `CacheableSource` interface,
allowing each source to define a TTL most appropriate to its own
settings loading implementation.
Fix bug where `generation` time was computed incorrectly by `CachedSource`.
Include tests to exercise all `CachedSource` interactions with
`CacheInterface` and `CacheableSource`.
Bug: T296771
Change-Id: I97c42cf6ca716516c32104de25245aa21ec110e4
The `SettingsBuilder` now accepts a PSR-16 cache interface with which to
store and query settings before attempting to load from each source. By
default, no cache is used, but any object that implements the
`Psr\SimpleCache\CacheInterface` may be provided to the constructor.
An explicit dependency on "psr/simple-cache" has been added to
`composer.json`. Note that this dependency already existed in vendor
albeit it as a transitive one.
An APCu based `SharedMemoryCache` adapter is provided as a canonical
PSR-16 compliant interface for production use.
Sources are now queued by the `SettingsBuilder` when calling `load()`.
If a cache interface has been provided, and the source is considered
cacheable (implements `CacheableSource`), then it is wrapped as a
`CachedSource` which will query the cache first before loading from the
wrapped source.
Cache stampedes are mitigated using probabilistic early expiry. The
implementation for this was partially based on symfony/cache-contract
source code but also from the Wikipedia article and paper referenced
therein.
See https://en.wikipedia.org/wiki/Cache_stampede#Probabilistic_early_expiration
Bug: T294748
Change-Id: I52ab3899731546876ee58265bd4a1927886746dc