mirror of
https://github.com/plausible/analytics.git
synced 2024-12-23 01:22:15 +03:00
Replace caching engine (#3878)
* Dependencies: swap Cachex for ConCache * Implement Cache adapter wrapping ConCache * Implement cache stats tracker, for metrics * Use Cache.Adapter in Plausible.Cache Marking the test as not slow anymore * Use Cache Adapter when tracking sessions * Use Cache Adapter for UA parsing * Rename child identifiers - cachex is obsolete now * Test stats tracking * Update grafana metrics * Put all caches under common child specification * Try less * Shorten the function delegation path
This commit is contained in:
parent
bb8a272a36
commit
437a3350ff
@ -10,6 +10,7 @@ defmodule Plausible.Application do
|
||||
on_full_build(do: Plausible.License.ensure_valid_license())
|
||||
|
||||
children = [
|
||||
Plausible.Cache.Stats,
|
||||
Plausible.Repo,
|
||||
Plausible.ClickhouseRepo,
|
||||
Plausible.IngestRepo,
|
||||
@ -23,13 +24,15 @@ defmodule Plausible.Application do
|
||||
Supervisor.child_spec(Plausible.Event.WriteBuffer, id: Plausible.Event.WriteBuffer),
|
||||
Supervisor.child_spec(Plausible.Session.WriteBuffer, id: Plausible.Session.WriteBuffer),
|
||||
ReferrerBlocklist,
|
||||
Supervisor.child_spec({Cachex, name: :user_agents, limit: 10_000, stats: true},
|
||||
id: :cachex_user_agents
|
||||
Plausible.Cache.Adapter.child_spec(:user_agents, :cache_user_agents,
|
||||
ttl_check_interval: :timer.seconds(5),
|
||||
global_ttl: :timer.minutes(60)
|
||||
),
|
||||
Supervisor.child_spec({Cachex, name: :sessions, limit: nil, stats: true},
|
||||
id: :cachex_sessions
|
||||
Plausible.Cache.Adapter.child_spec(:sessions, :cache_sessions,
|
||||
ttl_check_interval: :timer.seconds(1),
|
||||
global_ttl: :timer.minutes(30)
|
||||
),
|
||||
{Plausible.Site.Cache, []},
|
||||
{Plausible.Site.Cache, ttl_check_interval: false},
|
||||
{Plausible.Cache.Warmer,
|
||||
[
|
||||
child_name: Plausible.Site.Cache.All,
|
||||
@ -44,7 +47,7 @@ defmodule Plausible.Application do
|
||||
interval: :timer.seconds(30),
|
||||
warmer_fn: :refresh_updated_recently
|
||||
]},
|
||||
{Plausible.Shield.IPRuleCache, []},
|
||||
{Plausible.Shield.IPRuleCache, ttl_check_interval: false},
|
||||
{Plausible.Cache.Warmer,
|
||||
[
|
||||
child_name: Plausible.Shield.IPRuleCache.All,
|
||||
@ -59,7 +62,7 @@ defmodule Plausible.Application do
|
||||
interval: :timer.seconds(35),
|
||||
warmer_fn: :refresh_updated_recently
|
||||
]},
|
||||
{Plausible.Shield.CountryRuleCache, []},
|
||||
{Plausible.Shield.CountryRuleCache, ttl_check_interval: false},
|
||||
{Plausible.Cache.Warmer,
|
||||
[
|
||||
child_name: Plausible.Shield.CountryRuleCache.All,
|
||||
@ -168,16 +171,6 @@ defmodule Plausible.Application do
|
||||
)
|
||||
end
|
||||
|
||||
def report_cache_stats() do
|
||||
case Cachex.stats(:user_agents) do
|
||||
{:ok, stats} ->
|
||||
Logger.info("User agent cache stats: #{inspect(stats)}")
|
||||
|
||||
e ->
|
||||
IO.puts("Unable to show cache stats: #{inspect(e)}")
|
||||
end
|
||||
end
|
||||
|
||||
defp setup_opentelemetry() do
|
||||
OpentelemetryPhoenix.setup()
|
||||
OpentelemetryEcto.setup([:plausible, :repo])
|
||||
|
@ -8,10 +8,10 @@ defmodule Plausible.Cache do
|
||||
# - Optionally override `unwrap_cache_keys/1`
|
||||
# - Populate the cache with `Plausible.Cache.Warmer`
|
||||
|
||||
Serves as a thin wrapper around Cachex, but the underlying
|
||||
Serves as a wrapper around `Plausible.Cache.Adapter`, where the underlying
|
||||
implementation can be transparently swapped.
|
||||
|
||||
Even though the Cachex process is started, cache access is disabled
|
||||
Even though normally the relevant Adapter processes are started, cache access is disabled
|
||||
during tests via the `:plausible, #{__MODULE__}, enabled: bool()` application env key.
|
||||
This can be overridden on case by case basis, using the child specs options.
|
||||
|
||||
@ -61,29 +61,33 @@ defmodule Plausible.Cache do
|
||||
@behaviour Plausible.Cache
|
||||
@modes [:all, :updated_recently]
|
||||
|
||||
alias Plausible.Cache.Adapter
|
||||
|
||||
@spec get(any(), Keyword.t()) :: any() | nil
|
||||
def get(key, opts \\ []) do
|
||||
def get(key, opts \\ []) when is_list(opts) do
|
||||
cache_name = Keyword.get(opts, :cache_name, name())
|
||||
force? = Keyword.get(opts, :force?, false)
|
||||
|
||||
if Plausible.Cache.enabled?() or force? do
|
||||
case Cachex.get(cache_name, key) do
|
||||
{:ok, nil} ->
|
||||
nil
|
||||
|
||||
{:ok, item} ->
|
||||
item
|
||||
|
||||
{:error, e} ->
|
||||
Logger.error("Error retrieving key from '#{inspect(cache_name)}': #{inspect(e)}")
|
||||
|
||||
nil
|
||||
end
|
||||
Adapter.get(cache_name, key)
|
||||
else
|
||||
get_from_source(key)
|
||||
end
|
||||
end
|
||||
|
||||
@spec get_or_store(any(), (-> any()), Keyword.t()) :: any() | nil
|
||||
def get_or_store(key, fallback_fn, opts \\ [])
|
||||
when is_function(fallback_fn, 0) and is_list(opts) do
|
||||
cache_name = Keyword.get(opts, :cache_name, name())
|
||||
force? = Keyword.get(opts, :force?, false)
|
||||
|
||||
if Plausible.Cache.enabled?() or force? do
|
||||
Adapter.get(cache_name, key, fallback_fn)
|
||||
else
|
||||
get_from_source(key) || fallback_fn.()
|
||||
end
|
||||
end
|
||||
|
||||
def unwrap_cache_keys(items), do: items
|
||||
defoverridable unwrap_cache_keys: 1
|
||||
|
||||
@ -117,10 +121,10 @@ defmodule Plausible.Cache do
|
||||
def merge_items(new_items, opts) do
|
||||
new_items = unwrap_cache_keys(new_items)
|
||||
cache_name = Keyword.get(opts, :cache_name, name())
|
||||
true = Cachex.put_many!(cache_name, new_items)
|
||||
:ok = Adapter.put_many(cache_name, new_items)
|
||||
|
||||
if Keyword.get(opts, :delete_stale_items?, true) do
|
||||
{:ok, old_keys} = Cachex.keys(cache_name)
|
||||
old_keys = Adapter.keys(cache_name)
|
||||
|
||||
new = MapSet.new(Enum.into(new_items, [], fn {k, _} -> k end))
|
||||
old = MapSet.new(old_keys)
|
||||
@ -128,7 +132,7 @@ defmodule Plausible.Cache do
|
||||
old
|
||||
|> MapSet.difference(new)
|
||||
|> Enum.each(fn k ->
|
||||
Cachex.del(cache_name, k)
|
||||
Adapter.delete(cache_name, k)
|
||||
end)
|
||||
end
|
||||
|
||||
@ -139,11 +143,7 @@ defmodule Plausible.Cache do
|
||||
def child_spec(opts) do
|
||||
cache_name = Keyword.get(opts, :cache_name, name())
|
||||
child_id = Keyword.get(opts, :child_id, child_id())
|
||||
|
||||
Supervisor.child_spec(
|
||||
{Cachex, name: cache_name, limit: nil, stats: true},
|
||||
id: child_id
|
||||
)
|
||||
Adapter.child_spec(cache_name, child_id, opts)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@ -154,7 +154,7 @@ defmodule Plausible.Cache do
|
||||
@spec ready?(atom()) :: boolean
|
||||
def ready?(cache_name \\ name()) do
|
||||
case size(cache_name) do
|
||||
n when n > 0 ->
|
||||
n when is_integer(n) and n > 0 ->
|
||||
true
|
||||
|
||||
0 ->
|
||||
@ -165,21 +165,8 @@ defmodule Plausible.Cache do
|
||||
end
|
||||
end
|
||||
|
||||
@spec size() :: non_neg_integer()
|
||||
def size(cache_name \\ name()) do
|
||||
case Cachex.size(cache_name) do
|
||||
{:ok, size} -> size
|
||||
_ -> 0
|
||||
end
|
||||
end
|
||||
|
||||
@spec hit_rate() :: number()
|
||||
def hit_rate(cache_name \\ name()) do
|
||||
case Cachex.stats(cache_name) do
|
||||
{:ok, stats} -> Map.get(stats, :hit_rate, 0)
|
||||
_ -> 0
|
||||
end
|
||||
end
|
||||
defdelegate size(cache_name \\ name()), to: Plausible.Cache.Adapter
|
||||
defdelegate hit_rate(cache_name \\ name()), to: Plausible.Cache.Stats
|
||||
|
||||
@spec telemetry_event_refresh(atom(), atom()) :: list(atom())
|
||||
def telemetry_event_refresh(cache_name \\ name(), mode) when mode in @modes do
|
||||
|
107
lib/plausible/cache/adapter.ex
vendored
Normal file
107
lib/plausible/cache/adapter.ex
vendored
Normal file
@ -0,0 +1,107 @@
|
||||
defmodule Plausible.Cache.Adapter do
|
||||
@moduledoc """
|
||||
Interface for the underlying cache implementation.
|
||||
Currently: ConCache
|
||||
|
||||
Using the Adapter module directly, the user must ensure that the relevant
|
||||
processes are available to use, which is normally done via the child specification.
|
||||
"""
|
||||
|
||||
require Logger
|
||||
|
||||
@spec child_spec(atom(), atom(), Keyword.t()) :: Supervisor.child_spec()
|
||||
def child_spec(name, child_id, opts \\ [])
|
||||
when is_atom(name) and is_atom(child_id) and is_list(opts) do
|
||||
cache_name = Keyword.get(opts, :cache_name, name)
|
||||
child_id = Keyword.get(opts, :child_id, child_id)
|
||||
ttl_check_interval = Keyword.get(opts, :ttl_check_interval, false)
|
||||
|
||||
opts =
|
||||
opts
|
||||
|> Keyword.put(:name, cache_name)
|
||||
|> Keyword.put(:ttl_check_interval, ttl_check_interval)
|
||||
|
||||
Supervisor.child_spec(
|
||||
{ConCache, opts},
|
||||
id: child_id
|
||||
)
|
||||
end
|
||||
|
||||
@spec size(atom()) :: non_neg_integer() | nil
|
||||
def size(cache_name) do
|
||||
ConCache.size(cache_name)
|
||||
catch
|
||||
:exit, _ -> nil
|
||||
end
|
||||
|
||||
@spec get(atom(), any()) :: any()
|
||||
def get(cache_name, key) do
|
||||
cache_name
|
||||
|> ConCache.get(key)
|
||||
|> Plausible.Cache.Stats.track(cache_name)
|
||||
catch
|
||||
:exit, _ ->
|
||||
Logger.error("Error retrieving key from '#{inspect(cache_name)}'")
|
||||
nil
|
||||
end
|
||||
|
||||
@spec get(atom(), any(), (-> any())) :: any()
|
||||
def get(cache_name, key, fallback_fn) do
|
||||
cache_name
|
||||
|> ConCache.get_or_store(key, fn ->
|
||||
{:from_fallback, fallback_fn.()}
|
||||
end)
|
||||
|> Plausible.Cache.Stats.track(cache_name)
|
||||
catch
|
||||
:exit, _ ->
|
||||
Logger.error("Error retrieving key from '#{inspect(cache_name)}'")
|
||||
nil
|
||||
end
|
||||
|
||||
@spec put(atom(), any(), any()) :: any()
|
||||
def put(cache_name, key, value) do
|
||||
:ok = ConCache.put(cache_name, key, value)
|
||||
value
|
||||
catch
|
||||
:exit, _ ->
|
||||
Logger.error("Error putting a key to '#{cache_name}'")
|
||||
nil
|
||||
end
|
||||
|
||||
@spec put_many(atom(), [any()]) :: :ok
|
||||
def put_many(cache_name, items) when is_list(items) do
|
||||
true = :ets.insert(ConCache.ets(cache_name), items)
|
||||
:ok
|
||||
catch
|
||||
:exit, _ ->
|
||||
Logger.error("Error putting keys to '#{cache_name}'")
|
||||
:ok
|
||||
end
|
||||
|
||||
@spec delete(atom(), any()) :: :ok
|
||||
def delete(cache_name, key) do
|
||||
ConCache.dirty_delete(cache_name, key)
|
||||
catch
|
||||
:exit, _ ->
|
||||
Logger.error("Error deleting a key in '#{cache_name}'")
|
||||
:ok
|
||||
end
|
||||
|
||||
@spec keys(atom()) :: Enumerable.t()
|
||||
def keys(cache_name) do
|
||||
ets = ConCache.ets(cache_name)
|
||||
|
||||
Stream.resource(
|
||||
fn -> :ets.first(ets) end,
|
||||
fn
|
||||
:"$end_of_table" -> {:halt, nil}
|
||||
prev_key -> {[prev_key], :ets.next(ets, prev_key)}
|
||||
end,
|
||||
fn _ -> :ok end
|
||||
)
|
||||
catch
|
||||
:exit, _ ->
|
||||
Logger.error("Error retrieving key from '#{inspect(cache_name)}'")
|
||||
[]
|
||||
end
|
||||
end
|
74
lib/plausible/cache/stats.ex
vendored
Normal file
74
lib/plausible/cache/stats.ex
vendored
Normal file
@ -0,0 +1,74 @@
|
||||
defmodule Plausible.Cache.Stats do
|
||||
@moduledoc """
|
||||
Keeps track of hit/miss ratio for various caches.
|
||||
"""
|
||||
|
||||
use GenServer
|
||||
|
||||
def start_link(opts) do
|
||||
GenServer.start_link(__MODULE__, opts)
|
||||
end
|
||||
|
||||
def init(opts) do
|
||||
table = Keyword.get(opts, :table, __MODULE__)
|
||||
|
||||
^table =
|
||||
:ets.new(table, [
|
||||
:public,
|
||||
:named_table,
|
||||
:ordered_set,
|
||||
read_concurrency: true,
|
||||
write_concurrency: true
|
||||
])
|
||||
|
||||
{:ok, table}
|
||||
end
|
||||
|
||||
def gather(cache_name, table \\ __MODULE__) do
|
||||
{:ok,
|
||||
%{
|
||||
hit_rate: hit_rate(cache_name, table),
|
||||
count: size(cache_name) || 0
|
||||
}}
|
||||
end
|
||||
|
||||
defdelegate size(cache_name), to: Plausible.Cache.Adapter
|
||||
|
||||
def track(item, cache_name, table \\ __MODULE__)
|
||||
|
||||
def track({:from_fallback, item}, cache_name, table) do
|
||||
bump(cache_name, :miss, 1, table)
|
||||
item
|
||||
end
|
||||
|
||||
def track(nil, cache_name, table) do
|
||||
bump(cache_name, :miss, 1, table)
|
||||
nil
|
||||
end
|
||||
|
||||
def track(item, cache_name, table) do
|
||||
bump(cache_name, :hit, 1, table)
|
||||
item
|
||||
end
|
||||
|
||||
def bump(cache_name, type, increment, table \\ __MODULE__) do
|
||||
:ets.update_counter(
|
||||
table,
|
||||
{cache_name, type},
|
||||
increment,
|
||||
{{cache_name, type}, 0}
|
||||
)
|
||||
end
|
||||
|
||||
def hit_rate(cache_name, table \\ __MODULE__) do
|
||||
hit = :ets.lookup_element(table, {cache_name, :hit}, 2, 0)
|
||||
miss = :ets.lookup_element(table, {cache_name, :miss}, 2, 0)
|
||||
hit_miss = hit + miss
|
||||
|
||||
if hit_miss == 0 do
|
||||
0.0
|
||||
else
|
||||
hit / hit_miss * 100
|
||||
end
|
||||
end
|
||||
end
|
@ -388,11 +388,9 @@ defmodule Plausible.Ingestion.Event do
|
||||
end
|
||||
|
||||
defp parse_user_agent(%Request{user_agent: user_agent}) when is_binary(user_agent) do
|
||||
case Cachex.fetch(:user_agents, user_agent, &UAInspector.parse/1) do
|
||||
{:ok, user_agent} -> user_agent
|
||||
{:commit, user_agent} -> user_agent
|
||||
_ -> nil
|
||||
end
|
||||
Plausible.Cache.Adapter.get(:user_agents, user_agent, fn ->
|
||||
UAInspector.parse(user_agent)
|
||||
end)
|
||||
end
|
||||
|
||||
defp parse_user_agent(request), do: request
|
||||
|
@ -19,27 +19,22 @@ defmodule Plausible.Session.CacheStore do
|
||||
defp find_session(_domain, nil), do: nil
|
||||
|
||||
defp find_session(event, user_id) do
|
||||
from_cache = Cachex.get(:sessions, {event.site_id, user_id})
|
||||
from_cache = Plausible.Cache.Adapter.get(:sessions, {event.site_id, user_id})
|
||||
|
||||
case from_cache do
|
||||
{:ok, nil} ->
|
||||
nil ->
|
||||
nil
|
||||
|
||||
{:ok, session} ->
|
||||
session ->
|
||||
if Timex.diff(event.timestamp, session.timestamp, :minutes) <= 30 do
|
||||
session
|
||||
end
|
||||
|
||||
{:error, e} ->
|
||||
Sentry.capture_message("Cachex error", extra: %{error: e})
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
defp persist_session(session) do
|
||||
key = {session.site_id, session.user_id}
|
||||
Cachex.put(:sessions, key, session, ttl: :timer.minutes(30))
|
||||
session
|
||||
Plausible.Cache.Adapter.put(:sessions, key, session)
|
||||
end
|
||||
|
||||
defp update_session(session, event) do
|
||||
|
@ -19,7 +19,7 @@ defmodule Plausible.Shield.CountryRuleCache do
|
||||
def name(), do: @cache_name
|
||||
|
||||
@impl true
|
||||
def child_id(), do: :cachex_country_blocklist
|
||||
def child_id(), do: :cache_country_blocklist
|
||||
|
||||
@impl true
|
||||
def count_all() do
|
||||
|
@ -19,7 +19,7 @@ defmodule Plausible.Shield.IPRuleCache do
|
||||
def name(), do: @cache_name
|
||||
|
||||
@impl true
|
||||
def child_id(), do: :cachex_ip_blocklist
|
||||
def child_id(), do: :cache_ip_blocklist
|
||||
|
||||
@impl true
|
||||
def count_all() do
|
||||
|
@ -37,7 +37,7 @@ defmodule Plausible.Site.Cache do
|
||||
def name(), do: @cache_name
|
||||
|
||||
@impl true
|
||||
def child_id(), do: :cachex_sites
|
||||
def child_id(), do: :cache_sites
|
||||
|
||||
@impl true
|
||||
def count_all() do
|
||||
|
@ -88,29 +88,23 @@ defmodule Plausible.PromEx.Plugins.PlausibleMetrics do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Add telemetry events for Cachex user agents and sessions
|
||||
Fire telemetry events for various caches
|
||||
"""
|
||||
def execute_cache_metrics do
|
||||
{:ok, user_agents_stats} = Cachex.stats(:user_agents)
|
||||
{:ok, sessions_stats} = Cachex.stats(:sessions)
|
||||
{:ok, user_agents_stats} = Plausible.Cache.Stats.gather(:user_agents)
|
||||
{:ok, sessions_stats} = Plausible.Cache.Stats.gather(:sessions)
|
||||
|
||||
user_agents_hit_rate = Map.get(user_agents_stats, :hit_rate, 0.0)
|
||||
sessions_hit_rate = Map.get(sessions_stats, :hit_rate, 0.0)
|
||||
|
||||
{:ok, user_agents_count} = Cachex.size(:user_agents)
|
||||
{:ok, sessions_count} = Cachex.size(:sessions)
|
||||
|
||||
:telemetry.execute([:prom_ex, :plugin, :cachex, :user_agents], %{
|
||||
count: user_agents_count,
|
||||
hit_rate: user_agents_hit_rate
|
||||
:telemetry.execute([:prom_ex, :plugin, :cache, :user_agents], %{
|
||||
count: user_agents_stats.count,
|
||||
hit_rate: user_agents_stats.hit_rate
|
||||
})
|
||||
|
||||
:telemetry.execute([:prom_ex, :plugin, :cachex, :sessions], %{
|
||||
count: sessions_count,
|
||||
hit_rate: sessions_hit_rate
|
||||
:telemetry.execute([:prom_ex, :plugin, :cache, :sessions], %{
|
||||
count: sessions_stats.count,
|
||||
hit_rate: sessions_stats.hit_rate
|
||||
})
|
||||
|
||||
:telemetry.execute([:prom_ex, :plugin, :cachex, :sites], %{
|
||||
:telemetry.execute([:prom_ex, :plugin, :cache, :sites], %{
|
||||
count: Site.Cache.size(),
|
||||
hit_rate: Site.Cache.hit_rate()
|
||||
})
|
||||
@ -144,32 +138,32 @@ defmodule Plausible.PromEx.Plugins.PlausibleMetrics do
|
||||
[
|
||||
last_value(
|
||||
metric_prefix ++ [:cache, :sessions, :size],
|
||||
event_name: [:prom_ex, :plugin, :cachex, :sessions],
|
||||
event_name: [:prom_ex, :plugin, :cache, :sessions],
|
||||
measurement: :count
|
||||
),
|
||||
last_value(
|
||||
metric_prefix ++ [:cache, :user_agents, :size],
|
||||
event_name: [:prom_ex, :plugin, :cachex, :user_agents],
|
||||
event_name: [:prom_ex, :plugin, :cache, :user_agents],
|
||||
measurement: :count
|
||||
),
|
||||
last_value(
|
||||
metric_prefix ++ [:cache, :user_agents, :hit_ratio],
|
||||
event_name: [:prom_ex, :plugin, :cachex, :user_agents],
|
||||
event_name: [:prom_ex, :plugin, :cache, :user_agents],
|
||||
measurement: :hit_rate
|
||||
),
|
||||
last_value(
|
||||
metric_prefix ++ [:cache, :sessions, :hit_ratio],
|
||||
event_name: [:prom_ex, :plugin, :cachex, :sessions],
|
||||
event_name: [:prom_ex, :plugin, :cache, :sessions],
|
||||
measurement: :hit_rate
|
||||
),
|
||||
last_value(
|
||||
metric_prefix ++ [:cache, :sites, :size],
|
||||
event_name: [:prom_ex, :plugin, :cachex, :sites],
|
||||
event_name: [:prom_ex, :plugin, :cache, :sites],
|
||||
measurement: :count
|
||||
),
|
||||
last_value(
|
||||
metric_prefix ++ [:cache, :sites, :hit_ratio],
|
||||
event_name: [:prom_ex, :plugin, :cachex, :sites],
|
||||
event_name: [:prom_ex, :plugin, :cache, :sites],
|
||||
measurement: :hit_rate
|
||||
)
|
||||
]
|
||||
|
4
mix.exs
4
mix.exs
@ -67,7 +67,6 @@ defmodule Plausible.MixProject do
|
||||
{:bamboo_mua, "~> 0.1.4"},
|
||||
{:bcrypt_elixir, "~> 3.0"},
|
||||
{:bypass, "~> 2.1", only: [:dev, :test, :small_test]},
|
||||
{:cachex, "~> 3.4"},
|
||||
{:ecto_ch, "~> 0.3"},
|
||||
{:cloak, "~> 1.1"},
|
||||
{:cloak_ecto, "~> 1.2"},
|
||||
@ -140,7 +139,8 @@ defmodule Plausible.MixProject do
|
||||
{:ex_aws, "~> 2.5"},
|
||||
{:ex_aws_s3, "~> 2.5"},
|
||||
{:sweet_xml, "~> 0.7.4"},
|
||||
{:testcontainers, "~> 1.6", only: [:test, :small_test]}
|
||||
{:testcontainers, "~> 1.6", only: [:test, :small_test]},
|
||||
{:con_cache, "~> 1.0"}
|
||||
]
|
||||
end
|
||||
|
||||
|
2
mix.lock
2
mix.lock
@ -8,7 +8,6 @@
|
||||
"bcrypt_elixir": {:hex, :bcrypt_elixir, "3.1.0", "0b110a9a6c619b19a7f73fa3004aa11d6e719a67e672d1633dc36b6b2290a0f7", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "2ad2acb5a8bc049e8d5aa267802631912bb80d5f4110a178ae7999e69dca1bf7"},
|
||||
"bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"},
|
||||
"bypass": {:hex, :bypass, "2.1.0", "909782781bf8e20ee86a9cabde36b259d44af8b9f38756173e8f5e2e1fabb9b1", [:mix], [{:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: false]}, {:ranch, "~> 1.3", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "d9b5df8fa5b7a6efa08384e9bbecfe4ce61c77d28a4282f79e02f1ef78d96b80"},
|
||||
"cachex": {:hex, :cachex, "3.6.0", "14a1bfbeee060dd9bec25a5b6f4e4691e3670ebda28c8ba2884b12fe30b36bf8", [:mix], [{:eternal, "~> 1.2", [hex: :eternal, repo: "hexpm", optional: false]}, {:jumper, "~> 1.0", [hex: :jumper, repo: "hexpm", optional: false]}, {:sleeplocks, "~> 1.1", [hex: :sleeplocks, repo: "hexpm", optional: false]}, {:unsafe, "~> 1.0", [hex: :unsafe, repo: "hexpm", optional: false]}], "hexpm", "ebf24e373883bc8e0c8d894a63bbe102ae13d918f790121f5cfe6e485cc8e2e2"},
|
||||
"castore": {:hex, :castore, "1.0.5", "9eeebb394cc9a0f3ae56b813459f990abb0a3dedee1be6b27fdb50301930502f", [:mix], [], "hexpm", "8d7c597c3e4a64c395980882d4bca3cebb8d74197c590dc272cfd3b6a6310578"},
|
||||
"certifi": {:hex, :certifi, "2.12.0", "2d1cca2ec95f59643862af91f001478c9863c2ac9cb6e2f89780bfd8de987329", [:rebar3], [], "hexpm", "ee68d85df22e554040cdb4be100f33873ac6051387baf6a8f6ce82272340ff1c"},
|
||||
"ch": {:hex, :ch, "0.2.4", "d510fbb5542d009f7c5b00bb1ecab73307b6066d9fb9b220600257d462cba67f", [:mix], [{:db_connection, "~> 2.0", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.5", [hex: :ecto, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: false]}], "hexpm", "8f065d15aaf912ae8da56c9ca5298fb2d1a09108d006de589bcf8c2b39a7e2bb"},
|
||||
@ -19,6 +18,7 @@
|
||||
"combination": {:hex, :combination, "0.0.3", "746aedca63d833293ec6e835aa1f34974868829b1486b1e1cb0685f0b2ae1f41", [:mix], [], "hexpm", "72b099f463df42ef7dc6371d250c7070b57b6c5902853f69deb894f79eda18ca"},
|
||||
"combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm", "1b1dbc1790073076580d0d1d64e42eae2366583e7aecd455d1215b0d16f2451b"},
|
||||
"comeonin": {:hex, :comeonin, "5.4.0", "246a56ca3f41d404380fc6465650ddaa532c7f98be4bda1b4656b3a37cc13abe", [:mix], [], "hexpm", "796393a9e50d01999d56b7b8420ab0481a7538d0caf80919da493b4a6e51faf1"},
|
||||
"con_cache": {:hex, :con_cache, "1.0.0", "6405e2bd5d5005334af72939432783562a8c35a196c2e63108fe10bb97b366e6", [:mix], [], "hexpm", "4d1f5cb1a67f3c1a468243dc98d10ac83af7f3e33b7e7c15999dc2c9bc0a551e"},
|
||||
"cors_plug": {:hex, :cors_plug, "3.0.3", "7c3ac52b39624bc616db2e937c282f3f623f25f8d550068b6710e58d04a0e330", [:mix], [{:plug, "~> 1.13", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "3f2d759e8c272ed3835fab2ef11b46bddab8c1ab9528167bd463b6452edf830d"},
|
||||
"cowboy": {:hex, :cowboy, "2.10.0", "ff9ffeff91dae4ae270dd975642997afe2a1179d94b1887863e43f681a203e26", [:make, :rebar3], [{:cowlib, "2.12.1", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "3afdccb7183cc6f143cb14d3cf51fa00e53db9ec80cdcd525482f5e99bc41d6b"},
|
||||
"cowboy_telemetry": {:hex, :cowboy_telemetry, "0.4.0", "f239f68b588efa7707abce16a84d0d2acf3a0f50571f8bb7f56a15865aae820c", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7d98bac1ee4565d31b62d59f8823dfd8356a169e7fcbb83831b8a5397404c9de"},
|
||||
|
50
test/plausible/cache/stats_test.exs
vendored
Normal file
50
test/plausible/cache/stats_test.exs
vendored
Normal file
@ -0,0 +1,50 @@
|
||||
defmodule Plausible.Cache.StatsTest do
|
||||
use Plausible.DataCase, async: true
|
||||
|
||||
alias Plausible.Cache.Stats
|
||||
|
||||
test "when tracking is not initialized, stats are 0" do
|
||||
assert {:ok, %{hit_rate: +0.0, count: 0}} = Stats.gather(:foo)
|
||||
end
|
||||
|
||||
test "when cache is started, stats are 0", %{test: test} do
|
||||
{:ok, _} = Stats.start_link(name: test, table: test)
|
||||
assert {:ok, %{hit_rate: +0.0, count: 0}} = Stats.gather(test, test)
|
||||
end
|
||||
|
||||
test "tracking changes hit ratio", %{test: test} do
|
||||
{:ok, _} = Stats.start_link(name: test, table: test)
|
||||
|
||||
Stats.track(nil, test, test)
|
||||
assert {:ok, %{hit_rate: +0.0, count: 0}} = Stats.gather(test, test)
|
||||
|
||||
Stats.track(:not_nil, test, test)
|
||||
assert {:ok, %{hit_rate: 50.0, count: 0}} = Stats.gather(test, test)
|
||||
|
||||
Stats.track(:not_nil, test, test)
|
||||
Stats.track(:not_nil, test, test)
|
||||
assert {:ok, %{hit_rate: 75.0, count: 0}} = Stats.gather(test, test)
|
||||
|
||||
Stats.track(nil, test, test)
|
||||
Stats.track(nil, test, test)
|
||||
assert {:ok, %{hit_rate: 50.0, count: 0}} = Stats.gather(test, test)
|
||||
end
|
||||
|
||||
test "bump by custom number", %{test: test} do
|
||||
{:ok, _} = Stats.start_link(name: test, table: test)
|
||||
Stats.bump(test, :miss, 10, test)
|
||||
assert {:ok, %{hit_rate: +0.0, count: 0}} = Stats.gather(test, test)
|
||||
Stats.bump(test, :hit, 90, test)
|
||||
assert {:ok, %{hit_rate: 90.0, count: 0}} = Stats.gather(test, test)
|
||||
end
|
||||
|
||||
test "count comes from cache adapter", %{test: test} do
|
||||
{:ok, _} = Stats.start_link(name: test, table: test)
|
||||
%{start: {m, f, a}} = Plausible.Cache.Adapter.child_spec(test, test)
|
||||
{:ok, _} = apply(m, f, a)
|
||||
|
||||
Plausible.Cache.Adapter.put(test, :key, :value)
|
||||
assert Stats.size(test) == 1
|
||||
assert {:ok, %{hit_rate: +0.0, count: 1}} = Stats.gather(test, test)
|
||||
end
|
||||
end
|
@ -50,7 +50,7 @@ defmodule Plausible.CacheTest do
|
||||
assert ExampleCache.get("key", force?: true, cache_name: NonExistingCache) == nil
|
||||
end)
|
||||
|
||||
assert log =~ "Error retrieving key from 'NonExistingCache': :no_cache"
|
||||
assert log =~ "Error retrieving key from 'NonExistingCache'"
|
||||
end
|
||||
|
||||
test "cache is not ready when it doesn't exist", %{test: test} do
|
||||
@ -58,6 +58,32 @@ defmodule Plausible.CacheTest do
|
||||
end
|
||||
end
|
||||
|
||||
describe "stats tracking" do
|
||||
test "get affects hit rate", %{test: test} do
|
||||
{:ok, _} = start_test_cache(test)
|
||||
:ok = ExampleCache.merge_items([{"item1", :item1}], cache_name: test)
|
||||
assert ExampleCache.get("item1", cache_name: test, force?: true)
|
||||
assert {:ok, %{hit_rate: 100.0}} = Plausible.Cache.Stats.gather(test)
|
||||
refute ExampleCache.get("item2", cache_name: test, force?: true)
|
||||
assert {:ok, %{hit_rate: 50.0}} = Plausible.Cache.Stats.gather(test)
|
||||
end
|
||||
|
||||
test "get_or_store affects hit rate", %{test: test} do
|
||||
{:ok, _} = start_test_cache(test)
|
||||
|
||||
:ok = ExampleCache.merge_items([{"item1", :item1}], cache_name: test)
|
||||
assert ExampleCache.get("item1", cache_name: test, force?: true)
|
||||
|
||||
assert "value" ==
|
||||
ExampleCache.get_or_store("item2", fn -> "value" end,
|
||||
cache_name: test,
|
||||
force?: true
|
||||
)
|
||||
|
||||
assert {:ok, %{hit_rate: 50.0}} = Plausible.Cache.Stats.gather(test)
|
||||
end
|
||||
end
|
||||
|
||||
describe "merging cache items" do
|
||||
test "merging adds new items", %{test: test} do
|
||||
{:ok, _} = start_test_cache(test)
|
||||
|
@ -323,7 +323,6 @@ defmodule Plausible.Site.CacheTest do
|
||||
@items1 for i <- 1..200_000, do: {i, nil, :batch1}
|
||||
@items2 for _ <- 1..200_000, do: {Enum.random(1..400_000), nil, :batch2}
|
||||
@max_seconds 2
|
||||
@tag :slow
|
||||
test "merging large sets is expected to be under #{@max_seconds} seconds", %{test: test} do
|
||||
{:ok, _} = start_test_cache(test)
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user