parent
b9e8e0aa36
commit
978af2df39
@ -0,0 +1,115 @@ |
|||||||
|
defmodule Explorer.Chain.TransactionCountCache do |
||||||
|
@moduledoc """ |
||||||
|
Cache for estimated transaction count. |
||||||
|
""" |
||||||
|
|
||||||
|
use GenServer |
||||||
|
|
||||||
|
alias Explorer.Chain.Transaction |
||||||
|
alias Explorer.Repo |
||||||
|
|
||||||
|
@tab :transaction_count_cache |
||||||
|
# 2 hours |
||||||
|
@cache_period 1_000 * 60 * 60 * 2 |
||||||
|
@default_value 0 |
||||||
|
@key "count" |
||||||
|
@name __MODULE__ |
||||||
|
|
||||||
|
def start_link([params, gen_server_options]) do |
||||||
|
GenServer.start_link(__MODULE__, params, name: gen_server_options[:name] || @name) |
||||||
|
end |
||||||
|
|
||||||
|
def init(params) do |
||||||
|
cache_period = params[:cache_period] || @cache_period |
||||||
|
current_value = params[:default_value] || @default_value |
||||||
|
|
||||||
|
init_ets_table() |
||||||
|
|
||||||
|
schedule_cache_update() |
||||||
|
|
||||||
|
{:ok, {{cache_period, current_value}, nil}} |
||||||
|
end |
||||||
|
|
||||||
|
def value(process_name \\ __MODULE__) do |
||||||
|
GenServer.call(process_name, :value) |
||||||
|
end |
||||||
|
|
||||||
|
def handle_call(:value, _, {{cache_period, default_value}, task}) do |
||||||
|
{value, task} = |
||||||
|
case cached_values() do |
||||||
|
nil -> |
||||||
|
{default_value, update_cache(task)} |
||||||
|
|
||||||
|
{cached_value, timestamp} -> |
||||||
|
task = |
||||||
|
if current_time() - timestamp > cache_period do |
||||||
|
update_cache(task) |
||||||
|
end |
||||||
|
|
||||||
|
{cached_value, task} |
||||||
|
end |
||||||
|
|
||||||
|
{:reply, value, {{cache_period, default_value}, task}} |
||||||
|
end |
||||||
|
|
||||||
|
def update_cache(nil) do |
||||||
|
async_update_cache() |
||||||
|
end |
||||||
|
|
||||||
|
def update_cache(task) do |
||||||
|
task |
||||||
|
end |
||||||
|
|
||||||
|
def handle_cast({:update_cache, value}, {{cache_period, default_value}, _}) do |
||||||
|
current_time = current_time() |
||||||
|
tuple = {value, current_time} |
||||||
|
|
||||||
|
:ets.insert(@tab, {@key, tuple}) |
||||||
|
|
||||||
|
{:noreply, {{cache_period, default_value}, nil}} |
||||||
|
end |
||||||
|
|
||||||
|
def handle_info({:DOWN, _, _, _, _}, {{cache_period, default_value}, _}) do |
||||||
|
{:noreply, {{cache_period, default_value}, nil}} |
||||||
|
end |
||||||
|
|
||||||
|
def handle_info(_, {{cache_period, default_value}, _}) do |
||||||
|
{:noreply, {{cache_period, default_value}, nil}} |
||||||
|
end |
||||||
|
|
||||||
|
def async_update_cache do |
||||||
|
Task.async(fn -> |
||||||
|
result = Repo.aggregate(Transaction, :count, :hash, timeout: :infinity) |
||||||
|
|
||||||
|
GenServer.cast(__MODULE__, {:update_cache, result}) |
||||||
|
end) |
||||||
|
end |
||||||
|
|
||||||
|
defp init_ets_table do |
||||||
|
if :ets.whereis(@tab) == :undefined do |
||||||
|
:ets.new(@tab, [ |
||||||
|
:set, |
||||||
|
:named_table, |
||||||
|
:public, |
||||||
|
write_concurrency: true |
||||||
|
]) |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
defp cached_values do |
||||||
|
case :ets.lookup(@tab, @key) do |
||||||
|
[{_, cached_values}] -> cached_values |
||||||
|
_ -> nil |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
defp schedule_cache_update do |
||||||
|
Process.send_after(self(), :update_cache, 2_000) |
||||||
|
end |
||||||
|
|
||||||
|
defp current_time do |
||||||
|
utc_now = DateTime.utc_now() |
||||||
|
|
||||||
|
DateTime.to_unix(utc_now, :millisecond) |
||||||
|
end |
||||||
|
end |
@ -0,0 +1,58 @@ |
|||||||
|
defmodule Explorer.Chain.TransactionCountCacheTest do |
||||||
|
use Explorer.DataCase |
||||||
|
|
||||||
|
alias Explorer.Chain.TransactionCountCache |
||||||
|
|
||||||
|
test "returns default transaction count" do |
||||||
|
TransactionCountCache.start_link([[], []]) |
||||||
|
|
||||||
|
result = TransactionCountCache.value() |
||||||
|
|
||||||
|
assert result == 0 |
||||||
|
end |
||||||
|
|
||||||
|
test "updates cache if initial value is zero" do |
||||||
|
TransactionCountCache.start_link([[], []]) |
||||||
|
|
||||||
|
insert(:transaction) |
||||||
|
insert(:transaction) |
||||||
|
|
||||||
|
result = TransactionCountCache.value() |
||||||
|
|
||||||
|
assert result == 0 |
||||||
|
|
||||||
|
Process.sleep(500) |
||||||
|
|
||||||
|
updated_value = TransactionCountCache.value() |
||||||
|
|
||||||
|
assert updated_value == 2 |
||||||
|
end |
||||||
|
|
||||||
|
test "does not update cache if cache period did not pass" do |
||||||
|
TransactionCountCache.start_link([[], []]) |
||||||
|
|
||||||
|
insert(:transaction) |
||||||
|
insert(:transaction) |
||||||
|
|
||||||
|
result = TransactionCountCache.value() |
||||||
|
|
||||||
|
assert result == 0 |
||||||
|
|
||||||
|
Process.sleep(500) |
||||||
|
|
||||||
|
updated_value = TransactionCountCache.value() |
||||||
|
|
||||||
|
assert updated_value == 2 |
||||||
|
|
||||||
|
insert(:transaction) |
||||||
|
insert(:transaction) |
||||||
|
|
||||||
|
_updated_value = TransactionCountCache.value() |
||||||
|
|
||||||
|
Process.sleep(500) |
||||||
|
|
||||||
|
updated_value = TransactionCountCache.value() |
||||||
|
|
||||||
|
assert updated_value == 2 |
||||||
|
end |
||||||
|
end |
Loading…
Reference in new issue