Nebulex.Adapters.Multilevel (Nebulex.Distributed v3.0.0-rc.1)
View SourceAdapter module for the multi-level cache topogy.
The Multi-level adapter is a simple layer that works on top of a local or
distributed cache implementation, enabling 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,
inclusion_policy: :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:
:stats
(boolean/0
) - A flag to determine whether to collect cache stats. The default value istrue
.:levels
(non-emptykeyword/0
) - Required. This option is to define the levels, a list of tuples in the shape{cache_level :: Nebulex.Cache.t(), opts :: keyword()}
, where the first element is the module that defines the cache for that level, and the second one is the options given to that level in thestart_link/1
. The multi-level cache adapter relies on the list order to determine the level hierarchy. For example, the first element in the list will be the L1 cache (level 1), and so on; the Nth element will be the LN cache. This option is required; if it is not set or empty, the adapter raises an exception.:inclusion_policy
- Specifies the cache inclusion policy::inclusive
or:exclusive
.For an "inclusive" cache, the same data can be present in all cache levels. On the other hand, in an "exclusive" cache, the data can be present in only one cache level; the key cannot exist in the rest of the levels at the same time. This option applies to the callback
get
only; if the cache inclusion policy is:inclusive
, when the key does exist in a level N, that entry is duplicated backward (to all previous levels: 1..N-1). However, when the mode is:inclusive
, theget_all
operation is translated into multipleget
calls underneath (which may be a significant performance penalty) since it requires replicating the entries properly with their current TTLs. It is possible to skip the replication when callingget_all
using the option:replicate
.The default value is
:inclusive
.
Shared options
Almost all of the cache functions outlined in Nebulex.Cache
module
accept the following options:
:timeout
(timeout/0
) - The time in milliseconds to wait for a command to finish (:infinity
to wait indefinitely). The default value is5000
.:level
(pos_integer/0
) - Dictates the level where the cache command will take place. The evaluation is performed by default throughout the cache hierarchy (all levels).
Queryable API options
The following options apply to get_all
, count_all
, delete_all
,
and stream
commands:
:replicate
(boolean/0
) - This option applies only to theget_all
callback when using the inclusive policy. Determines whether the entries should be replicated to the backward levels or not. The default value istrue
.:on_error
(:raise
|:nothing
) - Indicates whether to raise an exception when an error occurs or do nothing (skip errors).When the stream is evaluated, the adapter attempts to execute the
stream
command on the different cache levels. Still, the execution could fail at any of the cache levels. If the option is set to:raise
, the command will raise an exception when an error occurs on the stream evaluation. On the other hand, if it is set to:nothing
, the error is skipped.The default value is
:raise
.
Telemetry events
Since the multi-level adapter works as a wrapper for the configured cache
levels, these will emit the Nebulex Telemetry events. Therefore, there will
be events emitted for each cache level. For example, the cache defined before
MyApp.Multilevel
will emit the following events:
Top level cache:
[:my_app, :multilevel, :command, :start]
[:my_app, :multilevel, :command, :stop]
[:my_app, :multilevel, :command, :exception]
L1 cache:
[:my_app, :multilevel, :command, :start]
[:my_app, :multilevel, :command, :stop]
[:my_app, :multilevel, :command, :exception]
L2 cache:
[:my_app, :multilevel, :command, :start]
[:my_app, :multilevel, :primary, :command, :start]
[:my_app, :multilevel, :command, :stop]
[:my_app, :multilevel, :primary, :command, :stop]
[:my_app, :multilevel, :command, :exception]
[:my_app, :multilevel, :primary, :command, :exception]
As you may notice, the telemetry prefix by default for all cache levels is
[:my_app, :multilevel]
, but you can get the details about the cache from
the metadata. Alternatively, you could specify the :telemetry_prefix
for
each cache level within the :levels
option. For example:
config :my_app, MyApp.Multilevel,
levels: [
{MyApp.Multilevel.L1, telemetry_prefix: [:my_app, :multilevel, :l1]},
{MyApp.Multilevel.L2, telemetry_prefix: [:my_app, :multilevel, :l2]}
]
In this case, the telemetry prefix for the L1 cache will be
[:my_app, :multilevel, :l1]
and the L2 cache will be
[:my_app, :multilevel, :l2]
.
See also the Telemetry guide for more information.
Info API
As explained above, the multi-level adapter uses the configured cache levels.
Therefore, the information provided by the info
command will depend on the
adapters configured for each level. The Nebulex built-in adapters support the
recommended keys :server
, :memory
, and :stats
. Additionally, the
multi-level adapter supports:
:levels_info
- A list with the info map for each cache level.
For example, the info for MyApp.Multilevel
may look like this:
iex> MyApp.Multilevel.info!()
%{
memory: %{total: nil, used: 206760},
server: %{
cache_module: MyApp.Multilevel,
cache_name: :multilevel_inclusive,
cache_adapter: Nebulex.Adapters.Multilevel,
cache_pid: #PID<0.998.0>,
nbx_version: "3.0.0"
},
stats: %{
hits: 0,
misses: 0,
writes: 0,
evictions: 0,
expirations: 0,
deletions: 0,
updates: 0
},
levels_info: [
%{
memory: %{total: nil, used: 68920},
server: %{
cache_module: MyApp.Multilevel.L1,
cache_name: MyApp.Multilevel.L1,
cache_adapter: Nebulex.Adapters.Local,
cache_pid: #PID<0.1000.0>,
nbx_version: "3.0.0"
},
stats: %{
hits: 0,
misses: 0,
writes: 0,
evictions: 0,
expirations: 0,
deletions: 0,
updates: 0
}
},
%{
memory: %{total: nil, used: 68920},
nodes: [:"node1@127.0.0.1"],
server: %{
cache_module: MyApp.Multilevel.L2,
cache_name: MyApp.Multilevel.L2,
cache_adapter: Nebulex.Adapters.Partitioned,
cache_pid: #PID<0.1015.0>,
nbx_version: "3.0.0"
},
stats: %{
hits: 0,
misses: 0,
writes: 0,
evictions: 0,
expirations: 0,
deletions: 0,
updates: 0
},
nodes_info: %{
"node1@127.0.0.1": %{
memory: %{total: nil, used: 68920},
server: %{
cache_module: MyApp.Multilevel.L2.Primary,
cache_name: MyApp.Multilevel.L2.Primary,
cache_adapter: Nebulex.Adapters.Local,
cache_pid: #PID<0.1017.0>,
nbx_version: "3.0.0"
},
stats: %{
hits: 0,
misses: 0,
writes: 0,
evictions: 0,
expirations: 0,
deletions: 0,
updates: 0
}
}
}
}
]
}
Extended API
This adapter provides some additional convenience functions to the
Nebulex.Cache
API.
inclusion_policy/0,1
Returns the inclusion policy of the cache.
iex> MyCache.inclusion_policy()
:inclusive
CAVEATS
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.