parent
fd30125b1f
commit
845b399281
@ -0,0 +1,117 @@ |
|||||||
|
defmodule Explorer.Counters.AddessesWithBalanceCounter do |
||||||
|
@moduledoc """ |
||||||
|
Caches the number of addresses with fetched coin balance > 0. |
||||||
|
|
||||||
|
It loads the count asynchronously and in a time interval of 30 minutes. |
||||||
|
""" |
||||||
|
|
||||||
|
use GenServer |
||||||
|
|
||||||
|
alias Explorer.Chain |
||||||
|
|
||||||
|
@table :addresses_with_balance_counter |
||||||
|
|
||||||
|
@cache_key "addresses_with_balance" |
||||||
|
|
||||||
|
def table_name do |
||||||
|
@table |
||||||
|
end |
||||||
|
|
||||||
|
def cache_key do |
||||||
|
@cache_key |
||||||
|
end |
||||||
|
|
||||||
|
# It is undesirable to automatically start the consolidation in all environments. |
||||||
|
# Consider the test environment: if the consolidation initiates but does not |
||||||
|
# finish before a test ends, that test will fail. This way, hundreds of |
||||||
|
# tests were failing before disabling the consolidation and the scheduler in |
||||||
|
# the test env. |
||||||
|
config = Application.get_env(:explorer, Explorer.Counters.AddessesWithBalanceCounter) |
||||||
|
@enable_consolidation Keyword.get(config, :enable_consolidation) |
||||||
|
|
||||||
|
@doc """ |
||||||
|
Starts a process to periodically update the counter of the token holders. |
||||||
|
""" |
||||||
|
@spec start_link(term()) :: GenServer.on_start() |
||||||
|
def start_link(_) do |
||||||
|
GenServer.start_link(__MODULE__, :ok, name: __MODULE__) |
||||||
|
end |
||||||
|
|
||||||
|
@impl true |
||||||
|
def init(args) do |
||||||
|
create_table() |
||||||
|
|
||||||
|
if enable_consolidation?() do |
||||||
|
Task.start_link(&consolidate/0) |
||||||
|
schedule_next_consolidation() |
||||||
|
end |
||||||
|
|
||||||
|
{:ok, args} |
||||||
|
end |
||||||
|
|
||||||
|
def create_table do |
||||||
|
opts = [ |
||||||
|
:set, |
||||||
|
:named_table, |
||||||
|
:public, |
||||||
|
read_concurrency: true |
||||||
|
] |
||||||
|
|
||||||
|
:ets.new(table_name(), opts) |
||||||
|
end |
||||||
|
|
||||||
|
defp schedule_next_consolidation do |
||||||
|
if enable_consolidation?() do |
||||||
|
Process.send_after(self(), :consolidate, :timer.minutes(30)) |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
@doc """ |
||||||
|
Inserts new items into the `:ets` table. |
||||||
|
""" |
||||||
|
def insert_counter({key, info}) do |
||||||
|
:ets.insert(table_name(), {key, info}) |
||||||
|
end |
||||||
|
|
||||||
|
@impl true |
||||||
|
def handle_info(:consolidate, state) do |
||||||
|
consolidate() |
||||||
|
|
||||||
|
schedule_next_consolidation() |
||||||
|
|
||||||
|
{:noreply, state} |
||||||
|
end |
||||||
|
|
||||||
|
@doc """ |
||||||
|
Fetches the info for a specific item from the `:ets` table. |
||||||
|
""" |
||||||
|
def fetch do |
||||||
|
do_fetch(:ets.lookup(table_name(), cache_key())) |
||||||
|
end |
||||||
|
|
||||||
|
defp do_fetch([{_, result}]), do: result |
||||||
|
defp do_fetch([]), do: 0 |
||||||
|
|
||||||
|
@doc """ |
||||||
|
Consolidates the info by populating the `:ets` table with the current database information. |
||||||
|
""" |
||||||
|
def consolidate do |
||||||
|
counter = Chain.count_addresses_with_balance() |
||||||
|
|
||||||
|
insert_counter({cache_key(), counter}) |
||||||
|
end |
||||||
|
|
||||||
|
@doc """ |
||||||
|
Returns a boolean that indicates whether consolidation is enabled |
||||||
|
|
||||||
|
In order to choose whether or not to enable the scheduler and the initial |
||||||
|
consolidation, change the following Explorer config: |
||||||
|
|
||||||
|
`config :explorer, Explorer.Counters.AddressesWithBalanceCounter, enable_consolidation: true` |
||||||
|
|
||||||
|
to: |
||||||
|
|
||||||
|
`config :explorer, Explorer.Counters.AddressesWithBalanceCounter, enable_consolidation: false` |
||||||
|
""" |
||||||
|
def enable_consolidation?, do: @enable_consolidation |
||||||
|
end |
@ -0,0 +1,15 @@ |
|||||||
|
defmodule Explorer.Counters.AddessesWithBalanceCounterTest do |
||||||
|
use Explorer.DataCase |
||||||
|
|
||||||
|
alias Explorer.Counters.AddessesWithBalanceCounter |
||||||
|
|
||||||
|
test "populates the cache with the number of addresses with fetched coin balance greater than 0" do |
||||||
|
insert(:address, fetched_coin_balance: 0) |
||||||
|
insert(:address, fetched_coin_balance: 1) |
||||||
|
insert(:address, fetched_coin_balance: 2) |
||||||
|
|
||||||
|
AddessesWithBalanceCounter.consolidate() |
||||||
|
|
||||||
|
assert AddessesWithBalanceCounter.fetch() == 2 |
||||||
|
end |
||||||
|
end |
Loading…
Reference in new issue