View Source Nebulex.Adapters.Multilevel (Nebulex v2.6.4)
Adapter module for Multi-level Cache.
This is just a simple layer on top of local or distributed cache
implementations that enables to have a cache hierarchy by levels.
Multi-level caches generally operate by checking the fastest,
level 1 (L1) cache first; if it hits, the adapter proceeds at
high speed. If that first cache misses, the next fastest cache
(level 2, L2) is checked, and so on, before accessing external
memory (that can be handled by a cacheable
decorator).
For write functions, the "Write Through" policy is applied by default;
this policy ensures that the data is stored safely as it is written
throughout the hierarchy. However, it is possible to force the write
operation in a specific level (although it is not recommended) via
level
option, where the value is a positive integer greater than 0.
We can define a multi-level cache as follows:
defmodule MyApp.Multilevel do
use Nebulex.Cache,
otp_app: :nebulex,
adapter: Nebulex.Adapters.Multilevel
defmodule L1 do
use Nebulex.Cache,
otp_app: :nebulex,
adapter: Nebulex.Adapters.Local
end
defmodule L2 do
use Nebulex.Cache,
otp_app: :nebulex,
adapter: Nebulex.Adapters.Partitioned
end
end
Where the configuration for the cache and its levels must be in your
application environment, usually defined in your config/config.exs
:
config :my_app, MyApp.Multilevel,
model: :inclusive,
levels: [
{
MyApp.Multilevel.L1,
gc_interval: :timer.hours(12),
backend: :shards
},
{
MyApp.Multilevel.L2,
primary: [
gc_interval: :timer.hours(12),
backend: :shards
]
}
]
If your application was generated with a supervisor (by passing --sup
to mix new
) you will have a lib/my_app/application.ex
file containing
the application start callback that defines and starts your supervisor.
You just need to edit the start/2
function to start the cache as a
supervisor on your application's supervisor:
def start(_type, _args) do
children = [
{MyApp.Multilevel, []},
...
]
See Nebulex.Cache
for more information.
Options
This adapter supports the following options and all of them can be given via the cache configuration:
:levels
- This option is to define the levels, a list of tuples{cache_level :: Nebulex.Cache.t(), opts :: Keyword.t()}
, where the first element is the module that defines the cache for that level, and the second one is the options that will be passed to that level in thestart/link/1
(which depends on the adapter this level is using). The order in which the levels are defined is the same the multi-level cache will use. For example, the first cache in the list will be the L1 cache (level 1) and so on; the Nth element will be the LN cache. This option is mandatory, if it is not set or empty, an exception will be raised.:model
- Specifies the cache model::inclusive
or:exclusive
; defaults to:inclusive
. In an inclusive cache, the same data can be present in all caches/levels. In an exclusive cache, data can be present in only one cache/level and a key cannot be found in the rest of caches at the same time. This option applies to theget
callback only; if the cache:model
is:inclusive
, when the key is found in a level N, that entry is duplicated backwards (to all previous levels: 1..N-1). However, when the mode is set to:inclusive
, theget_all
operation is translated into multipleget
calls underneath (which may be a significant performance penalty) since is required to replicate the entries properly with their current TTLs. It is possible to skip the replication when callingget_all
using the option:replicate
.:replicate
- This option applies only to theget_all
callback. Determines whether the entries should be replicated to the backward levels or not. Defaults totrue
.
Shared options
Almost all of the cache functions outlined in Nebulex.Cache
module
accept the following options:
:level
- It may be an integer greater than 0 that specifies the cache level where the operation will take place. By default, the evaluation is performed throughout the whole cache hierarchy (all levels).
Telemetry events
This adapter emits all recommended Telemetry events, and documented
in Nebulex.Cache
module (see "Adapter-specific events" section).
Since the multi-level adapter is a layer/wrapper on top of other existing
adapters, each cache level may Telemetry emit events independently.
For example, for the cache defined before MyApp.Multilevel
, the next
events will be emitted for the main multi-level cache:
[:my_app, :multilevel, :command, :start]
[:my_app, :multilevel, :command, :stop]
[:my_app, :multilevel, :command, :exception]
For the L1 (configured with the local adapter):
[:my_app, :multilevel, :l1, :command, :start]
[:my_app, :multilevel, :l1, :command, :stop]
[:my_app, :multilevel, :l1, :command, :exception]
For the L2 (configured with the partitioned adapter):
[:my_app, :multilevel, :l2, :command, :start]
[:my_app, :multilevel, :l2, :primary, :command, :start]
[:my_app, :multilevel, :l2, :command, :stop]
[:my_app, :multilevel, :l2, :primary, :command, :stop]
[:my_app, :multilevel, :l2, :command, :exception]
[:my_app, :multilevel, :l2, :primary, :command, :exception]
See also the Telemetry guide for more information and examples.
Stats
Since the multi-level adapter works as a wrapper for the configured cache levels, the support for stats depends on the underlying levels. Also, the measurements are consolidated per level, they are not aggregated. For example, if we enable the stats for the multi-level cache defined previously and run:
MyApp.Multilevel.stats()
The returned stats will look like:
%Nebulex.Stats{
measurements: %{
l1: %{evictions: 0, expirations: 0, hits: 0, misses: 0, writes: 0},
l2: %{evictions: 0, expirations: 0, hits: 0, misses: 0, writes: 0}
},
metadata: %{
l1: %{
cache: NMyApp.Multilevel.L1,
started_at: ~U[2021-01-10 13:06:04.075084Z]
},
l2: %{
cache: MyApp.Multilevel.L2.Primary,
started_at: ~U[2021-01-10 13:06:04.089888Z]
},
cache: MyApp.Multilevel,
started_at: ~U[2021-01-10 13:06:04.066750Z]
}
}
IMPORTANT: Those cache levels with stats disabled won't be included
into the returned stats (they are skipped). If a cache level is using
an adapter that does not support stats, you may get unexpected errors.
Therefore, and as overall recommendation, check out the documentation
for adapters used by the underlying cache levels and ensure they
implement the Nebulex.Adapter.Stats
behaviour.
Stats with Telemetry
In case you are using Telemetry metrics, you can define the metrics per level, for example:
last_value("nebulex.cache.stats.l1.hits",
event_name: "nebulex.cache.stats",
measurement: &get_in(&1, [:l1, :hits]),
tags: [:cache]
)
last_value("nebulex.cache.stats.l1.misses",
event_name: "nebulex.cache.stats",
measurement: &get_in(&1, [:l1, :misses]),
tags: [:cache]
)
See the section "Instrumenting Multi-level caches" in the Telemetry guide for more information.
Extended API
This adapter provides one additional convenience function for retrieving
the cache model for the given cache name
:
MyCache.model()
MyCache.model(:cache_name)
Caveats of multi-level adapter
Because this adapter reuses other existing/configured adapters, it inherits all their limitations too. Therefore, it is highly recommended to check the documentation of the adapters to use.