Skip to content

Commit

Permalink
Improve stability by disabling dynamic leaderboard queries
Browse files Browse the repository at this point in the history
Due to the extensive computational demand of dynamic queries for various MapRoulette leaderboards — including those for global standings, project-specific, challenge-specific, and country-specific rankings — system stability issues have been observed.

To enhance system reliability, this patch disables the execution of dynamic leaderboard calculations. Requests triggering a dynamic query now result in an HTTP 400 response, encouraging the use of static data.

Affected leaderboards include:
- Project-specific leaderboards
- Challenge-specific leaderboards
- Leaderboards featuring items not currently enabled
- Country-specific leaderboards, excluding the global leaderboard, when multiple countries are selected
- Any use of custom date ranges outside the predefined set of durations
  • Loading branch information
ljdelight committed Feb 23, 2024
1 parent 883dc01 commit 202e56b
Show file tree
Hide file tree
Showing 2 changed files with 47 additions and 14 deletions.
47 changes: 37 additions & 10 deletions app/org/maproulette/framework/service/LeaderboardService.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ package org.maproulette.framework.service
import javax.inject.{Inject, Singleton}
import org.joda.time.DateTime
import org.maproulette.Config
import org.maproulette.exception.NotFoundException
import org.maproulette.exception.{InvalidException, NotFoundException}
import org.maproulette.framework.model.{LeaderboardChallenge, LeaderboardUser, Task, User}
import org.maproulette.framework.mixins.LeaderboardMixin
import org.maproulette.framework.repository.{ChallengeRepository, LeaderboardRepository}
Expand Down Expand Up @@ -151,11 +151,20 @@ class LeaderboardService @Inject() (
fetchedUserId => this.getUserTopChallenges(fetchedUserId, params)
)

if (result.length > 0) {
return result
}
// NOTE: The result may be an empty list for users who are not in the leaderboard.
return result
}

// The provided arguments are invalid because the combination cannot query from the pre-built table, throw an error (http 400).
throw new InvalidException(
"Dynamic queries are disabled. Adjust query parameters for static results."
)

//
// TODO(ljdelight): The below needs to be moved to a new endpoint because it CAN CRASH THE SERVER. We MUST always
// know whether the leaderboard is coming from a pre-built table or not. THUS SEPARATE ENDPOINTS.
//

val (startDate, endDate) = this.setupDates(params.monthDuration, params.start, params.end)

val query = this
Expand Down Expand Up @@ -220,11 +229,20 @@ class LeaderboardService @Inject() (
fetchedUserId => this.getUserTopChallenges(fetchedUserId, params)
)

if (result.length > 0) {
return result
}
// NOTE: The result may be an empty list for users who are not in the leaderboard.
return result
}

// The provided arguments are invalid because the combination cannot query from the pre-built table, throw an error (http 400).
throw new InvalidException(
"Dynamic queries are disabled. Adjust query parameters for static results."
)

//
// TODO(ljdelight): The below needs to be moved to a new endpoint because it CAN CRASH THE SERVER. We MUST always
// know whether the leaderboard is coming from a pre-built table or not. THUS SEPARATE ENDPOINTS.
//

val (startDate, endDate) = this.setupDates(params.monthDuration, params.start, params.end)

val rankQuery = this.leaderboardWithRankSQL(
Expand Down Expand Up @@ -294,11 +312,20 @@ class LeaderboardService @Inject() (
)
)

if (result.length > 0) {
return result
}
// NOTE: The result may be an empty list for users who are not in the leaderboard.
return result
}

// The provided arguments are invalid because the combination cannot query from the pre-built table, throw an error (http 400).
throw new InvalidException(
"Dynamic queries are disabled. Adjust query parameters for static results."
)

//
// TODO(ljdelight): The below needs to be moved to a new endpoint because it CAN CRASH THE SERVER. We MUST always
// know whether the leaderboard is coming from a pre-built table or not. THUS SEPARATE ENDPOINTS.
//

val (startDate, endDate) = setupDates(params.monthDuration, params.start, params.end)
val (boundingSearch, taskTableIfNeeded) = setupBoundingSearch(params.countryCodeFilter)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,10 @@ class LeaderboardServiceSpec(implicit val application: Application) extends Fram
var challenge: Challenge = null

"LeaderboardService" should {
"get mapper leaderboard" taggedAs (LeaderboardTag) in {
val params = SearchLeaderboardParameters(onlyEnabled = false)
// TODO(ljdelight): Temporarily ignored due to the disabling of dynamic leaderboard queries to improve system stability.
// This test will be revisited once an alternative approach or solution is implemented.
"get mapper leaderboard" taggedAs (LeaderboardTag) ignore {
val params = SearchLeaderboardParameters(onlyEnabled = true)
val results = this.service.getMapperLeaderboard(params)
results.size mustEqual 2

Expand Down Expand Up @@ -85,13 +87,17 @@ class LeaderboardServiceSpec(implicit val application: Application) extends Fram
ccResults.size mustEqual 2
}

"get leaderboard for user" taggedAs (LeaderboardTag) in {
// TODO(ljdelight): Temporarily ignored due to the disabling of dynamic leaderboard queries to improve system stability.
// This test will be revisited once an alternative approach or solution is implemented.
"get leaderboard for user" taggedAs (LeaderboardTag) ignore {
val results = this.service.getLeaderboardForUser(randomUser.id, SearchLeaderboardParameters())
results.size mustEqual 1
results.head.userId mustEqual randomUser.id
}

"get leaderboard for user with bracketing" taggedAs (LeaderboardTag) in {
// TODO(ljdelight): Temporarily ignored due to the disabling of dynamic leaderboard queries to improve system stability.
// This test will be revisited once an alternative approach or solution is implemented.
"get leaderboard for user with bracketing" taggedAs (LeaderboardTag) ignore {
val results = this.service
.getLeaderboardForUser(randomUser.id, SearchLeaderboardParameters(), bracket = 1)
results.size mustEqual 2
Expand Down

0 comments on commit 202e56b

Please sign in to comment.