Skip to content

Commit

Permalink
Improve stability by disabling dynamic leaderboard queries (#1105)
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.

This patch disables the execution of dynamic leaderboard calculations. Requests triggering a dynamic query now result in an HTTP 400 response.

The below leaderboards will not function until a workaround or alternative solution is determined:
- 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 49bffd1
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 49bffd1

Please sign in to comment.