Skip to content

Commit

Permalink
Merge pull request #32 from Syriiin/better-global-leaderboard-score-c…
Browse files Browse the repository at this point in the history
…ache

Refresh global leaderboard top score cache on interval
  • Loading branch information
Syriiin authored Apr 14, 2024
2 parents 8ccbf0a + d8fba11 commit a32aa68
Show file tree
Hide file tree
Showing 4 changed files with 58 additions and 28 deletions.
27 changes: 21 additions & 6 deletions leaderboards/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,17 @@ def get_pp_record(self) -> typing.Union[Score, None]:

return scores.aggregate(Max("sorting_pp"))["sorting_pp__max"]

def get_top_scores(self, limit=5):
scores = (
Score.objects.non_restricted()
.distinct()
.filter(membership__leaderboard_id=self.id)
.select_related("user_stats", "user_stats__user", "beatmap")
.get_score_set(score_set=self.score_set)
)

return scores[:limit]

def get_top_membership(self):
if self.access_type == LeaderboardAccessType.GLOBAL:
memberships = Membership.global_memberships
Expand Down Expand Up @@ -165,9 +176,11 @@ def update_membership(self, user_id):
)
elif self.score_set == ScoreSet.NEVER_CHOKE:
membership.pp = calculate_pp_total(
score.nochoke_performance_total
if score.result & ScoreResult.CHOKE
else score.performance_total
(
score.nochoke_performance_total
if score.result & ScoreResult.CHOKE
else score.performance_total
)
for score in scores
)
elif self.score_set == ScoreSet.ALWAYS_FULL_COMBO:
Expand Down Expand Up @@ -321,9 +334,11 @@ def recalculate(self):
)
elif self.leaderboard.score_set == ScoreSet.NEVER_CHOKE:
self.pp = calculate_pp_total(
score.nochoke_performance_total
if score.result & ScoreResult.CHOKE
else score.performance_total
(
score.nochoke_performance_total
if score.result & ScoreResult.CHOKE
else score.performance_total
)
for score in self.scores.order_by("-performance_total").all()
)
elif self.leaderboard.score_set == ScoreSet.ALWAYS_FULL_COMBO:
Expand Down
24 changes: 21 additions & 3 deletions leaderboards/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from celery import shared_task
from django.conf import settings
from django.core.cache import cache
from django.db import transaction

from common.discord_webhook_sender import DiscordWebhookSender
Expand Down Expand Up @@ -51,9 +52,11 @@ def update_memberships(user_id, gamemode=Gamemode.STANDARD):
)
elif leaderboard.score_set == ScoreSet.NEVER_CHOKE:
membership.pp = calculate_pp_total(
score.nochoke_performance_total
if score.result & ScoreResult.CHOKE
else score.performance_total
(
score.nochoke_performance_total
if score.result & ScoreResult.CHOKE
else score.performance_total
)
for score in scores
)
elif leaderboard.score_set == ScoreSet.ALWAYS_FULL_COMBO:
Expand Down Expand Up @@ -111,6 +114,21 @@ def send_notification(
return memberships


@shared_task
def update_global_leaderboard_top_5_score_cache():
for gamemode in Gamemode:
leaderboards = Leaderboard.objects.filter(
access_type=LeaderboardAccessType.GLOBAL, gamemode=gamemode
)
for leaderboard in leaderboards:
scores = leaderboard.get_top_scores(limit=5)
cache.set(
f"leaderboards::global_leaderboard_top_5_scores::{leaderboard.id}",
scores,
900,
)


@shared_task
def send_leaderboard_top_score_notification(leaderboard_id: int, score_id: int):
# passing score_id instead of querying for top score in case it changes before the job is picked up
Expand Down
31 changes: 12 additions & 19 deletions leaderboards/views.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
from collections import OrderedDict

from django.core.cache import cache
from django.core.exceptions import ValidationError
from django.core.validators import URLValidator
from django.views.decorators.cache import cache_page
from rest_framework import permissions, status
from rest_framework.exceptions import NotFound, ParseError, PermissionDenied
from rest_framework.response import Response
Expand Down Expand Up @@ -265,7 +265,7 @@ class LeaderboardScoreList(APIView):

permission_classes = (permissions.IsAuthenticatedOrReadOnly,)

def _get(self, request, leaderboard_type, gamemode, leaderboard_id):
def get(self, request, leaderboard_type, gamemode, leaderboard_id):
osu_user_id = (
request.user.osu_user_id if request.user.is_authenticated else None
)
Expand All @@ -284,24 +284,17 @@ def _get(self, request, leaderboard_type, gamemode, leaderboard_id):
except Leaderboard.DoesNotExist:
raise NotFound("Leaderboard not found.")

scores = (
Score.objects.non_restricted()
.distinct()
.filter(membership__leaderboard_id=leaderboard_id)
.select_related("user_stats", "user_stats__user", "beatmap")
.order_by("-performance_total", "date")
.get_score_set(score_set=leaderboard.score_set)
)
serialiser = LeaderboardScoreSerialiser(scores[:limit], many=True)
return Response(serialiser.data)

# TODO: get rid of this cache by optimising
def get(self, request, leaderboard_type, gamemode, leaderboard_id):
if leaderboard_type == "global":
cached_page = cache_page(60 * 60)(self._get)
return cached_page(request, leaderboard_type, gamemode, leaderboard_id)
if leaderboard.access_type == LeaderboardAccessType.GLOBAL:
scores = cache.get_or_set(
f"leaderboards::global_leaderboard_top_5_scores::{leaderboard.id}",
lambda: leaderboard.get_top_scores(limit=limit),
900,
)
else:
return self._get(request, leaderboard_type, gamemode, leaderboard_id)
scores = leaderboard.get_top_scores(limit=5)

serialiser = LeaderboardScoreSerialiser(scores, many=True)
return Response(serialiser.data)


class LeaderboardMemberList(APIView):
Expand Down
4 changes: 4 additions & 0 deletions osuchan/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,10 @@ class EnvSettings(BaseSettings):
"task": "profiles.tasks.dispatch_update_all_global_leaderboard_top_members",
"schedule": crontab(minute=0, hour=0), # midnight UTC
},
"update-global-leaderboard-top-5-score-cache-every-10-minutes": {
"task": "leaderboards.tasks.update_global_leaderboard_top_5_score_cache",
"schedule": crontab(minute="*/10"),
},
}


Expand Down

0 comments on commit a32aa68

Please sign in to comment.