regularly generate statistics in the background
This commit is contained in:
parent
77ecd6d034
commit
a5ab4fa6a8
4 changed files with 109 additions and 26 deletions
|
@ -1,4 +1,5 @@
|
||||||
package Travelynx::Command::maintenance;
|
package Travelynx::Command::maintenance;
|
||||||
|
|
||||||
# Copyright (C) 2020 Daniel Friesel
|
# Copyright (C) 2020 Daniel Friesel
|
||||||
#
|
#
|
||||||
# SPDX-License-Identifier: MIT
|
# SPDX-License-Identifier: MIT
|
||||||
|
@ -136,6 +137,22 @@ sub run {
|
||||||
|
|
||||||
$tx->commit;
|
$tx->commit;
|
||||||
|
|
||||||
|
# Computing stats may take a while, but we've got all time in the
|
||||||
|
# world here. This means users won't have to wait when loading their
|
||||||
|
# own journey log.
|
||||||
|
say 'Generating missing stats ...';
|
||||||
|
for
|
||||||
|
my $user ( $db->select( 'users', ['id'], { status => 1 } )->hashes->each )
|
||||||
|
{
|
||||||
|
$tx = $db->begin;
|
||||||
|
$self->app->journeys->generate_missing_stats( uid => $user->{id} );
|
||||||
|
$self->app->journeys->get_stats(
|
||||||
|
uid => $user->{id},
|
||||||
|
year => $now->year
|
||||||
|
);
|
||||||
|
$tx->commit;
|
||||||
|
}
|
||||||
|
|
||||||
# Add estimated polylines to journeys logged before 2020-01-28
|
# Add estimated polylines to journeys logged before 2020-01-28
|
||||||
|
|
||||||
$tx = $db->begin;
|
$tx = $db->begin;
|
||||||
|
|
|
@ -288,19 +288,6 @@ sub run {
|
||||||
$self->app->traewelling_api->checkin( %{$candidate},
|
$self->app->traewelling_api->checkin( %{$candidate},
|
||||||
trip_id => $trip_id );
|
trip_id => $trip_id );
|
||||||
}
|
}
|
||||||
|
|
||||||
# Computing yearly stats may take a while, but we've got all time in the
|
|
||||||
# world here. This means users won't have to wait when loading their
|
|
||||||
# own by-year journey log.
|
|
||||||
for my $user ( $db->select( 'users', 'id', { status => 1 } )->hashes->each )
|
|
||||||
{
|
|
||||||
$self->app->journeys->get_stats(
|
|
||||||
uid => $user->{id},
|
|
||||||
year => $now->year
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
# TODO wait until all background jobs have terminated
|
|
||||||
}
|
}
|
||||||
|
|
||||||
1;
|
1;
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
package Travelynx::Model::JourneyStatsCache;
|
package Travelynx::Model::JourneyStatsCache;
|
||||||
|
|
||||||
# Copyright (C) 2020 Daniel Friesel
|
# Copyright (C) 2020 Daniel Friesel
|
||||||
#
|
#
|
||||||
# SPDX-License-Identifier: MIT
|
# SPDX-License-Identifier: MIT
|
||||||
|
@ -19,7 +20,7 @@ sub new {
|
||||||
sub add {
|
sub add {
|
||||||
my ( $self, %opt ) = @_;
|
my ( $self, %opt ) = @_;
|
||||||
|
|
||||||
my $db = $opt{db} // $self->{pg}->db;
|
my $db = $opt{db} // $self->{pg}->db;
|
||||||
|
|
||||||
eval {
|
eval {
|
||||||
$db->insert(
|
$db->insert(
|
||||||
|
@ -28,20 +29,20 @@ sub add {
|
||||||
user_id => $opt{uid},
|
user_id => $opt{uid},
|
||||||
year => $opt{year},
|
year => $opt{year},
|
||||||
month => $opt{month},
|
month => $opt{month},
|
||||||
data => JSON->new->encode($opt{stats}),
|
data => JSON->new->encode( $opt{stats} ),
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
if ( my $err = $@ ) {
|
if ( my $err = $@ ) {
|
||||||
if ( $err =~ m{duplicate key value violates unique constraint} )
|
if ( $err =~ m{duplicate key value violates unique constraint} ) {
|
||||||
{
|
|
||||||
# If a user opens the same history page several times in
|
# If a user opens the same history page several times in
|
||||||
# short succession, there is a race condition where several
|
# short succession, there is a race condition where several
|
||||||
# Mojolicious workers execute this helper, notice that there is
|
# Mojolicious workers execute this helper, notice that there is
|
||||||
# no up-to-date history, compute it, and insert it using the
|
# no up-to-date history, compute it, and insert it using the
|
||||||
# statement above. This will lead to a uniqueness violation
|
# statement above. This will lead to a uniqueness violation
|
||||||
# in each successive insert. However, this is harmless, and
|
# in each successive insert. However, this is harmless, and
|
||||||
# thus ignored.
|
# thus ignored.
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
# Otherwise we probably have a problem.
|
# Otherwise we probably have a problem.
|
||||||
|
@ -53,7 +54,7 @@ sub add {
|
||||||
sub get {
|
sub get {
|
||||||
my ( $self, %opt ) = @_;
|
my ( $self, %opt ) = @_;
|
||||||
|
|
||||||
my $db = $opt{db} // $self->{pg}->db;
|
my $db = $opt{db} // $self->{pg}->db;
|
||||||
|
|
||||||
my $stats = $db->select(
|
my $stats = $db->select(
|
||||||
'journey_stats',
|
'journey_stats',
|
||||||
|
@ -97,4 +98,25 @@ sub invalidate {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sub get_yyyymm_having_stats {
|
||||||
|
my ( $self, %opt ) = @_;
|
||||||
|
my $uid = $opt{uid};
|
||||||
|
my $db = $opt{db} // $self->{pg}->db;
|
||||||
|
my $res = $db->select(
|
||||||
|
'journey_stats',
|
||||||
|
[ 'year', 'month' ],
|
||||||
|
{ user_id => $uid },
|
||||||
|
{ order_by => { -asc => [ 'year', 'month' ] } }
|
||||||
|
);
|
||||||
|
|
||||||
|
my @ret;
|
||||||
|
for my $row ( $res->hashes->each ) {
|
||||||
|
if ( $row->{month} != 0 ) {
|
||||||
|
push( @ret, [ $row->{year}, $row->{month} ] );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return @ret;
|
||||||
|
}
|
||||||
|
|
||||||
1;
|
1;
|
||||||
|
|
|
@ -763,6 +763,62 @@ sub get_months_for_year {
|
||||||
return @ret;
|
return @ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sub get_yyyymm_having_journeys {
|
||||||
|
my ( $self, %opt ) = @_;
|
||||||
|
my $uid = $opt{uid};
|
||||||
|
my $db = $opt{db} // $self->{pg}->db;
|
||||||
|
my $res = $db->select(
|
||||||
|
'journeys',
|
||||||
|
"distinct to_char(real_departure, 'YYYY.MM') as yearmonth",
|
||||||
|
{ user_id => $uid },
|
||||||
|
{ order_by => { -asc => 'yearmonth' } }
|
||||||
|
);
|
||||||
|
|
||||||
|
my @ret;
|
||||||
|
for my $row ( $res->hashes->each ) {
|
||||||
|
push( @ret, [ split( qr{[.]}, $row->{yearmonth} ) ] );
|
||||||
|
}
|
||||||
|
|
||||||
|
return @ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub generate_missing_stats {
|
||||||
|
my ( $self, %opt ) = @_;
|
||||||
|
my $uid = $opt{uid};
|
||||||
|
my $db = $opt{db} // $self->{pg}->db;
|
||||||
|
my @journey_months = $self->get_yyyymm_having_journeys(
|
||||||
|
uid => $uid,
|
||||||
|
db => $db
|
||||||
|
);
|
||||||
|
my @stats_months = $self->stats_cache->get_yyyymm_having_stats(
|
||||||
|
uid => $uid,
|
||||||
|
$db => $db
|
||||||
|
);
|
||||||
|
|
||||||
|
my $stats_index = 0;
|
||||||
|
|
||||||
|
for my $journey_index ( 0 .. $#journey_months ) {
|
||||||
|
if ( $stats_index < @stats_months
|
||||||
|
and $journey_months[$journey_index][0]
|
||||||
|
== $stats_months[$stats_index][0]
|
||||||
|
and $journey_months[$journey_index][1]
|
||||||
|
== $stats_months[$stats_index][1] )
|
||||||
|
{
|
||||||
|
$stats_index++;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
my ( $year, $month ) = @{ $journey_months[$journey_index] };
|
||||||
|
$self->get_stats(
|
||||||
|
uid => $uid,
|
||||||
|
db => $db,
|
||||||
|
year => $year,
|
||||||
|
month => $month,
|
||||||
|
write_only => 1
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
sub get_nav_months {
|
sub get_nav_months {
|
||||||
my ( $self, %opt ) = @_;
|
my ( $self, %opt ) = @_;
|
||||||
|
|
||||||
|
@ -1048,7 +1104,8 @@ sub get_stats {
|
||||||
# checks out of a train or manually edits/adds a journey.
|
# checks out of a train or manually edits/adds a journey.
|
||||||
|
|
||||||
if (
|
if (
|
||||||
my $stats = $self->stats_cache->get(
|
not $opt{write_only}
|
||||||
|
and my $stats = $self->stats_cache->get(
|
||||||
uid => $uid,
|
uid => $uid,
|
||||||
db => $db,
|
db => $db,
|
||||||
year => $year,
|
year => $year,
|
||||||
|
|
Loading…
Reference in a new issue