Skip to content

Commit 454da01

Browse files
Backend: Introduce MAX_LEADERBOARD_QUERY_LIMIT setting to optimize leaderboard data queries and update filtering logic in calculate_distinct_sorted_leaderboard_data function to enhance performance. (#4994)
1 parent dd1f407 commit 454da01

File tree

3 files changed

+42
-14
lines changed

3 files changed

+42
-14
lines changed

apps/jobs/utils.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from base.utils import get_model_object, suppress_autotime
99
from challenges.models import ChallengePhaseSplit, LeaderboardData
1010
from challenges.utils import get_challenge_phase_model
11+
from django.conf import settings
1112
from django.db.models import ExpressionWrapper, F, FloatField, Q, fields
1213
from django.db.models.expressions import RawSQL
1314
from django.utils import timezone
@@ -364,10 +365,14 @@ def calculate_distinct_sorted_leaderboard_data(
364365
response_data = {"error": "Sorry, the leaderboard is not public!"}
365366
return response_data, status.HTTP_400_BAD_REQUEST
366367

367-
leaderboard_data = LeaderboardData.objects.exclude(
368-
Q(submission__created_by__email__in=challenge_hosts_emails)
369-
& Q(submission__is_baseline=False)
370-
).filter(is_disabled=False)
368+
leaderboard_data = LeaderboardData.objects.filter(is_disabled=False)
369+
# Only exclude host submissions when there are host emails (avoids
370+
# auth_user join)
371+
if challenge_hosts_emails:
372+
leaderboard_data = leaderboard_data.exclude(
373+
Q(submission__created_by__email__in=challenge_hosts_emails)
374+
& Q(submission__is_baseline=False)
375+
)
371376

372377
# Get all the successful submissions related to the challenge phase split
373378
all_valid_submission_status = [Submission.FINISHED]
@@ -457,6 +462,10 @@ def calculate_distinct_sorted_leaderboard_data(
457462

458463
all_banned_participant_team = []
459464

465+
# Apply query limit to prevent slow queries on popular challenges
466+
max_limit = getattr(settings, "MAX_LEADERBOARD_QUERY_LIMIT", 10000)
467+
leaderboard_data = leaderboard_data[:max_limit]
468+
460469
# Convert to list to allow multiple iterations
461470
leaderboard_data = list(leaderboard_data)
462471

settings/common.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,9 @@
156156

157157
SITE_ID = 1
158158

159+
# Maximum number of leaderboard rows to load per query (prevents slow queries)
160+
MAX_LEADERBOARD_QUERY_LIMIT = 10000
161+
159162
REST_FRAMEWORK = {
160163
"DEFAULT_PAGINATION_CLASS": (
161164
"rest_framework.pagination.LimitOffsetPagination"

tests/unit/jobs/test_utils.py

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,11 @@ def test_calculate_distinct_sorted_leaderboard_data(
4949
mock_challenge_phase_split = MagicMock()
5050
mock_leaderboard_data = MagicMock()
5151
mock_filter.return_value = mock_leaderboard_data
52-
mock_exclude.return_value = mock_leaderboard_data
52+
mock_leaderboard_data.exclude.return_value = mock_leaderboard_data
53+
mock_leaderboard_data.filter.return_value = mock_leaderboard_data
54+
mock_leaderboard_data.order_by.return_value = mock_leaderboard_data
55+
mock_leaderboard_data.annotate.return_value = mock_leaderboard_data
56+
mock_leaderboard_data.values.return_value = []
5357

5458
(
5559
leaderboard_data,
@@ -302,7 +306,11 @@ def test_calculate_distinct_sorted_leaderboard_data_order_by_in_labels(
302306
mock_challenge_phase_split = MagicMock()
303307
mock_leaderboard_data = MagicMock()
304308
mock_filter.return_value = mock_leaderboard_data
305-
mock_exclude.return_value = mock_leaderboard_data
309+
mock_leaderboard_data.filter.return_value = mock_leaderboard_data
310+
mock_leaderboard_data.order_by.return_value = mock_leaderboard_data
311+
mock_leaderboard_data.annotate.return_value = mock_leaderboard_data
312+
mock_leaderboard_data.values.return_value = []
313+
# When challenge_hosts_emails is [], exclude is not called
306314

307315
mock_leaderboard = MagicMock()
308316
mock_leaderboard.schema = {
@@ -541,9 +549,11 @@ def test_banned_email_ids(
541549
]
542550

543551
# Set up chainable mock for:
544-
# .exclude().filter().filter().order_by().annotate().values()
552+
# .filter().exclude().filter().filter().order_by().annotate().values()
545553
mock_qs = MagicMock()
546-
mock_leaderboard_data_objects.exclude.return_value = mock_qs
554+
mock_filter_result = MagicMock()
555+
mock_leaderboard_data_objects.filter.return_value = mock_filter_result
556+
mock_filter_result.exclude.return_value = mock_qs
547557
mock_qs.filter.return_value = mock_qs
548558
mock_qs.order_by.return_value = mock_qs
549559
mock_qs.annotate.return_value = mock_qs
@@ -594,9 +604,11 @@ def test_team_in_all_banned_participant_team(
594604
]
595605

596606
# Set up chainable mock for:
597-
# .exclude().filter().filter().order_by().annotate().values()
607+
# .filter().exclude().filter().filter().order_by().annotate().values()
598608
mock_qs = MagicMock()
599-
mock_leaderboard_data_objects.exclude.return_value = mock_qs
609+
mock_filter_result = MagicMock()
610+
mock_leaderboard_data_objects.filter.return_value = mock_filter_result
611+
mock_filter_result.exclude.return_value = mock_qs
600612
mock_qs.filter.return_value = mock_qs
601613
mock_qs.order_by.return_value = mock_qs
602614
mock_qs.annotate.return_value = mock_qs
@@ -664,9 +676,11 @@ def test_bulk_prefetch_avoids_n_plus_one_queries(
664676
]
665677

666678
# Set up chainable mock for:
667-
# .exclude().filter().filter().order_by().annotate().values()
679+
# .filter().exclude().filter().filter().order_by().annotate().values()
668680
mock_qs = MagicMock()
669-
mock_leaderboard_data_objects.exclude.return_value = mock_qs
681+
mock_filter_result = MagicMock()
682+
mock_leaderboard_data_objects.filter.return_value = mock_filter_result
683+
mock_filter_result.exclude.return_value = mock_qs
670684
mock_qs.filter.return_value = mock_qs
671685
mock_qs.order_by.return_value = mock_qs
672686
mock_qs.annotate.return_value = mock_qs
@@ -741,9 +755,11 @@ def test_multiple_banned_participants_in_team(
741755
]
742756

743757
# Set up chainable mock for:
744-
# .exclude().filter().filter().order_by().annotate().values()
758+
# .filter().exclude().filter().filter().order_by().annotate().values()
745759
mock_qs = MagicMock()
746-
mock_leaderboard_data_objects.exclude.return_value = mock_qs
760+
mock_filter_result = MagicMock()
761+
mock_leaderboard_data_objects.filter.return_value = mock_filter_result
762+
mock_filter_result.exclude.return_value = mock_qs
747763
mock_qs.filter.return_value = mock_qs
748764
mock_qs.order_by.return_value = mock_qs
749765
mock_qs.annotate.return_value = mock_qs

0 commit comments

Comments
 (0)