Merge pull request #1388 from poanetwork/ab-cache-max-min-block-number

cache max and min block number
pull/1398/head
Andrew Cravenho 6 years ago committed by GitHub
commit 32107089d5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 7
      apps/explorer/lib/explorer/application.ex
  2. 35
      apps/explorer/lib/explorer/chain.ex
  3. 93
      apps/explorer/lib/explorer/chain/block_number_cache.ex
  4. 79
      apps/explorer/test/explorer/chain/block_number_cache_test.exs
  5. 20
      apps/explorer/test/explorer/chain_test.exs
  6. 2
      apps/explorer/test/support/data_case.ex

@ -6,6 +6,7 @@ defmodule Explorer.Application do
use Application use Application
alias Explorer.Admin alias Explorer.Admin
alias Explorer.Chain.BlockNumberCache
alias Explorer.Repo.PrometheusLogger alias Explorer.Repo.PrometheusLogger
@impl Application @impl Application
@ -33,7 +34,11 @@ defmodule Explorer.Application do
opts = [strategy: :one_for_one, name: Explorer.Supervisor] opts = [strategy: :one_for_one, name: Explorer.Supervisor]
Supervisor.start_link(children, opts) res = Supervisor.start_link(children, opts)
BlockNumberCache.setup()
res
end end
defp configurable_children do defp configurable_children do

@ -26,6 +26,7 @@ defmodule Explorer.Chain do
Address.CurrentTokenBalance, Address.CurrentTokenBalance,
Address.TokenBalance, Address.TokenBalance,
Block, Block,
BlockNumberCache,
Data, Data,
Hash, Hash,
Import, Import,
@ -881,7 +882,7 @@ defmodule Explorer.Chain do
...> insert(:block, number: index) ...> insert(:block, number: index)
...> end ...> end
iex> Explorer.Chain.indexed_ratio() iex> Explorer.Chain.indexed_ratio()
Decimal.new(1, 50000000000000000000, -20) Decimal.new(1, 50, -2)
If there are no blocks, the percentage is 0. If there are no blocks, the percentage is 0.
@ -891,21 +892,33 @@ defmodule Explorer.Chain do
""" """
@spec indexed_ratio() :: Decimal.t() @spec indexed_ratio() :: Decimal.t()
def indexed_ratio do def indexed_ratio do
# subquery so we need to cast less {min, max} = BlockNumberCache.min_and_max_numbers()
decimal_min_max_query =
case {min, max} do
{0, 0} ->
Decimal.new(0)
_ ->
result = Decimal.div(max - min + 1, max + 1)
Decimal.round(result, 2, :down)
end
end
@spec fetch_min_and_max_block_numbers() :: {non_neg_integer(), non_neg_integer}
def fetch_min_and_max_block_numbers do
query =
from(block in Block, from(block in Block,
select: %{min_number: type(min(block.number), :decimal), max_number: type(max(block.number), :decimal)}, select: {min(block.number), max(block.number)},
where: block.consensus == true where: block.consensus == true
) )
query = result = Repo.one!(query)
from(decimal_min_max in subquery(decimal_min_max_query),
# math on `NULL` returns `NULL` so `coalesce` works as expected
select:
coalesce((decimal_min_max.max_number - decimal_min_max.min_number + 1) / (decimal_min_max.max_number + 1), 0)
)
Repo.one!(query) case result do
{nil, nil} -> {0, 0}
_ -> result
end
end end
@doc """ @doc """

@ -0,0 +1,93 @@
defmodule Explorer.Chain.BlockNumberCache do
@moduledoc """
Cache for max and min block numbers.
"""
alias Explorer.Chain
@tab :block_number_cache
# 30 minutes
@cache_period 1_000 * 60 * 30
@key "min_max"
@opts_key "opts"
@spec setup(Keyword.t()) :: :ok
def setup(opts \\ []) do
if :ets.whereis(@tab) == :undefined do
:ets.new(@tab, [
:set,
:named_table,
:public,
write_concurrency: true
])
end
setup_opts(opts)
update_cache()
:ok
end
def max_number do
value(:max)
end
def min_number do
value(:min)
end
def min_and_max_numbers do
value(:all)
end
defp value(type) do
initial_cache = {_min, _max, old_current_time} = cached_values()
{min, max, _current_time} =
if current_time() - old_current_time > cache_period() do
update_cache()
cached_values()
else
initial_cache
end
case type do
:max -> max
:min -> min
:all -> {min, max}
end
end
defp update_cache do
current_time = current_time()
{min, max} = Chain.fetch_min_and_max_block_numbers()
tuple = {min, max, current_time}
:ets.insert(@tab, {@key, tuple})
end
defp setup_opts(opts) do
cache_period = opts[:cache_period] || @cache_period
:ets.insert(@tab, {@opts_key, cache_period})
end
defp cached_values do
[{_, cached_values}] = :ets.lookup(@tab, @key)
cached_values
end
defp cache_period do
[{_, cache_period}] = :ets.lookup(@tab, @opts_key)
cache_period
end
defp current_time do
utc_now = DateTime.utc_now()
DateTime.to_unix(utc_now, :millisecond)
end
end

@ -0,0 +1,79 @@
defmodule Explorer.Chain.BlockNumberCacheTest do
use Explorer.DataCase
alias Explorer.Chain.BlockNumberCache
describe "max_number/1" do
test "returns max number" do
insert(:block, number: 5)
BlockNumberCache.setup()
assert BlockNumberCache.max_number() == 5
end
test "invalidates cache if period did pass" do
insert(:block, number: 5)
BlockNumberCache.setup(cache_period: 2_000)
assert BlockNumberCache.max_number() == 5
insert(:block, number: 10)
Process.sleep(2_000)
assert BlockNumberCache.max_number() == 10
assert BlockNumberCache.min_number() == 5
end
test "does not invalidate cache if period time did not pass" do
insert(:block, number: 5)
BlockNumberCache.setup(cache_period: 10_000)
assert BlockNumberCache.max_number() == 5
insert(:block, number: 10)
assert BlockNumberCache.max_number() == 5
end
end
describe "min_number/1" do
test "returns max number" do
insert(:block, number: 2)
BlockNumberCache.setup()
assert BlockNumberCache.max_number() == 2
end
test "invalidates cache" do
insert(:block, number: 5)
BlockNumberCache.setup(cache_period: 2_000)
assert BlockNumberCache.min_number() == 5
insert(:block, number: 2)
Process.sleep(2_000)
assert BlockNumberCache.min_number() == 2
assert BlockNumberCache.max_number() == 5
end
test "does not invalidate cache if period time did not pass" do
insert(:block, number: 5)
BlockNumberCache.setup(cache_period: 10_000)
assert BlockNumberCache.max_number() == 5
insert(:block, number: 2)
assert BlockNumberCache.max_number() == 5
end
end
end

@ -937,6 +937,26 @@ defmodule Explorer.ChainTest do
end end
end end
describe "fetch_min_and_max_block_numbers/0" do
test "fetches min and max block numbers" do
for index <- 5..9 do
insert(:block, number: index)
end
assert {5, 9} = Chain.fetch_min_and_max_block_numbers()
end
test "fetches min and max when there are no blocks" do
assert {0, 0} = Chain.fetch_min_and_max_block_numbers()
end
test "fetches min and max where there is only one block" do
insert(:block, number: 1)
assert {1, 1} = Chain.fetch_min_and_max_block_numbers()
end
end
# Full tests in `test/explorer/import_test.exs` # Full tests in `test/explorer/import_test.exs`
describe "import/1" do describe "import/1" do
@import_data %{ @import_data %{

@ -39,6 +39,8 @@ defmodule Explorer.DataCase do
Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo, {:shared, self()}) Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo, {:shared, self()})
end end
Explorer.Chain.BlockNumberCache.setup(cache_period: 0)
:ok :ok
end end

Loading…
Cancel
Save