mirror of
https://github.com/Freika/dawarich.git
synced 2026-01-10 01:01:39 -05:00
Fix stats calculation performance
This commit is contained in:
parent
bd2558ed29
commit
0c904a6b84
10 changed files with 87 additions and 14 deletions
|
|
@ -1 +1 @@
|
|||
0.30.1
|
||||
0.30.2
|
||||
|
|
|
|||
|
|
@ -4,6 +4,13 @@ All notable changes to this project will be documented in this file.
|
|||
The format is based on [Keep a Changelog](http://keepachangelog.com/)
|
||||
and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
# [0.30.2] - 2025-07-22
|
||||
|
||||
## Fixed
|
||||
|
||||
- Stats calculation is now significantly faster.
|
||||
|
||||
|
||||
# [0.30.1] - 2025-07-22
|
||||
|
||||
## Fixed
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ class StatsController < ApplicationController
|
|||
def show
|
||||
@year = params[:year].to_i
|
||||
@stats = current_user.stats.where(year: @year).order(:month)
|
||||
@year_distances = { @year => Stat.year_distance(@year, current_user) }
|
||||
end
|
||||
|
||||
def update
|
||||
|
|
|
|||
|
|
@ -37,12 +37,7 @@ class Stat < ApplicationRecord
|
|||
end
|
||||
|
||||
def calculate_daily_distances(monthly_points)
|
||||
timespan.to_a.map.with_index(1) do |day, index|
|
||||
daily_points = filter_points_for_day(monthly_points, day)
|
||||
# Calculate distance in meters for consistent storage
|
||||
distance_meters = Point.total_distance(daily_points, :m)
|
||||
[index, distance_meters.round]
|
||||
end
|
||||
Stats::DailyDistanceQuery.new(monthly_points, timespan).call
|
||||
end
|
||||
|
||||
def filter_points_for_day(points, day)
|
||||
|
|
|
|||
64
app/queries/stats/daily_distance_query.rb
Normal file
64
app/queries/stats/daily_distance_query.rb
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Stats::DailyDistanceQuery
|
||||
def initialize(monthly_points, timespan)
|
||||
@monthly_points = monthly_points
|
||||
@timespan = timespan
|
||||
end
|
||||
|
||||
def call
|
||||
daily_distances = daily_distances(monthly_points)
|
||||
distance_by_day_map = distance_by_day_map(daily_distances)
|
||||
|
||||
convert_to_daily_distances(distance_by_day_map)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :monthly_points, :timespan
|
||||
|
||||
def daily_distances(monthly_points)
|
||||
Stat.connection.select_all(<<-SQL.squish)
|
||||
WITH points_with_distances AS (
|
||||
SELECT
|
||||
EXTRACT(day FROM to_timestamp(timestamp)) as day_of_month,
|
||||
CASE
|
||||
WHEN LAG(lonlat) OVER (
|
||||
PARTITION BY EXTRACT(day FROM to_timestamp(timestamp))
|
||||
ORDER BY timestamp
|
||||
) IS NOT NULL THEN
|
||||
ST_Distance(
|
||||
lonlat::geography,
|
||||
LAG(lonlat) OVER (
|
||||
PARTITION BY EXTRACT(day FROM to_timestamp(timestamp))
|
||||
ORDER BY timestamp
|
||||
)::geography
|
||||
)
|
||||
ELSE 0
|
||||
END as segment_distance
|
||||
FROM (#{monthly_points.to_sql}) as points
|
||||
)
|
||||
SELECT
|
||||
day_of_month,
|
||||
ROUND(COALESCE(SUM(segment_distance), 0)) as distance_meters
|
||||
FROM points_with_distances
|
||||
GROUP BY day_of_month
|
||||
ORDER BY day_of_month
|
||||
SQL
|
||||
end
|
||||
|
||||
def distance_by_day_map(daily_distances)
|
||||
daily_distances.index_by do |row|
|
||||
row['day_of_month'].to_i
|
||||
end
|
||||
end
|
||||
|
||||
def convert_to_daily_distances(distance_by_day_map)
|
||||
timespan.to_a.map.with_index(1) do |day, index|
|
||||
distance_meters =
|
||||
distance_by_day_map[day.day]&.fetch('distance_meters', 0) || 0
|
||||
|
||||
[index, distance_meters.to_i]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -21,7 +21,7 @@ module Stats
|
|||
last_calculated_at ||= DateTime.new(1970, 1, 1)
|
||||
|
||||
time_diff = last_calculated_at.to_i..Time.current.to_i
|
||||
Point.where(user_id:, timestamp: time_diff).pluck(:timestamp)
|
||||
Point.where(user_id:, timestamp: time_diff).pluck(:timestamp).uniq
|
||||
end
|
||||
|
||||
def extract_months(timestamps)
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ class Stats::CalculateMonth
|
|||
.tracked_points
|
||||
.without_raw_data
|
||||
.where(timestamp: start_timestamp..end_timestamp)
|
||||
.select(:lonlat, :timestamp, :city, :country)
|
||||
.select(:lonlat, :timestamp)
|
||||
.order(timestamp: :asc)
|
||||
end
|
||||
|
||||
|
|
@ -59,7 +59,13 @@ class Stats::CalculateMonth
|
|||
end
|
||||
|
||||
def toponyms
|
||||
CountriesAndCities.new(points).call
|
||||
toponym_points = user
|
||||
.tracked_points
|
||||
.without_raw_data
|
||||
.where(timestamp: start_timestamp..end_timestamp)
|
||||
.select(:city, :country)
|
||||
.distinct
|
||||
CountriesAndCities.new(toponym_points).call
|
||||
end
|
||||
|
||||
def create_stats_update_failed_notification(user, error)
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@
|
|||
|
||||
<%= area_chart(
|
||||
stat.daily_distance.map { |day, distance_meters|
|
||||
[day, Stat.convert_distance(distance_meters, current_user.safe_settings.distance_unit).round(2)]
|
||||
[day, Stat.convert_distance(distance_meters, current_user.safe_settings.distance_unit).round]
|
||||
},
|
||||
height: '200px',
|
||||
suffix: " #{current_user.safe_settings.distance_unit}",
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@
|
|||
<% end %>
|
||||
<%= column_chart(
|
||||
@year_distances[year].map { |month_name, distance_meters|
|
||||
[month_name, Stat.convert_distance(distance_meters, current_user.safe_settings.distance_unit).round(2)]
|
||||
[month_name, Stat.convert_distance(distance_meters, current_user.safe_settings.distance_unit).round]
|
||||
},
|
||||
height: '200px',
|
||||
suffix: " #{current_user.safe_settings.distance_unit}",
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@ class AddIndexOnPlacesGeodataOsmId < ActiveRecord::Migration[8.0]
|
|||
disable_ddl_transaction!
|
||||
|
||||
def change
|
||||
add_index :places, "(geodata->'properties'->>'osm_id')",
|
||||
using: :btree,
|
||||
add_index :places, "(geodata->'properties'->>'osm_id')",
|
||||
using: :btree,
|
||||
name: 'index_places_on_geodata_osm_id',
|
||||
algorithm: :concurrently
|
||||
end
|
||||
|
|
|
|||
Loading…
Reference in a new issue