mirror of
https://github.com/Freika/dawarich.git
synced 2026-01-10 17:21:38 -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/)
|
The format is based on [Keep a Changelog](http://keepachangelog.com/)
|
||||||
and this project adheres to [Semantic Versioning](http://semver.org/).
|
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
|
# [0.30.1] - 2025-07-22
|
||||||
|
|
||||||
## Fixed
|
## Fixed
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ class StatsController < ApplicationController
|
||||||
def show
|
def show
|
||||||
@year = params[:year].to_i
|
@year = params[:year].to_i
|
||||||
@stats = current_user.stats.where(year: @year).order(:month)
|
@stats = current_user.stats.where(year: @year).order(:month)
|
||||||
|
@year_distances = { @year => Stat.year_distance(@year, current_user) }
|
||||||
end
|
end
|
||||||
|
|
||||||
def update
|
def update
|
||||||
|
|
|
||||||
|
|
@ -37,12 +37,7 @@ class Stat < ApplicationRecord
|
||||||
end
|
end
|
||||||
|
|
||||||
def calculate_daily_distances(monthly_points)
|
def calculate_daily_distances(monthly_points)
|
||||||
timespan.to_a.map.with_index(1) do |day, index|
|
Stats::DailyDistanceQuery.new(monthly_points, timespan).call
|
||||||
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
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def filter_points_for_day(points, day)
|
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)
|
last_calculated_at ||= DateTime.new(1970, 1, 1)
|
||||||
|
|
||||||
time_diff = last_calculated_at.to_i..Time.current.to_i
|
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
|
end
|
||||||
|
|
||||||
def extract_months(timestamps)
|
def extract_months(timestamps)
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,7 @@ class Stats::CalculateMonth
|
||||||
.tracked_points
|
.tracked_points
|
||||||
.without_raw_data
|
.without_raw_data
|
||||||
.where(timestamp: start_timestamp..end_timestamp)
|
.where(timestamp: start_timestamp..end_timestamp)
|
||||||
.select(:lonlat, :timestamp, :city, :country)
|
.select(:lonlat, :timestamp)
|
||||||
.order(timestamp: :asc)
|
.order(timestamp: :asc)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -59,7 +59,13 @@ class Stats::CalculateMonth
|
||||||
end
|
end
|
||||||
|
|
||||||
def toponyms
|
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
|
end
|
||||||
|
|
||||||
def create_stats_update_failed_notification(user, error)
|
def create_stats_update_failed_notification(user, error)
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@
|
||||||
|
|
||||||
<%= area_chart(
|
<%= area_chart(
|
||||||
stat.daily_distance.map { |day, distance_meters|
|
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',
|
height: '200px',
|
||||||
suffix: " #{current_user.safe_settings.distance_unit}",
|
suffix: " #{current_user.safe_settings.distance_unit}",
|
||||||
|
|
|
||||||
|
|
@ -83,7 +83,7 @@
|
||||||
<% end %>
|
<% end %>
|
||||||
<%= column_chart(
|
<%= column_chart(
|
||||||
@year_distances[year].map { |month_name, distance_meters|
|
@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',
|
height: '200px',
|
||||||
suffix: " #{current_user.safe_settings.distance_unit}",
|
suffix: " #{current_user.safe_settings.distance_unit}",
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,8 @@ class AddIndexOnPlacesGeodataOsmId < ActiveRecord::Migration[8.0]
|
||||||
disable_ddl_transaction!
|
disable_ddl_transaction!
|
||||||
|
|
||||||
def change
|
def change
|
||||||
add_index :places, "(geodata->'properties'->>'osm_id')",
|
add_index :places, "(geodata->'properties'->>'osm_id')",
|
||||||
using: :btree,
|
using: :btree,
|
||||||
name: 'index_places_on_geodata_osm_id',
|
name: 'index_places_on_geodata_osm_id',
|
||||||
algorithm: :concurrently
|
algorithm: :concurrently
|
||||||
end
|
end
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue