From 15ffd1c996ec8657261f28022b5894755a093dda Mon Sep 17 00:00:00 2001 From: Victor Baranov Date: Mon, 9 Nov 2020 18:09:37 +0300 Subject: [PATCH] Rewrite missing blocks range query --- CHANGELOG.md | 1 + apps/explorer/lib/explorer/chain.ex | 108 +++++++++++++++------------- 2 files changed, 58 insertions(+), 51 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ddfa351d2..1e2cdce886 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ ### Fixes +- [#3440](https://github.com/poanetwork/blockscout/pull/3440) - Rewrite missing blocks range query - [#3439](https://github.com/poanetwork/blockscout/pull/3439) - Dark mode color fixes (search, charts) - [#3437](https://github.com/poanetwork/blockscout/pull/3437) - Fix Postgres Docker container - [#3428](https://github.com/poanetwork/blockscout/pull/3428) - Fix address tokens search diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index 809d9b4b36..1d3b426f2a 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -17,7 +17,6 @@ defmodule Explorer.Chain do select: 3, subquery: 1, union: 2, - union_all: 2, where: 2, where: 3 ] @@ -2456,66 +2455,73 @@ defmodule Explorer.Chain do range_min = min(range_start, range_end) range_max = max(range_start, range_end) - missing_prefix_query = - from(block in Block, - select: %{min: type(^range_min, block.number), max: min(block.number) - 1}, - where: block.consensus == true, - having: ^range_min < min(block.number) and min(block.number) < ^range_max - ) - - missing_suffix_query = - from(block in Block, - select: %{min: max(block.number) + 1, max: type(^range_max, block.number)}, - where: block.consensus == true, - having: ^range_min < max(block.number) and max(block.number) < ^range_max + ordered_missing_query = + from(b in Block, + right_join: + missing_range in fragment( + """ + (SELECT distinct b1.number + FROM generate_series((?)::integer, (?)::integer) AS b1(number) + WHERE NOT EXISTS + (SELECT 1 FROM blocks b2 WHERE b2.number=b1.number AND b2.consensus)) + """, + ^range_min, + ^range_max + ), + on: b.number == missing_range.number, + select: missing_range.number, + order_by: missing_range.number, + distinct: missing_range.number ) - missing_infix_query = - from(block in Block, - select: %{min: type(^range_min, block.number), max: type(^range_max, block.number)}, - where: block.consensus == true, - having: - (is_nil(min(block.number)) and is_nil(max(block.number))) or - (^range_max < min(block.number) or max(block.number) < ^range_min) - ) + missing_blocks = Repo.all(ordered_missing_query, timeout: :infinity) - # Gaps and Islands is the term-of-art for finding the runs of missing (gaps) and existing (islands) data. If you - # Google for `sql missing ranges` you won't find much, but `sql gaps and islands` will get a lot of hits. + [block_ranges, last_block_range_start, last_block_range_end] = + missing_blocks + |> Enum.reduce([[], nil, nil], fn block_number, [block_ranges, last_block_range_start, last_block_range_end] -> + cond do + !last_block_range_start -> + [block_ranges, block_number, block_number] - land_query = - from(block in Block, - where: block.consensus == true and ^range_min <= block.number and block.number <= ^range_max, - windows: [w: [order_by: block.number]], - select: %{last_number: block.number |> lag() |> over(:w), next_number: block.number} - ) + block_number == last_block_range_end + 1 -> + [block_ranges, last_block_range_start, block_number] - gap_query = - from( - coastline in subquery(land_query), - where: coastline.last_number != coastline.next_number - 1, - select: %{min: coastline.last_number + 1, max: coastline.next_number - 1} - ) - - missing_query = - missing_prefix_query - |> union_all(^missing_infix_query) - |> union_all(^gap_query) - |> union_all(^missing_suffix_query) + true -> + block_ranges = block_ranges_extend(block_ranges, last_block_range_start, last_block_range_end) + [block_ranges, block_number, block_number] + end + end) - {first, last, direction} = - if range_start <= range_end do - {:min, :max, :asc} + final_block_ranges = + if last_block_range_start && last_block_range_end do + block_ranges_extend(block_ranges, last_block_range_start, last_block_range_end) else - {:max, :min, :desc} + block_ranges end - ordered_missing_query = - from(missing_range in subquery(missing_query), - select: %Range{first: field(missing_range, ^first), last: field(missing_range, ^last)}, - order_by: [{^direction, field(missing_range, ^first)}] - ) + ordered_block_ranges = + final_block_ranges + |> Enum.sort(fn %Range{first: first1, last: _}, %Range{first: first2, last: _} -> + if range_start <= range_end do + first1 <= first2 + else + first1 >= first2 + end + end) + |> Enum.map(fn %Range{first: first, last: last} = range -> + if range_start <= range_end do + range + else + %Range{first: last, last: first} + end + end) + + ordered_block_ranges + end - Repo.all(ordered_missing_query, timeout: :infinity) + defp block_ranges_extend(block_ranges, block_range_start, block_range_end) do + # credo:disable-for-next-line + block_ranges ++ [Range.new(block_range_start, block_range_end)] end @doc """