This commit is contained in:
Graham McIntire 2025-07-04 12:49:35 -05:00
parent b620e2891a
commit 5bdb40b9f8
No known key found for this signature in database
4 changed files with 103 additions and 8 deletions

View file

@ -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.

View file

@ -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)

View file

@ -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>

View 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