From ed3de0b8324e02bcc6e66f771959528600c4a2c9 Mon Sep 17 00:00:00 2001 From: Mateu Hunter Date: Sun, 5 Apr 2026 13:19:23 -0600 Subject: [PATCH] perf: optimize portable update_points database path --- lib/Bracket/Model/DBIC.pm | 230 +++++++++++++++++++++++--------------- t/model_update_points.t | 4 + 2 files changed, 143 insertions(+), 91 deletions(-) diff --git a/lib/Bracket/Model/DBIC.pm b/lib/Bracket/Model/DBIC.pm index b78c0c0..2a1bb11 100644 --- a/lib/Bracket/Model/DBIC.pm +++ b/lib/Bracket/Model/DBIC.pm @@ -155,34 +155,63 @@ sub _update_points_portable { my @perfect_picks = $schema->resultset('Pick')->search( { player => 1 }, - { columns => [qw/game pick/] } + { + columns => [qw/game pick/], + result_class => 'DBIx::Class::ResultClass::HashRefInflator', + } )->all; my %perfect_winner_for_game = map { - $_->get_column('game') => $_->get_column('pick') + $_->{game} => $_->{pick} } @perfect_picks; - my %games = map { - $_->get_column('id') => $_ - } $schema->resultset('Game')->search({})->all; - my %teams = map { - $_->get_column('id') => $_ - } $schema->resultset('Team')->search({})->all; + my %game_round_for = map { + $_->{id} => $_->{round} + } $schema->resultset('Game')->search( + {}, + { + columns => [qw/id round/], + result_class => 'DBIx::Class::ResultClass::HashRefInflator', + } + )->all; + + my %team_seed_for = map { + $_->{id} => $_->{seed} + } $schema->resultset('Team')->search( + {}, + { + columns => [qw/id seed/], + result_class => 'DBIx::Class::ResultClass::HashRefInflator', + } + )->all; my %parent_games; - foreach my $edge ($schema->resultset('GameGraph')->search({}, { order_by => [qw/game parent_game/] })->all) { - push @{$parent_games{ $edge->get_column('game') }}, $edge->get_column('parent_game'); + foreach my $edge ($schema->resultset('GameGraph')->search( + {}, + { + columns => [qw/game parent_game/], + order_by => [qw/game parent_game/], + result_class => 'DBIx::Class::ResultClass::HashRefInflator', + } + )->all) { + push @{$parent_games{ $edge->{game} }}, $edge->{parent_game}; } my %seeded_teams; - foreach my $row ($schema->resultset('GameTeamGraph')->search({}, { order_by => [qw/game team/] })->all) { - push @{$seeded_teams{ $row->get_column('game') }}, $row->get_column('team'); + foreach my $row ($schema->resultset('GameTeamGraph')->search( + {}, + { + columns => [qw/game team/], + order_by => [qw/game team/], + result_class => 'DBIx::Class::ResultClass::HashRefInflator', + } + )->all) { + push @{$seeded_teams{ $row->{game} }}, $row->{team}; } my $current_round = 0; foreach my $game_id (keys %perfect_winner_for_game) { - my $game = $games{$game_id}; - next if !$game; - my $round = $game->get_column('round'); + my $round = $game_round_for{$game_id}; + next if !defined $round; $current_round = $round if $round > $current_round; } @@ -191,103 +220,122 @@ sub _update_points_portable { } $schema->txn_do(sub { - foreach my $game_id (sort { $a <=> $b } keys %games) { - my $game = $games{$game_id}; - next if !$game || $game->get_column('round') != $current_round; - my $winner_team_id = $perfect_winner_for_game{$game_id}; - next if !$winner_team_id; - - my $winner_team = $teams{$winner_team_id}; - next if !$winner_team; - - my $lower_seed = 0; - if ($game->get_column('round') == 1) { - $lower_seed = $winner_team->get_column('seed') > 8 ? 1 : 0; - } - else { - my @parents = @{$parent_games{$game_id} || []}; - my @parent_winners = map { $perfect_winner_for_game{$_} } grep { exists $perfect_winner_for_game{$_} } @parents; - my $loser_team_id = first { defined $_ && $_ != $winner_team_id } @parent_winners; - if (defined $loser_team_id && $teams{$loser_team_id}) { - $lower_seed = $winner_team->get_column('seed') > $teams{$loser_team_id}->get_column('seed') ? 1 : 0; + $schema->storage->dbh_do(sub { + my ($storage, $dbh) = @_; + my $update_game = $dbh->prepare('update game set winner = ?, lower_seed = ? where id = ?'); + + foreach my $game_id (sort { $a <=> $b } keys %game_round_for) { + next if $game_round_for{$game_id} != $current_round; + my $winner_team_id = $perfect_winner_for_game{$game_id}; + next if !$winner_team_id; + my $winner_seed = $team_seed_for{$winner_team_id}; + next if !defined $winner_seed; + + my $lower_seed = 0; + if ($game_round_for{$game_id} == 1) { + $lower_seed = $winner_seed > 8 ? 1 : 0; + } + else { + my @parents = @{$parent_games{$game_id} || []}; + my @parent_winners = map { $perfect_winner_for_game{$_} } grep { exists $perfect_winner_for_game{$_} } @parents; + my $loser_team_id = first { defined $_ && $_ != $winner_team_id } @parent_winners; + my $loser_seed = defined $loser_team_id ? $team_seed_for{$loser_team_id} : undef; + if (defined $loser_seed) { + $lower_seed = $winner_seed > $loser_seed ? 1 : 0; + } } - } - $game->update({ - winner => $winner_team_id, - lower_seed => $lower_seed, - }); - } + $update_game->execute($winner_team_id, $lower_seed, $game_id); + } + }); }); $current_time = time(); $times{lower_seed} = $current_time - $previous_time; $previous_time = $current_time; $schema->txn_do(sub { - foreach my $game_id (sort { $a <=> $b } keys %games) { - my $game = $games{$game_id}; - next if !$game || $game->get_column('round') != $current_round; - my $winner_team_id = $perfect_winner_for_game{$game_id}; - next if !$winner_team_id; - - my $loser_team_id; - if ($current_round == 1) { - my @team_ids = @{$seeded_teams{$game_id} || []}; - $loser_team_id = first { $_ != $winner_team_id } @team_ids; - } - else { - my @parents = @{$parent_games{$game_id} || []}; - my @parent_winners = map { $perfect_winner_for_game{$_} } grep { exists $perfect_winner_for_game{$_} } @parents; - $loser_team_id = first { defined $_ && $_ != $winner_team_id } @parent_winners; - } + $schema->storage->dbh_do(sub { + my ($storage, $dbh) = @_; + my $update_team_round_out = $dbh->prepare('update team set round_out = ? where id = ?'); + + foreach my $game_id (sort { $a <=> $b } keys %game_round_for) { + next if $game_round_for{$game_id} != $current_round; + my $winner_team_id = $perfect_winner_for_game{$game_id}; + next if !$winner_team_id; + + my $loser_team_id; + if ($current_round == 1) { + my @team_ids = @{$seeded_teams{$game_id} || []}; + $loser_team_id = first { $_ != $winner_team_id } @team_ids; + } + else { + my @parents = @{$parent_games{$game_id} || []}; + my @parent_winners = map { $perfect_winner_for_game{$_} } grep { exists $perfect_winner_for_game{$_} } @parents; + $loser_team_id = first { defined $_ && $_ != $winner_team_id } @parent_winners; + } - next if !defined $loser_team_id || !$teams{$loser_team_id}; - $teams{$loser_team_id}->update({ round_out => $current_round }); - } + next if !defined $loser_team_id || !exists $team_seed_for{$loser_team_id}; + $update_team_round_out->execute($current_round, $loser_team_id); + } + }); }); $current_time = time(); $times{round_out} = $current_time - $previous_time; $previous_time = $current_time; - my %points_for; - foreach my $pick ($schema->resultset('Pick')->search({}, { prefetch => [qw/game pick/] })->all) { - my $game_id = $pick->get_column('game'); - my $winner_team_id = $perfect_winner_for_game{$game_id}; - next if !defined $winner_team_id || $winner_team_id != $pick->get_column('pick'); - - my $game_row = $pick->game; - my $team_row = $pick->pick; - my $points_for_pick = $game_row->get_column('round') * - (5 + $game_row->get_column('lower_seed') * $team_row->get_column('seed')); - $points_for{$pick->get_column('player')}{ $team_row->get_column('region') } += $points_for_pick; - } - $schema->txn_do(sub { - foreach my $player ($schema->resultset('Player')->search({})->all) { - my $player_id = $player->get_column('id'); - foreach my $region_id (1 .. 4) { - my $points = $points_for{$player_id}{$region_id} || 0; - $schema->resultset('RegionScore')->update_or_create({ - player => $player_id, - region => $region_id, - points => $points, - }); - } - } + $schema->storage->dbh_do(sub { + my ($storage, $dbh) = @_; + + $dbh->do('delete from region_score'); + $dbh->do(q{ + insert into region_score (player, region, points) + select + player.id, + region.id, + coalesce(player_region_points.points, 0) as points + from player + cross join region + left join ( + select + player_picks.player as player, + team.region as region, + sum(game.round * (5 + game.lower_seed * team.seed)) as points + from pick player_picks + join pick perfect_picks + on perfect_picks.game = player_picks.game + and perfect_picks.pick = player_picks.pick + join game + on game.id = player_picks.game + join team + on team.id = player_picks.pick + where perfect_picks.player = 1 + group by player_picks.player, team.region + ) as player_region_points + on player_region_points.player = player.id + and player_region_points.region = region.id + }); + }); }); $current_time = time(); $times{update_region_score} = $current_time - $previous_time; $previous_time = $current_time; $schema->txn_do(sub { - foreach my $player ($schema->resultset('Player')->search({})->all) { - my $player_id = $player->get_column('id'); - my $total_points = 0; - foreach my $region_id (1 .. 4) { - $total_points += $points_for{$player_id}{$region_id} || 0; - } - $player->update({ points => $total_points }); - } + $schema->storage->dbh_do(sub { + my ($storage, $dbh) = @_; + $dbh->do(q{ + update player + set points = coalesce( + ( + select sum(region_score.points) + from region_score + where region_score.player = player.id + ), + 0 + ) + }); + }); }); $current_time = time(); $times{update_player_points} = $current_time - $previous_time; diff --git a/t/model_update_points.t b/t/model_update_points.t index 9a6c8fe..e912980 100644 --- a/t/model_update_points.t +++ b/t/model_update_points.t @@ -74,4 +74,8 @@ is($all_region_scores, 4, 'portable path maintains all region score rows per pla my $player_row = $schema->resultset('Player')->find($player->id); is($player_row->get_column('points'), 47, 'player total points updated from region scores'); +my $admin_region_scores = $schema->resultset('RegionScore')->search({ player => 2 })->count; +is($admin_region_scores, 4, 'portable path also creates zeroed region rows for players with no winning picks'); +is($schema->resultset('Player')->find(2)->get_column('points'), 0, 'players without winning picks keep zero total points'); + done_testing();