From 684b67377a8eaf488845aa73d056da02bd427529 Mon Sep 17 00:00:00 2001 From: Glenn Rice Date: Sun, 20 Apr 2025 09:21:48 -0500 Subject: [PATCH] Improve loading time for the achievements leaderboard. Currently the user achievements are obtained from the database in the user loop. This takes a long time when there are lots of users. So instead, this gets all user achievements for all users before the loop and references them by user id and then achievement id. Testing this on a course for 5000 users shows this gives a significant speed up on load time for the page. With the develop branch it takes around 25 seconds, and with this branch it takes around 3 seconds. Note that there were also two redundant queries (one that listed all achievements and then one that fetched all achievements on lines 49 and 50 in the develop branch) for which the data from those queries was not even used. Those were removed. --- .../AchievementsLeaderboard.pm | 27 ++++++++++--------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/lib/WeBWorK/ContentGenerator/AchievementsLeaderboard.pm b/lib/WeBWorK/ContentGenerator/AchievementsLeaderboard.pm index 2c2ba971d4..371a66f57f 100644 --- a/lib/WeBWorK/ContentGenerator/AchievementsLeaderboard.pm +++ b/lib/WeBWorK/ContentGenerator/AchievementsLeaderboard.pm @@ -46,37 +46,40 @@ sub initialize ($c) { my %globalUserAchievements = map { $_->user_id => $_ } $db->getGlobalUserAchievementsWhere({ user_id => { not_like => 'set_id:%' } }); - my @allBadgeIDs = $db->listAchievements; - my @allBadges = @allBadgeIDs ? sortAchievements($db->getAchievements(@allBadgeIDs)) : (); - $c->{showUserNames} = $c->authz->hasPermissions($c->{userName}, 'view_leaderboard_usernames'); $c->{showLevels} = 0; # Hide level column unless at least one user has a level achievement. + my %allUserAchievements; + for ($db->getUserAchievementsWhere({ + user_id => { not_like => 'set_id:%' }, + achievement_id => [ map { $_->achievement_id } grep { $_->category ne 'level' } @achievements ], + })) + { + $allUserAchievements{ $_->user_id }{ $_->achievement_id } = $_; + } + my @rows; for my $user ($db->getUsersWhere({ user_id => { not_like => 'set_id:%' } })) { # Only include users who can be shown in stats. next unless $ce->status_abbrev_has_behavior($user->status, 'include_in_stats'); + my $globalData = $globalUserAchievements{ $user->user_id }; + my $userAchievements = $allUserAchievements{ $user->user_id }; + # Skip unless user has achievement data. - my $globalData = $globalUserAchievements{ $user->user_id }; - next unless $globalData; + next unless $globalData && $userAchievements; my $level = $globalData->level_achievement_id ? $achievementsById{ $globalData->level_achievement_id } : ''; - my %userAchievements = map { $_->achievement_id => $_ } $db->getUserAchievementsWhere({ - user_id => $user->user_id, - achievement_id => [ map { $_->achievement_id } grep { $_->category ne 'level' } @achievements ], - }); - my @badges; for my $achievement (@achievements) { # Skip level achievements and only show earned achievements. last if $achievement->category eq 'level'; push(@badges, $achievement) - if $userAchievements{ $achievement->achievement_id } + if $userAchievements->{ $achievement->achievement_id } && $achievement->enabled - && $userAchievements{ $achievement->achievement_id }->earned; + && $userAchievements->{ $achievement->achievement_id }->earned; } push(@rows, [ $globalData->achievement_points || 0, $level, $user, \@badges ]);