indexes
This commit is contained in:
parent
b620e2891a
commit
5bdb40b9f8
4 changed files with 103 additions and 8 deletions
|
|
@ -404,6 +404,35 @@ defmodule Aprsme.Packets do
|
|||
get_packets_for_replay(opts_with_time)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets recent packets optimized for initial map load.
|
||||
This uses a more efficient query pattern for the most common use case.
|
||||
"""
|
||||
@spec get_recent_packets_optimized(map()) :: [struct()]
|
||||
def get_recent_packets_optimized(opts \\ %{}) do
|
||||
# Always limit to the last hour for initial load
|
||||
one_hour_ago = DateTime.add(DateTime.utc_now(), -3600, :second)
|
||||
limit = Map.get(opts, :limit, 500)
|
||||
|
||||
# Use a more efficient query that leverages the partial indexes
|
||||
base_query =
|
||||
from(p in Packet,
|
||||
where: p.has_position == true,
|
||||
where: p.received_at >= ^one_hour_ago,
|
||||
order_by: [desc: p.received_at],
|
||||
limit: ^limit
|
||||
)
|
||||
|
||||
query =
|
||||
base_query
|
||||
|> filter_by_region(opts)
|
||||
|> filter_by_callsign(opts)
|
||||
|> filter_by_map_bounds(opts)
|
||||
|> select_with_virtual_coordinates()
|
||||
|
||||
Repo.all(query)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets weather packets for a specific callsign within a time range.
|
||||
This is optimized for weather queries by filtering at the database level.
|
||||
|
|
|
|||
|
|
@ -374,7 +374,9 @@ defmodule AprsmeWeb.MapLive.Index do
|
|||
}
|
||||
|
||||
socket = assign(socket, map_bounds: map_bounds)
|
||||
socket = load_historical_packets_for_bounds(socket, map_bounds)
|
||||
|
||||
# Use optimized query for initial load
|
||||
socket = load_historical_packets_for_bounds_optimized(socket, map_bounds)
|
||||
{:noreply, socket}
|
||||
else
|
||||
{:noreply, socket}
|
||||
|
|
@ -695,7 +697,7 @@ defmodule AprsmeWeb.MapLive.Index do
|
|||
</svg>
|
||||
<h2 class="text-xl font-bold">APRS.me</h2>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Close button for mobile -->
|
||||
<button
|
||||
class="lg:hidden text-white hover:text-slate-200 transition-colors"
|
||||
|
|
@ -718,7 +720,7 @@ defmodule AprsmeWeb.MapLive.Index do
|
|||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Content -->
|
||||
<div class="p-6 space-y-6 bg-slate-50 flex-1 overflow-y-auto">
|
||||
<!-- Callsign Search -->
|
||||
|
|
@ -751,7 +753,7 @@ defmodule AprsmeWeb.MapLive.Index do
|
|||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Trail Duration -->
|
||||
<div class="space-y-4">
|
||||
<label class="block text-sm font-semibold text-slate-700 flex items-center space-x-2">
|
||||
|
|
@ -810,7 +812,7 @@ defmodule AprsmeWeb.MapLive.Index do
|
|||
<span>How long should position trails be displayed</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Historical Data -->
|
||||
<div class="space-y-4">
|
||||
<label class="block text-sm font-semibold text-slate-700 flex items-center space-x-2">
|
||||
|
|
@ -863,7 +865,7 @@ defmodule AprsmeWeb.MapLive.Index do
|
|||
<span>How many hours of historical packets to load</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Navigation -->
|
||||
<div class="pt-4 border-t border-slate-200 space-y-3">
|
||||
<div class="flex items-center space-x-2 text-sm text-slate-600 mb-3">
|
||||
|
|
@ -879,7 +881,7 @@ defmodule AprsmeWeb.MapLive.Index do
|
|||
</div>
|
||||
<.navigation variant={:vertical} class="text-sm" />
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Deployment Information -->
|
||||
<div class="pt-4 border-t border-slate-200 space-y-3">
|
||||
<div class="flex items-center space-x-2 text-sm text-slate-600">
|
||||
|
|
@ -1123,6 +1125,32 @@ defmodule AprsmeWeb.MapLive.Index do
|
|||
end
|
||||
end
|
||||
|
||||
@spec load_historical_packets_for_bounds_optimized(Socket.t(), map()) :: Socket.t()
|
||||
defp load_historical_packets_for_bounds_optimized(socket, map_bounds) do
|
||||
bounds = [
|
||||
map_bounds.west,
|
||||
map_bounds.south,
|
||||
map_bounds.east,
|
||||
map_bounds.north
|
||||
]
|
||||
|
||||
# Use the optimized query for initial load
|
||||
packets_module = Application.get_env(:aprsme, :packets_module, Aprsme.Packets)
|
||||
|
||||
historical_packets =
|
||||
packets_module.get_recent_packets_optimized(%{
|
||||
bounds: bounds,
|
||||
# Smaller limit for faster initial load
|
||||
limit: 500
|
||||
})
|
||||
|
||||
if Enum.empty?(historical_packets) do
|
||||
assign(socket, historical_loaded: true)
|
||||
else
|
||||
process_historical_packets(socket, historical_packets)
|
||||
end
|
||||
end
|
||||
|
||||
@spec within_bounds?(map() | struct(), map()) :: boolean()
|
||||
defp within_bounds?(packet, bounds) do
|
||||
{lat, lon, _data_extended} = MapHelpers.get_coordinates(packet)
|
||||
|
|
|
|||
|
|
@ -244,7 +244,9 @@
|
|||
<% end %>
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.5.0/dist/chart.umd.js">
|
||||
</script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/chartjs-adapter-date-fns@3.0.0/dist/chartjs-adapter-date-fns.bundle.min.js">
|
||||
<script
|
||||
src="https://cdn.jsdelivr.net/npm/chartjs-adapter-date-fns@3.0.0/dist/chartjs-adapter-date-fns.bundle.min.js"
|
||||
>
|
||||
</script>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
36
priv/repo/migrations/20250704174406_optimize_map_queries.exs
Normal file
36
priv/repo/migrations/20250704174406_optimize_map_queries.exs
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
defmodule Aprsme.Repo.Migrations.OptimizeMapQueries do
|
||||
use Ecto.Migration
|
||||
@disable_ddl_transaction true
|
||||
|
||||
def up do
|
||||
# Add compound index for the most common map query pattern
|
||||
create_if_not_exists index(:packets, [:has_position, :received_at])
|
||||
create_if_not_exists index(:packets, [:sender, :received_at])
|
||||
create_if_not_exists index(:packets, [:sender, :has_position])
|
||||
create_if_not_exists index(:packets, [:data_type])
|
||||
create_if_not_exists index(:packets, [:data_type, :received_at])
|
||||
create_if_not_exists index(:packets, [:symbol_table_id, :symbol_code, :received_at])
|
||||
create_if_not_exists index(:packets, [:device_identifier])
|
||||
create_if_not_exists index(:packets, [:region, :has_position, :received_at])
|
||||
|
||||
# Add partial index for weather packets (this is valid)
|
||||
execute """
|
||||
CREATE INDEX CONCURRENTLY IF NOT EXISTS packets_weather_idx
|
||||
ON packets (received_at DESC)
|
||||
WHERE data_type = 'weather'
|
||||
OR (symbol_table_id = '/' AND symbol_code = '_')
|
||||
"""
|
||||
end
|
||||
|
||||
def down do
|
||||
execute "DROP INDEX IF EXISTS packets_weather_idx"
|
||||
drop index(:packets, [:region, :has_position, :received_at])
|
||||
drop index(:packets, [:device_identifier])
|
||||
drop index(:packets, [:symbol_table_id, :symbol_code, :received_at])
|
||||
drop index(:packets, [:data_type, :received_at])
|
||||
drop index(:packets, [:data_type])
|
||||
drop index(:packets, [:sender, :has_position])
|
||||
drop index(:packets, [:sender, :received_at])
|
||||
drop index(:packets, [:has_position, :received_at])
|
||||
end
|
||||
end
|
||||
Loading…
Add table
Reference in a new issue